Skip to content

#381 PR-A: token-adoption measurement kernel (M3/M4/M5, RTK eligibility, MCP attempt states) - #432

Merged
seathatflowsinourveins merged 9 commits into
mainfrom
claude/w3-measure-20260927
Sep 27, 2026
Merged

seathatflowsinourveins merged 9 commits into
mainfrom
claude/w3-measure-20260927

Conversation

@seathatflowsinourveins

@seathatflowsinourveins seathatflowsinourveins commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

PR-A of the #381 token-adoption E2E. The branch extends the existing WP4
transcript tools with the preregistered measurement kernel: M3/M5 result
sizes, M4 fetch routing, RTK part eligibility for M-R1/M-R3/M6c, hook-context
claims versus insertion, MCP attempt states and per-message provider usage.
The Claude lane (child-usage.mjs) and the Codex lane (skill_usage.py,
through a Node bridge) share one kernel. This is measurement tooling: full
PR-A acceptance (M1, M15 and the other items under Residuals) and any E2E
savings claim remain open.

The last round (fixup3, 2026-09-27) repairs the four findings of the Opus
verification of fixup2 (verdict block): M4 heredoc resolution (D1, high),
the workflow checksum file (D2), offset-exact possible-fetch accounting (D3)
and this body (D4). It also adopts the verifier's recommendation 6: raw
curl/wget/gh api misses now count as possible fetches, like
HTTP_SCRIPT misses, so a parser false negative on these patterns cannot
raise the M4 lower bound.

Changes

Whole branch (9 files, replayed onto main 55fc8d17, after #433):

  • examples/claude-native/workflows/child-usage.mjs: the kernel. M3/M5 count
    UTF-8 content bytes per carrier (text blocks without wrappers), minus
    witnessed exceptions from a digest-bound private sidecar. M4 counts
    confirmed remote fetches per carrier, including nested ctx and Codex
    sandbox fetches, and reports routed_share, fetch_mentions_unconfirmed
    and routed_share_lower_bound. rtk_parts replays native
    rtk hook check (RTK 0.50.0) per simple part for M-R1 coverage; the
    deterministic M-R3/M6c zero counter is separate from advisory log/find
    review, and proxy parts are outside eligibility. The git option prefix
    that logFindPart and two RTK exclusion patterns share is read by a
    linear walk over word positions, and interpreter option words match
    -[\w-]+ (the CodeQL repair below). Hook-context claims,
    inserted context, MCP states and per-message usage are reported
    separately. A sweep reports main transcripts apart from the child
    population, which stays comparable with the Lane use per Claude child and Codex worker, with a frozen-window baseline for the workstation #369 receipt.
  • tools/skill-usage/skill_usage.py: the Codex adapter onto the same kernel.
    Function, custom and local-shell calls pair with their outputs; code-mode
    nested operations stay out of context bytes; MCP state falls back to the
    native item status; Codex measurements omit the Claude usage object.
  • tests/test_token_measurement.py and tests/test_skill_usage.py: synthetic
    controls for both lanes.
  • examples/claude-native/workflows/test-child-usage.mjs: one assertion now
    treats stdout-only hook context as a claim, never proof of insertion.
  • examples/claude-native/workflows/README.md and tools/skill-usage/README.md:
    counter definitions, the gate reading and the limits.
  • examples/claude-native/workflows/SHA256SUMS: checksums of the changed
    workflow files.
  • manifests/evidence.json: registration of the branch's own changed files,
    in the last commit (docs/lanes.md hot-file protocol).

Builder commit handoffs, dated fixup receipts and this body stay in the
coordinator's unit notes, outside the tree. An earlier head committed them
under units/w3/measure/; this revision removes them and their registrations.

This round (fixup3):

  • D1. A heredoc body is source only when the simple command that contains
    its << operator runs stdin as source. That command is the text between
    the nearest control operators around the operator, with quotes removed and
    redirections and their targets dropped. bash <<'EOF' 2>&1 | tail -n 5,
    bash <<'EOF' > log.txt, &&/; suffixes and python3 - <<'EOF' | tail
    therefore keep their executed bodies. Shells follow POSIX sh STDIN and
    bash(1): -c or a script operand leaves stdin as data, -s keeps it, a
    shell's - equals --, -o/+o/-O/+O take a name,
    --rcfile/--init-file take a file, and -n reads without executing.
    ssh runs stdin in the remote login shell when no remote command follows
    the destination, or when the remote command reads stdin as source; -n,
    -f, -N, -s, -W, -O, -G, -V and -Q never do. A << inside
    quotes, a comment or $(( )) opens no heredoc. Two limits remain and err
    toward counting a fetch (residual N1 below): $( is not tracked inside
    double quotes, and a quoted string that a shell runs is added without its
    own heredoc resolution. The legacy
    fetchKind/lanes.fetch.bash_curl_wget counter again follows the workflows
    README's description (heredoc bodies count under ssh or a shell heredoc).
    The source comment that claimed unknown option forms stayed possible
    fetches for every detector is replaced.
  • Recommendation 6. fetch_mentions_unconfirmed also counts raw
    curl/wget in command position and raw gh api matches that
    executed-text analysis did not confirm. Both READMEs document it.
  • D3. The executed-text analysis keeps each character's raw offset. An
    executed match confirms only the raw match at its own raw offset, anchored
    at the call name, the curl/wget word or gh. Matches created by
    backslash-newline joins, unescaping or quoted strings a shell runs cannot
    offset a raw miss. The exported executedText string API is unchanged.
  • D2. SHA256SUMS is regenerated with the documented
    sha256sum -- *.mjs *.js *.json (13 entries; README.md removed).
  • D4. This single body replaces both earlier bodies.

M4 scope

Possible fetches are raw HTTP_SCRIPT matches anywhere in the command or
code, raw curl/wget in command position (a line start, or after ;, &,
|, (, a backtick or $(, also behind a shell keyword or wrapper) and raw
gh api at a line start or after a separator. They include data-only
mentions such as a heredoc that writes a script; those lower the bound
conservatively and leave M4 status incomplete. In neither count: a curl
inside a quoted string the parser does not treat as run
(ssh -o Opt=value host 'curl ...', watch 'curl ...'), behind xargs,
find -exec or an unrecognized wrapper, or passed to a subprocess inside
interpreter code; loops, dynamic code, external scripts and aliases.

Evidence

Classes: synthetic fixtures (static transcript analysis; no fixture command
is executed), local integration (unit and contract suites, the native RTK
hook check), and local measurement (an installed-shell probe whose heredoc
bodies only echo). No live provider execution and no unchanged upstream
test suite. Returned outputs are retained in the coordinator's unit notes
(fixup3/).

Verifier reproduction (verify-fixup2, "Verification gaps"). Columns are
fetchKind, shell_fetch, unclassifiable and
fetch_mentions_unconfirmed:

Module Rows 1-4 Row 5 Row 6
71c0a991 (before) null 0 0 0 null 0 0 1 null 0 1 0
e7d3c5a3 "fetch" 1 0 undefined null 0 1 undefined null 0 1 undefined
fixup3 "fetch" 1 0 0 null 0 1 0 null 0 1 1

The verifier's predicted table held exactly for both earlier revisions; row 6
at e7d3c5a3 had no prediction.

Failing first. The final tests/test_token_measurement.py run against the
71c0a991 module returned Ran 38 tests, FAILED (failures=145), exit 1.
All 145 subtest failures are in the five new tests (66, 36, 12, 9 and 22
subtests), and the 33 existing tests pass. The new tests are
test_m4_heredoc_resolves_its_whole_simple_command_on_every_carrier,
test_m4_unexecuted_heredoc_curl_wget_and_gh_api_stay_possible_fetches,
test_m4_heredoc_openers_respect_quotes_comments_and_arithmetic,
test_m4_confirmations_trace_back_to_raw_offsets and
test_fetch_kind_and_legacy_lane_follow_the_heredoc_resolution. They cover
every shape the verifier listed on all three shell carriers plus fetchKind
and the legacy lane counter.

Fixup3 results:

  • python3 -m unittest -v tests.test_token_measurement: Ran 38 tests, OK,
    no skips. The RTK replay controls therefore ran (Linux, rtk 0.50.0).
  • python3 -m unittest tests.test_token_measurement tests.test_child_usage_suite tests.test_skill_usage tests.test_adoption_docs_consistency.RelativeLinkTests tests.test_adoption_docs_consistency.ScriptPathTests: Ran 143 tests, OK,
    exit 0.
  • Node suites: test-child-usage.mjs 92/92, test-envelope.mjs 254/254,
    test-contract-mutations.mjs 74/74; check-syntax.mjs and node --check
    exit 0.
  • Differential check: old versus new executedText (both modes) and
    fetchKind over the 198 commands the covering tests feed the kernel. 171 are
    identical; the 27 that differ are exactly new D1 controls. A fuzz of 40,000
    heredoc-free inputs in each of the two modes (80,000 checks) gave 0
    mismatches. Trace integrity over 20,144 inputs in each mode (40,288 checks,
    1,028,897 traced characters) gave 0 offset violations.
  • sha256sum --check --strict SHA256SUMS: 13 OK, exit 0. git diff --check:
    exit 0.
  • Installed-shell probe (GNU bash 5.2.21, dash 0.5.12). Stdin ran for
    bash -euo pipefail, -oe pipefail, +o pipefail, -O extglob,
    +O extglob, --norc, --rcfile /dev/null, -s, -s arg, -, --,
    sh -e, dash -o nounset, and the 2>&1 | tail -n 5 and > log.txt
    suffixes. It did not run for -n, -s -c, -c cat or a script operand,
    including one after -.
  • python3 scripts/validate.py at 419550a2: exit 1, with SHA-256 and
    byte-count mismatches for exactly the six registered files that round
    changed, pending re-registration.
  • Full suite at 419550a2, python3 -m unittest -q with
    TMPDIR=/var/tmp/claude-measure: Ran 6685 tests in 699.400s,
    FAILED (failures=2, errors=1, skipped=761), exit 1 (the 6,680 tests at
    71c0a991 plus the five new ones). The error is this host's known
    environment case for a worktree under /tmp: test_order_throughput
    test_cli_refuses_live_base_url_from_env_file_without_network
    (credential_file_permissions:worktree). One failure is also
    environmental: test_gpt6_family_tiering_20260926
    test_sigterm_records_the_running_call_as_interrupted_without_counting_it
    (the temporary directory must lie outside every repository). The other,
    test_catalog_freshness_propose
    test_general_publication_validator_passes_after_the_run, is the same
    publication validation on the then-unregistered changes. There is no other
    failure or error.
  • At f3ec6485, after the replay onto e82e6be7 (the last replay, onto 55fc8d17, kept content lines identical and validate.py at FAILS=0): with TMPDIR outside every repository, python3 -m unittest tests.test_token_measurement tests.test_skill_usage ran 137 tests, OK with no skips; in examples/claude-native/workflows, node test-child-usage.mjs passed 92 of 92 (95 of 95 after the CodeQL repair), test-envelope.mjs 254 of 254 and test-contract-mutations.mjs 74 of 74, and check-syntax.mjs exited 0; sha256sum --check --strict SHA256SUMS passed; git diff --check is clean; scripts/validate.py reports FAILS=0. The replay kept every content line identical, registered the branch's 8 files, and left no units/w3 entry in manifests/evidence.json (count 0).
  • Pin check: in a local upstream clone of mksglu/context-mode (133 commits past
    the tag), tag v1.0.169 resolves to
    589d8214d56740a28b5f7bf63167743d586b0b40. hooks/core/routing.mjs at the
    tag and in the installed source share blob
    e92ab46b2faf6597789bdc5eb3a5a90460d9da3c.

CodeQL repair (2026-09-27, content commit fce6274d, first pushed as 7ee9d03c): CI at 7aba17ac
failed the CodeQL check. It reported four high-severity js/redos alerts
(exponential backtracking) in child-usage.mjs:

  • three in RUN_HTTP_CODE (the node, bun and deno option word --?[\w-]+);
  • one in logFindPart (the git option prefix, where --git-dir reads
    either as an option taking the next word or as a lone --option).

The query help recommends removing the ambiguity. The fix:

  • -[\w-]+ matches the same words as --?[\w-]+ with one reading.
  • A walk over word positions follows every reading of the git option
    words, then tries the rest of the pattern at each word it can reach.
  • The same walk evaluates the two RTK exclude_commands patterns that share
    the prefix. Their strings, which the RTK config is written from, are
    unchanged; RTK itself compiles them with Rust's regex crate, which is
    worst case O(m * n).

Local integration checks:

  • A seeded equivalence run compared old and new logFindPart,
    sensitivePart and RUN_HTTP_CODE: 4,167,264 generated inputs, 0
    mismatches. It combined exhaustive four-word strings with 300,000 random
    strings per family, and the FIVE_EXCLUSIONS array is identical.
  • The CodeQL witness strings at 5,000 repetitions take under 7 ms. The old
    expressions take 3, 47 and 187 ms at 16, 20 and 22 repetitions.
  • The three new tests fail against the old module with its internals
    exported (2 of 95 fail: 28 repetitions exceed 1 s), then pass (95 of 95).
  • tests.test_token_measurement and tests.test_skill_usage ran 137 tests, OK.
  • test-usage-receipts.mjs passed 18 of 18, test-envelope.mjs 254 of 254
    and test-contract-mutations.mjs 74 of 74.
  • test-codex-envelope.mjs exited 0 with no failures, and
    sha256sum -c SHA256SUMS passed.
  • validate.py passed.

Dated history (all 2026-09-27):

  • Build 5139fdf5 (GPT-6 builder): 117 covering tests passed.
  • Repair 6b0f31a4, after an independent review with eleven findings: 130
    covering tests, 92 Node checks and 13 workflow checksums passed.
  • Fixup e7d3c5a3: interpreter heredocs and quoted interpreter code kept in
    the M4 denominator (builder receipt in the unit notes).
  • Fixup2 c60899f7: ignored stdin, fetch_mentions_unconfirmed and
    routed_share_lower_bound; 133 covering tests passed (builder receipt in
    the unit notes). The builder's own full-suite attempt, with TMPDIR
    inside the repository, returned 6,656 tests with 247 failures, 151 errors
    and 783 skips. Its retained errors included fixtures that require
    temporary state outside every repository; the complete failure inventory
    was not retained, and the run was never acceptance.
  • Coordinator acceptance at 71c0a991: validate.sh FAILS=0; full suite
    Ran 6680 tests in 717.989s, OK (skipped=761).
  • Opus verification of fixup2: block (D1 to D4, recommendation 6).
  • Fixup3 419550a2: results above.
  • Opus re-check of fixup3: residual. D1, recommendation 6, D2 and D3
    verified; the new controls fail on 71c0a991 (145 failures, all in the five
    new tests). New low defect N1 (below); D4 partly accurate (the fuzz and
    trace counts, the error/failure split and the heredoc rule, all corrected in
    this body).
  • This revision: the README states the N1 limit instead of the wrong rule,
    the scratch files leave the tree, and the branch is replayed with its own
    registrations in the last commit. The N1 fix follows in its own PR, per the
    one-review, one-repair bound.

SOTA sources

Residuals and not done

  • M1 per-lane eligibility: attempt, load and insertion counters do not
    compute successful opportunities among eligible tasks separately for every
    required lane (RUNBOOK.md:564).
  • M15 infrastructure errors: MCP attempt states do not separate
    infrastructure errors from invoked-command nonzero exits or establish the
    per-server error ceiling (RUNBOOK.md:582).
  • Other PR-A items: CLI lane counters and mcporter downstream attribution;
    JSON/uniform-tabular classification; final-return quality classification;
    a private native-ID ledger for rejected, cancelled and unfinished execution;
    Codex inherited-versus-injected markers, custom-role grouping, spawn fork
    metadata and provider-verified routes; frozen historical baselines, task
    identity joins, matched A/B comparisons and actual E2E acceptance.
  • N1, heredocs inside "$( ... )" and shell-run strings (low; from the
    fixup3 repair):
    openers() does not track $( inside double quotes, and
    a quoted string that a shell runs is appended without heredoc resolution.
    Reproduced at 419550a2 with the verifier's probes:
    git commit -m "$(cat <<'EOF' with a line-start curl in the body gives
    fetchKind=fetch, shell_fetch=1, remote_fetches=1;
    gh pr create --body "$(cat <<'EOF' with a gh api line gives
    unclassifiable=1; bash -c 'cat <<EOF > x.sh ...' gives shell_fetch=1.
    71c0a991 gave 0 for all three. The effect only lowers the routed shares,
    so it cannot pass the M4 gate falsely; the workflows README states the
    limit. Fix: track $( inside double quotes and resolve heredocs in
    shell-run strings, with failing-first controls, in a follow-up PR.
  • M4 static limits: see M4 scope. Shapes the heredoc resolution still
    leaves as data count only through the raw patterns: sudo -u user bash,
    docker exec/kubectl exec and other unrecognized wrappers,
    { ...; } <<EOF, a backslash-newline continuation before the opener,
    <<\EOF and $'...' quoting, Python -W/-X or Node --input-type
    before stdin, and zsh- or ksh-specific options. The Python shift-syntax and
    Node comment-apostrophe misses remain possible fetches.
  • Python mirror: tools/skill-usage/skill_usage.py:690-726 still cites
    the removed programOf. Its legacy Codex fetch_kind now differs from the
    Claude lane on -c, -n, ssh -n, bash - script.sh and a << inside
    quotes or comments. M4 itself is unaffected, because the Codex lane uses the
    Node kernel.
  • Code mode: the outer Codex code-mode exec code
    (skill_usage.py:957) is not scanned for fetches.
  • RTK binary-hash attestation, live provider/E2E measurement, semantic
    sidecar witness review and ambiguous interleaved or resumed code-mode
    associations remain outside this branch. The shared sandbox bucket does
    not establish exclusive context-mode use.
  • Publication: done in this revision. The branch is replayed onto
    55fc8d17 and registers only its own changed files in the last commit
    (docs/lanes.md hot-file protocol).

Recommended lane label

lane:foundation

🤖 Generated with Claude Code

@seathatflowsinourveins seathatflowsinourveins added the lane:foundation Foundation lane: Claude/Codex setup, hosts, memory, RAG, research, workers label Sep 27, 2026
Comment thread examples/claude-native/workflows/child-usage.mjs Fixed
Comment thread examples/claude-native/workflows/child-usage.mjs Fixed
Comment thread examples/claude-native/workflows/child-usage.mjs Fixed
Comment thread examples/claude-native/workflows/child-usage.mjs Fixed
@seathatflowsinourveins
seathatflowsinourveins force-pushed the claude/w3-measure-20260927 branch 2 times, most recently from 7aba17a to f3ec648 Compare September 27, 2026 21:23
Scout and others added 9 commits September 27, 2026 17:42
Add M3/M4/M5, native RTK part eligibility and explicit-wrap guards to the
existing child usage tool; reuse its kernel from the Codex skill usage tool.
Report main actors separately, retain reviewed digest-bound exceptions,
count nested fetches and unknowns, and preserve per-message/cumulative usage.
Keep stdout hook claims separate from inserted context and retain MCP states.

Sources: #381 token-adoption-e2e-20260926 preregistration and RUNBOOK;
rtk-ai/rtk v0.50.0 (1d87b8e719ce0a50c223cd93ca64dd16921f9aec),
src/main.rs, src/discover/lexer.rs and registry.rs;
mksglu/context-mode v1.0.169 hooks/posttooluse.mjs, hooks/core/routing.mjs
and src/session/extract.ts; ccusage/ccusage v20.0.24
(ecb676cce27cb5dd0090c7804a5cecc35e8ba805) Claude daily and Codex parser;
openai/codex rust-v0.157.1 protocol.rs and models.rs.

Validation: 117 covering unittests pass, including the Node synthetic suite
and installed native RTK hook-check controls. Publication validation reaches
only stale evidence hashes/byte counts; the coordinator owns their refresh.

Content written by GPT-6 (gpt-6-astra, effort max) through the Codex worker lane and the OmniRoute
pool; committed by the coordinator harness.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keep quoted operators out of redirect guards, preserve native RTK eligibility,
separate conditional log/find review, and exclude proxy parts from coverage.
Normalize Codex custom/local-shell calls and keep code-mode sandbox results
out of context bytes. Fix text-block sizes, HTTP data detection, sweep
populations, digest-binding counts and Codex usage shape.

Sources: rtk-ai/rtk v0.50.0 (1d87b8e719ce0a50c223cd93ca64dd16921f9aec),
src/discover/lexer.rs and registry.rs; mksglu/context-mode v1.0.169,
hooks/core/routing.mjs:727-804 and src/session/extract.ts:1060-1069;
openai/codex rust-v0.157.1, protocol/src/models.rs:1060-1165,
app-server-protocol/src/protocol/v2/item.rs:333-352 and
core/tests/suite/code_mode.rs:721-760,3436-3752; repository #381
preregistration/RUNBOOK and adopted conditional RTK exceptions.

Verification: 130 covering unittests passed; 92 Node checks passed;
13 workflow bundle checksums passed; git diff --check passed. New controls
reproduced defects before fixes. Publication validation reports inventory
binding mismatches; the manifest is outside this repair's write scope.
No live-provider or unchanged-upstream-suite acceptance is claimed.

Content written by GPT-6 (gpt-6-astra, effort max) through the Codex worker lane and the OmniRoute
pool; committed by the coordinator harness.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Content written by GPT-6 (gpt-6-astra, effort max) through the Codex worker lane and the OmniRoute
pool; committed by the coordinator harness.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Content written by GPT-6 (gpt-6-astra, effort max) through the Codex worker lane and the OmniRoute
pool; committed by the coordinator harness.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Fixup3 for unit measure (#381 PR-A), repairing the Opus verification of
fixup2 (verdict block).

D1 (high): a heredoc body is source only when the simple command around
its << operator runs stdin as source: the text between the nearest
control operators, quotes removed, redirections and their targets
dropped. Shells follow POSIX.1-2024 sh STDIN and bash(1) 5.2
OPTIONS/ARGUMENTS (-c or a script operand keeps data; -s keeps stdin;
-o/+o/-O/+O take a name; --rcfile/--init-file take a file; -n does not
execute). ssh follows OpenSSH ssh(1): stdin runs in the remote login
shell without a remote command, or when the remote command reads stdin.
A << inside quotes, a comment or $(( )) opens no heredoc; one inside a
quoted string a shell runs is left to the quoted-string recursion.
fetchKind and the legacy bash_curl_wget lane follow the same rule again.
The misleading stdin-selection comment is replaced.

Recommendation 6: raw curl/wget in command position and raw gh api
matches that executed-text analysis does not confirm join HTTP_SCRIPT
misses in fetch_mentions_unconfirmed (both READMEs).

D3 (low): executed text keeps each character's raw offset; a confirmed
match accounts only for the raw match at its own offset, so matches
created by joins, unescaping or run quoted strings cannot offset a raw
miss. The verifier's backslash-newline control and a no-whitespace join
control are included.

D2 (medium): SHA256SUMS regenerated with the documented
sha256sum -- *.mjs *.js *.json (README.md removed; 13 entries).

D4 (medium): units/w3/measure/pr-body.md is now the single whole-branch
body.

Evidence: the verifier's reproduction held at 71c0a991 and e7d3c5a3;
after the fix rows 1-4 give "fetch" 1 0 0, row 5 null 0 1 0 and row 6
null 0 1 1. The five new tests failed first against the 71c0a991 module
(145 subtests) and now pass: test_token_measurement 38 OK with no skips,
covering modules 143 OK, Node suites 92/92, 254/254 and 74/74. A
differential run kept 171 of 198 corpus commands byte-identical (the 27
others are the new controls); fuzz 80,000/0 mismatches; trace integrity
1,028,897 characters/0 violations. Full suite: Ran 6685 tests,
failures=2, errors=1, skipped=761: the two known /tmp-worktree
environment failures and the publication-validator test on the same
inventory mismatches, nothing else. validate.py exits 1 only on SHA/byte
mismatches of the six re-registration paths.

Sources: #381 preregistration.json M4; mksglu/context-mode v1.0.169
(589d8214d56740a28b5f7bf63167743d586b0b40) hooks/core/routing.mjs:228-229
and 727-804; POSIX.1-2024 utilities/sh.html; GNU bash 5.2 bash(1);
OpenSSH ssh(1) (man.openbsd.org/ssh, installed 9.6p1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tch notes out of the tree

The Opus re-check of fixup3 reproduced a conservative false positive: `$(` is not
tracked inside double quotes, and a quoted string a shell runs is added without its
own heredoc resolution, so a data heredoc in `git commit -m "$(cat <<'EOF' ...)"` or
`bash -c '...'` counts as executed. The README now states that limit instead of the
wrong rule; the fix follows in its own PR.

The builder's handoffs, receipts and PR body under units/w3/measure/ were scratch
work products, not repository content, and leave the tree; the hot-file commit
drops their registrations.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…alerts

CodeQL reported four exponential-backtracking alerts on this branch:
- RUN_HTTP_CODE (node, bun, deno eval): the option word --?[\w-]+ split '---' two ways. -[\w-]+ matches the same words with one reading.
- logFindPart: the git option prefix could read '--git-dir' either as an option taking the next word or as a lone --option.

The git prefix is now walked over word positions: every reading of the option words, then the rest of the pattern at each word it can reach. The same walk evaluates the two RTK exclude_commands patterns that share the prefix. Their strings, which the RTK config is written from, are unchanged.

A seeded check compared old and new over 4,167,264 generated inputs, with 0 mismatches. The CodeQL witness strings at 5,000 repetitions now take under 7 ms.

New tests fail against the old module (2 of 95: 28 repetitions exceed 1 s) and pass against the new one (95 of 95).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@seathatflowsinourveins
seathatflowsinourveins merged commit bfdc99c into main Sep 27, 2026
24 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the claude/w3-measure-20260927 branch September 27, 2026 22:15
seathatflowsinourveins added a commit that referenced this pull request Sep 29, 2026
…er-bash parser (#516)

* Add failing-first N1 probes and must-stay controls for M4

N1 is the #432 fixup3 residual: openers() does not track "$(" inside
double quotes, and a quoted string that a shell runs is appended
without its own heredoc resolution, so heredoc data reads as executed.

Adds three tests through the existing measure/exports bridges on all
three shell carriers plus fetchKind:
- p1 (git commit -m "$(cat <<'EOF' ...)" with a line-start curl), its
  body variants with ', " and ( ), p2 (gh api in the body), p3
  (bash -c 'cat <<EOF > x.sh ...') and a double-quoted p3 variant;
- must-stay controls: an executed curl in "$( )", a shell heredoc in
  "$( )", an escaped substitution in a double-quoted run string, the
  command after a heredoc in "$( )" (guards against resolving one
  heredoc twice), the outer-shell heredoc in a double-quoted run
  string, unquoted $( ) and the "Subject (scope)" body;
- the nested-quote case from design 1.5.

At cf3fb72e the new tests fail 23 subtests (p1, the ' variant, p3 and
the double-quoted p3: shell fetch 1 != 0 and fetchKind 'fetch'; p2:
unclassifiable 1 != 0; nested quotes: 0 != 1 and fetchKind None); the
must-stay test passes. Source: POSIX.1-2024 XCU 2.6.3 Command
Substitution, checked with GNU bash 5.2.21 and dash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first N1 tests for strings a shell runs

The N1 fix analyzes a string that a shell runs (RUN_QUOTED) as that
shell's input in both modes, instead of appending it verbatim unless
inlineHttp is set. Two consequences get their own tests first:
- in a double-quoted run string the outer shell removes the backslash
  before " (POSIX.1-2024 XCU 2.2.3), so `bash -c "echo \"a; curl
  ...\""` runs only echo: base reads a confirmed shell fetch;
- a string the inner shell runs in turn is analyzed too, so the curl
  in `ssh host 'bash -c "curl ..."'` is confirmed: base counts it in
  neither the confirmed nor the unconfirmed fetches, so the gate's
  lower bound could not see it.

Against the cf3fb72e kernel the test fails 7 subtests (1 != 0 and
0 != 1 on each carrier, fetchKind ['fetch', None]). GNU bash 5.2.21
and dash print `a; echo RAN` for the escaped form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Resolve heredocs inside "$( )" and in strings a shell runs (N1)

Fixes the #432 fixup3 residual N1 in child-usage.mjs. POSIX.1-2024 XCU
2.6.3: inside double quotes the text from "$(" to the matching ")" is
tokenized recursively; 2.2.3: in a double-quoted word the outer shell
removes the backslash before $ ` " \ and newline. GNU bash 5.2.21 and
dash agree on every shape below.

- step() is one frame machine (', ", `, and $( with depth) shared by
  phase 1 openers() and the phase 2 closeQuote()/matchParen(), so the
  phases agree on where a quote or substitution ends. With no frame
  open it reads exactly as the old openers(); only a double-quoted
  word gains $( and backquote frames, so a << inside "$( )" is found
  and its body resolved like any other heredoc.
- executedTrace splits into resolveHeredocs (phase 1) and scanQuotes
  (phase 2). The naive end-of-quote scan becomes closeQuote; a "$( )"
  body in double-quoted data is spliced as "$(" + scanQuotes(body) +
  ")" with exact offsets (no second phase 1 over it).
- A string a shell runs (RUN_QUOTED) is analyzed with the full
  executedTrace in both modes, a double-quoted one after unquoted()
  removes the outer shell's escapes at its own level. A shared set of
  resolved operator offsets keeps a heredoc the outer shell resolved
  in "$( )" from reading later lines as its body again.
- NESTING_LIMIT (32) bounds recursion: deeper analyses read as data.
  Without it '"$(' x 3000 and a 3000-deep shell heredoc chain throw
  RangeError, which would abort a sweep.
- The paren-free DOUBLE_QUOTED_DATA regex is removed; the scanners are
  linear character scanners (no new regex; CodeQL js/redos).

The failing-first tests from 1d206a12 and 54299c44 now pass; the
must-stay controls are unchanged. SHA256SUMS is regenerated in a
later stage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Document the N1 reading and add kernel unit cases

The workflows README replaces the N1 limit paragraph with the rule the
kernel now follows: "$( )" inside double quotes is read recursively
(POSIX.1-2024 XCU 2.6.3), a string a shell runs is analyzed as that
shell's input after the outer escapes are removed (XCU 2.2.3), and
reading text as data moves a raw detector match to the unconfirmed
count instead of dropping it. It states the remaining limits:
backquoted spans inside double quotes, case patterns inside "$( )",
the 32-level nesting bound, and text bash rejects as incomplete.

tools/skill-usage/README.md notes that the legacy Python executed_text
rule predates this reading and can disagree on these shapes.

test-child-usage.mjs adds fetchKind and exact executedText cases for
the N1 shapes and a nesting witness. Against the cf3fb72e kernel the
three new cases fail (95/98); with NESTING_LIMIT removed the witness
fails (97/98, RangeError); with the fix all 98 pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* State how the N1 reading moves the M4 lower bound

The previous wording said reading text as data leaves the denominator
of routed_share_lower_bound unchanged. That holds for raw detector
matches only. Measured against the cf3fb72e kernel:
- `echo "$(echo "a" && curl http://127.0.0.1:9/)"` moves from
  unconfirmed to confirmed loopback, which leaves the denominator;
- `echo "$(\curl ...)"` gains a confirmed fetch no raw detector sees;
- `echo "$(echo " ; \curl ...")"` loses a confirmation the old
  reading created (bash prints it as data), so the bound can rise.
The README now states those three cases. It also adds a limit: a
backquoted span outside quotes is still read as plain command text,
so a quote it leaves open runs past its closing backquote.

test-envelope (254/254), test-usage-receipts (18/18) and
test-contract-mutations (74/74) pass; they read this README as
routing_doc and contract_doc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first CLI-lane, state and proxy-position fixtures

Add the U1 design section 7 fixtures before the kernel change. The
Python integration cases feed measureTranscript, aggregateMeasurements
and the sweep CLI: the stack.json lane commands (:1003, :797, :579, :762,
:519, :629, :280, :407), wrappers, compound commands and substitutions,
runners, every mcporter call form, negatives (data, lookups,
registrations, near names, gcm and serena-hooks), version and help,
remote and unresolved programs, the call states, the rtk proxy
population by command position with prefix_rule_calls, M3 and M4
by_carrier, aggregation with actors_with_success, and ID-free sweep
output. The node suite adds commandInvocations unit cases.

Sources: #381 AA-PLAN PR-A item 3 and M6/M14; POSIX.1-2024 XCU 2.9.1-2.9.4
and 2.6.3; rtk-ai/rtk@1d87b8e7 src/main.rs:68-90,708-713,3008-3042 and
the installed rtk 0.50.0 (proxy -- and --skip-env bind, -v after proxy
is the program); openclaw/mcporter@93e0916c src/cli.ts:115-364,
src/cli/command-inference.ts:10-95, call-arguments.ts:79-233 and
call-command.ts:114-173,309-348 (a first positional with = is the
selector). Before the fix: 10 Python tests fail (102 subtests) and the
8 new node cases fail on the missing export.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Count CLI lanes and rtk proxy calls by command position

Export commandInvocations: every simple command of the executed text
(POSIX.1-2024 XCU 2.9.1-2.9.4, 2.6.3) is resolved past reserved words,
assignments, wrappers (timeout, env, nice, stdbuf, command, exec, time,
nohup, sudo 1.9.15p5, xargs), package runners and rtk proxy to a lane
executable by exact basename. mcporter operations and a call's
downstream server follow openclaw/mcporter@93e0916c (cli.ts,
command-inference.ts, call-arguments.ts, call-command.ts); HTTP and
stdio selectors read (http) and (stdio), and no host is emitted. rtk
proxy follows rtk-ai/rtk@1d87b8e7 src/main.rs:68-90,708-713,3008-3042
and the installed rtk 0.50.0 (no shell runs the proxied program).

One memoized reading per call drives carrierOf (M3 and M4 by_carrier),
measurement.proxy (adds invocations, nested, in_ctx_code and
prefix_rule_calls) and the new measurement.cli_lanes with per-call
states (AA-PLAN "Successful" and M14; not_executed from observed
transcript markers). aggregateMeasurements sums the fields and adds
actors_with_success; the sweep limits gain the static limits.
executedTrace takes an optional marks collector for ssh-run text and
data-heredoc substitutions; its output is unchanged (0 differences in
executedText and fetchKind over 24,489 inputs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Read mcporter URL and stdio selector forms with char scanners

Replace the character-class regexes in httpUrl and httpToolSelector,
the path-prefix alternation for an ad-hoc stdio selector and the
blank split of an inline npx server with character checks and the
existing shellWords splitter. The brief asks for linear character
scanners rather than regexes in this kernel (CodeQL js/redos flagged it
before). Behaviour is unchanged: the rules still follow
openclaw/mcporter@93e0916c src/cli/http-utils.ts:1-69,
call-argument-values.ts:68-83 and ephemeral-target.ts:134-158, and the
node suite passes unchanged (107/107).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Document CLI lanes, call states and the command-position proxy rule

Add a CLI-lanes subsection to the PR-A measurement fields: the lane
executables with the gcm and serena-hooks exclusions, the resolver
(wrappers, runners, rtk proxy), each cli_lanes field, the mcporter
operation and downstream-server precedence of mcporter v0.14.1, the
call states with the observed not-executed markers and their M14
mapping, the static limits, and the changed meanings of proxy.calls,
M3 by_carrier.rtk_proxy and m4.by_carrier with prefix_rule_calls kept
for #369-era comparison. Record the decision that proxy.in_ctx_code
stays outside M6 (Gate A plan M6 and rtk rows) and state that
rtk_parts, proxy_parts and model_typed keep their prefix rules. The
skill-usage README points Codex readers to the same fields and states
that declined items and legacy exec headers still read succeeded until
the adapter maps them.

Sources: rtk-ai/rtk v0.50.0 src/main.rs:68-90,3008-3042; openclaw/mcporter
v0.14.1 src/cli and docs/call-heuristic.md; mksglu/context-mode v1.0.169
src/exit-classify.ts:15-33; POSIX.1-2024 XCU 2.9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add a failing case: exec -- reads as an unresolved program

U1 design 2.2 reads only the POSIX exec form and marks any word that
starts with '-' after exec as unresolved, because bash's exec options
are not pinned. The shells disagree on `exec -- cmd`: GNU bash 5.2.21
runs cmd, while dash tries to execute "--" (probe on this host). The
kernel currently takes `--` as the end of options and reads qmd.
Observed before the fix: "exec -- qmd mcp" => ["qmd/qmd"].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Read any dash word after exec as an unresolved program

Follow U1 design 2.2: only the POSIX exec form is read, so a word that
starts with '-' after exec, `--` included, leaves the program
unresolved. bash 5.2.21 runs the command after `exec --` while dash
executes "--" itself, so neither reading is safe to assume. The failing
case from 1fcc9f23 now passes (node suite 107/107).

Also cover the inline npx server of an mcporter call
(openclaw/mcporter@93e0916c src/cli/ephemeral-target.ts:134-158): a
--server value that is an npx command line is an ad-hoc stdio server,
and another blank-holding name reads (other). These cases were added
after the implementation, so they are coverage, not failing-first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first Codex fixtures for shell call states

U1 design 6 fixtures (i)-(vii) run rollout records through
measure_codex_records, the Node bridge and the kernel's cli_lanes and
proxy. Record shapes follow openai/codex rust-v0.157.1 (36650394):
CommandExecutionItem and its snake_case status (protocol/src/items.rs
:199-285), exec end states (core/src/tools/events.rs:529-573: a
rejection is declined with exit -1) and the unified exec response
header (core/src/tools/context.rs:524-575). A rollout output never
carries the success flag (protocol/src/models.rs:2173-2182).

Before the adapter change 6 of 8 subtests fail:
- (iii) code-mode item declined: qmd succeeded 1, expected failed 1
  and not_executed 1; the same for a direct call's declined item;
- (iv) legacy exec_command with "Process exited with code 1":
  rtk_proxy succeeded 1, expected failed 1;
- (v) "Process running with session ID 3": qmd succeeded 1, expected
  unknown 1;
- (vi) no header and no item: qmd succeeded 1, expected unknown 1;
- (vii) local_shell_call running stack.json:280:
  codebase-memory-mcp succeeded 1 and downstream codebase-memory
  succeeded 1, expected unknown 1 in both.
(i) nested rtk proxy (proxy.calls 1, proxy.nested 1, succeeded 1) and
(ii) a failed item already pass. Must-stay controls pass: an exit 0
header, an exit line inside the output, a completed item that beats
a running header, a failed item with its header, and the aggregate
passing cli_lanes and proxy through with actors_with_success.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Map declined items and exec headers to Codex call states

U1 design 6: the Codex adapter now hands the kernel's callState what a
rollout shows about a shell call's outcome, so cli_lanes stops reading
declined or failed legacy commands as succeeded.

- A declined CommandExecution (openai/codex rust-v0.157.1 36650394
  core/src/tools/events.rs:562-573, exit -1) sets is_error and
  native_state "declined" on both result paths: the call's own
  function_call_output and the item's aggregated output. The kernel
  counts it failed and not_executed.
- A Bash-mapped call (exec_command, shell_command, shell or
  local_shell_call, chosen by call kind, never by output text) with no
  persisted item state is read by the unified exec header of
  core/src/tools/context.rs:524-548, and only by the lines before
  "Output:", in a linear line scan with a strict ASCII exit code.
  Exit 0 is success, another exit is_error. A running line (it wins
  over an exit line, unified_exec/process_manager.rs:1066-1071), a
  header with neither line or text without the header sets
  native_state "unknown": a rollout output never carries success
  (protocol/src/models.rs:2173-2182), and legacy history mode keeps no
  item (rollout/src/policy.rs:94-112). No shell, shell_command or
  local-shell handler exists at that revision, so those outputs read
  unknown unless an item state decides; apply_patch's "Exit code: N"
  text is not read.
- codex_call_name shares the MCP namespace rule between the call loop
  and the shell-call set, so an MCP tool named exec_command is not a
  shell call.

The failing-first fixtures from 4a7aa75a now pass (the three Python
suites: 160 tests OK). The header unit test
(test_exec_header_state_reads_only_the_pinned_header) was written
after the parser and is coverage, not failing-first. The lanes CLI
still writes nothing to stderr, and the Codex aggregate passes
cli_lanes and proxy through unchanged.
tools/skill-usage/README.md replaces the stage B sentence that said
these calls still read succeeded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Regenerate workflow SHA256SUMS for the U1 kernel and tests

The N1 fix and the CLI-lane reading changed child-usage.mjs and
test-child-usage.mjs; stage C leaves the kernel byte-identical to
0c421c66 (sha256 acc7bb51...). Regenerated with the documented
command, `sha256sum -- *.mjs *.js *.json > SHA256SUMS` in
examples/claude-native/workflows, and `sha256sum --check --strict
SHA256SUMS` passes for all 13 files. Only those two digests change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Re-register the U1 files in manifests/evidence.json

The hot-file protocol (docs/lanes.md:94-103) puts the shared manifest
edit in the branch's last commit and re-registers every changed file
that the manifest already lists, with host_receipts.register_file.
scripts/validate.py checks SHA-256 and byte counts for each of them
and failed before this commit ("SHA-256 mismatch", "byte count
mismatch" for the workflows README, child-usage.mjs,
test-child-usage.mjs, tests/test_token_measurement.py and
tools/skill-usage/README.md). The eight re-registered entries are the
two READMEs, SHA256SUMS, child-usage.mjs, test-child-usage.mjs,
skill_usage.py, test_skill_usage.py and test_token_measurement.py.
Only their sha256 and bytes change, and validate.py now passes
(status passed, 7986 hashed files).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for the U1 pivot loader and M4 repairs

Tests only, run against 7091a300 before any fix (U1 pivot brief D1, D6-D9,
D11; GPT-6 findings #8, #9, #11, #12, #13; Claude review R1, R2, R3, R9).

- D1 loader (test-child-usage.mjs, test_skill_usage.py): pin file contents,
  loadShellParser statuses (installed, not_installed, hash_mismatch),
  directory order, --shell-parser, commandInvocations null and cli_lanes
  parser_unavailable with proxy prefix_fallback, aggregate statuses,
  withShellTree freeing its tree, and the Node bridge awaiting the loader.
- D6: shell_script quotes argv with shlex.join, and -c is a script only for
  a shell; exec_header_state bounds the exit code to 9 digits (GPT-6 #11,
  #12 inputs verbatim).
- D7: outer-shell substitutions in a double-quoted run string are executed
  text (GPT-6 #8 verbatim), with single-quote, comment, backquote, sh, eval
  and ssh variants and must-stay controls that count once, not once per view.
- D8: n = 8000..64000 doubling for unclosed ((, $((, "$( ((, and ( << E
  runs, at most 2.5x per doubling and under 150 ms at 64000.
- D9: the stress check lets an exception fail it, includes an unquoted
  '$('.repeat(3000) input (NESTING_LIMIT), and a mutation control (a reader
  that throws for long input) shows the check now fails.
- R1, R3: a heredoc after a closed "$( )" keeps its reader; an escaped blank,
  semicolon or newline before a # is not a comment start.

Every expected value of the D7, R1 and R3 commands was run under GNU bash
5.2.21 and dash with a stub curl (and a stub ssh that runs sh on its stdin,
the remote login shell): both shells agreed on every count.

At 7091a300: node suite 115 passed, 31 failed (5 linear, 26 parser); D7 9
commands, R1 5, R3 3 fail with 0 != 1 on all three carriers; exec_header_state
raises ValueError on 5000 digits; shell_script flattens argv. The old D9
assertion passes when 7 of 7 stress inputs throw.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Quote Codex argv elements and bound the exec header exit code

U1 pivot brief D6 (GPT-6 findings #11 and #12).

shell_script joined an argv array with spaces, so a metacharacter inside one
element became shell syntax that was never executed: ["echo", "qmd; rtk proxy
qmd status"] counted one qmd call and one rtk proxy call. It now joins with
shlex.join (Python 3 shlex.join, the inverse of shlex.split), so each element
is one quoted word. The [shell, -lc, script] form (openai/codex rust-v0.157.1
codex-rs/core/src/shell.rs) stays the script itself, but only when the program
is a shell: -c of `echo -c ...` is one of echo's arguments.

exec_header_state passed the exit code text to int() whenever it was ASCII
digits, and CPython raises ValueError above 4,300 digits (int max str digits,
docs.python.org/3/library/stdtypes.html#int-max-str-digits), which stopped the
scan on one malformed header. The code is an i32 written in decimal (at most 10
digits with its sign, codex-rs/core/src/tools/context.rs:534-540); a code of
more than 9 digits is no native header and reads (False, "unknown").

The D6 tests of the previous commit pass: exec_header_state on 5000 digits,
the shell_script cases, and the three Codex bridge cases (local shell call,
code-mode item, `echo -c`). The existing Codex state tests still pass.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Scan unclosed (( in linear time: one paren table per text (D8, R9)

U1 pivot brief D8 (GPT-6 finding #9; Claude review R9). Every unclosed "((" or
"$((" made step() look ahead to the end of its line (indexOf the newline, then
count parentheses), so n = 8000, 16000 and 32000 of "((" took 246, 1218 and
4888 ms at 7091a300 (four times per doubling), and openers() filtered its whole
cut list once per heredoc operator.

parenClose(s) now matches every "(" of a text with its ")" in one stack pass per
line (bash(1) ARITHMETIC EVALUATION: (( )) and $(( )) count every ( and ) up to
the end of the line, which is classic parenthesis matching), and step() reads
its answer from the table. The last eight texts stay memoized so the scans that
alternate between a text and its slices do not rebuild it; executedTrace clears
the memo when a top-level analysis ends. openers() finds each operator's bounds
in the ascending cut list by binary search instead of filter and find.

Output is unchanged: executedText (both modes) and fetchKind of the base
kernel and this one agree on all 150,000 checks (the 243 covering commands, the
10,000 heredoc fuzz strings, and two seeded 20,000-string token corpora with
and without heredocs; 0 differences, 0 throws). Timings at n = 8000, 16000,
32000, 64000: "((" 3/6/13/27 ms, "$((" 4/9/19/40 ms, unclosed (( inside "$( "
5/8/18/38 ms, "(<<E" runs 8/14/30/64 ms, each within 2.5x per doubling and
under 150 ms at 64000. The D8 test allows 5 ms of timer noise on the 2.5x
bound (a linear scan showed 7.6 -> 19.4 ms once) and takes the best of five.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Read a # after an escaped blank or metacharacter as data (R3)

Claude review R3 of 3cb7c4f6. In a "$( )" frame step() treated a # as a comment
start whenever the previous character was a word break, ignoring whether that
break was escaped. `x="$(echo a\ #b)"; curl ...` therefore read `#b)"; curl ...`
as a comment: closeQuote and matchParen ran to the end of the command and the
`; curl` fell inside double-quoted data (shell_fetch 0, fetchKind null, one
unconfirmed mention). bash(1) COMMENTS: a word beginning with # starts a
comment, and an escaped blank, `;`, `(` ... or newline (a line continuation)
does not end a word (QUOTING: a backslash escapes the next character).

escapedAt(s, k) counts the backslashes before s[k]; an odd count means it is
escaped. It runs only for a # that follows a word break, and reads one run of
backslashes per such #, so the scan stays linear.

Verified with GNU bash 5.2.21 and dash and a stub curl: for each of the six
R3 inputs (escaped blank, escaped semicolon, backslash-newline, an even
backslash count before a real comment, a real comment inside "$( )", and a
top-level escaped blank) both shells ran the stub curl once, so each is one
executed fetch.

R3 test passes on all three shell carriers. Differential of the previous
commit's kernel and this one over 90,243 inputs (243 covering commands,
10,000 heredoc fuzz strings, and two seeded 40,000-string token corpora, with
and without heredocs; executedText in both modes and fetchKind): 228 differing
inputs, every one containing an escaped word break before a # (0 unexplained).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Add failing-first R1 shapes and the --shell-parser refusal test

Tests only. Extends the R1 cases of 62cb6935 with the shapes the fix has to
cover beyond the reviewer's repro, each run under GNU bash 5.2.21 and dash with
a stub curl (both shells agreed):

- a quote inside the substitution: FOO="$(echo "a b")" bash <<'EOF' and the
  ssh variant, where the naive word split cuts the quoted word at the inner
  blank (curl ran once; with cat as the reader it ran 0 times);
- a string that runs over lines before the operator: x="a\nb" bash <<'EOF' and
  the single-quoted variant, and a "$( )" that closes on a later line with text
  after its ")" (curl ran once each);
- two closed "$( )" words, and controls that already read correctly at
  f83f8367 (a separator before the operator, a backquote span, a nested
  "$( )" with its own heredoc, cat as the reader).

At f83f8367 the 11 executed shapes each fail on all three shell carriers (33
subtests: shell_fetch 0 != 1), the four controls pass. Also: an explicit
--shell-parser that names a directory with no install exits 2 with the reason
and no report (the --rtk-db and --exceptions convention), never a silent
fallback to the default.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Bound each heredoc by the cuts of its own frame level (R1)

Claude review R1 of 3cb7c4f6. openers() kept one cut list for every nesting
level, so the ( and ) that a double-quoted "$( )" adds also bounded the heredoc
operators outside it: in FOO="$(pwd)" bash <<'EOF' the operator's command
started at the closing quote, its reader resolved to the empty string, and a
body that bash reads as a script became data (shell_fetch 0, one unconfirmed
mention, fetchKind null; real bash and dash run the stub curl once).

step() now reports each cut with its frame (on.cut(i, frame): the "$(" frame
that a ( opens and a ) closes, else the frame the unit sits in, undefined for
the top level) and openers() keeps one ascending cut list per level; an
operator is bounded by the cuts of the level on top of the stack, found by
binary search. The cuts of a substitution bound the commands inside it and no
longer the command that contains it. commandWords() reads a "$( )" or a
backquoted span inside double quotes as one unit of its word (POSIX.1-2024 XCU
2.6.3: the substitution's text is tokenized on its own), so a quote or blank
inside it does not split the word: FOO="$(echo "a b")" bash <<'EOF' names bash.

R1 tests (62cb6935, 58502c60) pass on all three shell carriers, and the lane
reading of FOO="$(pwd)" bash <<'EOF' with qmd in the body counts one qmd call.
The shapes below are documented limits, pinned by their own test: a command that
a quote or "$( )" carries over lines (x="$(cat <<'A' ... \n)" bash <<'B'
and x="a\nb" bash <<'EOF') has its head on an earlier line, so this line alone
gives the operator no reader and the body reads as data (fetch_mentions_unconfirmed
1, status incomplete: a possible fetch, never a lost one). A first attempt cut
the line at the closing quote's word end; measured on the fuzz corpus it named
the wrong owner as often as the right one (ssh host cat "a\nb" bash <<EOF), so
it was dropped.

Differential of f83f8367 and this commit over 90,243 inputs (243 covering
commands, 10,000 heredoc fuzz strings, two seeded 40,000-string token corpora,
without and with heredocs; executedText in both modes, fetchKind and the M4
confirmed and unconfirmed counts): 17 inputs differ, all with a quote,
backquote or "$( )" before a heredoc operator (0 unexplained); no input lost
mass (confirmed plus unconfirmed); 16 keep both counts, one moves a fetch from
confirmed to unconfirmed. That one, bash -s -- "$(pwd)"bash <<EOF with an
unquoted-delimiter body, now names bash as the reader (correct) and reads the
body as source, where a substitution inside single quotes is data: the
"$( )"-free twin reads C=0 U=2 at 7091a300 as well (real bash runs curl once:
the outer shell expands an unquoted heredoc body before the reader sees it, a
limit that predates R1 and is unchanged).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Read a double-quoted run string in two views: outer expansion (D7)

U1 pivot D7, GPT-6 finding #8. N1 read a double-quoted string that a shell runs
only as that shell's input, so a substitution the OUTER shell runs while it
expands the word was lost whenever the inner shell reads it as data: a quoted
heredoc, single quotes, a comment. bash -c "cat <<'EOF'\n$(curl https://example.org)\nEOF"
went from fetchKind 'fetch' (cf3fb72e) to null with shell_fetch 0. bash 5.2.21 and
dash run the stub curl once: the expanding shell runs each unescaped "$( )" and
backquote before the shell it starts reads anything (POSIX.1-2024 XCU 2.2.3
Double-Quotes, 2.6.3 Command Substitution). Two views now: outer bodies first,
each as commands of their own, then the inner shell's read of the string with
each outer span replaced by a placeholder word (its output is unknown), so a
substitution both views could see counts once. A backslash-escaped \$( ) or
backquote is no outer span and stays the inner shell's (bash -c "echo \$(curl u)"
runs curl once, in the inner shell). Single-quoted run strings have no outer
expansion and are unchanged.

outerSpans() is the one scanner of a double-quoted string's substitutions (escape
pairs skipped, "$((" arithmetic not a span but the substitutions inside found,
NESTING_LIMIT and unterminated spans as before); quotedData() now consumes it, so
the data view and the run view cannot disagree. The ssh remote ranges (marks)
cover the literal text between spans, not the local substitutions.

D7 tests (62cb6935) pass on all three carriers: 13 executed, 5 inner and 2
twice-run commands, 2 data commands; the qmd lane test counts one call.

Evidence, all against GNU bash 5.2.21 and dash with stub curl/wget/ssh (ssh runs
its arguments with sh -c, or reads stdin as the remote login shell), counting
stub calls against the kernel's confirmed count (shell_fetch + loopback):
- compositional oracle, 360 commands (4 double-quoted run carriers x 10 contexts
  x 4 atoms, two-atom strings, single-quoted carriers, heredoc readers x
  delimiters x bodies): base 7091a300 301 exact, 59 under, 0 over; 43b9d947 329,
  31, 0; this commit 353, 7, 0. The 7 left are one class, a shell-read heredoc
  with an unquoted delimiter whose body has a substitution inside single quotes
  (the outer shell expands the body first), next commit.
- random valid-bash fuzz, 40,000 candidates, 7,883 accepted by bash -n and dash -n
  with equal stub-call counts under both (361 with a fetch): base, f83f8367,
  43b9d947 and this commit agree on the same 7,789 (98.81%) and the same 94
  disagreements (12 over: word glue and a trailing backslash; 82 under: remote
  ssh commands and glued words), all present at 7091a300.
- differential of 43b9d947 and this commit over the covering commands and three
  fuzz corpora (90,243 inputs): 13 inputs lose confirmed mass, every one invalid
  bash in at least one shell; of the 24 differing inputs without HTTP-library or
  gh tokens, 21 are invalid, 2 valid inputs move closer to real bash and 1
  (xcat &&bash -c "'$(curl u)'") counts a substitution that a failing && never
  reaches, which no static reading models.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Add failing-first tests: an unquoted heredoc body is expanded first

Tests only. D7 for a heredoc body (U1 pivot D7, same defect class as GPT-6 #8):
with an unquoted delimiter the shell that reads the command line expands the
body before the shell that reads the heredoc sees it, so each unescaped
substitution runs in the outer shell whatever its quotes or a comment say
(bash(1) Here Documents; POSIX.1-2024 XCU 2.7.4), quotes are literal, and a
backslash escapes only $ ` \ and a newline. 20 commands, each run under GNU bash
5.2.21 and dash with stub curl and ssh: curl ran once for 17 (a single-quoted
substitution, a comment, nested quotes, a substitution over lines, a <<- body
with tab indents, arithmetic, an ssh reader, a run string in the body, and
controls that already read correctly), twice for one (an outer and an inner
substitution), and never for two (a quoted delimiter passes the body as is).

At 67a4acbb 9 of the 17 fail on all three shell carriers (shell_fetch 0 != 1,
one unconfirmed mention: bash <<EOF with echo '$(curl u)', the FOO="$(pwd)" and
ssh readers, a comment, nested quotes, a multi-line substitution, <<-, and
arithmetic); the rest and the data commands pass.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Expand an unquoted heredoc body a shell reads before reading it

The heredoc form of D7 (GPT-6 #8: the outer shell runs what it expands before
the inner shell sees the text). With an unquoted delimiter the shell that reads
the command line expands the body first: each unescaped $( ) and backquote runs
there whatever its quotes or a comment say, quotes are literal in a body, and a
backslash escapes only $ ` \ and a newline (bash(1) Here Documents; POSIX.1-2024
XCU 2.7.4). resolveHeredocs() analyzed a body that a shell (or in inlineHttp
mode an interpreter) reads only as that reader's source, so bash <<EOF with
echo '$(curl u)' read as data although real bash and dash run curl once, and
so did a substitution in a comment, nested quotes inside it, one over lines,
arithmetic, and a <<- body: the same 9 shapes as the failing-first commit
b6f3b454. R1 made the FOO="$(pwd)" bash <<EOF form read this way too, so this
also removes the accident that had hidden the limit there.

The body now has two views, sharing D7's outerSpans/withoutSpans/outerBody: the
outer spans first, as commands of their own (heredocs inside them resolved
there), then the reader's source with each span replaced by the placeholder word
and its own escapes applied (heredocText). A quoted delimiter is unchanged
(nothing is expanded), and so is a data heredoc such as cat <<EOF, which keeps
the substitutions its regular expression finds. The ssh remote ranges cover the
body's literal text, not its local substitutions.

The heredoc test of b6f3b454 passes on all three carriers (17 once, 1 twice, 2
data commands); tests.test_token_measurement passes (59). Evidence, real bash
5.2.21 and dash with stub curl/wget/ssh, kernel confirmed count against stub calls:
- compositional oracle, 441 commands (D7's 360 plus escaped, comment and
  nested-quote bodies): base 7091a300 368 exact, 73 under; 67a4acbb 420, 21;
  this commit 441 of 441 exact, 0 under, 0 over.
- grammar fuzz, 6,000 valid nested commands (statements, sequences, substitutions,
  backquotes, single and double quotes, run strings, heredocs with unique
  delimiters) that bash and dash ran with equal stub-call counts (3,049 with a
  fetch, 1,597 with a run string, 1,471 with a heredoc): base 5,788 exact
  (96.47%), 212 under, 0 over; 67a4acbb 5,970 (99.50%), 30 under, 0 over; this
  commit 5,975 (99.58%), 25 under, 0 over. All 25 are one older gap, a run string
  right after a backquote (echo `bash -c "curl u"`), whose text is invisible to
  the raw detector as well; fixed next.
- random valid-bash fuzz, two seeds of 40,000 candidates (7,883 and 7,838 valid,
  361 and 374 with a fetch): base, 67a4acbb and this commit agree on every
  input.
- differential of 67a4acbb and this commit over 90,243 inputs: 924 differ, 20 with
  a lower confirmed count, of which 19 are invalid bash or valid only with a
  heredoc-at-end-of-file warning, and the one clean input (a backquote span that
  holds bash <<EOF) moves from 3 to 0 confirmed where real bash and dash run
  curl 0 times.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Add failing-first tests: a run string after an opening backquote

Tests only. RUN_QUOTED anchors a shell, eval or ssh word at the text start, a
blank, ; & | or (, but not at a backquote, so bash -c "curl u" inside `...` read
as double-quoted data. The fetch was lost, and the raw detector, which anchors
curl itself and not the quote, counted no possible fetch either (C=0 U=0). Found
by the grammar fuzz of 2a33ef90: all 25 commands left under-counted there were
this one shape.

8 commands run under GNU bash 5.2.21 and dash with stub curl and wget: the stub
ran once for each (bash -c and sh -c with double or single quotes, eval, an ssh
string, a backquote inside a single-quoted run string, an escaped backquote in a
double-quoted one, and two controls that already read correctly: a blank after the
backquote and $( ) in its place) and never for echo `echo "curl u"` (data).

At 2a33ef90 the 6 backquote-adjacent shapes fail on all three shell carriers
(shell_fetch 0 != 1, no unconfirmed mention: 19 failures with fetchKind).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Anchor a run string at an opening backquote too

RUN_QUOTED and RUN_HTTP_CODE anchored a shell, eval, ssh or interpreter word at the
text start, a blank, ; & | or (, but not at the backquote that opens a `...`
substitution, so bash -c "curl u" inside backquotes read as double-quoted data: the
fetch was lost and no possible fetch was counted either (C=0 U=0). bash(1)
Command Substitution: a `...` body is shell text with its own command positions,
as a $( ) body is; the anchor class now holds the backquote (one character in
each regular expression; no new alternative, so no new backtracking).

The 6 backquote shapes of the failing-first commit (66550644) pass on all three
carriers; tests.test_token_measurement passes (60).

Evidence, real bash 5.2.21 and dash with stub curl/wget/ssh, kernel confirmed count
against stub calls: the 441-command oracle stays 441 exact; the grammar fuzz that
found the gap is now exact on every input, 6,000 of 6,000 (3,049 with a fetch,
1,597 with a run string, 1,471 with a heredoc; 2a33ef90 5,975 exact, 7091a300
5,788, 96.47%) and, on a second seed, 11,999 of 11,999 (base 11,608, 96.74%, 391
under; 0 over throughout). Differential of 2a33ef90 and this commit over 90,243
inputs: 216 differ, 1 with a lower confirmed count, an unterminated string (invalid
bash).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Add a pinned, verified tree-sitter-bash loader that fails closed (D1)

U1 pivot D1. The CLI-lane reading is to run on tree-sitter-bash, the grammar
OpenAI Codex uses for the same job (openai/codex rust-v0.157.1 36650394
codex-rs/shell-command/src/bash.rs; tree-sitter-bash = "0.25" at
codex-rs/Cargo.toml:522, both read at the tag). This commit adds the seam and the
gate; the AST reading itself is the next stage.

shell-parser.pin.json pins the install: web-tree-sitter 0.27.0 and
tree-sitter-bash 0.25.1 with their npm integrity values (checked against the
registry's dist.integrity), upstream tags and commits (v0.27.0 6070dbfe =
npm gitHead; v0.25.1 names a06c2e44 while the package was published from
80132668, five minutes earlier, recorded as a note), the sha256 of the six
files the loader reads, and the install command (npm install --prefix <dir>
--ignore-scripts --no-audit --no-fund --save-exact). loadShellParser(dir)
resolves the directory as the argument (the new --shell-parser flag), then
CHILD_USAGE_SHELL_PARSER, then the ecosystem tools directory under HOME, reads
each pinned file once, compares its sha256 and the lockfile's version and
integrity values with the pin, and executes only those bytes: the runtime module
is imported from a private 0600 copy of the hashed bytes and the two wasm files
are handed over as bytes, so the code that was hashed is the code that runs.
Results carry no path: { ok, versions, wasm_sha256 } or { ok: false, reason }
with not_installed (directory, a pinned file or the lockfile missing),
hash_mismatch, load_error (an unreadable file, the pin file, init) and
not_loaded (this process never awaited the loader, so a forgotten await is not
mistaken for a missing install). A failed load leaves no parser, whatever an
earlier call did; a successful one reuses the initialized runtime. withShellTree()
frees every tree in a finally.

Fail closed: commandInvocations() returns null without a parser and measurement
reports cli_lanes { status: parser_unavailable, reason }, keeps the prefix rule for
the rtk proxy carrier (proxy.rule prefix_fallback, invocations and in_ctx_code
null) and never falls back to the text scanners for lane counting; M4 stays text
based. A measured cli_lanes carries status measured and the parser record (versions
and the two wasm sha256 values); aggregates report measured, incomplete (mixed
actors, with the counts), parser_unavailable or not_measured, and sum proxy
counters null-aware. The CLI awaits the loader; a --shell-parser that cannot be
honored exits 2 with the reason (like --rtk-db), any other missing install only
leaves cli_lanes unavailable. The Codex bridge awaits the loader too.

Interim, until the AST reading lands: with a verified parser loaded,
commandInvocations() still runs the scanner-based lane layer of 3cb7c4f6, so every
existing lane fixture reads as before; the gate, the status fields and the seam
(withShellTree) are final.

Tests (62cb6935, 58502c60 first, run failing at 7091a300): the pin's contents;
not_installed for an absent directory, a missing pinned file and a missing
lockfile; hash_mismatch for one flipped byte in the bash wasm, a modified
web-tree-sitter.js that is never imported, a modified package.json, another
integrity value and another version in the lockfile; load_error for an unreadable
file; directory order; --shell-parser; a good install loading again after
failures; unavailable and mixed aggregates; the bridge in both states. The lane
fixtures load the parser first and skip with a message on a host without an install.
tests: node suite 149 passed; tests.test_token_measurement, test_skill_usage and
test_child_usage_suite 175 passed. D9's stress check reports the TypeError the
lane checks threw before the parser was loaded (threw: TypeError x8), which the
old assertion would have passed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Add failing-first timing tests for quote-heavy scripts (D8)

Tests only. D8 asks the M4 text scanners for linear time; the unclosed "((" input
of GPT-6 #9 is fixed (5e814331), but profiling a realistic 346 KB script (4,000
lines of echo "step N: $(date)" >> log; qmd search "term N" | head) at e5238f39
showed the same quadratic shape from another term: 51% of the time in the
run-string regular expression and 33% in scanQuotes, both from one read of the
whole text built so far per quote (the regex, and the string flattening that
indexing a concatenation rope forces). A 42 KB script takes 47 ms, 84 KB 108 ms,
172 KB 467 ms, 346 KB 2,078 ms and 694 KB 8,236 ms a scan, and the kernel scans
each shell call several times.

Five doubling checks (n = 8000..64000, best of five, at most 2.5x per doubling
plus 5 ms of noise, 64,000 under 1.5 s): a run of quoted words, a run of
double-quoted substitutions in inlineHttp mode, a run of `bash -c "x"` strings,
a run of words with a # inside, and a long script of echo, substitution and pipe
lines through fetchKind. At e5238f39: 61, 230, 902, 5,861 ms; 168, 651, 3,728 ms;
991, 4,486 ms; 9, 32, 120, 459 ms; 89, 392, 1,930 ms (the series end where a run
passes 1.5 s). All five fail.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Read only the tail of the built text at each quote (D8: linear scripts)

scanQuotes() tested RUN_QUOTED (and in inlineHttp mode RUN_HTTP_CODE) against the
whole text built so far at every quote. Reading out.s for that flattens the string
concatenation and scans the full prefix each time, so a script with n quoted
words cost n squared. CPU profile of a 346 KB script (4,000 lines of echo "step N:
$(date)" >> log; qmd search "term N" | head) at e5238f39: 51% in the run-string
regular expression, 33% in scanQuotes itself (the flattening), 10% in GC. The
five timing tests of 9da57414 failed on it.

scanQuotes now keeps the end of its output in `tail`, cut back to 512 characters
whenever it passes 1,024, updated at every write (put and putTrace; quotedData
builds into its own buffer first), and reads only that: the # test uses the last
character of the tail, the emptiness test out.p.length. Once a cut has been made
the two detectors run without their start-of-text alternative (a cut is no start
of text). Limit, stated in the code: a run keyword more than 512 characters
before its quote, after a text that has grown past 1,024, is missed and the
string reads as data. Only a single ssh option word that long can put it there
(ssh -oProxyCommand=<1500 characters> host "curl u": fetch before, null after;
100, 400, 520 and 700 characters read the same), since the words between a
shell, eval or ssh and its string are short options and one host.

Timings (best of five, ms), n = 8000, 16000, 32000, 64000: quoted words 61, 230,
902, 5,861 -> 8, 15, 31, 69; double-quoted substitutions (inlineHttp) 168, 651,
3,728 -> 15, 31, 67, 140; run strings 991, 4,486 -> 38, 83, 173, 358; words with
# 9, 32, 120, 459 -> 3, 6, 14, 37; a long echo/substitution/pipe script through
fetchKind 89, 392, 1,930 -> 18, 34, 71, 154. Realistic scripts, executedText: 84
KB 108 -> 37 ms, 346 KB 2,078 -> 100 ms, 694 KB 8,236 -> 205 ms, 1.4 MB 393 ms
(2x per doubling); the node suite runs in 12 s instead of 41 s.

Output is unchanged: executedText (both modes) and fetchKind of e5238f39 and this
commit agree on all 1,084 shell texts of this repository (822 .sh files and fenced
shell blocks of .md files, 1.66 MB, largest 82 KB, 83 over 4,000 characters), on
the 243 covering commands and the three fuzz corpora (90,000 inputs), and on 600
synthetic long scripts (0 differences, 0 throws); the 441-command oracle stays 441
exact, and the 6,000-command grammar fuzz stays 6,000 exact (real bash 5.2.21 and
dash with stub curl).

Also fixed while rewriting the call: the ssh remote offset test listed the
separators before RUN_QUOTED's keyword but not the backquote that 296c4f1d added,
so in echo `ssh host "qmd get a"` the qmd call read as local; it now reads
qmd/qmd remote (fixture added; the same input read local at e5238f39).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Flip one real byte in each pinned file in the loader tests

The loader tests changed the bash grammar wasm by reading it as UTF-8 and
appending a NUL, which rewrites the whole binary: the test passed because the
file differed, not because one byte did. It now reads a Buffer and flips bit 0 of
the middle byte in place (same length, so only the hash can tell), and does the
same for each of the six pinned files in turn: every one gives hash_mismatch,
leaves no parser (commandInvocations returns null, the scanners do not step in) and
runs no unverified code (the modified web-tree-sitter.js sets a global if it is
ever executed; it is not). The missing-file and missing-lockfile cases now also
assert that no parser stays loaded. Node suite: 160 passed (149 before).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Add a failing-first test: a run keyword past the tail window

Tests only. The brief's D8 says a work budget "marks the command unclassifiable for
M4 instead of throwing". 1d77c34c bounds scanQuotes to the last 512 characters of
its output, but an ssh option word long enough to push `ssh` out of the window
(in a text past 1,024 characters) drops the run string silently: the fetch in
ssh -oProxyCommand=<1500 characters> host "curl u" is read as data with no
possible fetch counted either (shell_fetch 0, unclassifiable 0, unconfirmed 0),
which a budget must not do.

The test: option words of 100, 400 and 700 characters read as before (the fetch
confirmed, unclassifiable 0); 1500 characters counts one unclassifiable operation
(remote_fetches 1, no confirmed shell fetch); the count is once per command and
only for the command that held the keyword (two later quoted commands add nothing);
a long printf with hundreds of words and quotes but no run keyword adds none.

At 1d77c34c the two 1500-character cases fail on all three shell carriers
(unclassifiable 0 != 1: 6 failures); the 100, 400 and 700 cases and the printf
control pass.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Count a run string the tail window lost track of as unclassifiable

The brief's D8 fallback for input past a work budget is "marks the command
unclassifiable for M4", not a silent miss. 1d77c34c bounds scanQuotes to the last
512 characters of its output; an ssh option word long enough to push `ssh` out of
the window (in a text past 1,024 characters) dropped the run string with no trace:
ssh -oProxyCommand=<1500 characters> host "curl u" read as shell_fetch 0,
unclassifiable 0, unconfirmed 0.

scanQuotes now notes, outside quotes, when a shell, eval, ssh or su word (and its
blank) has been written since the last command separator (a last-letter pre-check
keeps it off the hot path). When a cut has left the tail without that word and
without a separator, the next quote that does not read as a run string is counted
once in windowMisses, which countFetches adds to unclassifiable (an operation of
unknown kind: remote_fetches 1, no confirmed shell fetch). The keyword is noted at
write time because the discarded text cannot say whether a word was quoted: a first
version that looked for the word in the discarded text also fired on 4 real scripts
of this repository (adoption/bootstrap-linux.sh, bootstrap-macos.sh,
launchd-agents.sh and a memory-stack README block), where separators inside quoted
data are blanked and a word such as sh sat in a long quoted list.

The test of 0b2b5778 passes on all three carriers (100, 400 and 700 characters
read as before; 1500 counts one unclassifiable, once for the command, and adds
nothing for later commands or for a long printf with no run keyword).

The counter is zero on every corpus: complete m4 results of the pre-window kernel
(e5238f39) and this one are identical on the 243 covering commands, 10,000 heredoc
fuzz strings, two 40,000-string token corpora and all 1,084 shell texts of this
repository (822 .sh files and fenced shell blocks, 1.66 MB), 2 tools each; the 600
synthetic long scripts agree; the 441-command oracle is 441 exact and the grammar
fuzz 11,999 of 11,999 exact against real bash and dash. Realistic scripts, executedText:
84 KB 50 ms, 346 KB 125 ms, 694 KB 285 ms (2,078 and 8,236 ms at e5238f39).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Keep 150 ms for the brief input only; loosen the extra timing shapes

The brief's D8 requires at most 2.5x per doubling and under 150 ms at n = 64,000 for
'(('.repeat(n) + 'qmd'. The timing tests added alongside it (a run of $((, the same
"$( ((, a run of ( << E, and the quote-heavy shapes) had inherited that absolute
bound. Three of them run 64-80 ms at 64,000 against 150 ms on this host, thin
headroom for a slower CI runner. Only the brief's input keeps 150 ms (it runs about
30 ms); every other shape keeps the 2.5x ratio (plus 5 ms of timer noise, best of
five) and takes an absolute bound of 1.5 s at 64,000, which is still far below the
quadratic behavior they replaced (the same shapes take 1.9 to 10 s and fail the ratio
at 7091a300).

Run against the base kernel (7091a300) with this test file, all ten timing shapes
fail (for example (( 313, 1,219, 5,109 ms; quoted words 62, 239, 928, 6,211 ms), and
against this head all ten pass (for example (( 3, 7, 17, 32 ms; quoted words
8, 15, 32, 71 ms; run strings 42, 95, 205, 416 ms).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Retry the doubling timing rounds so one pause cannot fail a linear scan

A direct run of test-child-usage.mjs at da2d578d failed one assertion, "a run of unclosed
(( at n = 8000, 16000, 32000, 64000 takes [4, 7, 27, 31] ms": the 32,000 reading was 27 ms
where the scan takes about 15, so the ratio to the 7 ms before it (3.9x) broke the 2.5x
(+5 ms) bound. The same suite had passed inside the Python run seconds earlier. A flaky
timing check is a CI hazard, so the doubling measurement now takes the best of five per
size and, when the ratio still fails, up to two more rounds and the elementwise minimum of
all of them: a pause cannot fail a linear scan, while a quadratic one fails every round.

Checks on the final file: six consecutive runs and two runs beside six busy CPU loops, all
160 passed; against the base kernel 7091a300 all ten timing shapes still fail (the retry
does not hide quadratic behavior).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Add failing-first tests for the AST command-position layer (D10, D11)

The GPT-6 review of the scanner-based lane reading (3cb7c4f6) found ten
command-position defects (#1-#7, #10 here; #8, #9, #11-#13 were repaired
earlier) and the Claude review five more (R4-R7). This commit holds their
probes before any repair, so each one fails for its defect.

- tests/test_token_measurement.py: one test per finding, with the GPT-6
  inputs verbatim, asserting what the finding states (lane calls, proxy
  calls, exclusions, unresolved programs, downstream keys, call states),
  not a display string. Also parse_errors (D2), the closed name vocabulary
  (D5) and mcporter's help/version tokens (R6: checked against
  openclaw/mcporter@93e0916c src/cli.ts:137-145 and flag-utils.ts:34-41,
  the code already matches; the test pins it).
- test-child-usage.mjs: a program name is emitted only for a lane
  executable or a name the reading interprets (GPT-6 #10).
- tests/test_command_position_oracle.py (D10): a deterministic generator
  and a hand-written probe list run under REAL bash 5.2 with env -i, a
  PATH of logging stubs, a temporary HOME and cwd and a 5 s limit; the
  multiset of lanes that ran must equal the lanes commandInvocations reads.
  Against the scanner layer: 38 of 99 probes and 59 of 457 generated
  commands disagree with the run.

Every expected value is the run of real bash and dash under stub
executables (the oracle covers each input), not what this kernel says.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Read shell text with the tree-sitter-bash AST for CLI lanes (D2-D4)

Replaces the scanner-based command-position layer (simpleCommands,
resolveInvocation, analyzeScript) with a walk of the tree-sitter-bash
tree, the grammar openai/codex rust-v0.157.1 codex-rs/shell-command
src/bash.rs uses for the same job. The option tables, runner maps and
mcporter grammar are unchanged and now operate on word values.

- D2: every `command` node counts wherever it sits (substitutions inside
  strings, arithmetic, arrays and unquoted heredoc bodies, subshells,
  compound and control statements, function bodies); array elements, case
  patterns, [[ ]] operands, function names and the words of any non-lane
  program never do. ERROR nodes and what is under them are skipped and the
  call counts once in cli_lanes.parse_errors.
- D3: wordValue() returns a literal or null: unquoted escapes, '...', the
  double-quote rule (a backslash goes only before $ ` " \ and newline),
  $'...' decoded as bash does, concatenations; any expansion, glob, brace
  expansion is unknown. A tilde prefix keeps the word known for program
  identity (only the directory changes: ~30 lane invocations by ~/path in
  136,361 real commands) but not as a script.
- D4: bash|sh|dash|zsh|ksh -c reads the first operand after the options
  (-n, -D and -o noexec run nothing; `--`, `-` end the options); a shell
  with no -c reads standard input, so a heredoc or here-string attached to
  the last command of a list, pipeline or negation, through env, timeout,
  nice, nohup, stdbuf, rtk proxy (not xargs, which consumes it), has its
  body read; eval and ssh join their words as the callee does; rtk proxy
  resolves its program with exec semantics (command and exec run nothing,
  time is unresolved, an expansion in its one argument is unresolved).
- Backquotes and outer substitutions of an unquoted heredoc body and of a
  double-quoted script are read from the raw text with the kept M4 helpers
  (tree-sitter-bash leaves backquotes in heredoc_content and mis-reads
  some bodies); a script with an expansion is read with a placeholder.
- The grammar folds the next line into a command in some shapes (an
  unquoted == before ;, &&, | or a newline; a trailing blank before a
  newline; `<<'EOF' 2>&1 | tail`): 732 of 136,361 real commands with no
  error and 163 with one. A separator ERROR or an unescaped newline inside
  one command's words starts a new command.
- simple counts assignment-only statements, declarations and each loop,
  if and case as well as commands (POSIX.1-2024 XCU 2.9.1, 2.9.4), so the
  ambiguity of existing fixtures is unchanged.

Fixtures: EMPTY_CLI gains parse_errors (D2), and `ssh host bash -s <<EOF`
now also reads the remote bash as a command of its own (ssh(1): the words
after the destination run remotely). Every other existing lane fixture
passes unchanged.

Against real bash 5.2: the oracle agrees on 457 of 457 generated commands
(581 lane runs) and 99 of 99 probes. Over 136,361 distinct real commands
the old and new readings agree on 136,167; the differences are old false
positives, unknown wrappers (setsid, bwrap) and grammar defects. Still
failing here by design, fixed next: D5 vocabulary (5 tests) and R7 states
(7 tests); the node suite fails only on the D5 program-name check.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Re-expect the closed name vocabulary in the lane fixtures (D5)

U1 pivot D5 and GPT-6 #10: commandInvocations returns `program` only for
a lane executable or a name this reading interprets itself (a wrapper, a
shell, eval, ssh, rtk), and an mcporter downstream key is emitted only for
the stack's own servers; every other name-shaped string (a host, an id, a
package, `linear`) reads null or (other). The old expectations that showed
`-/echo`, `-/git`, `-/pytest` or `@linear` are wrong under D5, so they are
changed here, before the code: 7 node checks and 3 Python tests fail
against the walker of 1d35df12 with the observed values recorded in the
log (`program` still holds "my-private-host.example", the downstream key
still holds "linear" and "call_PRIVATE").

The selector-form coverage the `linear` cases gave is kept with servers of
the stack (`socraticode`, `serena`, `jcodemunch`, `ai-memory`, `qmd`,
`headroom`, `context-mode`, `codebase-memory`), read by name in every form.
Sources of the closed set: manifests/stack.json:280 (codebase-memory),
:407 (context-mode), :629 and :966 (socraticode); the rest is the
coordinator's list in the pivot brief.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Emit only fixed names: a closed program set and mcporter server set (D5)

GPT-6 #10: `program` carried any name-shaped basename (a host, an id, a
package) and an mcporter downstream key any name that passed a character
class, so the no-id, no-host output contract held only by luck.

- `program` is set only for a lane executable or a name the reading
  interprets itself (the wrappers, the shells, eval, ssh, rtk); every other
  program reads null.
- An mcporter downstream key is one of codebase-memory, context-mode,
  jcodemunch, serena, socraticode, qmd, headroom or ai-memory (the
  stack's servers: manifests/stack.json:280, :407, :629, :966 and the
  coordinator's list), else (other), (http), (stdio) or (unresolved).

The tests of the previous commit now pass (node 162 of 162; the Python
name-vocabulary test and the three re-expected fixtures).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Expect an interrupted counter in lane and downstream rows (R7)

Claude review R7: a Claude Code result whose toolUseResult.interrupted is
true read as succeeded. The command started and was cut short, so it is a
state of its own. The shared fixture helpers (lane_row, downstream_row and
the Codex cli_lane_row and downstream row) now carry `interrupted: 0`; the
lane and downstream rows of the kernel do not have the key yet, so the
tests that compare whole rows fail (see the log of this run) until the next
commit adds the counter and the state.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Read not-executed calls where the client records them; add interrupted (R7)

Claude review R7: the marker "The user doesn't want to proceed" was matched
against the row's toolUseResult, where it was never observed; it opens the
result CONTENT. Observed on this host's Claude Code transcripts (122,648
Bash results in 4,355 files, count-only): the rejection content (25 rows,
toolUseResult "User rejected tool use"), a row-level toolDenialKind on
every client denial (permission-rule 750, user-rejected 46, cancelled 1,
automode-unavailable 1), the classifier text "The server-side auto mode
classifier gave no verdict" (its own text says the check failed, not the
action), and 209 rows of a host hook's refusal text ("This agent is
isolated") that carry no toolDenialKind and stay failed (open: its meaning
is the hook's, not the client's).

- callState: not_executed when the row has a toolDenialKind, or the content
  opens with <tool_use_error>, "The user doesn't want to proceed" or the
  classifier text, or toolUseResult names a hook denial, a permission
  denial or a rejection.
- toolUseResult.interrupted === true is its own state, `interrupted`
  (none of 25,506 observed object results, so it is a synthetic fixture),
  with a counter in lane and downstream rows; it no longer reads succeeded.

The R7 test of the first commit and the fixtures of the previous commit
pass (measurement and Codex modules: 188 tests).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Remove the lane-only marks plumbing from the M4 text machinery

The scanner-based lane reading asked executedTrace for `marks` (the raw
range of each ssh body and the offset of each command substitution of a
data heredoc body). The AST walker reads both from the tree and the raw
text, so nothing passes marks any more: the parameter and every branch
that wrote to it (resolveHeredocs, scanQuotes, outerBody, quotedData) and
rawRange are removed. The M4 readings are unchanged: the complete m4
object of measureTranscript (Bash and ctx shell carriers), executedText in
both modes and fetchKind are identical between the kernel before the
walker (f1ed98ac) and this one over 136,361 distinct real commands, the
243 covering commands, and 2 x 40,000 fuzz inputs (0 differences, 0
exceptions).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Test the recovery of commands the grammar joined to the previous one

The walker of 1d35df12 starts a new command at a separator ERROR or an
unescaped newline inside one command's words, because tree-sitter-bash
0.25.1 reads what follows a command as its arguments in these shapes
(measured on this host's transcripts: 732 of 136,361 distinct real
commands with no error and 163 with one): an unquoted == or =~ before ;,
&&, | or a newline, a redirection after a here-document operator followed
by | or ;, and a multi-line shape found in the real corpus. This commit
adds the tests that pin it:

- tests/test_command_position_oracle.py: 12 recovery probes whose expected
  lanes are what REAL bash 5.2 ran (12 of 12 agree; 10 carry a parse
  error, 2 none).
- tests/test_token_measurement.py: the same shapes through
  measureTranscript, with parse_errors counted once for the calls whose
  tree has an ERROR, and two controls that are not boundaries (a
  backslash-newline continuation and a comment).

Mutation control (recovery disabled in a scratch copy of the kernel): all
12 oracle probes fail (real bash ran the lane, the kernel read none or
one fewer, e.g. `echo ==; qmd get a` ran qmd, read []) and 9 of the 12
measurement cases fail with the observed values ({} != {'qmd': 1}).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Add failing-first tests: a line that begins with a backslash

Found by the 14,461-command run of the oracle generator over more seeds
against real bash: a line that begins with a backslash (`\ls`, the alias
bypass; `\markitdown a.json`) reaches tree-sitter-bash 0.25.1 as a word that
begins with the newline, so the line joins the command before it, and when
it is the first line of a here-document body the grammar leaves the body
node without it and adds its words to the operator's arguments (which hid
that the owner is a shell). A here-string that precedes such a line was fed
to the last command of the joined node instead of its own.

Five shapes are added to the oracle's recovery probes and to the
measurement recovery test; real bash ran every lane in them. Against the
kernel of 907ac35f the two tests fail with the observed values (recorded in
the log of this run), e.g. `{ timeout 5 repomix` + newline +
`\markitdown --flag; }` read {'repomix': 1} where bash ran both, and the
shell heredoc whose first body line starts with a backslash read {}.

Also hardens the oracle generator: it drops a text bash rejects
(`bash -n`), keeps a function definition and its call in one group,
does not put `!` or a heredoc on the right of a pipe (`x | ! y` is not
POSIX; a reader that ignores its pipe kills the writer with SIGPIPE), and
writes data heredocs to /dev/null.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Read a line that begins with a backslash as a command of its own

Repairs the shapes of the previous commit's tests:

- commandSegments starts a new command at a word that begins on a new
  line (the grammar puts the newline of a `\cmd` line at the start of its
  word) and drops that newline from the word's value.
- A here-document operator's arguments are only those on its own l…
seathatflowsinourveins added a commit that referenced this pull request Sep 30, 2026
… E2E (call states, M15, ledgers, D7, joins) (#528)

* Add failing-first tests for hook blocks, loaded servers, JSON shapes

Failing-first fixtures for PR-A items 1, 5 and 4 (U2 design sections 1-3,
with the review-u2 findings that cite them), in the shapes a count-only
scan of this host's 2.1.283/2.1.284 transcripts found.

Item 1: inserted stays the row count M12 reads (E2E README M12 row);
new inserted_blocks, blocks_by_event, claimed_by_event, plain-stdout
claims only on UserPromptSubmit, UserPromptExpansion, SessionStart and
PostModelSwitch (code.claude.com/docs/en/hooks, "Exit code 0", fetched
2026-09-29), other hook_* rows, the sibling-row and sibling-child
negatives, the M12 positive-control key, and a fold that keeps an
over-long <Event>:mcp__<server>__<tool> hook name at server level.
Item 5: loaded references per server and actors per server (RUNBOOK
'Loaded, never called'). Item 4: JSON and uniform-tabular counts per
carrier after the ctx_execute(_file) code echo (mksglu/context-mode
v1.0.169 src/server.ts:1511-1527) and the Read cat -n numbers, unknown
counted, never 0 (TOON v4.1.1 packages/toon/README.md:199), and the
section-1 per-actor figures the scope extract lists.

At fe9f511b: by_hook has (other) for the long MCP name, the new keys
are absent (inserted_blocks, loaded, large_json, shell_web: None), the
node check fails 1 of 176; every control passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Count hook blocks and claims by event, loaded servers and JSON shapes

Implements PR-A items 1, 5 and 4 (U2 design sections 1-3) with the
review-u2 findings that cite them; makes 06826ff3's tests pass.

Item 1: hook_context adds inserted_blocks, blocks_by_event,
claimed_by_event, claimed_plain_stdout (only UserPromptSubmit,
UserPromptExpansion, SessionStart and PostModelSwitch, and only stdout
that does not both start with { and end with }: code.claude.com/docs/
en/hooks "Exit code 0"), other_hook_rows and hook_names_folded; an
over-long <Event>:mcp__<server>__<tool> name keeps a validated
<Event>:mcp__<server> key instead of '(other)'. inserted, claimed,
with_marker and by_event keep their #432 meaning. The aggregate adds
actors_with_insertion.

Item 5: measurement.loaded (references per server, called or not);
the aggregate adds loaded, loaded_actors and loaded_not_called_actors.

Item 4: every sizes object counts large_json, large_uniform_keys,
large_uniform_flat and large_shape_unknown. The payload is read after
the carrier's wrapper: the ctx_execute(_file) code echo, rebuilt by the
exported ctxEcho (mksglu/context-mode v1.0.169, 442f1eb6,
src/server.ts:1511-1527), and the Read cat -n line numbers, both with
linear scans; what cannot be read is unknown. uniform_flat follows TOON
v4.1.1 packages/toon/README.md:199. The aggregate adds the section-1
per-actor figures: shell_web_large_results_per_actor,
actors_with_mcp_call, actors_with_non_ctx_mcp_call (per-actor
mcp_attempted) and actors_with_ctx_results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for advisor iterations and cache tier fields

Failing-first fixtures for binding decision B9 (per-child usage reads
usage.iterations[]: advisor_iterations apart, totals including them,
usage.complete false when iterations cannot be read) and U2 correction
1 (cache_creation_5m/1h, speed, service_tier, inference_geo per message
and per advisor iteration, null when absent, never 0).

Shapes follow a count-only scan of this host's transcripts and the beta
Messages API reference (platform.claude.com/docs/en/api/beta/messages/
create, BetaIterationsUsage), the advisor tool doc ("Usage and
billing") and the refusals-and-fallback doc ("Billing and rate limits"),
fetched 2026-09-29. Cases: tiers both nonzero, combined counter only
(split null), fast speed, us geo, an advisor iteration with its own
model and split, a multi-iteration row whose top-level cache_creation
object is the first iteration's split (3,706 of 3,706 such rows here),
streamed rows, adjacent windows, an empty list, and the reasons usage
is incomplete: top level unequal to the message entries, fallback,
compaction, a non-object or non-numeric entry, a successful advisor
result without its entry, an advisor call without a result, an advisor
entry without a model. Run-mode control: the run stays complete, exit 0.

At 0eb8771c: the new fields are absent, every unreadable case reads
complete true, the aggregate reads (True, 503) for (False, 503); node
177/179 with the control passing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Read usage iterations: advisor apart, tier fields, split per entry

Implements binding decision B9 and U2 correction 1 in transcriptUsage
(measurement.usage: sweep actors[] and run-mode children[] and
superseded_attempts[] lanes.measurement); makes 2bf419de's tests pass.

usage.iterations[] (beta Messages API BetaIterationsUsage; advisor tool
doc "Usage and billing"; refusals-and-fallback "Billing and rate
limits"; fetched 2026-09-29): message entries are the executor's and
sum to the top-level counters; advisor_message entries are listed in
advisor_iterations (message_ordinal, own model, counters, 5m/1h split,
tier fields null) and counted in the window of the first row carrying
them; advisor_totals and totals_including_advisor sit beside the
unchanged executor totals. Each message adds cache_creation_5m/1h and
speed, service_tier, inference_geo (null when absent). The split is the
message entries' own splits added up, because on multi-iteration rows
the top-level cache_creation object holds the first iteration's split
(3,706 of 3,706 rows on this host); the top-level object only when a
row carries no entries.

usage.complete is false, with iteration_issues naming the reason, for
an unread entry (fallback_message, compaction, other types, non-object
or non-numeric), top-level counters unequal to the message entries, a
successful advisor result without its entry, an advisor call without a
result, or an advisor entry without a model. The aggregate sums them.
Run-mode exit codes are unchanged. On this host's 4,667 transcripts
with messages: 143 become incomplete (105 advisor results without
usage, 48 calls without a result, 1 fallback, 1 inconsistent); 2,070
advisor iterations; message splits equal the combined counter on
201,150 of 201,151 known.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Quote the hooks reference and the M12 row exactly in kernel comments

Comment-only: a quote check against the fetched bytes (sources.json in
evidence/artifacts/pra-u2-differential-20260929) found two kernel
comments whose quotation marks enclosed text with the source's markup
removed. The hooks reference sentence now keeps its backticks, the E2E
README M12 row is paraphrased without quotation marks, and the Read
count names the text results it counts (133 more hold a non-text
block). No code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add U2 host scans, upstream sources and differential allow-list

evidence/artifacts/pra-u2-differential-20260929 (new): the count-only
scans that verified B9's usage.iterations structure, the hook row shapes
of item 1 and the large-result shapes of item 4 on this host's
transcripts (producers and their outputs verbatim: key names, enum
values and counts only, no ids, paths or text), the two checks that run
the committed kernel's transcriptUsage over the same store, sources.json
(every fetched upstream document: URL, fetch time, sha256, the exact
sentence each rule rests on, verified against the fetched bytes), and
expected-changes.json, the allow-list the later differential stage
consumes (review-u2 medium finding): the paths this stage adds, and the
two pre-existing paths it changes on purpose, by_hook['(other)'] (the
MCP hook-name fold) and usage.complete (B9: 143 of 4,667 transcripts
with messages here). Registration in manifests/evidence.json follows in
the unit's last commit (docs/lanes.md hot-file protocol).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name the documented source of the Read line numbers

The Read cat -n strip of item 4 had no named source (final advisor
review). code.claude.com/docs/en/tools-reference ("Read tool
behavior", fetched 2026-09-29, sha256 cc0eda98) states that Read
"returns the contents with line numbers"; the exact form (spaces,
digits and a tab before every line) stays marked as observed client
behavior, 11,467 of 11,467 text Read results over 5,120 B on this host.
sources.json gains the entry; the kernel comment cites it and notes
that a PARTIAL view notice, which carries no number, reads as unknown.
Comment and evidence only; no code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Pin the advisor usage output as ID-free in a sweep control

Must-stay control, added after the implementation and not failing-first:
a lanes sweep over a child transcript whose message carries an advisor
call reads the advisor iteration (actor advisor_iterations 1, aggregate
advisor_iteration_count 1) and prints neither the message id nor the
advisor server-tool call id nor the directory (the unit rule: never
emit call ids in a published field). Mutation check: a scratch kernel
that adds the message's advisor call ids to each advisor record prints
the call id once in the same sweep; this kernel prints it 0 times.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for callState templates, causes and accessor

U2 item 7, the explicit callState extension the review's dependency
finding asks for (review-u2 medium: U1's rule lacks templates the M14
fixtures need, and exports no per-call not_executed flag).

What U1 already covers, from a count-only scan of this host's
transcripts (scans/call-states.py, 5,153 files): every observed row
that did not run carries a toolDenialKind, a <tool_use_error> content
or the user-rejection content. The user template is fully read. The
config text "Permission to use ... has been denied." (8 rows) and the
cancelled text "Not run: the response that made this tool call ..."
(1 row) are read only through their denial kind; the interrupt marker
"[Request interrupted by user for tool use]" is a user text block after
a user-rejection result (37 of 37), never a result.

The fixtures without a denial kind fail at ebcca292: cli_lanes qmd
not_executed 0 != 1 for the config text (content, and toolUseResult
only), the Not run text, the marker as a result (the design's
constant), the rejection only in the toolUseResult and "Permission
for" only in the content; and cu.callState is not a function. Cases
labelled (control) read not-executed at the base already.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Read not-executed templates in both fields and name each cause

U2 item 7, the callState extension (review-u2 medium, U1 dependency).
callState and a notExecuted accessor are exported; every template is
read at the start of the result content and of the toolUseResult
string (after "Error: "); the config text "Permission to use <tool>
... has been denied", the client's "Not run: the response that made
this tool call" and the design's constant "[Request interrupted by
user for tool use]" join the list.

A call that did not run gets its cause in the vocabulary of the OTel
tool_decision event (code.claude.com/docs/en/monitoring-usage, "Tool
decision event" and "Tool result event"): a template's cause unless it
is 'other', then the toolDenialKind, then the template's 'other', then
a native declined state. Hook denials carry permission-rule (809 of
809 here), so the text decides first; the built-in safety check's
"Permission for this command was denied" carries permission-rule too
(11 of 11), an automatic decision the doc's "config" source names.

scans/not-executed-delta.mjs compares U1's rule, copied verbatim from
ebcca292, with the new one over this host's 228,531 calls: 1,228 did
not run under both and 0 differ. cli_lanes.failed keeps U1's meaning
(is_error; not_executed a subset): tests/test_skill_usage.py:970-974,
outside this unit's paths, pins it for the Codex declined fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for M14 call states, the ledger and M15

U2 design sections 4-5 (PR-A item 7 and protocol M15) with the review's
corrections, as failing-first fixtures.

M14 (E2E README M14 row; preregistration thresholds.M14 states): 20
calls project callState onto the sealed names. A call with a transcript
result is executed, rejected (by source, OTel tool_decision vocabulary,
code.claude.com/docs/en/monitoring-usage), invalid or
cancelled_with_result, never "no result"; cancelled_or_unfinished is
only the calls with no result; decided is null; mcp_states and
calls_without_result keep their published meaning (review mediums).

Ledger (AA:319): callLedger and --call-ledger in both modes, create-only
with mode 0600, refused inside any git work tree, on an existing file or
a missing directory; stdout stays free of call, session and agent ids,
and run-mode stdout of call and session ids (review lows).

M15 (E2E README M15 row; preregistration thresholds.M15): classes in
context-mode v1.0.169's exact shapes, echo-free templates tested before
the echo (review high), the version notice stripped, the client's MCP
texts, the Codex approval denial (rust-v0.157.1 mcp_tool_call.rs:1612),
and rates under binding decision B2.

At 486da6ac all 8 fail: call_states None, m15 None, aggregates
undefined, cu.callLedger is not a function, and --call-ledger an
unknown option (exit 2), including the refusal test's accepted control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Project call states onto the sealed M14 names per actor and server

PR-A item 7 (U2 design 4.2-4.4, 4.7 with the review's corrections):
measureTranscript adds call_states, aggregateMeasurements sums it, and
m14State(call, result) is exported. One callState per call feeds both
call_states and cli_lanes, so there is one classifier.

Names follow the E2E README M14 row and preregistration.json
thresholds.M14.criteria.states. A call with a transcript result is
executed (succeeded, failed, interrupted), rejected (by source: config,
hook, user, other, declined, the OTel tool_decision vocabulary of
code.claude.com/docs/en/monitoring-usage), invalid or
cancelled_with_result, never "no result" (review medium);
cancelled_or_unfinished is only the calls with no result; decided is
null. mcp_states keeps the #432 rule and calls_without_result its
published meaning (review medium): test_skill_usage.py:828-829 and
:936-939 pin both, and deriving mcp_states from callState would move a
declined call without a result from unfinished to failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Classify MCP errors by M15 class per server with B2 rate bounds

Protocol M15 (E2E README M15 row; preregistration.json thresholds.M15):
measureTranscript adds m15.by_server, aggregateMeasurements sums it and
computes the rates again, and mcpErrorClass is exported.

Order (review-u2 high finding): the anchored templates context-mode
v1.0.169 returns before any code echo (boundary server.ts:1196-1201,
returned at :2118-2119; the deny firewall; Runtime/Batch/Index/Search
errors; storage and usage errors; batch timeout; fetch failures; search
throttling) are tested on the raw text; the echo is required and
stripped only for ctx_execute(_file) outputs after execution (timeouts,
exit-classify.ts exits, module errors, indexed exits under both labels:
review lows). The outdated-version notice (server.ts:892-896) is
stripped first. The client's MCP texts are read on every server: not
connected, the documented idle timeout (code.claude.com/docs/en/mcp),
and the MCP SDK 1.30.1 "Connection closed" and -32602 validation error;
the Codex approval denial is rust-v0.157.1 mcp_tool_call.rs:1612.

Rates follow binding decisions B1-B2: rate counts the six classes,
every new class (a new class is our misuse until reproduced) and every
unknown class or outcome; rate_lower_bound counts unknowns as
successes; threshold_sensitive flags bounds on both sides of 0.01;
every_error_classified replaces the design's status (B1: no new
status); rate_upper_bound keeps the design's ceiling, descriptive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Write the private per-call ledger behind --call-ledger

PR-A item 7's ledger (AA:319, the per-child list of tool_use_ids with
each call's state; U2 design 4.5 with the review's corrections).

callLedger(transcript, {window}) returns one record per attempted call:
session_id, owner, tool_use_id, tool, server, M14 state and cause,
native_status, background, sandbox, code_mode and the M15 class, keyed
by (session_id, tool_use_id) as the E2E README M14 row requires. owner
is the row's agentId, 'main' with a sessionId alone, and null when the
row has neither (a Codex bridge row: U3's correction 1 supplies
conversation.id and the owner kind through the bridge).

--call-ledger PATH works in both modes: the sweep adds the published
actor ordinal, run mode the journal label and whether the attempt was
superseded. The path is checked before any work: an existing directory
outside every git work tree (skill_usage.py write_out's precedent,
widened), and a new file, created with 'wx' and mode 0600 (review low:
an existing file's mode is never taken for 0600). Refusals and failed
writes exit 2 with no report. Stdout is unchanged by the flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add item 7 and M15 host scans, sources and differential entries

Evidence for U2 stage 2 (count-only scans of this host's Claude Code
transcripts; no id, path, command or result text in any output).

- scans/call-states.py now also counts the client's MCP texts (16 not
  connected, 4 idle timeouts, 1 Connection closed, 1 -32602 error), the
  built-in "Permission for" and "Not run" templates, and where the
  interrupt marker sits (37 of 37 user text blocks after a
  user-rejection result).
- scans/kernel-m14-m15.mjs runs the kernel over every transcript:
  0 call_states invariant violations over 4,349 actors, and
  calls_without_result equal to cancelled_or_unfinished for each. The
  context-mode server's 34,427 calls classify completely (0 unmatched,
  0 echo_mismatch), rate 0.0028 and ceiling 0.0044. Servers outside the
  stack's vocabulary fold into (other), as U1's closed vocabulary does.
- sources.json pins the documents and code the M14 and M15 rules rest
  on, each quote checked verbatim against the fetched bytes: the
  monitoring, MCP and env-vars docs; context-mode v1.0.169 server.ts
  lines, exit-classify.ts and session/db.ts (byte-identical to the
  installed copy); codex rust-v0.157.1 mcp_tool_call.rs; and the MCP
  SDK 1.30.1 files, equal to the registry tarball (its sha512 is the
  npm integrity).
- expected-changes.json lists stage 2's added paths and the one
  pre-existing path that can change (cli_lanes not_executed; 0 calls
  here), with what stays unchanged by design and why.

The kernel and test comments now cite the retained scan's counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add a failing-first test for the M15 threshold at rounding precision

Every kernel share is rounded to four places, so a server just over
the M15 threshold (preregistration.json thresholds.M15, at most 0.01)
can print as 0.01: 202 of 20,100 calls is 1.005%. The B2 flag
threshold_sensitive must therefore compare counts, not rounded rates.

At 7810ed70 the first case fails: 190 infrastructure errors and 12
unknowns of 20,100 calls give (rate, rate_lower_bound,
threshold_sensitive) = (0.01, 0.0095, False) where True is right. The
two controls (exactly 1%, and both bounds over) pass at the base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Compare the M15 threshold with counts, not the rounded rate

threshold_sensitive (binding decision B2) now tests errors * 100 >
attempted for each bound: preregistration.json thresholds.M15 allows
at most one infrastructure-class error in 100 calls, and a share is
rounded to four places, so 202 of 20,100 calls (1.005%) printed as
0.01 and read as within the threshold. rate, rate_lower_bound and
rate_upper_bound stay rounded like every kernel share; a grader
compares the published counts (infrastructure_errors,
new_class_errors, unknowns, attempted), never the rounded rate.

scans/kernel-m14-m15.json (run with the kernel before this change)
keeps its values: recomputed from its counts, every server's flag is
the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for item 6, RTK M1 fields and B8 parts

What: fixtures for PR-A item 6 (empty and wait-notice returns,
background tasks without a terminal notification at the final message,
final_return per actor and in aggregates, run-level exit 1), the rtk-claude
M1 numerator (rtk_parts.eligible_call_states and
observed_covered_succeeded_calls), and binding decision B8 (heredoc and
arithmetic calls parsed, unknown calls counted once and left out of the
eligible-part denominators, unknown_call_share and the 5% status rule).

Why: U2 design sections 6 and 7 and binding decisions B5 and B8. The
background-task sources (Bash backgroundTaskId, Monitor and Workflow taskId,
async Agent agentId), the terminal <status> set and TaskStop come from
count-only scans of this host's transcripts; code.claude.com/docs/en/
tools-reference ("Background commands") says a foreground subagent's
background command stops at its final response. rtk-ai/rtk v0.50.0
(tag 1d87b8e7) src/discover/registry.rs rewrite_command_precompiled
returns None for any heredoc or $(( command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Mark empty, wait-notice and pending-background returns incomplete

What: PR-A item 6. returnQuality reads a child's journal result (a
string, the E2E { answer, evidence } schema by its answer, or any other
value by its leaves); an empty or wait-notice return is an issue, so the
child and its run are incomplete and the CLI exits 1. finalReturn gives
every measurement a final_return (the final assistant row's kind, whether
its text is empty or a wait notice, and the background tasks still
running at that row); a child that left one running is incomplete too.
Aggregates count actors by final_return status and final kind.

Why: #381 AA-PLAN item 6 and U2 design section 6, with three evidenced
departures. waitNotice is the design's rule as a linear word scanner (no
backtracking pattern; CodeQL js/redos has flagged this kernel), plus the
typographic apostrophe. A value with no string at all is not a wait
notice (the design's leaf rule made { ok: true } one). Background tasks
come from Bash backgroundTaskId, Monitor and Workflow taskId and async
Agent agentId, and end only at a <status> of completed, failed, killed
or stopped or at a TaskStop that names them: count-only scans of this
host found Monitor events without a status and no notification after a
TaskStop of a Bash, Monitor or Workflow task (88 of 88). The client
stops a foreground subagent's background command at its final response
(code.claude.com/docs/en/tools-reference, "Background commands").
final_return is not_applicable without provider message ids (the Codex
bridge's rows) and unobserved past the window end (the review's finding
on the sweep).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Parse heredoc and arithmetic calls into RTK parts; add M1 success

What: binding decision B8 and the rtk-claude M1 fields. shellParts reads
the command unit by unit with U1's frame machine (step(): quotes,
escapes, comments, $(( )) and (( )), "$( )" and backquote frames, and
the redirection forms of & and |), treats here-document bodies as data
(any delimiter word, heredocWord), and returns null only for text it
cannot read. rtk_parts counts an unclassifiable call once in
unknown_calls and adds none of its parts to the eligible, ineligible,
observed or replayed counters; explicit-rtk, log/find and proxy counters
read the command's own parts, so M-R3/M6c's zero counter keeps a
violation in a call M-R1 cannot classify. New fields: unknown_call_share,
eligible_call_states (M14 states of calls with an eligible part) and
observed_covered_succeeded_calls (the per-child-task M1 numerator,
B5). status is incomplete only when unknown calls exceed 5% of calls,
on the counts, and an aggregate applies that rule to its sums.

Why: B5, B8, U2 design 7.3. bash(1) Here Documents and DEFINITIONS;
POSIX.1-2024 XCU 2.7.4. rtk-ai/rtk v0.50.0 (tag 1d87b8e7)
src/discover/registry.rs rewrite_command_precompiled returns None for
any heredoc or $(( command, so the eligible parts of such calls, unknown
before, now count as observed uncovered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Begin a heredoc body at a newline of its operator's frame depth

What: shellParts reads the bodies a newline begins only for the
here-documents whose operators sit at that newline's frame depth or
deeper, so a newline inside "$( )" no longer begins an outer body, and a
here-document a substitution leaves open is read after the enclosing
line. At most 64 here-documents wait at once (a command with more is
unknown), which keeps the scan linear. Two fixtures pin the two cases,
and two timing shapes join the linear-time checks.

Why: run on 2026-09-29 under GNU bash 5.2.21 and dash, the outer body of
"cat <<'EOF' && x=$(echo a<newline>echo b)" is the line after the
substitution (the previous rule returned null, an unknown call), and
"x=$(cat <<EOF)" takes its body from the lines after its line (bash
warns "command substitution: 1 unterminated here-document"). bash(1)
Here Documents; POSIX.1-2024 XCU 2.7.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Document the U2 measurement fields and extend the sweep limits

What: the workflows README gains a "U2 measurement fields" section
(hook_context rows, blocks and claims; loaded references and actors;
large-result shapes and the section-1 figures; the usage tier split and
advisor iterations; the M14 call_states table; the private --call-ledger
contract; the M15 class table and its bounds; item 6's return_quality and
final_return), the B8 part rule and the rtk-claude M1 fields in the
--rtk-check paragraph, the new exit-1 conditions, the ledger in the sweep
example and the unobserved final return of an actor past the window end.
The skill-usage README says which U2 fields Codex actors carry and which
parts belong to the Codex adapter. The sweep's limits string gains the
same U2 limits.

Why: U2 design section 11 and review-u2 (the run-mode stdout contract,
final_return not applicable on Codex actors, the Codex ledger owner);
every rule cites its source beside it in child-usage.mjs and in
evidence/artifacts/pra-u2-differential-20260929/sources.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Regenerate the workflows SHA256SUMS after the final kernel edit

What: the checksums of child-usage.mjs and test-child-usage.mjs, the two
files U2 changed in this directory; the other twelve are unchanged.

Why: U2 design section 10, step 6 (sha256sum -- *.mjs *.js *.json >
SHA256SUMS, then sha256sum --check --strict SHA256SUMS, which exits 0);
the byte-identity check in this README reads the file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add the U2 differential, B8 property check and item 6 scans

What: differential.mjs (both kernels as their own CLIs, every differing
leaf path classified against expected-changes.json, which gains stage 3
and machine-readable paths), combine-chunks.mjs, the sweep differentials
over W_AA without and with --rtk-check and the run-mode differential over
232 runs (0 unexpected paths in each), shell-parts-property.mjs (2,000
generated commands bash accepts, all split as built), parts-compare.mjs
(the two splitters over W_AA's 30,673 Bash calls with bash -n on every
disagreement), waa-invariants.py (the design's real-window invariants)
and the count-only item 6 scans (background-task sources, notification
statuses, TaskStop, final rows). The README gives the figures.

Why: U2 design 10 steps 3 to 5 and review-u2's differential finding;
binding decisions B7 (W_AA), B8 and B5. Counts only: no ids, paths,
host names or command text; the sweep ran on a frozen copy of the
window's transcripts, deleted after the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Cite the Monitor tool's own stop rule in the item 6 comment

What: the comment above finalReturn quoted the "Background commands"
rule for monitors too; it now quotes the Monitor tool section's own
sentence (the monitors of a stopped subagent "stop with it") and says
what both mean for a return: it did not wait for the task's outcome. The
workflows SHA256SUMS is regenerated after this final kernel edit
(sha256sum --check --strict exits 0). A comment only: no output changes.

Why: code.claude.com/docs/en/tools-reference, "Monitor tool" (the
fetched copy's sha256 is the one in sources.json); a citation must say
what its source says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add stage-3 sources and fold host server names in the invariants

What: sources.json gains the four documents stage 3's rules cite: the
tools reference's "Background commands" and "Monitor tool" sections (the
same fetched bytes as its Read entry), rtk-ai/rtk v0.50.0
src/discover/registry.rs (tag commit 1d87b8e7: has_heredoc and
rewrite_command_precompiled), POSIX.1-2024 XCU 2.7.4 and the installed
GNU bash 5.2.21 manual's Here Documents, each with sha256 and the
sentences a rule rests on. waa-invariants.py folds server names outside
the stack vocabulary into (other), as scans/kernel-m14-m15.mjs does, and
waa-invariants.json is regenerated from a fresh sweep of the same window
(every other figure unchanged; the copy of the window's transcripts was
deleted after the run).

Why: the evidence README says sources.json holds every source a U2 rule
cites, and published evidence names only the stack's servers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Register the U2 files in manifests/evidence.json

What: the new hashes of the six listed files U2 changed (child-usage.mjs,
test-child-usage.mjs, the workflows SHA256SUMS and README,
tests/test_token_measurement.py, tools/skill-usage/README.md) and the 44
files of evidence/artifacts/pra-u2-differential-20260929, added with
scripts/host_receipts.py register_file. component_matrix.py --write and
new_host_grand_list.py --write change nothing else.

Why: the hot-file protocol (docs/lanes.md): every shared hot-file edit in
the branch's last commit; python3 scripts/validate.py exits 0
(hashed_files 8220, status passed). At merge, take main's manifest and
re-register these files as the protocol says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for grading M15 on the ceiling

What: the review's high finding as tests. A server whose every call
fails with the client's "No such tool available" <tool_use_error> (an
invalid call) must read over the 0.01 threshold; each class outside the
named groups (policy_deny, remote_fetch, search_throttle, rejected,
invalid, cancelled_with_result, and a class no list names) alone at 2 of
100 calls is over; the invoked command's own exit stays excluded; the
rounding cases gain the ceiling, its count verdict over_threshold and
unassigned_errors, the residual of the ceiling after the named groups.

Why: the U2 design 5.3 grades on rate_upper_bound until a dated
amendment assigns the other classes, and the Gate A binding decisions
apply the harder-to-pass reading; the frozen M15 row
(evidence/artifacts/token-adoption-e2e-20260926/README.md, M15;
preregistration.json thresholds.M15) excludes only the invoked
command's non-zero exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Test each unassigned M15 class in its own failing-first test

What: the per-class ceiling checks (policy_deny, remote_fetch,
search_throttle, rejected, invalid, cancelled_with_result, a class no
list names, and the excluded invoked_command_exit) move out of the
reviewer-input test into their own test, so they run, and fail, on the
kernel before the fix instead of sitting behind the first assertion.

Why: a test that never failed proves nothing; the U2 design 5.3 grades
on rate_upper_bound until a dated amendment assigns these classes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Grade M15 on the ceiling until Amendment 4 assigns the classes

What: finishM15Row publishes unassigned_errors (the residual of the
ceiling after the infrastructure, new and unknown groups) and
over_threshold (the ceiling's count against 0.01: errors * 100 >
attempted), and threshold_sensitive now compares the two published
bounds, rate_lower_bound and rate_upper_bound. rate and
rate_lower_bound keep their meaning as descriptive figures.

Why: the review's high finding. A server whose every call fails with
the client's "No such tool available" <tool_use_error> read rate 0,
rate_lower_bound 0 and threshold_sensitive false. The U2 design 5.3
grades on rate_upper_bound until a dated amendment assigns the other
classes, the Gate A binding decisions apply the harder-to-pass reading,
and the frozen M15 row (token-adoption-e2e-20260926 README.md M15)
excludes only the invoked command's non-zero exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Document M15 grading on rate_upper_bound in README and limits

What: the workflows README splits the old "not infrastructure" row into
the six unassigned classes (graded as errors until Amendment 4 assigns
them) and the excluded invoked_command_exit, and states that grading
reads rate_upper_bound with over_threshold as its count verdict; the
sweep's U2 limits string says the same.

Why: the README and the limits string pointed graders at rate, which
scores unassigned classes as successes (the review's high finding); the
U2 design 5.3 and 12 name rate_upper_bound as the M15 field until the
dated amendment, and the frozen M15 row excludes only the invoked
command's own exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Regenerate the workflows SHA256SUMS after the M15 grading fix

What: the child-usage.mjs line of the workflows SHA256SUMS takes the
kernel's new sha256; every other line is unchanged, and
`sha256sum --check SHA256SUMS` reads OK for all 14 files.

Why: the kernel changed in the M15 grading fix and its limits string;
the SHA256SUMS file is the workflows directory's integrity record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Remove the apostrophe that ended the U2 limits string early

What: the M15 sentence of the single-quoted U2_LIMITS string said
"the invoked command's own exit"; its apostrophe closed the string and
the kernel failed to load (node --check: SyntaxError: Unexpected
identifier 's'; 282 Python tests failed with it at 01af2f62). The
sentence now reads "a non-zero exit of the command it ran".

Why: 1a189bdb introduced the quote; the kernel must parse. Found by
the acceptance run of tests.test_token_measurement and the other
suites (repair-round log, not published).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Regenerate the workflows SHA256SUMS after the limits quote fix

What: the child-usage.mjs line takes the sha256 of the kernel that
parses again; `sha256sum --check SHA256SUMS` reads OK for all 14 files.

Why: the previous regeneration (01af2f62) recorded the kernel with the
broken U2_LIMITS string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Re-run the U2 differentials and M15 scans at the repair kernel

What: expected-changes.json gains a repair-round stage that allows the
two new m15.by_server keys (unassigned_errors, over_threshold);
waa-invariants.py folds succeeded, invoked_command_exit and
unassigned_errors and computes the ceiling as finishM15Row does. The
three differentials (sweep over W_AA 254,003 new paths, twelve rtk
windows 286,988, run mode 770,476; 0 unexpected, exit 0 each), the W_AA
invariants, the ledger check and scans/kernel-m14-m15.json (5,283 files)
are re-run at kernel sha256 c1d9ad16; their effects equal the earlier
run's. The README records the new counts and the real-data case of the
finding (plugin_context-mode: 2 invalid calls, rate 0, ceiling 1).

Why: the kernel's M15 output changed in this repair round, so the
recorded differential and M15 evidence had to describe the final kernel
(U2 design 10; review-u2's differential finding: an explicit
allow-list, equality everywhere else).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Re-register the repair-round files in manifests/evidence.json

What: the new sha256 and byte counts of the twelve files the repair
round changed (child-usage.mjs, the workflows README and SHA256SUMS,
tests/test_token_measurement.py and eight files of
evidence/artifacts/pra-u2-differential-20260929), written with
scripts/host_receipts.py register_file; component_matrix.py --write and
new_host_grand_list.py --write change nothing else.

Why: the hot-file protocol (docs/lanes.md): every shared hot-file edit
in the branch's last commit. python3 scripts/validate.py exited 1 on
the twelve hash mismatches before and exits 0 after (hashed_files
8220, status passed). b2dd1eb7, the earlier registration, now sits
mid-branch; at merge, take main's manifest and re-register every file
the branch changed, as the protocol says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name the pre-fix kernel and an unexamined cause in the M15 texts

What: the reviewer-input test's docstring names b2dd1eb7, the kernel
before the fix, instead of "the base kernel" (fe9f511b has no m15 at
all); the evidence README no longer gives a cause for the 2 lines the
store scan could not parse, since the scan prints only their count.

Why: published evidence and test text state only what was observed
(docs/acceptance-evidence-policy.md: claims quote the recording's own
output).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Re-register the two reworded files in manifests/evidence.json

What: the new sha256 and byte counts of tests/test_token_measurement.py
and evidence/artifacts/pra-u2-differential-20260929/README.md after
54774c0f, written with scripts/host_receipts.py register_file;
component_matrix.py --write and new_host_grand_list.py --write change
nothing else.

Why: the hot-file protocol (docs/lanes.md) keeps the shared hot-file
edit in the branch's last commit; python3 scripts/validate.py exits 0
(hashed_files 8220, status passed). b2dd1eb7 and 962687be, the earlier
registrations, sit mid-branch; at merge, take main's manifest and
re-register every file the branch changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for the U3 Codex measures

PR-A U3 step 1 (research-u3.design.md commit 1, amended by its review).
Tests only. At the base fe9f511b the 10a and 10b tests fail; the tests of
later design commits are marked pending (expectedFailure), so a marked
test that starts passing fails the run until its commit lifts the marker:

- 10a marker split and codex_hook_context (CodexMarkerSplit; the retained
  sub-agent test now asserts the split);
- 10b role grouping (CodexRoleGroups);
- 10g route values (commit 4) and spawn join states (commit 5), with the
  integer fork_turns form, a per-field route basis, follow-ups and an
  ID-free check with negative controls (CodexRouteValues, CodexSpawnJoin);
- the Codex normalization (commit 6), 10e nested attribution (commit 7)
  and legacy spans (commit 8) (CodexCommandNormalization,
  CodexCodeModeAttribution).

Must-stay controls in the same commit: the configured route, E1 and E4 at
this base, the legacy lane counters, model call ids after an exec, the
wait and wait_agent carriers, the unscanned outer exec code (F2), the
retained isolated fixture's direct items, one actor per spawn case.

Sources: openai/codex rust-v0.157.1 (36650394) protocol.rs:772-779,
:2901-2916, :3116-3191, :3293-3350, :3534-3544, :4382-4390; items.rs:364-370;
models.rs:958-993; hook_runtime.rs:128-154, :848-872; spawn.rs:126-130,
:216-226, :253-299; child_config.rs:196-253; multi_agents_spec.rs:217-241,
:646-650; policy.rs:94-112; description.rs:19-24; globals.rs:36-48;
user_shell.rs:199, :453-480; the frozen launch matrix
(preregistration.json seed-binding-1 to 5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Split the Codex marker into inherited and injected

PR-A item 10a (research-u3.design.md section 1, with the review's kinds
and window findings). The legacy `marker` keeps its meaning (the marker in
any developer message). Additive fields:

- marker_inherited / marker_injected: the marker per content item, in a
  record copied from the parent (ordinal below
  subagent_history_start_ordinal) or in the session's own record;
- marker_injected_kinds: injected items by content kind, from
  internal_chat_message_metadata_passthrough.content_item_kinds, (none)
  when the kinds do not align with the items or are not strings;
- group marker_inherited_sessions, marker_injected_sessions,
  marker_inherited_only_sessions and marker_injected_by_kind;
- measurement.codex_hook_context {inserted, with_marker, inherited,
  inherited_with_marker} over developer items of kind
  hooks.additional_context: own items in the window of their record,
  copied items once, in the window of the child's first own record, so
  adjacent windows add up; groups sum it.

The kernel's hook_context (Claude attachments) is unchanged and reads
zero for Codex; the README says so.

Sources: openai/codex rust-v0.157.1 (36650394)
core/src/context/hook_additional_context.rs:15-22,
core/src/hook_runtime.rs:128-154, :848-872,
context-fragments/src/fragment.rs:35-53, protocol/src/models.rs:958-993,
protocol/src/models/item_metadata.rs:5-7, protocol/src/protocol.rs:3180-3185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Group Codex sessions by custom-agent role

PR-A item 10b (research-u3.design.md section 2). Additive fields:
actors[].role, a top-level sessions_by_role and groups.workers_by_role
(the lane aggregate of the workers of each role).

A session's role comes from its first session_meta: agent_role, which
also reads from agent_type, else the agent_role (or agent_type) of its
source.subagent.thread_spawn. It is trimmed as the spawn handler trims
it, with Rust str::trim's Unicode White_Space set (not Python's strip,
which also removes U+001C to U+001F); an empty value or a non-string is
no role. A role that is not name-shaped is (other), a sub-agent without
a role (none), every other session (root).

Sources: openai/codex rust-v0.157.1 (36650394)
protocol/src/protocol.rs:2904-2913, :3153-3155;
core/src/tools/handlers/multi_agents_v2/spawn.rs:126-130;
core/src/agent/child_config.rs:86-98 (the configured default role);
Unicode PropList.txt White_Space.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for Codex usage counters and routes

PR-A U3, design commit 4 (research-u3.design.md section 3) with binding
correction 4 of the U3 build (u3-corrections.md). Tests only; at this
head they fail on assertions:

- CodexUsageCounters: cache_write_input_tokens is differenced like the
  other cumulative counters (30 then 45 across two snapshots, from a
  child's copied baseline too); a rollout without the field reports it
  as null, never 0, with no usage gap, and a group total is null when an
  actor lacks it; max_request_input_tokens keeps each attempt's largest
  last_token_usage.input_tokens over its counted, non-repeated snapshots.
- CodexRouteValues: a route value that is not a string is null (a bool
  is an int in Python and str(True) is name-shaped).

Sources: openai/codex rust-v0.157.1 (36650394)
codex-rs/protocol/src/protocol.rs:2233-2253 (TokenUsage, serde default
on cache_write_input_tokens), :2268-2275 (TokenUsageInfo), :3328,
:3344-3345 (TurnContextItem model and effort).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep Codex route values and add cache-write usage

PR-A U3, design commit 4 (research-u3.design.md section 3) with binding
correction 4 (u3-corrections.md). It changes delivered
provider_usage.attempts[] values, so it is its own commit with a
before/after host differential.

- effort: any name-shaped string is kept (none, minimal, ultra,
  persistent and custom strings were null), any other string is (other),
  a non-string is null (effort_key).
- configured_model: a name-shaped model with at most one provider
  segment in front is kept (cx/gpt-6-astra read (other)); a path, '..',
  more segments or an empty segment is (other); a non-string, which
  safe_key turned into text ('True', '7'), is null (model_key).
- cache_write_input_tokens joins CODEX_COUNTERS and is differenced like
  the others; being serde(default), a rollout without it reports null,
  never 0, and its absence is no usage gap, so complete keeps its
  meaning. A group total is null when an actor's is (the existing rule).
- attempts[].max_request_input_tokens: the largest
  last_token_usage.input_tokens of the attempt's counted snapshots.
- A token_count whose info is not an object is a gap instead of an
  AttributeError.

Sources: openai/codex rust-v0.157.1 (36650394)
codex-rs/protocol/src/openai_models.rs:59-72 (ReasoningEffort),
protocol/src/protocol.rs:2233-2253 (TokenUsage), :2268-2275
(TokenUsageInfo), :3328 and :3344-3345 (TurnContextItem).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for the Codex spawn join states

PR-A U3, design commit 5 (research-u3.design.md section 4). Tests only;
the new ones fail on assertions at this head, the pending ones stay
marked until the fix:

- c22, a legacy parent: a legacy rollout persists SubAgentActivity items
  of kind completed only, so its child reads the new join state
  parent_without_started_item (requested unknown, follow-ups counted);
- c23, a V1 spawn (namespace multi_agent_v1): its history comes from
  fork_context, so fork_context false reads none, and a V1 resume_agent
  with the child's id is a follow-up;
- follow-ups are null when the parent was not scanned (c13, c19);
- subagent_spawns counts every state (requested.fork_turns and
  fork_consistent pinned);
- requested_fork_turns follows the spawn handler: str::trim, ASCII
  case-insensitive none/all, usize::from_str with an optional '+' and
  ASCII digits within u64, nonzero; U+001C, '2_000', non-ASCII digits,
  bools and floats are invalid;
- a forked child's copied spawn call, sibling started item, turn context
  and settings stay the parent's: with the parent out of the roots the
  sibling reads parent_not_scanned, never parent_mismatch.

Amended pending expectation (with evidence): c11's effort basis is
agents_default_or_model_default, not model_default, because an [agents]
default effort applies before the model's default effort.

Sources: openai/codex rust-v0.157.1 (36650394)
core/src/tools/handlers/multi_agents_v2/spawn.rs:216-226, :253-299;
core/src/agent/child_config.rs:62-99, :196-253;
core/src/tools/handlers/multi_agents_spec.rs:217-266, :591-665;
rollout/src/policy.rs:94-120.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Publish Codex sub-agent spawn states from an in-memory join

PR-A U3, design commit 5 (research-u3.design.md section 4, with the
review's fork_turns, resume, route-basis and privacy findings).
Additive: actors[].spawn for each sub-agent and subagent_spawns, the
count of every state.

scan_lanes_file keeps private join facts from the session's own records
before until, whatever since (never the records a forked child copied
from its parent): its thread, parent thread, task path and raw role, own
spawn_agent calls (fork_turns, fork_context, agent_type, model,
reasoning_effort and the parent's turn route at the call), own started
SubAgentActivity items, turn routes, turns, the first own settings
snapshot and follow-up targets. scan_codex_lanes indexes them over every
scanned rollout and publishes states only: join (joined,
activity_without_spawn_call, parent_mismatch, the new
parent_without_started_item for a scanned parent without a started item,
as a legacy rollout persists none, and parent_not_scanned), requested,
effective, fork_consistent, role_state, route_vs_request,
route_vs_parent_turn, route_changes_within_child, a per-field
expected_route_basis, reroute_evidence and followups (followup_task
target or V1 resume_agent id; null for an unscanned parent).
requested_fork_turns mirrors SpawnAgentArgs::fork_mode; a positive JSON
integer reads as last_n (the frozen launch matrix writes 2), and a V1
spawn reads fork_context. Non-string ids, targets and route values are
ignored instead of raising.

Sources: openai/codex rust-v0.157.1 (36650394)
core/src/tools/handlers/multi_agents_v2/spawn.rs:126-130, :216-226,
:253-299; core/src/agent/child_config.rs:62-121, :196-253, :283-299;
core/src/tools/handlers/multi_agents_spec.rs:217-266, :591-665;
protocol/src/protocol.rs:1403-1410, :2192-2231, :2901-2916, :3116-3191,
:3293-3350, :4382-4390; protocol/src/items.rs:364-370;
rollout/src/policy.rs:94-120, :141-204.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for the Codex command normalization

PR-A U3, design commit 6 (research-u3.design.md section 6, with the
review's shell_script, unresolved-command and window findings). Tests
only; the new ones fail on assertions at this head, the pending ones
stay marked until the fix:

- a shell is typed by its name or file stem, case-sensitively, as
  Codex types it: pwsh, powershell and cmd (Windows paths included) are
  not POSIX, zsh, bash and sh are, and a name Codex does not know (dash)
  runs its OS fallback shell, which the rollout does not record, so it
  is unresolved (codex_commands.unknown_shell); M4 reads incomplete, and
  without --rtk-check no unknown call is added;
- codex_commands count a call once, at its first record, inside
  [since, until), a function_call and its own CommandExecution item
  being one call, so adjacent windows add up;
- a user_shell or interaction item leaves no tool_use and no result
  (nothing in M3, no orphan), and a group's M4 status stays incomplete
  for an unresolved command although the kernel recomputes it;
- with RTK replay (rtk 0.50.0 on Linux) an unresolved command is an
  explicit unknown call; at this head a pwsh argv was replayed as
  measured text (calls 2, unknown 0, measured).

A local RTK_REPLAY_SUPPORTED gate mirrors tests/test_token_measurement.

Sources: openai/codex rust-v0.157.1 (36650394)
shell-command/src/shell_detect.rs:39-59, :315-334; core/src/shell.rs:
22-49; core/src/tools/handlers/unified_exec.rs:27-50, :99-144;
protocol/src/protocol.rs:3534-3544; core/src/tasks/user_shell.rs:
142-146, :190-205, :453-481.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for Codex shell typing and -c options

PR-A U3, design commit 6 (research-u3.design.md section 6), second test
step. A probe at 0a9bada8 showed three readings the first test step did
not pin; tests only, and at this head they fail on assertions:

- an argv program is typed by its file stem with / and \ as
  separators (a Windows Git Bash path is POSIX, CMD.EXE is cmd), while
  exec_command's shell is typed as Codex does: its file stem again and
  again, case-sensitively (pwsh.exe.bak is pwsh, PWSH is unknown), a
  non-string shell fails ExecCommandArgs (unknown), and null is the
  session shell; observed codex_commands None;
- with -c among the short options the first non-option argument is the
  script (-cl, -e -c, -o pipefail -c); observed M4 shell_fetch 1 of 3,
  as the joined -cl and -o argvs hid their curl;
- rtk replay of a -ec script: observed eligible 0, ineligible 1 (the
  joined argv), expected eligible 1.

The legacy shell_script reading of a -cl argv is pinned as must-stay.

Sources: bash(1) INVOCATION and OPTIONS (-c, -o option); openai/codex
rust-v0.157.1 (36650394) shell-command/src/shell_detect.rs:39-59,
:315-334; core/src/tools/handlers/unified_exec.rs:27-33, :117-121.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Resolve Codex shell calls by the shell that ran them

PR-A U3, design commit 6 (research-u3.design.md section 6, normalization
A for U1's commandInvocations), with the review's shell_script finding:
shell_script keeps its reading for the legacy lane counters, and the PR-A
measurement reads each shell call through a separate resolver. It can
change delivered PR-A fields, so it is its own commit with a host
differential.

- An argv runs its program, typed by its file stem with / and \ as
  separators: sh, bash, zsh, dash and ksh with -c among their short
  options run the first non-option argument (bash(1) INVOCATION; each o
  or O takes the next word), which widens the design's "cluster ending
  in c" to -cl and -o pipefail -c; pwsh, powershell and cmd (ASCII
  case-insensitive, as Windows resolves names) are unresolved.
- exec_command's shell is typed as Codex types it: the value, else its
  file stem again and again, case-sensitively; any other value, or one
  that is not a string, runs an unrecorded fallback shell (unknown).
  Absent or null is the session shell, read as POSIX.
- An unresolved call is a Bash call with no text, counted in
  measurement.codex_commands; m4.status reads incomplete (actor and
  groups, as the kernel aggregate recomputes it), and with --rtk-check it
  is an explicit rtk unknown call. The kernel is unchanged.
- user_shell and unified_exec_interaction CommandExecution items are no
  model tool calls: no tool_use, no result, counted apart.
- codex_commands counts a call once, at its first record in the window.

Removes the three commit-6 pending markers.

Sources: openai/codex rust-v0.157.1 (36650394)
shell-command/src/shell_detect.rs:39-59, :315-334; core/src/shell.rs:
22-49; core/src/tools/handlers/unified_exec.rs:27-50, :99-144;
protocol/src/items.rs:243-285; protocol/src/protocol.rs:3534-3544;
core/src/tasks/user_shell.rs:142-146, :190-205, :453-481; bash(1)
INVOCATION and OPTIONS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Pin the frozen fork_turns launch forms through the spawn join

PR-A U3, the stage's binding correction for design commit 5: M11's
frozen launch forms are fork_turns none, integer 2, all (agent_type
stack-researcher), all (agent_type null) and resume. The classifier test
already read each value, but the join fixtures held no explicit "all"
and no null agent_type. This control joins all five end to end: joined,
requested fork_turns and fork_n, role, history, turns, fork_consistent,
role_state and followups, with the ID-free report check.

"resume" is no spawn argument (c08 reads it invalid, and upstream fails
such a call), so the resumed finished researcher child is its own spawn
plus a followup_task to its thread and a second own turn.

A control, not failing-first: it passes at 1bbf81e9, as commit 5 reads
every form. A scratch negative control (the integer form read invalid,
"all" read as the default) made it fail on its assertion, at m02.

Sources: evidence/artifacts/token-adoption-e2e-20260926/
preregistration.json tasks 64-68 (launch), RUNBOOK.md:411-417;
openai/codex rust-v0.157.1 (36650394) core/src/tools/handlers/
multi_agents_v2/spawn.rs:253-299; multi_agents_spec.rs:217-241.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for Codex nested attribution bounds

PR-A U3 design commit 7 (research-u3.design.md section 7, with the U3
review's position, item-kind and web_search_call findings). Tests only;
at cd04dc34 all five fail, beside the three pending commit-7 tests:

- a web_search_call id is a model call id, so an item carrying it stays
  direct inside an open exec span (sandbox_operations 1 at cd04dc34);
- only emitted items (CommandExecution, McpToolCall, web.search
  Extension) count as nested or unattributed (code_mode absent);
- the turn aliases turn_complete, turn_started and turn_failed end
  nesting, and another direct call no longer does (1 != 2);
- the code_mode counters count once in the window of their first
  record, so adjacent windows add up (code_mode absent);
- groups sum the code_mode counters (code_mode absent).

Sources: openai/codex rust-v0.157.1 (36650394) protocol/src/models.rs:
1182-1203 (WebSearchCall), rollout/src/policy.rs:56, :94-112,
protocol/src/items.rs:57-61 (TurnItem::WebSearch), protocol.rs:
1403-1415 (task_started/turn_started, task_complete/turn_complete),
code-mode-protocol/src/description.rs:19-24 and lib.rs:51-52.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Attribute Codex code-mode items by exec position in the turn

PR-A U3 design commit 7 (10e normalization B), with the U3 review's
position, item-kind and web_search_call findings.

An emitted item (CommandExecution, McpToolCall, web.search Extension)
whose id is no model call id is now nested when an own exec call came
earlier in its turn; before, nesting ended at the exec return or at the
next direct call. A cell keeps running after exec returns, through
yield_control() and the code-mode wait tool, so only the turn bounds it.
Model call ids now include a hosted web_search_call's id. Every turn
event the module reads ends a turn, the upstream aliases included. A
wait call without a namespace after an exec of its turn is code mode,
so its output counts under M3's code_mode carrier. New
measurement.code_mode {exec_calls, wait_calls, nested_items,
unattributed_items}, counted once at the first record in the window and
summed by groups. The three pending commit-7 tests lose their marker.

Changed meanings (the design's list): sandbox_operations, M3 by_carrier,
the M4 shell_fetch/ctx_sandbox_fetch split, U1's proxy.nested and the
cli_lanes carrier nested. A count-only census of this host's store
(1,620 rollouts) found 271 items after the exec span in the same turn,
none without an exec in its turn, and 352 wait calls, all after an exec
of their turn.

Sources: openai/codex rust-v0.157.1 (36650394) code-mode-protocol/src/
description.rs:19-51, lib.rs:51-52; protocol/src/models.rs:1060-1165,
:1182-1203; rollout/src/policy.rs:56, :94-112; protocol.rs:1403-1415;
protocol/src/items.rs:57-61; multi_agents_spec.rs:268-291.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for Codex legacy spans and outer JS scope

PR-A U3 design commit 8 (10e legacy-mode spans), with the U3 review's
per-exec finding and the explicit outer-JS decision the stage brief
requires. Tests only; at 9799d5fa every new test fails, beside the two
pending commit-8 tests (code_mode legacy and outer keys absent, M4
status not forced):

- rows with no session_meta, or a history_mode other than paginated,
  read as legacy (no session_meta: None, None, not_applicable);
- static sites: the global tools with a fetch-capable name after a dot
  (blanks and ?. allowed) and any bracket access, not ALL_TOOLS,
  x.tools, longer names or other nested tools (expected 5 sites);
- legacy spans count once in the window of the exec call;
- outer HTTP_SCRIPT-shaped mentions are counted, never as fetches, and
  a client whose isolate was not read (0.158.0, or none named) makes
  M4 incomplete for the actor and the group.

The shared test metas now name cli_version 0.157.1, a client whose
isolate was read. Sources: openai/codex rust-v0.157.1 (36650394)
protocol.rs:772-779, rollout/src/policy.rs:94-112,
code-mode-protocol/src/description.rs:24, code-mode-runtime/src/
runtime/globals.rs:36-48; rust-v0.155.1 (be2951ea) description.rs:20
and a byte-identical globals.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Mark Codex legacy exec spans and scope outer JS to read isolates

PR-A U3 design commit 8 (10e legacy-mode spans), with the U3 review's
per-exec finding and an explicit outer-JS decision.

Legacy spans: a rollout that is not paginated (no history_mode, or no
session_meta, is legacy upstream) persists no nested item. There an
exec call whose code has a static fetch-capable site and no attributed
nested item (after it, before the next exec or the turn's end) counts
in code_mode.legacy_unobservable_exec_calls and _sites, once in the
window of its call record, and M4 is incomplete for the actor and its
groups. Sites come from a linear scan (code_mode_fetch_sites): the
global tools with exec_command, web__run or mcp__*__ctx_* after a dot,
or any bracket access, which can name any tool.

Outer JS (the review's medium finding, decided here, not declared
unnecessary): the isolate has no network access at 0.155.1 and 0.157.1,
read at both tags, so outer HTTP_SCRIPT mentions (a linear scan) are
only counted in code_mode.outer_http_mentions. For any other client,
or none named, an exec with a mention counts in
outer_http_unverified_exec_calls and makes M4 incomplete. The dated
record, alternatives, host residual and overturn conditions are in
tools/skill-usage/README.md; docs/decisions/ is outside this unit, so
the PR-A owner accepts or rejects it. The two pending markers lift and
the unused pending helper goes.

Host census (count only): all 1,620 rollouts are paginated and name
0.155.1 (277) or 0.157.1 (1,343); 1,532 mentions in 1,091 of 16,410
exec calls, 51 of them in 33 calls with no nested ctx code item.

Sources: openai/codex rust-v0.157.1 (36650394) protocol.rs:772-779,
rollout/src/policy.rs:94-112, code-mode-protocol/src/description.rs:21,
:24, :365-387, code-mode-runtime/src/runtime/globals.rs:36-48;
rust-v0.155.1 (be2951ea) description.rs:20 and globals.rs (the same
sha256); child-usage.mjs HTTP_SCRIPT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add failing-first tests for the Codex RTK replay agent and D7

PR-A U3 design commit 9 (10d), with the U3 review's findings on the
d7.wrapped_exceptions provenance and the Claude byte-identity check.
Tests only; at e79a5c18 all fail:

- the Codex bridge asks the kernel for rtkAgent codex and labels
  rtk_parts.agent (payload option absent);
- with a home settings file denying Bash(git status) and the cwd
  outside any project, the Codex lane replays as claude today and
  counts an unknown call (0, 1, incomplete), not the rewrite;
- measureTranscript refuses an rtkAgent other than claude or codex
  (accepted today), keeps the Claude output without d7, and every
  fixed-config field agrees between the agents where no rule applies;
- rtk_parts.d7 drops reviewed requires_raw log/find parts, prefixed or
  not, counts prefixed ones as wrapped exceptions, keeps unreviewed
  ones and marks d7 incomplete (d7 absent);
- an unresolved command leaves d7 incomplete; groups and the kernel
  aggregate sum d7 for Codex measurements only.

The fake home registers the rtk hook: a Claude directory without one
makes rtk 0.50.0 print a once-a-day "No hook installed" warning on the
claude path, which fails the kernel's five-exclusion probe (observed
here; src/hooks/hook_check.rs:26-35, :88-135). The measure helper
gains a cwd.

Sources: rtk-ai/rtk v0.50.0 (1d87b8e7) src/main.rs:992-1000,
src/hooks/decision.rs:61-97, :196-204, permissions.rs:56-67, :141-175,
hook_check.rs:26-135; adoption/templates/codex.AGENTS.template.md
(the D7 exception list); preregistration.json thresholds.M6c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Replay Codex RTK parts as the Codex hook and add the D7 view

PR-A U3 design commit 9 (10d), with the U3 review's findings on the
d7.wrapped_exceptions provenance and on the Claude byte-identity check.

Kernel: measureTranscript takes rtkAgent, claude by default or codex,
and refuses any other value. rtkChecker keeps one checker per agent,
each with its own five-exclusion probe, and asks rtk hook check
--agent <agent>; the claude checker is unchanged. For codex, rtk_parts
gains d7: the fixed-config eligible parts less the git log and find
parts a digest-bound review marks requires_raw, prefixed or not. An
unreviewed log or find part stays and leaves d7 incomplete, as does an
unknown call. wrapped_exceptions is explicit_rtk_on_excluded_or_sensitive
(M6c's zero counter, broader than the template list: stated, not
narrowed) plus wrapped_requires_raw_parts. aggregateMeasurements sums
d7 only over the measurements that carry it. SHA256SUMS regenerated.

Bridge: skill_usage.py passes rtkAgent codex and labels
rtk_parts.agent; an unresolved command, replayed as '' ("No rewrite
for:" under either agent), stays an explicit unknown call and now also
leaves d7 incomplete. The skill-usage README drops the "hypothetical
Claude-hook routing" wording and documents d7.

Why: rtk 0.50.0 maps codex to Host::Codex, which has no RTK-side
permission rules; claude merges the cwd project's and HOME's Claude
Bash rules, so before this a Claude deny rule on the machine running
the tool turned a Codex part into an unknown call (the failing-first
tests). Sources: rtk-ai/rtk v0.50.0 (1d87b8e7) src/main.rs:992-1000,
src/hooks/decision.rs:61-97, :196-204, permissions.rs:56-67, :141-175;
adoption/templates/codex.AGENTS.template.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Document Codex RTK replay, D7 and code-mo…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lane:foundation Foundation lane: Claude/Codex setup, hosts, memory, RAG, research, workers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants