Repository navigation
Checkpoint: development → main (PR #95 follow-ups, PR95 increment, Wave 3 Steps 9a.1–9b.2, NI, CLI MVP priority reset) - #96
Conversation
PR #95 merged to main as 6d2a737 at 22:44 UTC on 2026-10-18. Record the merge checkpoint in the Phase 2.5.5 register, point current.md and the agent guides at Step 9, and keep Wave 3 open: Steps 9-19 are not started. - phase register: merge checkpoint (completed scope, review attribution, the independent review's dispositions, publication state) and the maintainer-approved 9a-9d split of Step 9 as rows 9a-9d of the increment table - deferred-tasks: the independent review's unactioned findings, and the SonarCloud main-branch gate recorded as done - limits stated rather than implied: a3e164b has author verification only; CI for the merged head and the merge commit finished green after the merge; the SonarCloud dispositions were applied on main on 2026-10-19 Refs: ADR-0111 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An independent security sitting on a3e164b found that a scheme glued to a word character (xhttps://...), split by LF/TAB and carrying a raw @ in the credential, left a partial mask with part of the credential visible. The split-credential scanner still skipped any start preceded by a word character, although the URL rule no longer has a word boundary. The URL rule's boundary now blocks no start, and the word-character shortcut applies only when a rule's boundary rejects word characters. The anchored scanner gate and the URL pattern line are unchanged, so no causal-control anchor moves. - tests: two regression cases (reverting the gate fails exactly those), plus an underscore glue, a split inside the scheme, a multi-URL case and three characterisation cases for the fail-closed trade-offs the review found (prose before a URL and a later @ redacts the whole diagnostic; a longer scheme name such as sftp matches by suffix). A test comment wrongly said ws/wss/ftp/ftps were masked before the PR; they are the PR's additions - comments: the stale boundary and slice(2) contract text is corrected - pins: the scrub-secrets file set 103 -> 111, in mutations.mjs and provenance.test.mjs; the secrets-controls README counts follow (278, 111, 180, 1,343), measured from the control run Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The new gated-reservation case pinned the join of the owner's whenClosed only for the failed-spawn publication. A mutant that kept the join on that branch alone and dropped it for natural exit, abort and timeout passed every test in tool-host/process*.test.ts. - process-close.test.ts: one table over four branches. Removing the join fails all four; keeping it only on the failed-spawn path fails natural exit, abort and timeout; dropping it only there fails failed-spawn. The closed-child wait now carries an explicit bound - native-forwarding.test-support.ts: the longer waitFor bound applies only to the one wait that observes a real child booting (3 s, leaving 2 s of the 5 s default case timeout). Three chained 4 s waits could exceed the case timeout and lose the named assertion, and the other waits only observe registry reactions, so they return to the default. The docblock now says so Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ound Independent review of the follow-up found three weaknesses in the join table and one change that did not do what it claimed. - process-close.test.ts: the negative assertion waited one macrotask, so an ungated publication that was merely late (20 ms, 60 ms) slipped past; it now holds the gate for 100 ms and catches both in all four cases. A case that failed before the gate opened leaked an immortal node child (the abort mutant reproduced it); the case now reaps the child and releases the gate in a finally - native-forwarding.test-support.ts: restore the original default waits. The 3 s bound on the real-child boot wait could not help its only caller, whose outer waits in drive-home.test.ts use the 1 s default and enclose the boot, so a slow boot failed exactly as before, only later. No flake was ever reproduced (0 failures under heavy load), and the standard is wary of lengthening waits to hide one - mcp-orphan.e2e.test.ts: the skip comment said every case fails at the first execFileSync, but only one case reads the process table. It now says the whole suite is skipped on Windows out of caution because it has only run on POSIX Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Comment-only: no behaviour, anchor or pinned count changes. - the URL rule docblock and the characterisation-test comment said the consequences "never reveal" a credential; a credential split by a plain space that also holds a raw @ stays the documented opaque-fragments-across- spaces limit, as it was for the pre-PR matcher - the wordBlocksStart comment now states that 'a' stands for every ASCII word character, which holds because each current boundary class rejects all of them or none - secrets-controls README: the R1 recovery mutant now only affects the key rules, since the URL and header rules supply their own joined patterns Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Comment-only: no behaviour, anchor or pinned count changes. - the header docblock no longer attributes the whole-diagnostic redaction of prose next to a URL to the missing word boundary: the pre-PR matcher did the same for http(s), and dropping the boundary only extends it to prose that whitespace stripping glues to the scheme - the joinedPattern contract says the SyntaxError occurs on LF/TAB input that no earlier rule has already redacted, not on every such input - secrets-controls README: the R2 recovery mutant also selects the URL rule now that it has its own joined pattern, with no effect on its result Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
a3e164b (corrections from the third independent review of PR #95, including a secret-scrubbing rule) merged with author verification only. Record the independent review that followed and the open findings it left. - phase register: the merge checkpoint no longer claims the commit has no independent acceptance, and a new section records four review rounds and a security sitting (per-area results, the findings and their dispositions, what the reviewers did and did not cover, the follow-up commits) - deferred-tasks: the residuals the review found and did not fix, two of them security items that predate the follow-up (an api-key:Bearer token leak, and userinfo with a raw ? or # before the first colon left unmasked), plus the examined-and-unchanged waitFor bound and the review's nits - current.md, CLAUDE.md, AGENTS.md: the merge limits are stated as resolved or pointed at the open findings instead of "author verification only" Refs: ADR-0111 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A pre-implementation audit read the canonical documents for Wave 3 Steps 9-19 and the open PR #95 findings against the current source (sixteen auditors, fifteen adversarial verifiers and a completeness critic). The maintainer approved every recommendation on 2026-10-19. No new ADR is required. - Record the rulings, the PR95 plan amendment (scrubber residuals before 9a) and the recorded splits for Steps 9-19 in the phase register's increment table (29 increments, ID coverage unchanged apart from PR95). - Give every open PR #95 item an owner in deferred-tasks.md; LOW-1 and LOW-2 become approved deferrals with their triggers. - Reconcile stale status: the phase banner, the a3e164b review wording in current.md, where the post-merge follow-ups live (development, pushed, push CI 39275223233 green), README and CLAUDE/AGENTS status lines. - Add forward pointers from the preflight tables to the approval, a dated #45 note for ADR-0110's fixed infrastructure diagnostic, G30's pull forward to Step 13 in both phase files, and the Outcomes bullet's reconciliation with exit criterion 4. - Mark accepted Steps 1-8 task bullets DONE or certified. - AGENTS.md: describe the development-to-main PR branch model in use. Refs: ADR-0110, ADR-0111, ADR-0112, ADR-0113 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR #95 independent review left two security residuals in the diagnostic secret backstop and several scanner gaps. The maintainer ruled the policy on 2026-10-19 (Wave 3 PR95 increment); this implements it in llm, in core's journal projection twin, in the secrets causal controls (repo tooling) and in the canonical docs. - An api-key, x-api-key or x-goog-api-key value that begins with a Bearer/Basic label (also LF/TAB-split) is masked together with its token, so the header rule no longer erases the later label rules' anchor and leaks the token. Like those rules, a label that ends a line takes the next token with it, failing closed. - Raw ?/# userinfo is public only after a host-shaped prefix that an = assignment follows, in scrub-secrets and in core's bounding.ts twin: https://us?er:TOKEN@h, http://#TOKEN@h and https://my.user:1234?TOKEN@h are masked, and a host fragment without an assignment fails closed. Core keeps its documented ']' and short vanity-handle differences. - The finite URL scheme list gains sixteen connection-string schemes (postgres ... smtps). They mask colon-bearing userinfo only, so ssh://git@host stays visible; an unlisted suffix scheme still matches. - The split-credential scanner compiles its probes once. A package- internal splitProbe checks its two invariants at module load with a typed error, and a :// window skips URL starts that cannot match: an 8 MiB letter-heavy diagnostic no longer costs a RegExp attempt per character. - New witnesses, a parity test between the two maskers and nine causal controls pin each mechanism (66 controls, 416 named witnesses). The eight-MiB cases now have a 15 s case deadline, inside the causal CAUSAL_TIMEOUT_BUDGET, instead of a 5 s wall-clock assertion. - The CLI supported-leaf guard admits the leaf's own dependency-free probe module; the leaf still reaches nothing else but terminal-safe. - error-handling.md gains the canonical diagnostic secret backstop policy. built-in-tools.md and the effect journal contract record the shared ?/# predicate and its v5 consequence: a resumed effect whose args carried such userinfo now refuses instead of replaying. Refs: ADR-0113 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 1 of the PR95 review was NONCLEAN in all five lenses; every verified
finding is fixed here, in llm, core's twin predicate, the CLI tests, the
secrets causal controls (repo tooling) and the docs.
- The host-shaped ?/# predicate ran an unbounded dotted run through V8's
RegExp backtrack stack: an admitted-size prefix made scrubSecrets,
makeLlmError and core's redactSecretShapedText/boundForModel throw
RangeError. Both maskers now refuse a prefix longer than 260 characters
(a DNS name plus a port) before the RegExp, which fails closed.
- A header value with stacked or quoted labels (Bearer Bearer <token>,
Bearer "<token>") leaked the token. Up to eight labels and an optional
quote now precede the token, bounded so no label run can exhaust the
stack, and the standalone Bearer/Basic rules accept the same stacked
labels.
- Already-masked userinfo is a fixed point (an exactly masked password
under a connection-string scheme, or every part masked or empty under
any scheme), so a templated postgres://app:{{secrets.password}}@db no
longer raises the authored-content notice on every run.
- New tests pin the host-shaped anchors and port bound, multi-character
label whitespace, stacking, the length bound at admitted size and the
fixed point. Six new causal controls (72 in all, 430 named witnesses)
and the snapshot detector's 11-case workload follow.
- error-handling.md now states which rules probe LF/TAB joins, how the
?/# rule combines with the scheme rule, stacked labels, fixed points and
the unlisted-scheme and whitespace-delimited limits; built-in-tools.md
lists every core difference; commands.md, current.md and the rulings
record (per-increment security sittings) are corrected.
Refs: ADR-0113
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The second review round found that quote characters beside a
Bearer/Basic label let the api-key header rule take the label as its
token and leak the real one, that the eight-label bound failed open
past its limit, and that several bounds and documented behaviours were
unpinned or misdescribed.
- The header rule accepts (escaped) quotes before every label and
before the token, and its token is never another label or an
already-masked [REDACTED]. A ninth label therefore fails the rule
instead of leaking the token; the label rules and the
whitespace-stripped residual probe then blank the whole diagnostic.
- The standalone Bearer/Basic rules no longer stack labels: a label
never takes another label (or a masked marker) as its token, so any
number of stacked labels leaves the last one to mask the token, with
no repetition to bound. Masked header, Bearer and Basic tokens are
fixed points, so a templated `x-api-key: Bearer {{secrets.key}}`
stays quiet in the authored-content notice.
- Tests pin the eight-label bound and its fail-closed side,
admitted-size label runs, the 260-character host bound (260 kept,
261 masked) and case-insensitivity in both maskers, and core's
documented authority-delimiter difference from the LLM backstop.
- Seven new secrets causal controls (79 controls, 441 witnesses), with
updated provenance pins and README counts.
- error-handling.md, built-in-tools.md and the bounding.ts docblock
describe the label shapes, fixed points and core's authority
delimiters exactly. The phase register records ruling C2's
production-key constraint, the #308 DONE marker and the split-row
IDs; deferred-tasks.md schedules the key constraint for Wave 4b.
Refs: ADR-0113
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The third review round found that sequential rule application let one rule erase the context another needed: an api-key header token could swallow a Bearer/Basic label glued behind punctuation, and the round-2 masked-marker guard kept a tail glued to a mask (including the scrubber's own sk-/AIza masks) visible. A quote-closed label, quote runs, JSON-in-JSON headers and quoted or escaped placeholders were also handled incompletely, and a labels-only header value blanked the diagnostic depending on context. - Every non-query rule now finds its matches in the original text and the masked ranges of all rules are merged before anything is replaced, so no rule can erase another's context. A header or label rule masks only its named token group: header names, separators and labels stay visible, an LF/TAB-split label keeps its spelling, and masking an exact [REDACTED] is the identity, so the three masked-marker lookaheads are gone. - The header rule uses a zero-width boundary, accepts escaped quotes before its colon, skips up to eight punctuation-glued or quote-closed labels, refuses a run of up to eight concatenated labels or a nested header name as its token, and ends the token before a nested header name or at escapes before a delimiter. - The label rules accept a label closed by its own quote, refuse a glued or concatenated label as their token, and end the token before a label glued behind a non-word character. No token can start inside a quote run and every quantified label group is bounded, so admitted-size runs stay linear and stack-safe. - Tests pin each class, the quoted and escaped fixed points, the context-free labels-only value, core's ", ' and } delimiters and admitted-size escape and glued-label runs. 23 header and label controls replace ten (92 controls, 462 witnesses); provenance pins and README counts follow. - error-handling.md describes the merged masking, the shapes, the trade-offs and the " limit; commands.md notes quoted header placeholders; deferred-tasks.md and the rulings row record ruling C2 as approved. Refs: ADR-0113 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 4 of the PR95 increment's review (at 6fb7e9d) confirmed 19 findings across the scrubber, tests, docs, security, property and critic lenses. All are fixed or, for one, documented as a deliberate limit. LLM diagnostic backstop (packages/llm/src/scrub-secrets.ts): - The literal query names are one rule of the merged masking pass: their value ranges come from the original text like every other rule, so a header token cut short at `;` no longer leaves a query value's tail visible (SCRUB-R4-3). - An ambiguous authority (userinfo holding a raw ?/# that is not a public contact) is masked through its end unless a host-shaped name follows its last @, so `https://svc?password=P@ssw0rd123` no longer leaves `ssw0rd123` visible while `https://u:pa?ss@h.example` keeps its host (SEC-R4-1). The host test is length-guarded like the contact prefix. - A run of up to eight concatenated labels (`BearerBearer`) is one label in the header value and in the standalone rule, and ends a token glued behind punctuation; nine concatenated labels blank the diagnostic before any rule runs (SCRUB-R4-1, SEC-R4-2, GEN-R4-1, DOC-R4-1). - The Bearer and Basic rules are one rule whose first label is either label, so neither copy can drift from the other (CRIT-R4-1). - scrubSecrets repeats its merged pass until the output stops changing, at most three passes, and fails closed otherwise, so a second scrub changes nothing (SCRUB-R4-2, GEN-R4-2). - A word glued before `sftp://` still reads as sftp: reading it as the colonless ftp would make every whitespace-joined probe, the CLI's display residual included, blank `connect to sftp://user@host`. It is recorded as a limit and pinned (GEN-R4-4). Core projection (packages/core/src/tools/bounding.ts): - The same ambiguous-authority extent and a bounded fixed point. - A scheme label never takes a URL scheme as its token, so the split pass no longer hides a password behind `token postgres://` (GEN-R4-5). - An LF/TAB right after `://` or before the userinfo's @ belongs to the split userinfo (SEC-R4-3). Tests and controls: - 14 scrubber and 6 core cases cover every finding, including the header `'`/`}` stops, quoted standalone tokens, a range containing a later one, Basic copies of the round-3 cases and admitted-size runs that a quadratic regression would push past the case deadline (T4-1..T4-5). The merge-extent witnesses put a key inside an ambiguous authority's rest: the bounded repeat re-masks a truncated range wherever its rule matches again, so only a tail with no @ left discriminates the extent. - The secrets family has 109 controls (481 targets and witnesses): 19 new, the Bearer/Basic not-a-label pair merged, and the equivalent label concatenation-bound control retired. Edges that only change how much is masked are pinned by tests without a control, keeping the family within the 2,000-file evidence bound. The secrets CI job's allowance becomes 60 minutes, like the filesystem job's. Docs: error-handling.md's backstop policy (masking, URL userinfo, trade-offs, limits; DOC-R4-2 and GEN-R4-3 wording), built-in-tools.md, effect-journal.md's digest note and the controls README. Refs: ADR-0113 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 5 of the PR95 increment's review (at b2c50e8) confirmed 20 findings. Two were high: core's round-4 authority extent erased the context its own later rules read. All are fixed. Core projection (packages/core/src/tools/bounding.ts): - Core's rules run in sequence, so masking through an ambiguous authority's end swallowed the next credential's key name, label, scheme or PEM marker, and in the LF/TAB-joined view it ran across a real line break (SCRUB-R5-1, PR95R5-GEN-1). The extent is gone. An ambiguous userinfo is now masked together with exactly the query values core's opaque-query rule (24+ characters after ?/&) and secret key=value rule would mask whole, ending where those rules end them; the joined view masks the userinfo only. This also closes the tails ruling (2) left after a single-label host (PR95R5-GEN-2) and the nested URL the old resume point skipped (TST-R5-1). - The URL-scheme lookahead after a scheme label is removed: it refused any token whose run reached '://', so tokens glued to a URL stayed visible (SCRUB-R5-2, CONS-R5-1, SEC-R5-1). The split view now masks URL userinfo before scheme labels, as the main pass always did. - A separator inside a URL's 'scheme://' now splits its userinfo, as one at the userinfo's edges does: core had left 'https:/\n/user:pass@host' unmasked since before the increment. - The bounded fixed point moves to a tested helper (TST-R5-6). LLM backstop (packages/llm/src/scrub-secrets.ts): - The split scan retries the next start after a kept candidate whose first letter ends a line, so 'files\nftp://tok\nen@h' (read as sftp) blanks again while a real 'files\nsftp://user@h' stays (SCRUB-R5-3). - The fixed point uses the same tested helper; the CLI's leaf reachability test admits it. Tests and controls: - Discriminating tests for the label rule's trailing ';'/',', its embedded stop's quote run and bound, the nine-label bound's case and LF/TAB letters, an exactly masked password, the escaped joined header name, the split retry, the residual blank after another rule's mask, every core round-5 witness and both fixed-point helpers; each was checked against the mutation it kills (TST-R5-2..5). - The secrets family stays at 110 controls (483 targets and witnesses), the most the unchanged 2,000-file export bound admits: three core controls are replaced for the new mechanisms, two are added (split scheme, split retry) and one pattern-piece control is retired. From this round on, pieces inside a mechanism's pattern are pinned by discriminating tests rather than new controls. Docs: error-handling.md (exactly masked userinfo, core's extent, the residual blank after a mask, the glued-sftp limit's scope), built-in-tools.md, effect-journal.md, the controls README (counts, round 5, the escape-run wording), a PR95 implementation checkpoint in the phase register with status lines in current.md, CLAUDE.md and AGENTS.md, row 9a's split marker, the Step 9 mechanics link and the commit-and-pr skill's development-branch step (DOC-R5-1..4, TST-R5-7, PR95R5-GEN-3, CRIT-R5-1). Refs: ADR-0113 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 6 of the PR95 increment's review (at 6b2e94d) confirmed 21 findings, nine high. Round 5's value mirror for an ambiguous userinfo was quadratic, still masked through a later credential's key, label or PEM marker, and was dropped by the split view. All are fixed. Core projection (packages/core/src/tools/bounding.ts): - Core now works like the scrubber: every rule reports its ranges on one text and overlapping ranges merge before anything is replaced, so no mask removes the key name, scheme label, PEM marker or URL scheme another rule reads (R6-SCRUB-1, R6-SCRUB-3, SEC-R6-2, SEC-R6-3, GEN-R6-1). The value mirror (laterQueryValueEnd) and the split view's sequential rewrite are gone, which removes the quadratic scan (R6-SCRUB-2, CONS-R6-1, SEC-R6-1, GEN-R6-2). - A key or parameter in a URL userinfo's own text belongs to it: all of an unambiguous userinfo, and a user:password before a raw ?/#. The rest of an ambiguous userinfo is read by the key/value and opaque-query rules, so every value they mask whole is masked to its own end (R6-SCRUB-4), while a git remote such as https://x-access-token:<t>@github.com/o/r keeps its host and path. - The key/value rule resumes its scan at each value, so a key inside a value is read too, and reads values through earlier masks, line breaks included; the scheme-label rule resumes after each label; a split header label, and a scheme label even where a key's value runs on over it, may start right after a credential. These keep the cases the old order masked by accident. - PEM blocks are found with one shared END search instead of a bounded rescan after every BEGIN marker (CRIT-R6-1): an open-PEM tool result at admitted size takes under a second, not 22 s. LLM backstop (packages/llm/src/scrub-secrets.ts): - The nine-label bound also reads the raw text, which normalization can shorten by one label letter after ESC [ (CRIT-R6-2). - The literal secret query names also follow #, as in an OAuth implicit-flow #access_token= (GEN-R6-3). Tests and controls: - Discriminating core cases for every round-6 witness, each prefix form of the round-5 witnesses, the userinfo's zones, nested keys and labels, values through masks, label boundaries, the PEM body bound and admitted-size linearity; LLM cases for the raw-text bound and fragments; and both maskers' fail-closed values, observed through a module mock of the fixed-point helper (TST-R6-1, TST-R6-2). Each was checked against the mutation it kills. - The secrets family stays at 110 controls (491 targets and witnesses): six core controls pin the new mechanisms, the three round-5 core controls go with their mechanisms, six are re-anchored, and three single-character pieces of the header rule are retired to make room after their tests were checked to kill the same mutations. Docs: built-in-tools.md (core's merged ranges and zones), effect-journal.md (the v5 projection changes, the split-scheme rule included), error-handling.md (core's sentence, fragments, the raw-text bound and two more limits), the controls README, the phase register's round table and witness count, and the status lines (DOC-R6-1..3, TST-R6-3, CONS-R6-2, GEN-R6-4). Refs: ADR-0113 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 7 of the PR95 increment's review (at b89ac62) confirmed 17 findings, two high. A URL userinfo's owned zone still hid real keys, the quoted-value pattern exhausted V8's RegExp stack at an admitted 8 MiB value, a projection that did not settle could give two calls one journal digest, and the secrets family's evidence export no longer fit its byte bound. All material findings are fixed. Core projection (packages/core/src/tools/bounding.ts): - Only a userinfo's username owns its keys: the text before its first ':' when no raw ?/# comes before that colon, nothing for a colonless userinfo, and in the split view never past the first LF/TAB. A key in the password or parameter position, or on the line after a split URL, now masks its value through the last '@' (jdbc:sqlserver://…;password=P@ss…, DATABASE_URL=https://h\n DB_PASSWORD=P@ss…), while a git remote such as https://x-access-token:<t>@github.com/o/r keeps its host (PR95R7-SCRUB-1, SEC-R7-1). - Quoted values are found by a linear character scan instead of a repeated-group RegExp, so an exactly admitted 8 MiB value projects in about 50 ms instead of throwing RangeError after a write landed (PR95R7-SCRUB-2, SEC-R7-2). - A credential glued to the end of a fixed-length one (AKIA…ASIA…) is masked in the same pass, and a split span glued to the previous one is exempt from the original-boundary check, so such runs settle (PR95R7-GEN-1). - projectSecretShapedValue reports whether the journal projection settled (fixedPointOf), and makeSummary recognizes split shapes with the full fixed point (SEC-R7-3). - joinSplitHeaderLabels returns early when the text holds no ':' (PR95R7-CRIT-2; output-neutral). Registry (packages/core/src/tools/registry.ts): - A tiered effect whose journal projection does not settle is refused with a loop-correctable ToolArgsInvalidError before its prepare, its argument names masked, so the fail-closed constant never becomes a journal identity (PR95R7-GEN-1). Causal controls and CI: - The coordinator gains --shard index/count (round-robin, recorded in the summary, never complete), and the secrets job runs as a two-leg matrix whose legs export separately: one export of the 110-control family measured about 77 MiB against the unchanged 64-MiB bound (PR95R7-CRIT-1). Both shards ran locally with the export enabled: 1,087 files and 45.50 MiB, and 1,087 files and 45.48 MiB. - Ten controls: core-userinfo-username-zone, core-split-zone-cap, core-quoted-value-scan, core-standalone-chain, core-glued-span-boundary, core-summary-fixed-point, core-journal-settled, core-unsettled-refusal, and the two halves of the nine-label bound (diagnostic-label-run-normalized/-raw). The family is 120 controls / 501 targets and witnesses. Tests: discriminating cases for every witness above, the normalized nine-label view, values read through every kind of earlier mask, the merged run's replacement, the key rule's names and delimiters, and the refusal's masked names, each checked against the mutation it kills (TST-R7-1..5); the fixed-point mocks clear their history after each case, which drops the core file's heap from 725 MB to 130 MB (CONS-R7-1). Docs: built-in-tools.md, effect-journal.md, tool-registry.md and error-handling.md (DOC-R7-1 and the backstop's missing generic key/value rule), both controls READMEs, the phase register's round table (DOC-R7-2) and gates, and the status lines and freshness dates (DOC-R7-3). Refs: ADR-0113 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 8 of the PR95 increment's review (at 082962e) confirmed 16 findings, one high. Round 7's glued-credential chain could start a narrower family inside a wider credential or swallow a key name, and the round found four older gaps. All material findings are fixed; the password-position finding is documented as credential equality. Core projection (packages/core/src/tools/bounding.ts): - The round-7 chain and its glued-span exemption are removed; a glued credential is read at the earlier mask's boundary one pass later, as before, and a run that never settles is redacted and refused (PR95R8-SCRUB-1, TST-R8-3). - Structured summaries mask what JSON escaping hid (PR95R8-PROP-1, high): a non-string result is summarized from its JSON once every member under a credential-named key and every argument after a credential flag is masked whole, and the text rules read that JSON as one text, a value one JSON string down included, so a PEM block or a quoted value that runs across members keeps its context. A summary masks a quoted value that stops at a line break through a later quote that could close it. - The key rule reads one JSON string down: an escaped quote before the separator, a value opened by an escaped quote decoded one level, also behind escaped tabs and line breaks (where an apostrophe opens a value too). Only its escaped quote closes such a value. - A quoted value closes on its line. One that does not is decided once the pass's other masks are known (PR95R8-PROP-4): when one of them takes its line break it closes as the next pass reads it and is masked whole; otherwise it is read as an unquoted run from after its quote, to its line's end, since its quote may close a string the key sits in. - A header label at a line start inside a split credential's continuation is read through a second label view (PR95R8-PROP-2). - Two member names of one object that project alike make the journal projection unsettled, so the registry refuses the tiered call. - toText makes both texts from one media-redaction walk, and the key and scheme rules return early on text that cannot hold them. - The userinfo zone stays round 7's. A password-position key's value runs to the key rule's delimiter, through a URL's host and path, so calls that differ only there share a projection, as recognized credentials do; documented (DOC-R8-1, PR95R8-SCRUB-2). - A quoted value closes at the later of two readings: with the masks before and inside it filled, and with each mask keeping the backslashes it ends with. It applies to the key rule, the journal's closing view and the summary's closer search (SCRUB-PRE6-1, a regression against 9902e0a since round 6). The same rule masks the tail that an opaque value's mask unescaped behind a URL userinfo, which the author's oracles found to be a regression too. - A structured summary's walk keeps a private-key marker whose partner lies outside a member it masks whole, so a block across members is still masked (SEC-R8-PEM-1); the journal and display walks keep their whole mask. - Every summary also reads a view of the same length in which each escaped \n, \r or \t is that separator, for the split-credential, standalone and scheme-label rules, so a line-initial token in a run_command or MCP result is masked (PR95-GEN-R8P6-1, a gap 9902e0a shares). The journal projection reads no escape. Backstop (packages/llm/src/scrub-secrets.ts): the split probe also reads the raw text when terminal normalization changed it. A JSON-escaped separator before a quoted header value and terminal sequences that take a shape's first letter are documented limits, and a path-less hyperlink target the raw probe joins with a later @ a documented fail-closed trade-off. Tests and controls: - Discriminating cases for every change, each checked against its mutation (122 mutants: 118 killed, 4 output-neutral): structured summaries across members, line breaks and credential members, unclosed values read as runs or closed through the pass's other masks and the calls they keep distinct, one JSON level down, colliding member names (also in arrays), continuation labels, two and three PEM blocks (TST-R8-1), the scrubber's TAB retry (TST-R8-2), the fill's bracket, the escaped CR/LF and the ' delimiter (TST-R8-4), the raw split probe, header tokens a JSON escape begins, credentials before a terminal sequence masked in place, the host cut at a fragment, a real, unmocked non-settling refusal (PR95R8-PROP-3), a quoted value's two readings, the private-key markers a structured summary keeps and the escaped separators every summary reads. Core's eight-MiB cases take a case deadline instead of wall-clock bounds (CRIT-R8-2). - Twelve controls added, two removed with their mechanisms, six re-anchored and two witnesses added: 130 controls / 513 targets and witnesses in two shards of 65. Both shards ran locally with the export enabled: 1,177 files and 49.83 / 49.76 MiB. Before this commit the reviewers' generative harness, exhaustive identity and summary oracles, a distinctness oracle, the sixth pre-review's own generators, a mutation run and the affected causal controls ran against the uncommitted fix, and six internal multi-lens pre-reviews reviewed it as unreferenced commits; their findings are resolved here, and two low edge cases that 9902e0a shares are recorded in deferred-tasks.md. Mechanisms an earlier candidate added beyond the findings (a header escape guard, a terminal final-byte view, masking unclosed values to the line end, a trimmed opaque mask, a per-string structured summary) were withdrawn when a pre-review showed they leaked, merged identities or lost context. Docs: built-in-tools.md, effect-journal.md §11 (including the refusal's slot behaviour, CRIT-R8-1), tool-registry.md, error-handling.md and the scrubber's comments (PR95R8-PROP-5), commands.md's date (DOC-R8-2), the secrets controls README, ci.yml, the phase register's round table, ruling and gates, deferred-tasks.md's masker section, and the status lines. The maintainer's ruling of 2026-10-21 narrows this increment's acceptance bar: a round with no high or medium finding and no regression against 9902e0a accepts it, and each low finding is recorded in deferred-tasks.md with its file, an example and where to re-check it. The phase register's PR95 checkpoint records the ruling; every other increment keeps two clean complete cumulative rounds. Refs: ADR-0113 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 9 of the PR95 increment's review (at 6f7dd34) confirmed 18 findings: two high, eight medium, seven low and a nit. Under the maintainer's 2026-10-21 acceptance bar every high and medium finding is corrected here; three lows are recorded in deferred-tasks.md. Core projection (packages/core/src/tools/bounding.ts): - Round 8 compared whole views with === inside its loops and walked a shared run of backslashes once per mask, both quadratic at admitted size (SEC-R9-1, SEC-R9-2). Each view of a quoted value is now built once per pass and its presence decided once, and keepingEscapes remembers each run's start at every end it serves. - A value whose line break a scheme token takes is reread with and without the key rule's own masks, so another key's value that takes its closing quote or ends at its opening one no longer keeps it open, and the key rule's first decision also reads views with the scheme labels' tokens filled, which only move a closing a plain reading reaches (R9-SCRUB-1, R9-GEN-1). - A summary masks a value that one reading stops at a line break through its later closer as well as through a reading that closes early (R9-SCRUB-2). - A structured summary reads the unwalked JSON once: each value the walk masks whole goes back where the walk left a private-use placeholder, as a whole JSON value or as the text of a --flag=value argument, and its mask is a range of that same pass. A quoted value or a private-key block that runs into or out of a credential member is masked across members again, and round 8's kept PEM markers are withdrawn (R9-SCRUB-3, R9-SCRUB-4, R9-GEN-2). - A summary reads private-key markers that a line break, a tab or more than one space splits; the journal reads them as before (R9-SCRUB-5, R9-GEN-3). Tests and controls: - Witnesses for every finding and an admitted-size case deadline for the four quadratic shapes in the journal, display and summary paths; an opaque parameter in a URL username keeps its host (TST-R9-1). A mutation run of 144 mutants kills 139; five are output-neutral. - core-summary-pem-markers, core-summary-whole-json and core-unclosed-closing-view are re-anchored, and core-key-value-through-masks now targets round 7's reading through earlier masks, since the unclosed reread also reads round 6's quoted witnesses; the family stays 130 controls / 513 targets and witnesses in two shards of 65, whose local exports held 1,177 files each and measured 50.19 MiB and 50.09 MiB. Docs: built-in-tools.md (the readings, the unwalked summary, wrapped markers, the 33-character glued-key window, and the terminal-sequence, indented-value and private-key block limits), effect-journal.md §11, deferred-tasks.md (TST-R9-2, R9-GEN-4, CRIT-R9-1, the freshness line, and two author-found private-key block gaps, unchanged since base, pending the maintainer's confirmation), the phase register's PR95 rows, round table, ruling text and gates, the secrets controls README and the status lines. The maintainer's round cap of 2026-10-21 makes round 10 the last: after it only high findings are corrected, and its other findings are recorded in deferred-tasks.md before Step 9a.1 begins. Refs: ADR-0113 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 10 of the PR95 increment's review (at 59c6449), the last under the maintainer's 2026-10-21 round cap, reported eleven verified findings: seven medium and four low as verified (three of the mediums are one finding), no high and no blocker. On 2026-10-22 the maintainer counted GEN-R10-1 as high and approved the two private-key block gaps found after round 9 as deferrals; every other finding is recorded in deferred-tasks.md, as the round cap provides. Core projection (packages/core/src/tools/bounding.ts): - In a structured result's JSON a query value at the end of a line ran on through the next line's key and took the backslash of its value's escaped quote, so a `cat .env` result's POSTGRES_PASSWORD="…" after `?sslmode=require` showed in the summary that agent:tool_result carries (GEN-R10-1, unchanged since base). A summary now opens a key's value whose escaped opening quote ends an earlier mask's backslashes where that mask keeps them, reading it only in the views that keep them. The journal keeps its reading, so v5 digests do not change; a test pins that. - The docblocks that counted one summary difference, or said that no text rule reads a whole-masked member, are corrected (DOC-R10-2). Tests and controls: - A test with five witnesses (run_command, MCP text part, CRLF, Mongo and a tab) and a journal pin; 218 bounding cases. A mutation run of six mutants on the new code kills four; the two survivors are output-neutral, since a mask the kept view keeps ends before a delimiter, where a view that keeps the backslashes closes no later than the filled one. - core-quoted-value-scan and core-escaped-quote are re-anchored on the changed lines; the family stays 130 controls / 513 targets and witnesses in two shards of 65, whose local exports held 1,177 files each and measured 50.24 MiB and 50.14 MiB. - Differentials against 59c6449 and base: the journal and display text identical on 250,000 inputs; every summary that differs on 320,000 inputs shows a subsequence of 59c6449's visible text; no planted-token regression in the generators or in an exhaustive run of 837,606 sequences. Docs: built-in-tools.md (the kept opening and its journal limit; the wrapped markers' whitespace bound, the U+E000 placeholder's fallback and a PEM END marker glued to another match, named where the document claimed more), effect-journal.md §11 (the closing readings of a value that closes, INT-R10-2), deferred-tasks.md (round 10's medium and low findings with examples and re-checks, the journal half of GEN-R10-1, the two approved private-key block entries, the PR95 items of the PR #95 review closed), the phase register (round 10, the rulings, the closure and the round-9 records that DOC-R10-1 found stale), the secrets controls README and the status lines. The increment closes under the round cap. It is not independently accepted in the sense of the approved bar: round 10 was NONCLEAN, and this correction has author verification only. Refs: ADR-0113 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's engine coverage floor job failed on 9eb3815: round 9's admitted-size case 'keeps every projection linear where masks share a run of backslashes or end in one' ran ten 8-MiB projections in one case. It takes 3.7 s plain and 7.2 s under local V8 coverage, and on the GitHub runner it passed the 15 s case deadline (bounding.test.ts:2964, "Test timed out in 15000ms"). The case is now one case per shape (three input shapes and the structured summary), with the same inputs, assertions and deadline; each takes 1.1-2.8 s under local coverage. No deadline is extended. The core projection cases go from 218 to 221, so CORE_PROJECTION_CASES, the provenance pin and the secrets controls README follow. The local coverage gate passes: llm 97.79 % lines / 95.69 % branches, mcp 96.56 / 91.29, core (measured) 96.71 / 91.06. Refs: ADR-0113 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's filesystem causal controls failed on 2a1f735 at export with "Raw phase manifest bound" (tools/causal-controls/retention.mjs:400), and the lint · typecheck · test job's guard then refused its result. Every phase retains an exact manifest of the whole source graph; once Wave 3's PR95 increment grew that graph, a manifest measured 203,421 bytes and the 330 manifests of one export 67,128,930 bytes, 20,066 over the unchanged 64-MiB retained-evidence bound. The family's capacity arithmetic had counted files only (1,987 of 2,000). As the secrets family did in PR95's round 7, the filesystem job now runs two legs that each run and export a disjoint round-robin half (`--shard k/2`, fail-fast off, one artifact per leg) under the unchanged per-phase, case, output, 2,000-file and 64-MiB bounds. Both shards ran locally with the export at 2a1f735's source: 55 controls each, all passing; 165 manifests (33,564,465 bytes) and 997 files per export, 25.65 MiB and 25.59 MiB. require-w8-acceptance.test.mjs now checks both families' sharded jobs (an unsharded filesystem command fails it), and the filesystem controls README records the bound and the measurement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wave 3 Step 9a.1, the first increment of the CLI release map/custody substrate (#46, ADR-0111): the candidate build and its prepack finalizer. The receipt stays compiled_package_test; symbolication, the private map manifest, custody and the release state controller remain Steps 9a.2-9d. - Owned candidate tree (tools/cli-package/candidate.mjs): the producer never builds in the developer's workspace. It records the checkout's commit and porcelain status, copies every file Git would ship (by the repository's ignore rules and the developer's global excludes file, which is the only global setting it reads) into a fresh owner-only tree with exact modes, refuses if the commit or status changed during the copy, installs the locked closure offline, frozen and without lifecycle scripts, and verifies that every workspace link resolves inside the tree. - Producer (produce.mjs, run by pack.mjs): fresh uncached upstream tsc maps for the five engine packages, the sole prepack build, a build cache check before the seal, owner-only staging outside the artifact, and removal of the private root on every path; its composition is tested through injected ports. - Prepack (prepack.mjs) runs tsup once; with a validated staging directory the candidate-mode build emits external maps without sourcesContent and the finalizer (finalize.mjs) accounts for every output, validates every upstream and final map, rewrites sources to repository-relative original TypeScript, removes only the exact map trailers before hashing and stages maps and metafile privately. - The seal reads the candidate and its staging explicitly; the closure guard is map-aware and refuses any map or map link in an ordinary build; the PR #95 fold-ins remove the archive's dead TAR_FORMAT branches and replace the installed smoke's ancestor loop. - release.yml's publish job runs a fail-closed guard first: its entry point exits 1 before it evaluates its rule, so no artifact and no way of invoking it can reach npm publish until Step 9d's custody exists (the Interim release guard ruling of 2026-10-19). An internal pre-review of the prepared change found seventeen issues, none high once verified; all are corrected here (the guard's entry point, the tested producer, the global excludes, exact modes and the test gaps). A mutation run of twenty mutants over the new code kills all of them. The full local CI then caught a top-level await in closure.test.mjs that ran after most tests were registered, so a slow fixture build let the runner remove the file's scratch directory before a later test; both fixtures are now built before the first test. Docs: packaging.md (Status and the 9a.1 implementation boundary), the release runbook, an ADR-0051 amendment, the tooling README, the phase register (9a.1 row and checkpoint, and the PR95 closure's post-closure CI corrections 2a1f735 and 4c3cafe), deferred-tasks.md (the PR #95 tooling nits) and the status lines. Refs: ADR-0111, ADR-0051 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 1 (at a72c7ec) reported nine verified findings, two medium; its plan lens was clean. - An interrupted producer (SIGINT, SIGTERM) left its private maps, metafile, hash record and candidate copy in the temporary directory while its detached pnpm pack ran on. runCommand now tracks every live child tree and stopLiveCommands() kills them synchronously; the producer handles SIGINT, SIGTERM and SIGHUP by stopping them, removing the private root and build home, emptying the artifact directory and exiting with the signal's code. A producer killed outright is a named limit. - Private staging must be canonical and outside the candidate by real path, the repository's included. - Killing tests for the global-excludes wiring (and check.mjs's producer environment, now producerEnvironment()), the placement check's artifact half, the candidate's file-count and byte bounds (now injectable limits) and three finalizer refusals. - CLAUDE.md and AGENTS.md name 9a.1 as the candidate build and finalizer; the ci.yml pack step comment mirrors release.yml; the register's post-closure CI record now matches the runs, and the 9a.1 checkpoint records round 1. Gates: lint:tools, typecheck:tools and every pnpm run ci step outside the three control families (whose captured sources are unchanged since a72c7ec's full run), with the real producer in smoke:cli-package; a mutation run of nineteen mutants over the corrections kills all of them. Refs: ADR-0111 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 2 (at 8ddb7ec) reported twelve verified findings, four medium; its plan and integration lenses were clean. - The producer's finally removed its interrupt handlers before the final removal, so an interrupt during it ended the process with the private root half removed. The handlers now stay until everything private is gone; the removal is a port, and a test interrupts it. - SIGHUP, the handlers' removal and a whole build tree (a grandchild in the build's process group) are tested. - Path containment is decided by segments in one shared helper (paths.mjs); '..private' is inside. - Private staging and the producer's placement compare the real paths and letter case the filesystem stores (realpathSync.native). - pnpm smoke:cli-package forwards an interrupt to the producer and the consumer it started (forwardInterrupts) instead of ending first. - The finalizer gives its maps directory an exact mode; staged and public modes are tested under umask 0277; further finalizer and staged-closure refusals gained killing tests. - The ADR-0051 note links ADR-0111, and the 9a.1 checkpoint records round 2. Gates: lint:tools, typecheck:tools and every pnpm run ci step outside the three control families (their captured sources are unchanged), with the real producer in smoke:cli-package; a mutation run of eleven mutants over the corrections kills all of them. Refs: ADR-0111, ADR-0051 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 3 (at 184562b) reported eleven verified findings, two medium; its security lens was clean. - An interrupt that arrived after the build's last child exited, during the seal or the final removal, was dropped: Node delivers a caught signal only when the event loop next polls, and the handlers were removed first. Round 2's test asserted exit 0 and so pinned the dropped interrupt. exitOnInterrupt (process.mjs) now keeps the handlers until the loop has polled once more. That takes two turns when the work ran from an I/O callback; one turn was measured to drop the signal. The producer then empties the artifact directory and exits with the signal's code, tested from an I/O-callback tail. - An interrupted consumer stops its children, the bootstrap witness's included (tracked in tools/cli-bootstrap), removes its installation and fixtures and exits with the signal's code; completeConsumer requires that abandon step. - pnpm smoke:cli-package starts no later stage once an interrupt arrived, removes its root with retries, and is tested end to end from a copied tooling tree (check.test.mjs). - The producer's isolated build environment, its native real path of the checkout and the prepack catch-all gained killing tests. - Git reading the original checkout uses no home, global or system configuration (isolatedGitEnvironment); a skip-worktree or assume-unchanged file makes the source identity dirty. - An upstream map may map nothing only for tsc's type-only `export {};` output. - packaging.md, the README, current.md and the 9a.1 checkpoint record the corrections and round 3. Gates: lint:tools, typecheck:tools, every pnpm run ci step except the filesystem and secrets families (their captured sources are unchanged), with the real producer in smoke:cli-package, and the acceptance tools under CI=true GITHUB_ACTIONS=true; a mutation run of thirty-one mutants over the corrections kills all of them. Refs: ADR-0111, ADR-0051 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 4 (at aa5c994) reported ten verified findings, three medium; its integration and critic lenses were clean. - An interrupted consumer killed the installed smoke's worker before its own removal: the worker's directory stayed under the report directory and the native fixture target the installed CLI had started in its own process group ran on as an orphan. The fixture's processes now end once the smoke worker that owns their case is gone, and completeConsumer, given the report directory as a required supportRoot, abandons the bootstrap witness's children and fixtures and the smoke's worker roots before removing the installation. Interrupting the real consumer during the native cases leaves no process and no directory. - The local check's ordinary failures, its summary and its hand-off of the store and excludes variables are tested. - A dirty identity's path to the seal, the producer's removal of engine build state and the bounded children's process group are tested. - "Owner-only" is qualified where POSIX modes are not observable; the candidate copy's umask wording and the checkpoint's scope list are corrected, and the register records round 4. Gates: lint:tools, typecheck:tools, every pnpm run ci step except the three control families (their captured sources are unchanged), with the real producer and consumer in smoke:cli-package, and the acceptance tools under CI=true GITHUB_ACTIONS=true; a mutation run of fifteen mutants over the corrections kills all of them. Refs: ADR-0111, ADR-0051 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 5 (at d51a243) reported seven verified findings, one medium; its plan, correctness and integration lenses were clean. - prepareCandidate's link verification is tested as composed: an install that resolves an engine package or the root dependency directory to the original checkout refuses after the install. - The candidate copy refuses a listed path whose parent directory below the checkout is a link, so a tracked directory replaced by an ignored link cannot bring in bytes from outside the checkout. - A core.excludesFile that is present but empty disables global excludes, as in Git, instead of falling back to the default file. - Settled children leaving both live-child sets, the exact `export {};` rule, each field of a facade's empty map, an interrupt the consumer outlives and the smoke-root prefix limit are tested. - packaging.md and the 9a.1 checkpoint record the corrections and round 5. Gates: lint:tools, typecheck:tools, every pnpm run ci step except the three control families (their captured sources are unchanged), with the real producer and consumer in smoke:cli-package, and the acceptance tools under CI=true GITHUB_ACTIONS=true; a mutation run of ten mutants over the corrections kills all of them. Refs: ADR-0111, ADR-0051 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 6 (at 3c6c5dc) reported six verified findings (four distinct), one medium; its plan and correctness lenses were clean. - A test pins that a leader-exit native descendant watches the smoke worker, not its exited parent, so the product's group reap stays what the installed control proves. - The staging directory's ownership check has a killing test. - Every wait for the local check's exit in check.test.mjs is bounded. - Five live status statements that still said Wave 3 Steps 9–19 had not started now say that Step 9a.1 is under review; the register records round 6. Gates: lint:tools, typecheck:tools, every pnpm run ci step except the three control families (their captured sources are unchanged), with the real producer and consumer in smoke:cli-package, and the acceptance tools under CI=true GITHUB_ACTIONS=true; a mutation run of three mutants over the corrections kills all of them. Refs: ADR-0111, ADR-0051 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 7 (at 0e59f5f) reported three verified low findings; its plan, security, correctness and integration lenses were clean. - Git reading the original checkout ignores replace refs (GIT_NO_REPLACE_OBJECTS), so a replace ref on HEAD can no longer make a substituted tree look clean to the source identity; a real-repository test pins it. - The linked-parent refusal is tested for a link above the listed file's own directory and below the root, for both ignore routes. - The fixture owner tests bound every wait and clean up in an after-hook, so a regression fails within the test and leaves no process; packaging.md and the register record round 7. Gates: lint:tools, typecheck:tools, every pnpm run ci step except the three control families (their captured sources are unchanged), with the real producer and consumer in smoke:cli-package, and the acceptance tools under CI=true GITHUB_ACTIONS=true; a mutation run of five mutants over the corrections kills all of them. Refs: ADR-0111, ADR-0051 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 8 (at c691ef9) reported two verified low findings and two nits, all from its tests lens; plan, security, correctness, integration and the critic were clean. - The finalizer's pre-write refusals for a non-flat runtime output and for a runtime output whose own map entry was swapped are tested. - The excludes lookup's forwarded configuration-location variables are pinned, with real-Git cases for an excludes file set only in an XDG configuration directory or a GIT_CONFIG_GLOBAL file. - Further refusals gained tests (upstream name type, metafile shape, metafile and map byte bounds, the pinned pnpm version), as did the producer's exact modes under a restrictive umask; the reference index row and the register record round 8. Gates: lint:tools, typecheck:tools, every pnpm run ci step except the three control families (their captured sources are unchanged), with the real producer and consumer in smoke:cli-package, and the acceptance tools under CI=true GITHUB_ACTIONS=true; a mutation run of thirteen mutants over the corrections kills all of them. Refs: ADR-0111, ADR-0051 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 2 at 5cda645 reported two mediums: - the import control resolved every package manifest against the repository root, but pnpm runs a package's scripts in its directory, so a nested script could reach custody.mjs unseen; the control now refuses any quoted reference, outside the gate, to a file named custody.mjs, the one tracked file of that name, with separators, queries, fragments, percent-escapes and case normalized; - a trailing separator bypassed the gate's link refusal, because lstat('link/') follows the link; the destination and the private root must now be normalized absolute paths, and the destination is checked before it is probed for a receipt. Lows corrected: the sealer reads its options inside its fixed-code boundary; the validator reads the origin and inputs once; a dynamic import in the receipt's closure must be one string literal; a recipient root whose parent is a file is never absent; the 9d handoff widens all three directory admissions together. One low is recorded: encryptOnce called directly still follows a trailing-separator link. Refs: #46, ADR-0111 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 3 at 463a047: the plan and integration lenses were clean and the security sitting reported lows only. The tests lens reported four mediums, all in tests or static controls, the code itself correct: - a non-EEXIST link failure was untested; an injected EPERM now refuses UNEXPECTED, publishes nothing and keeps the record sealable; - the gate's refusal of a linked tarball was untested; - no test pinned the patterns' trailing anchors; - a template literal hid a custody.mjs reference from the control; the control now refuses any mention of the name outside the gate, comments included, after JavaScript and percent escapes, separators and case are normalized. Lows corrected: the closure scan reads every specifier after from or import whatever the spacing; new tests for single reads, throwing options, same-count renames, the toolchain bounds and the receipt bytes reuse returns; the contract's diagnostics names every path's code. The generated sweep gained throw, anchor, logical-operator, bound and statement-deletion operators: 233 mutants, three more gaps fixed, ten argued equivalent. Refs: #46, ADR-0111 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 4 at 563402e, a new complete cumulative review of 819f8cb..563402e with every reviewer in its own worktree, reported no blocker, high or medium finding and no regression; its separate crypto/custody security sitting found lows only. The full pnpm run ci passed plain and alone at that head (test:acceptance-tools 1024 of 1024, all 25 product map controls, every causal-control family), and the custody and release tests passed on Ubuntu 24.04 with GnuPG 2.4.4. Two lows are corrected here (the contract's diagnostics and the custody.mjs control's escape claim); the rest are recorded in deferred-tasks.md with their re-checks. This commit changes no code. Step 9c is next. Refs: #46, ADR-0111 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The maintainer paused Wave 3 after Step 9b.2 on 2026-10-26 and put the CLI MVP first; D58-D72 were ruled on 2026-11-04 from a multi-agent review of the roadmap documents and the code at 5a328bc. - current.md is reduced from 2,095 to 420 lines: live status, the MVP order (Gate 0, MVP-1...MVP-10, then v0.2.1), the after-MVP queue with Waves 3-7, the decision table with D58-D72, standing rules and history links. Every inbound anchor is kept. - Unique records move verbatim: the 2026-07-26 execution plan with Waves 0-1 and the PR #81 closing list (the #W15 definitions) to the Phase 2.5.5 register, the 2.6.F and 2.6.C implementation records to Phase 2.6, and the W4 start-gate ruling to Phase 2.6.5. Duplicated history is deleted. - Phase 2.5.5 records the priority reset: the pause, the evidence, the decisions, seven recorded Wave 3 splits with their order amendments, dated amendments to the Acceptance, Publication and interim release guard rulings, and NI's third green Windows run, which closes its clause. - Phase 2.6 gains the CLI MVP additions (MVP-A1...A23) as the one home for new items, the D62 direction on the default grant, the ADR row 9/11 splits and the M2.6-1 note. - The milestone spine gains M3.5 (CLI MVP, v0.2.0). deferred-tasks.md closes the NI entry and re-points CR-70, CR-71, #61 and the chat-resume entry. - The CLAUDE.md and AGENTS.md status blocks are condensed to the current state. Docs only; no source, test or workflow change. Refs: ADR-0111, ADR-0057, ADR-0095 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Architecture Inside the changed components — 2 viewsComponent view — Packaging & Custody The candidate tree, source map inspection, symbolication verification, and GnuPG archival components.
Component view — Host & Security Hardening Exact 64-bit filesystem pinning, projection settling, and diagnostic secret scrubbing.
Data flow Review ✨ Nothing flagged · reviewed Tip Run 🪧 More tips
Thanks for using PR Lens! It's free for OSS. If it helped, a star helps other teams find it. |
There was a problem hiding this comment.
Sorry @cemililik, this pull request is too large to review
It changes 35,039 lines. Sourcery can review pull requests with up to 20,000 changed lines.
|
Important Review skippedToo many files! This PR contains 179 files, which is 29 over the limit of 150. To get a review, reduce the PR to 150 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to Team to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.
|
Three verified PR #96 review findings in the journal and display projection: - A flag whose last word names a credential (--auth-token, --access-token, --pwd, --x-api-key, --credentials) now masks its argument, as the object-key rule already masked those names as keys; curl's -u/--user user:password pair is masked in every spelling. A flag that only mentions a credential (--password-stdin, --token-file, --max-tokens) or starts with no- keeps the next argument's identity. - A `,` or `/` in a URL password no longer ends the authority: the userinfo is read on to its `@`, unless a port, a public query contact, another scheme or a JSON/quote delimiter comes first. The leaking test pins are replaced; quotes, `}` and `]` stay documented differences. - boundForModel counts the inline media the texts omit (data URIs, base64 sources, binary buffers) against the model-facing ceiling. A 30 MiB data URI was measured as its marker, passed whole to the model and settled into history.db; the spill and preview stay media-free. Also clears Sonar's reduce-without-initial-value bugs (S6959, S7727) and two smells in the escape view. The secrets controls' pinned case counts follow the new tests (234 bounding, 116 registry); their unnecessary String.raw literals are plain strings with identical values. A manual mutation sweep killed all 24 mutants. Affected v5 journal rows refuse matching prepare, as the PR95 tightening documented. Refs: ADR-0080, ADR-0089 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The final summary's error row collapsed separators with sanitizeInline but did not mask, unlike every other sink for untrusted diagnostics. It now uses sanitizeUntrustedInline, with a regression test (PR #96 review, low). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tsup.config.ts decides whether a build carries source maps, yet no tsc program covered it (PR #96 review, low). A second typecheck program now does, and the one index-signature access it found uses bracket notation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR #96 review and SonarCloud findings in tools/: - The packed-manifest runtime dependency check is an allowlist (exact, caret or tilde registry versions) shared by the archive inspector and the receipt binding, so npm:, github:, git and tarball URLs, `*` and open ranges are refused; negative tests on both paths. - copyMatchesCommit asks Git for each copy's blob id (hash-object --no-filters, batched under the Windows command-line limit) instead of a plain SHA-1 of ours (S4790), keeps the ordinary-file proof that hash-object would follow a link past, and accepts the repository's object format; tests cover several batches, a linked copy and a short answer. - Every flagged sort states its code-unit order with one comparator (S2871 x24); localeCompare would reorder manifests and digests. - Fixtures resolve gpg, gpgconf and ps to absolute paths (S4036); the two maintainer/CI path arguments get an absolute-path check and a NOSONAR rationale (S8707). - Mechanical smells: S1121, S2094, S3358, S4043, S4123, S6353, S6557, S6582, S7755, S7763, S7780, S7781, S7786. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d lanes Maintainer rulings on the PR #96 review (2026-11-04): - windows-concurrency drops its advisory label; it carries ADR-0106's Windows filesystem-policy scenarios, which name required CI. Branch protection adds it as a required check. - publish runs in the `release` deployment environment, which is to hold NPM_TOKEN and its required reviewer; a repository secret is readable by any workflow a writer pushes. The guard is unchanged, and release.yml and packaging.md record that D59's release ADR supersedes its condition and must override ADR-0111's "missing custody cannot downgrade to test mode". - The model-catalog nightly names each provider lane live or skipped, as a warning and in the job summary (only DEEPSEEK_API_KEY is configured). - The coverage job's allowance rises 30 -> 45 minutes (15-27 measured) and the secrets causal-control legs' 60 -> 75 (up to 48 measured); their pins follow. The baseline-predecessor fetch merges a nested if (S1066). Refs: ADR-0106, ADR-0111 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…w's rulings The D66 reset kept current.md's live status and moved its 2026-07-26 plan verbatim, but a PR #96 review found its other unique records had left the docs (983 of 1,844 non-empty lines). They are moved verbatim now, links rewritten and headings lowered by one: the Wave 2/3 sections, the rows the reset edited in place, and current.md's, CLAUDE.md's and AGENTS.md's status blocks to Phase 2.5.5; the W7/W8 and Phase 2.6.5 records to Phase 2.6.5; Phase 2's status to Phase 2; "Not started yet" to Phase 2.6. Five lines remain by design (old metadata, two re-titled headings, one paraphrased duplicate whose phase copy is canonical); both move notes say so. Maintainer rulings (2026-11-04): - code-review.md states the single-maintainer review practice. - The LLM backstop's shape list moves to its reference home, docs/reference/shared-core/diagnostic-secret-backstop.md; error-handling.md keeps the rule. The module comment no longer claims an LF/TAB-split opaque Bearer value is joined, and the reference records that limit. - ADR-0105/0106's 2026-10-25 corrections take the dated "Amended" form. - 2.6.Q owns the stream usage-capture item; Step 9d owns a global encrypt-once record; the un-deferred item is "the CR-71 residual". Also: README and the local-dev runbook describe what works (CLI MVP first; a build-then-run CLI loop; desktop and extension not in the repo yet); the Wave 3 count reads 32; MVP-3 and MVP-8 split the Home strip; the branch rule matches AGENTS.md; and deferred-tasks.md records the review's verified lows and the Sonar dispositions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR #96 ruling on NPM_TOKEN said the release prerequisites would carry it: the publish credential is scoped to the release job before the guard lifts, by npm trusted publishing or a protected `release` environment that holds the token, with the repository-level secret deleted. The D59 release ADR chooses between them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SonarCloud's re-analysis of PR #96 (gate OK) reported two cognitive complexity increases from this correction: redactArgVector rose past the bound (21) and visitUrlUserinfo from 18 to 21. The inline-credential and comma/slash-password branches move into helpers with identical behavior, and two cases now pin what the move must keep: a username key does not swallow the host, and with no `@` to read on to the scan resumes inside the authority. The keyring stub gains the real Entry's refusing methods instead of a NOSONAR comment Prettier had moved off its line (S2094). Every mutant of the helpers is killed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR #96 review fix that counts omitted inline media against the model ceiling made the counter optional, and its three guards lifted redactInlineMediaForText's cognitive complexity to 18 (SonarCloud S3776, new on 2026-11-04). The observability projection now passes a discarded counter instead, so the walk keeps its pre-review shape. No behavior change: toText always passed a counter, and the event-field projection ignores it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
… URL passwords masked The D64 round 1 on the PR #96 review correction confirmed two regressions from 0826f30 in the journal and display projections: - A `-u`/`--user` whose value is no user:password pair consumed the next element, so a credential flag there (`unzip -u -P <pw>`, `--user --api-key <k>`) never masked its own value. Such a value is now processed as any element. - The `,`/`/` password helper gave the username the text before the first `:` even after a raw `?`/`#`, so the key/value rules never saw a credential key there and its value's tail after the `@` stayed visible (`http://svc?token=a:b/c@d`). The username now owns that text only when no raw `?`/`#` comes first, as in the authority scan. Each fix is pinned by new cases that fail when it is reverted. Comments now state that a raw `?`/`#` after the delimiter keeps a password public (a recorded limit) and that the seam refuses inline media in a tool result, and the userinfo test no longer claims every spelling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lows from the D64 round 1 on the PR #96 review correction: - The runtime-dependency allowlist follows semver's dot-separated prerelease identifiers, since npm reads `1.2.3-.` or `1.2.3-a..b` as a dist-tag; exact, caret, tilde and prerelease forms are now tested. - A stranded JSDoc moves back to `assertPublicPackagePath`, and the custody fixture generator resolves GnuPG before it creates its temporary home. - A test pins that the required coverage and Windows jobs keep their branch-protection names and carry no job-level `if` or `continue-on-error` (GitHub counts a skipped required check as passing). - ci.yml no longer calls the Windows job advisory, and release.yml says the `release` environment is meant to hold NPM_TOKEN (MVP-A23), not that it does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…review lows - Mark the D61 development→main checkpoint done: PR #96 merged as b3cf117 on 2026-11-04. current.md's status table, Gate 0, D61 row and history, the CLAUDE.md, AGENTS.md and README status lines, and a new D61 checkpoint record in the phase 2.5.5 register with the merge facts, the evidence and the repository settings. - Record the D64 round 1 on the review correction: five confirmed medium findings, all fixed (5a1006e, 3cb9712 and this commit). D64's bar still needs a clean round 2 over the corrected head. - Correct the documents the round found false: the `release` environment does not hold NPM_TOKEN yet (packaging.md, current.md); a raw `?`/`#` after a URL password's `,`/`/` keeps it public; the seam refuses inline media in a tool result. The media bound now lives in built-in-tools.md, the argv rule states that it does not know the program, and effect-journal.md states the identity cost. - Fix the round's documentation lows: a stale backstop pointer, the move notes (the five omitted lines named), six empty self-links in moved text, the Windows job's required-check basis, the admin bypass in code-review.md, and the 2.6.Q usage item narrowed to what W8 left. - deferred-tasks.md: rewrite four PR95 DONE bodies to their closure state and annotate the entries the MVP pulled (the PR #96 review's R12b-03 and R12b-04); record the round's remaining lows and the review's 86 unverified first-pass lows, triaged at f25fb83 (78 entries). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The D64 round 2 on the PR #96 review correction confirmed a medium regression from 0826f30: a boolean flag whose last word names a credential (`--pass-credentials`, `--with-token`, `--ask-password`) masked the element after it unconditionally, so a credential flag written there was masked in its place and its own value showed in toolInput and the journal digest (`helm ... --pass-credentials --password <pw>`). A credential flag in value position is now processed as a flag, which generalizes the round-1 fix for a boolean `-u`/`--user`. With it, lows from the same round: - the `--flag=value` spelling reads the flag name with the text rules, so a secret shape in a last-word flag's name stays masked; - a comment restores that a `,` in a username still ends the authority; - two surviving mutants are pinned (`-u=1000:1000`, a username key with a raw `?` in a `,`/`/` password). Every new case fails when its fix is reverted. A differential oracle over 365,640 argument vectors finds no credential that 64fb877 masked and this head shows, in either projection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… check could skip Lows from the D64 round 2 on the PR #96 review correction: - A runtime-dependency requirement is at most 100 characters (npm reads a version over semver's 256 as a dist-tag), and a name follows npm's rules (no part begins with `.` or `_`, not `node_modules` or `favicon.ico`). Each guard of the shared allowlist now has a test that fails without it, including a non-string requirement. - The required-check pin also asserts that the coverage and Windows jobs have no `needs`, that the Windows Build step and the steps before it and every coverage step carry no `if` or `continue-on-error`, and that `ci` keeps its context name; the reviewer's six surviving mutants now fail. - ci.yml calls the Windows leg of #294 "then-advisory". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The D64 round 3 on the PR #96 review correction found that each new reading could still show text 64fb877 masked: - Round 2's fix read a credential flag in value position as a flag, so a password spelled like one (`--password -MySecret`) showed (medium). Such an element is now masked as the previous flag's value and still masks the element after it, so neither reading leaks; the cost is the flag name in that position. - The `,`/`/` password reading gave the username its text, which hid a credential-word username (`x-access-token:`) or a key after a raw `?`/`;`/`&` from the key rules, so a password's tail after a raw `@` showed. The username now owns no text there, so the key rules mask as they did before and the reading only adds a mask. A superset oracle (markers in every position, flag-shaped values and credential-word usernames included) finds no marker 64fb877 hid that this head shows, over a million display, value and journal comparisons, and finds 60,299 at 07f653e. New cases pin the password tail's five stops and the glued `-u-` guard; every mutant of the sweep fails a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ds and step shells Lows from the D64 round 3 on the PR #96 review correction: - The batch test asserts each hash-object command line stays within 16 KiB, and an answer with an extra line is no match. The blob id is exactly 40 hex digits, as sourceIdentity admits only SHA-1 repositories. - The allowlist's refusal is tested on optionalDependencies and peerDependencies too; its comment says leading zeros are admitted, as npm's and pnpm's loose reading accepts them. - The required-check pin also refuses a job-level default shell and a step shell (only the headless smoke may name bash), and its comment states what a skip and a rename each do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… masked flag names hidden The D64 round 4 on the PR #96 review correction found the add-only property still broken in two places (0826f30): - When an authority ends in `scheme:` and the `/` delimiter begins a nested `//`, the `,`/`/` password reading masked to the nested URL's first `@` and moved the scheme scan past it, so that URL's password tail showed (`https://proxy:https://user:p@ss@target/`; medium). The reading now leaves a straddling `://`, and a backslash in its authority or tail (a JSON escape the key rules read), to the other rules. - A last-word flag's `--flag=value` spelling showed a name the text rules mask together with its value (`--db-token=<v>`). The name now stays only where the text rules keep it; otherwise the element is masked whole. An anchored name (`--password=`) stays as before. Two independent oracles, each first shown to catch 7a12ee6, find no marker 64fb877 hid that this head shows: the round's reviewer's (about 2.2 million comparisons) and ours extended with flag-name, nested-URL and escape shapes (one million). Each guard has a case that fails without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the aggregate's guards Lows from the D64 round 4 on the PR #96 review correction: - copyMatchesCommit refuses a relative repository or candidate root (PACKED_CLI_CANDIDATE_ROOT): the ordinary-file proof read against this process while Git hashed against the checkout. - The batch test pins that hash-object batches are packed, so a counter that is never reset (one git per path) fails it. - The required-check pin also holds the `ci` aggregate's guard steps to exactly their keys and refuses a default shell on `ci`. - ci.yml's floor-check comment names the three required checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rgv rules additive The maintainer ruled on 2026-11-05 to stop the D64 rounds on the PR #96 correction after one careful pass. That pass found: - The `,`/`/` URL password reading (added for the review's A4) cannot be shown to only add masks: the projection repeats until stable, and a reading's mask can change the text other rules read in a later pass. D64 rounds 1, 3 and 4 each found a credential it newly showed. It is removed: visitUrlUserinfo is again the base's, and the text projection equals 64fb877's on 200,000 generated texts. A4 is a recorded limit again; the test that pinned the reading now pins the base behavior. - The argv rules provably only add masks: at the base an element was masked when the element before it was an anchored credential flag that was not itself masked; now whenever the element before it is any credential flag. A glued `-u` pair now keeps its `-u` only where the text rules keep it, like the `--flag=value` name (`-utoken:<v>`). Both superset oracles (ours and the round-4 reviewer's, each validated on a known-bad version) find no violation; core's 4,941 and the CLI's 5,828 tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…correction - built-in-tools.md and effect-journal.md: the authority scan again ends at a `,` or `/` in a password (the review's A4, a recorded limit, and why the reading was removed); the argv rules state that they only add masks. - deferred-tasks.md: A4 recorded with its trigger (a range-merging design validated by a superset oracle); the six entries about the removed reading closed as moot; the resolution noted after round 4. - D61 record and current.md: the maintainer's 2026-11-05 ruling to close the correction with one careful pass instead of a fifth round, a recorded deviation from D64. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Summary
A scoped checkpoint of
developmentintomain, approved by the maintainer on 2026-11-04 (decision D61). Itbrings
mainlevel withdevelopmentbefore the CLI MVP work starts. It is not the Wave 3 PR: Wave 3 ispaused after Step 9b.2, and its PR still follows Step 19 when the wave resumes.
88 commits since the PR #95 merge (
6d2a7372): 168 files, +31,863 / −3,176.What it brings to
maina3e164b5and its corrections.api-key:Bearertoken erasure; raw
?/#URL userinfo). It closed under the maintainer's 2026-10-21 masker bar and round cap.development. They add:package_testreceipt;developmentonly. After this merge,main'srelease.ymlalso refuses every npm publication. That is intended: the guard stays in force until thesuperseding pre-1.0 release ADR (D59) is Accepted and its guard change passes a security sitting.
Accepted at
fc5af701; its Windows clause closed after three green advisory Windows runs (the last at5a328bca).64fb8772, docs only):current.mdis reduced to live status and the new order: Gate 0, MVP-1…MVP-10, then v0.2.1.Review and evidence
Each increment's review rounds, security sittings and CI evidence are recorded in the
Phase 2.5.5 register. The pushed head's push CI is
on the branch:
5a328bcapassed all 18 jobs, including the four installed-CLI consumers and the advisory Windows job;64fb8772is docs only and its CI is running.This PR adds no new review claim beyond those records.
Checklist
pnpm run ciand the CI Ubuntu runners)🤖 Generated with Claude Code