Skip to content

[Tracking] guarded-write chain (epic 1375): rules, port sources, chain-level rows #1991

Open

Description

@easonLiangWorldedtech

Part of the split-plan bookkeeping for the guarded-write (epic 1375, units U1 to U9) chain.

What belongs here

  • chain-level rules and design decisions that no single unit owns
  • port sources and the per-unit derivation notes for content that is ported rather than rebased
  • chain-level review rows that CodeRabbit raises against a unit but whose cause spans units
  • unit-scope notes for units that have no per-unit issue yet must name their unit in the first line

What does not belong here

  • unit-scope defects: those go on that unit's own issue (all nine units now have one - see the unit map below)
  • anything that requires a maintainer decision: raise it in the PR thread instead

Writing rules for this issue

  • no @mention in any comment here: mentions notify people who are not subscribed to this issue
  • do not reference a fork-repo issue number here; a bare number resolves against this repository and silently points at something unrelated
  • when recording an upstream pull request, write it without a leading hash (for example upstream PR 1066)
  • prefer editing an existing comment over posting a new one: edits notify nobody and recompute cross-references
  • this issue's history was consolidated into this body on 2026-10-13; add new chain records as edits to the matching section below rather than as new comments

Unit tracking issues (fws chain)

The relation this line records is a tracker naming its pull, U1 PR 1910 -> #2016, U2 PR 1911 -> #2017, U3 PR 1912 -> #2018, U4 PR 1913 -> #2019, U4b PR 2433 -> no tracker of its own, because its tracker is #2019, the one its dependency PR 1913 closes: PR 2433 travels on branch fws/u4b-json-publish at head 316abe2 (read 2026-11-12T03:43:37Z, recorded in .tmp-cr-audit/s3lane/u112-pr2433.json), and this row claims tracker-to-pull naming only, nothing about a sidebar, U5 PR 1914 -> #2020, U6 PR 1915 -> #2021, U7 PR 1918 -> #2426, re-pointed on 2026-11-12 to the tracker its pull actually closes, which this campaign has decided on 2026-11-12 is the canonical tracker for U7: the read of eight campaign pulls at 2026-11-12T01:41:53Z found PR 1918 carrying a closing link to #2426 and no closing link to #2022, that link made by a closing keyword line naming the number in the pull's own body and taking effect when the pull merges, which is a sidebar fact quoted as measurement and not as what this map line claims. The campaign retires #2022 in favour of #2426 on 2026-11-12, a decision of this campaign and not a state of the platform: the account that authored PR 1918 is the account that opened all four trackers of this pair, easonLiangWorldedtech, and no label marks any of the four, so the older tracker is the campaign's own to retire, while the platform record for #2022 at 2026-11-12T01:53Z stood at state open, state reason null, closed_at null, no closed event among the 7 events on its timeline and the title "Tracking fws U7: remaining write tools publish through the guard". This leg wrote no closure to #2022 and observed none., U8 PR 1916 -> #2427, re-pointed on 2026-11-12 to the tracker its pull actually closes, which this campaign has decided on 2026-11-12 is the canonical tracker for U8: the read of eight campaign pulls at 2026-11-12T01:41:53Z found PR 1916 carrying a closing link to #2427 and no closing link to #2023, that link made by a closing keyword line naming the number in the pull's own body and taking effect when the pull merges, which is a sidebar fact quoted as measurement and not as what this map line claims. The campaign retires #2023 in favour of #2427 on 2026-11-12, a decision of this campaign and not a state of the platform: the account that authored PR 1916 is the account that opened all four trackers of this pair, easonLiangWorldedtech, and no label marks any of the four, so the older tracker is the campaign's own to retire, while the platform record for #2023 at 2026-11-12T01:53Z stood at state open, state reason null, closed_at null, no closed event among the 6 events on its timeline and the title "Tracking fws U8: interactive diff-view saves publish through the guard". This leg wrote no closure to #2023 and observed none., U9 PR 1917 -> #2024, and not a link sitting in each PR's sidebar: a read of ten campaign pulls at 2026-11-12T01:23:28Z found a platform closing link on exactly one of them, PR 1930 reaching #1935 through a closing keyword in its own body, while the other nine reported zero linked closing issues, PR 1913 among them naming #2019 in prose with no closing keyword, because an issue named inside a body without a closing keyword produces no link for GitHub to act on. Closing keywords are now in the stored bodies of all nine PRs named above, eight of them found in the read of 2026-11-12T01:41:53Z recorded in .tmp-cr-audit/s3lane/eight-receipt.json and PR 1913 carrying Closes #2019 found in the read of 2026-11-12T02:18:08Z recorded in .tmp-cr-audit/s3lane/two-receipt.json, those keywords having been placed by the description rewrites this paragraph records next, and PR 1913 also carries a platform closing link to #2019, open with state reason null, in that same 02:18:08Z read while PR 2433 read beside it reports zero linked closing issues and no closing keyword in its 6,616-character body; all nine PRs named above have now had their own link state read, so what this line claims is still the tracker-to-pull naming and nothing about a sidebar. All nine PR descriptions were rewritten for human reviewers following maintainer feedback on PR 1910: each leads with what the PR does and why in plain terms, links its unit tracking issue with a Closes line (U9 additionally closes the epic as the last unit), and keeps the measured verification numbers under Test Procedure / Verification at this head.

Decision requests

One canonical write lock, or an accepted residual window

Two chain registrations converge on the same end state and neither can be closed inside a single unit. The runtime has no atomic compare-and-swap rename, so a pre-rename verification callback followed by a plain rename cannot be made sufficient by any unit, and a symlink refusal that walks ancestors once cannot be made sufficient while publication is path based. The only shapes that make both claims true are a single lock that every writer in the repository honours, including writers outside the extension host, or an explicit, documented residual window on both controls. A unit-level change cannot choose between those, and a registered row stays red at every head until one of them is chosen, because a citation does not clear a pre-merge row - only code at the head does. Until the choice is made, the units that wire these controls cannot reach an approving review at their own head.

Chain-level defect classes

1. A rollback that persists unapproved streamed content (defect class, not one call site)

A rollback path awaits workspace.applyEdit() or TextDocument.save(), discards the boolean, and continues as if the operation succeeded. When the editor refuses the restore, the buffer still holds content the user never approved and the following save writes it to an existing file, while the caller reports a completed rollback.

Units affected: U8 (PR 1916) in this chain, and the same primitive as consumed by PR 1921, PR 1929 and PR 1932 in the other two chains.

Two facts that are different and must not be merged into one attribution: the shared primitive is DiffViewProvider.restorePreStreamBuffer() / saveBufferClean(), whose primitive owner is PR 1930 (commit e41ebe0), with port direction 1930 -> 1916 / 1921 / 1929 / 1932; the semantic baseline is PR 1928 discardUnapprovedStream() and its editorFailure channel, i.e. a false return from applyEdit() means the rollback failed. The baseline was already broadcast, so it is not a new rule.

Acceptance for any unit that carries the row: restorePreStreamBuffer() throws when applyEdit() returns false; document.save() and the tab close are not called; the failure reaches the layer that reports it (revertChanges -> revertDiffChangesBeforeReset -> rollbackError); a test asserts the user does not receive a "successful rollback". A save that resolves false must propagate. For PR 1932 additionally: the discardFileTab() shape (restore and save hoisted above the tab loop) must stay intact, and prettier whitespace changes do not affect it. Fix it in the callee so every caller inherits the contract, rather than adding another inline check at each call site - this is the same lesson as the rollback fix in 9c54765: fixing one call site does not fix the class.

2. A confined publication must pin the directories the write itself created

The caller can only record the identity of directories that already exist, and a recursive mkdir reports nothing it made, so the pre-commit re-check walked a list that omitted exactly the components that appeared during the write. A local process that replaces one of those with a link to outside the scope sends the commit somewhere the confinement decision never made, while every recorded identity still matches.

Fixed on U6 (PR 1915) in commit 3289a72: after the mkdir the primitive records the identity of each directory it created and the re-check walks the recorded list plus those; the stat-with-mapping is shared by both call sites so no second branch set appears.

Inherited by any unit whose branch carries this version of the primitive - U1 and U8 among them. Ports are owed, not copied: each unit's call shape differs, so a port needs its own red-first test and its own negative controls. Carry a port when that unit is pushed for some other legitimate reason; do not push a unit solely to deliver it.

Acceptance for a port: (1) a confined write whose parent chain is created by that write, with one component swapped for an out-of-scope link after the mkdir, fails with the ancestor-replaced error and renames nothing; (2) the same shape without the swap publishes normally; (3) a swap of a pre-existing ancestor still fails. Negative controls, one per half of the fix: skipping the recording turns test 1 red, and recording without feeding the list to the re-check also turns it red.

3. Fail-closed DACL restore must reach the old-generation copies of the publish primitive

In the old-generation copies of src/services/file-safety/safeWriteText.ts the DACL restore runs after the commit rename and, on failure, only records a flag and warns, so a replacement is published even though the captured DACL could not be restored. The caller gets no signal: the primitive returns success and the only post-commit throw is PublishNotDurableError. A call-site preflight would be a second mechanism duplicating the primitive's own capability logic, and requesting a backup does not fail closed because the replacement is still published.

Ownership: the chain already carries the correct shape. The earliest unit in merge order implements a typed DaclRestoreError thrown after the commit rename when the restore fails, plus a fail-closed inspection error before the commit, and its own pre-merge row passes with that shape. The old-generation branches carry independently re-derived copies that predate it, so the fix is a convergence port of one shape, not a new design.

Acceptance, not bound to a commit: (1) a restore failure after the commit rename reaches the caller as a typed error, and no caller can observe success for a publish whose DACL was not restored; (2) the branch converges on the chain's single shape rather than adding a second mechanism in the caller; (3) the direct-save path surfaces the same failure instead of swallowing it; (4) backup retention on restore failure does not regress, and capture stays fail-closed before the commit; (5) every branch carrying its own copy of the block gets the port re-derived for that branch, not copied byte for byte. Negative-control shape: one mutant per production call site, each reddening only the tests naming that behaviour; run each mutant against the pre-fix spec to show the branch was previously uncovered; if a mutant reddens far more tests than the target, assume the mutant is wrong first; restore mutants byte for byte and verify with a hash.

4. Publish must not follow a swapped ancestor link when a caller asks for symlink refusal

The refusal walks the ancestors once, before anything is resolved, staged or locked, while the two later checks look only at the final component and publication is path based - the directory creation and the rename both take a path. An ancestor replaced by a link after the walk therefore redirects the commit, so the control the caller asked for is not what the caller gets. The runtime has no renameat equivalent, so this is a design decision in the primitive rather than a local patch: either publish through handles with no-follow semantics, or stop claiming the protection for paths that cannot be proven stable after the check.

Ownership correction measured at the earliest unit head: the primitive in the earliest unit of the declared merge order has no refusal option, no confinement option, no ancestor walk and no external callers, so the claim this registration narrows is vacuously true there and no change at that head can produce a test that is red first. The earliest unit that actually carries the control is the one whose helper resolves the caller path against a declared scope root and walks ancestors once before anything is staged or locked, and that is where the cheap half belongs - document precisely what the control does not cover, add a test that swaps an ancestor after the final component check and is red before the change, and decide whether the credential path keeps the claim. Handle-relative no-follow publication is not available in this runtime, so the narrowing is the only implementable half.

Carrier correction measured branch by branch: the confinement control is not new to the unit that first documented the window. The unit before it in the declared merge order already carries the identical control - the same scope-root resolution with the missing-ancestor walk, the same pre-lock and in-lock checks in the same order - and the same credential call sites that pass it. The two earliest units carry no such control at all. The documentation narrowing therefore has to be ported to that earlier unit and re-derived: where that copy still decides from a truthiness test, keep the boundary wording and drop the sentence about a declared but empty root, which only exists in the later copy.

Acceptance, not bound to a commit: (1) either publication is handle relative and no-follow for the directory creation and the rename, or the control is removed from the credential path so it no longer claims protection it cannot prove; (2) the chosen boundary is documented on the option, including what it does not cover; (3) a test swaps an ancestor after the final component check and shows the outcome the option now promises, red before the change; (4) the shape lands in the earliest unit that carries the control and is re-derived per branch when ported forward; (5) callers that pass the option keep their existing behaviour for ancestors that do not change under them. Negative-control shape: one mutant per production call site, each reddening only the test naming that behaviour, plus a run of the new test against the pre-fix production file; restore byte for byte and verify with a hash.

5. The symlinked-ancestor walk is not bounded by the authorized scope root (owed on U7, PR 1405)

A symlink above the authorized root - a linked workspace root, a linked temporary directory on a developer machine or a CI agent profile - refuses writes that the caller opted into for a reason. The refusal exists to stop a link redirecting a payload out of the scope; applied above the authorized root it stops the write without protecting anything, because nothing above that root is part of the containment decision.

Acceptance: (1) a write whose ancestor chain contains a symlink strictly above the authorized scope root publishes normally, with the payload at the named path; (2) a symlink strictly inside the authorized scope still refuses, with the same error the refusal gives today; (3) the credential-carrying case keeps its current behaviour: a link at the final component is an error, and a link inside the scope is an error; (4) the walk stops at the scope root rather than at the filesystem root, and the code says which root it stopped at.

Negative control: red first - the test for case 1 must fail against the current walk; then green with the bound in place; then red again with the load-bearing piece removed - drop the lower bound so the walk runs to the filesystem root again, and case 1 must fail. A second control for the other half: widen the bound so it also ignores links inside the scope, and case 2 must fail. Both mutants restored byte-exact.

Notes for whoever takes it: heavy lift, deliberately not bundled with the comment and documentation changes already landed on that unit. Not tied to any commit: carry it on the unit that legitimately pushes this primitive next, re-derived against that unit's call shape rather than copied. Related chain facts: the lock key and the publication destination are different paths, and the fold of a lock key has to match the way the lock entry is named (see the port-source record below).

6. A shared fixed staging-directory name lets a workspace-shipped symlink redirect the staging chmod and file

A repository that ships a directory entry named .file-safety-staging as a symlink to any directory the user owns has that outside directory set to 0o700 by the first confined write into the containing directory, and the staged temp file is created in the outside directory before the commit rename.

The chain's single shape is a per-write unique staging-directory name, which removes the hazard by construction: the entry the write creates, chmods and writes into is one it made. Every unit branch of the declared merge order (U1 through U9) already carries that shape.

Carriers of the defect: the old-generation copies on PR 1395 and PR 1405 still use the shared fixed name. PR 1408 carried the convergence port in commit 87ee84e (raised as inline finding 4240865298 against its head); this records the port still owed on the two earlier old-generation branches.

Acceptance for a port: (1) two self-staged writes into one directory receive different staging names, each of the generated shape, and each write's chmod and staged file land inside its own directory; (2) a pre-existing entry named .file-safety-staging - plain directory or symlink - is never chmod-ed or written into by this layer; (3) the test is red before the change on that branch. Negative-control shape: one mutant - revert the staging-directory name to the shared fixed name and confirm exactly the new ownership test reddens and nothing else; restore byte for byte and verify with a hash.

7. Empty declared root: reject semantics are canonical

Correction after two units fixed this independently: the two copies diverged. The unit that merges earlier in the declared order rejects an empty or whitespace-only declared root with the confinement error naming that root, before the lock key, the lock, directory creation or staging, because the helper's stance is that a failure must surface rather than be decided from a guess about the caller. The later copy currently resolves an empty string to the process working directory instead. The canonical shape is therefore the rejecting one, and the later copy must be re-derived to it when the earlier one lands, since merging the later copy as it stands would silently replace reject semantics with working-directory semantics in the landed default. Until then the later unit stays as it is rather than spending another review cycle, because it cannot merge before the earlier one anyway.

8. U9 teardown: the waiter cannot tell whether the pass it waited for succeeded (PR 1917 / PR 1918 - fix once and port)

runTeardown cannot tell a waiter whether the pass it waited for actually succeeded. The tracked promise is resolved by release() in the finally block, so it settles successfully whether the owner's cleanup or finalize returned or threw. The waiter's await this.teardownInFlight therefore resolves in both cases and it returns false - "not my pass" - with no signal that the pass it just waited through left the document mid-teardown. The owner's own caller does see the throw, so the failure is not swallowed outright; what is lost is the waiter's ability to react, which is exactly the case a cancellation lands in: the second caller arrives, waits, and walks away believing the buffer was reverted and the tabs closed.

The two structural halves of the original note are already in place and should not be re-derived: finalize now runs inside the tracked promise, so a cancellation arriving after the callback but before the restore and reset no longer starts a second full pass, and the gate is held until both settle.

Acceptance: a test where the owner's cleanup rejects and the waiter is already parked: the waiter must either see the failure or run the recovery itself - asserting only that the owner rejects is not enough, because that half already holds. Negative control: make the waiter's path indistinguishable again (resolve the gate unconditionally and drop whatever channel carries the outcome) and that test must fail.

Owed changes and ports

Stop accepting a bare staging path (U6, PR 1915)

The text writer still accepts a caller-supplied staging path by prefix alone, for compatibility, and the wording says it is checked rather than trusted. Verified at the head of U6: no production caller passes a bare path - the JSON writer takes the handle from the staging-file API and hands the handle down. So the compatibility acceptance has no user, and it keeps a bypass in the primitive: a prefix check on a path a caller invented is not the opaque capability the design relies on, because the caller can name a path it did not create.

Acceptance: (1) a bare path is refused with an error that names the handle API; (2) the handle path still works unchanged; (3) three assertions that the handle cannot be forged by naming a path - a path this call did not create, a link at the staging location, and the target itself. Negative control: restore the prefix acceptance and drop the handle identity check, and the unforgeability assertions go red.

Port source: folding a lock key the way the lock is placed (owed on U8, PR 1916)

Source: U6 (PR 1915) commit 5131bc0. Derived into U8 (PR 1916) commit 19aec00 - not byte-identical, because U8 computed the same-identity comparison inline while U6 has it as a helper.

The rule, which is what a port must reproduce rather than the code shape: the advisory lock is the entry <path>.lock beside the file, so a key must be folded the way that entry is named. On Windows the filesystem folds two spellings of one directory entry twice over - case anywhere, and short (8.3) names inside a component, which is how a CI agent hands out its runner profile directory - while a string comparison folds only case. So the fold canonicalises the deepest EXISTING ancestor and appends the segments below it, case-folded on Windows, and the walk starts at the parent, never at the file: resolving the final component through a symlink folds a link together with its referent, and those are two different lock entries.

Owed on U8: the mixed-ancestor acceptance test (an existing ancestor spelled short with a tail that does not exist yet) has not been derived there, because in U8 the same-identity comparison is only consulted when publishing over a link. The test must be re-derived against that shape, not copied. Verification limit recorded on the U8 commit: the four windows failures that motivated the port do not reproduce on a host whose temp directory has no short-name ancestor, so only CI exercises them.

Checkpoint-queue depth has no cap, and no caller in PR 1404 can observe one (PR 1404, per-write-checkpoints B1)

Raised on PR 1404 at head e406c09 (threads 607pkQ on src/core/task/Task.ts and 607p-r on src/core/checkpoints/index.ts) as a size bound on enqueueCheckpointWork. Not implemented, and the reason is a measurement rather than a preference: enqueueCheckpointWork (index.ts:251-282) is entered only from checkpointSave and checkpointRestore, and neither caller reads the queue's length. checkpointSave gates the start of each queued operation on its predecessor's settlement plus its own expiresAt and isCanceled (:302-311); checkpointRestore is passed no options at all, because a queued restore must never be dropped. A depth cap added in this unit would drop work that no caller in this unit can see being dropped, and its only observable effect would be a save returning the same no-commit result an expired request already returns.

Owed on: whoever owns the queue's depth contract. Queue depth becomes observable in unit 2 of the 1408 split (refs/tmp/s3-1408-u2-guardedwrite), where a caller can count queued entries, so the cap, its unit of measure (entries or bytes) and the drop policy for a bounded queue (fail, drop-oldest, or reject the caller) belong there and port back rather than being guessed where nothing can observe them. Size is not the reason for this deferral: the reconciliation push on that branch measured 325 of the 500 counted lines against the merge base, and the cost of a cap that does not exist yet was deliberately not quoted.

  • 1395 owes one test-only row out of its own review body (Regression Evidence), named here so it survives the reviewer. No test injects a non-committed publish rejection into saveDirectly. Inside describe("saveDirectly method") in src/integrations/editor/__tests__/DiffViewProvider.spec.ts at head 39d4b95128d3485ff5b9f8eac37f7432da490b11, the only injected safeWriteText rejections are DaclRestoreError at :886 and PublishNotDurableError at :906, both asserted as warnings; the rejection tests at :798 and :823 fail the guards instead, and :830 asserts that the publish never ran. Cost: 0 counted executable lines, because packageForPath in scripts/stryker-diff.mjs excludes __tests__/, so the row cannot move 1395's measured width of 359 counted lines of the 500-line cap. Condition: it lands in the push that follows @edelauna's answer on 1395's three open rows (4510971756, 4510971765, 4510971873), not in a push made to satisfy a review-body item. Owner: whoever holds 1395 when that answer arrives. Recorded 2026-11-09 by s3-1395, with the disposition comment 6746153431 on the PR.
  • 1395 owes the five named rows from fix(utils): key the advisory file lock by identity, not by path spelling #2368's lock-identity work, accepted by name and held, not scheduled. Names quoted as they read at 2368 head d76bbfd4f9f7a81cf517a9727c7cbc3bf5926677 (ref vps2f-filelock-identity-key, blob bd54acfe41fc of src/utils/__tests__/fileLock.test.ts, 688 line-endings / 33,378 bytes / 21 rows): contends an unrelated sibling named like the link target against the link's lock; keeps a waiting writer at the caller-named artefact across a leaf-link replacement and merges into the referent that answers when it proceeds; refuses a second key on a held artefact and a late release from the writer that let go; keeps identities whose digits concatenate alike apart; falls back to the caller's own path when no ancestor has an identity. Package sent by u5a-2359 on 2026-11-09 with nothing written into 1395's tree. Why held rather than scheduled: the primitive does not exist on this branch, and 1395 measured that rather than taking the package's word. At 1395's head 39d4b95128, src/utils/fileLock.ts (blob d8bdbf910b30, 121 line-endings / 4,985 bytes) contains no statIdentity, no canonicalLockKey, no lockArtefactPath, no canonicalLeafSpelling, no nearestExistingDirectory and no lockfilePath; it calls lockfile.lock(absoluteFilePath, { at line 48 with realpath: false at line 51; and src/utils/lockLeafSpelling.ts is absent from that tree. Under that implementation the library derives <file>.lock beside the file, so the first three names assert a caller-named-artefact contract this unit cannot exhibit, and the last two need an identity key that does not exist here. The port is therefore blocked on the same decision as 1395's open row 4510971873 and 1405's escalated row 4504216100 - one ruling on the lock-key identity contract, seen from three branches - and not on a push window. Target file chosen by this lane now so it is not re-litigated later: src/utils/__tests__/fileLock.test.ts (blob 92637fe7a0df, 34 line-endings / 1,088 bytes / 2 rows, the file that exercises acquireFileLock and withFileLock, rather than the 2,398-line-endings writer spec), and the package's own gating note travels with it: rows 1-3 are POSIX-gated and run as skips on Windows, so a Windows-only green says nothing about them. Cost 0 counted executable lines whenever it lands, because the destination sits under __tests__/. Two corrections to the version of this row posted earlier today. The earlier row measured the destination at the wrong commit: it quoted 2,399 lines, which is src/services/file-safety/__tests__/safeWriteText.spec.ts blob 5b07bffb1181 at 1395's current head 39d4b95128 (110,036 bytes, 2,398 line-endings), while the package was measured against 89384b314acd, where the same path is blob 86a53b388acb at 99,844 bytes and 2,190 line-endings, or 2,191 counted as split-lines. So the 2,191-against-689 figure is one counting convention applied to two files, not a disagreement between lanes. The earlier row also named the destination without its __tests__/ directory. Recorded by s3-1395; nothing pushed and 1395's head is unchanged and clean at 39d4b95128.
  • 1405 owes five accepted test-only rows out of its own review, named so they land rather than drift. Rows 4509757197 (POSIX 0o664/umask 022 and new-target/umask 077 published-permission assertions at src/utils/__tests__/safeWriteJson.test.ts:1081-1104, gated to non-win32 with the mask restored in finally), 4509757204 (Windows junction cleanup by fs.rmdir with mock removal in finally), 4509757233 (the error-stream mocks must close the stagedFd they are handed; stagedFd occurs 0 times in that 1,626-line test file while the writer creates it at safeWriteJson.ts:487), 4510030249 (real-writer export test through the public entry point, platform-gated for symlink privilege) and 4510030255 (catalog-loaded test asserting the rendered common:errors.settings_export_failed notification, the key already used at src/core/config/importExport.ts:360). Cost: 0 counted executable lines each, because packageForPath in scripts/stryker-diff.mjs excludes __tests__/, so none of them can move 1405's measured 497-of-500 width. Condition: they land in 1405's next push window. Answered in-thread 2026-11-09 17:32:58Z-17:33:23Z (replies 4515845286, 4515845747, 4515847610, 4515847974, 4515848354); no thread resolved and nothing pushed. Recorded by s3-1395.
  • 1405 owes four deferred production rows and one escalated design row, each priced against its own headroom. Deferred to 1405's follow-up unit on the S4a chain (unit tracking issue Tracking S4a: guarded write CAS core with per-path FIFO chain #1399), because 1405 measures 497 counted executable lines of the 500-line cap at head 8186900bfc - three counted lines of headroom, no test file charged - and each row needs a production edit beside its test: 4509757215 (the catch cleanup at safeWriteJson.ts:411-436 unlinks newFileToCleanupWithinCatch without recording whether this writer created the stage, so an EEXIST collision deletes another writer's file), 4509757220 (_flushFileToDisk at :531-542 reopens with r+ and only logs on failure, so a read-only stage is published unsynchronised; synchronise the descriptor from :487 instead), 4509757226 (fchmodSync failure is swallowed at :493-497, so a 0o664 target can publish 0o644 under umask 022), and 4510030261 (showErrorMessage is awaited inside the export catch at importExport.ts:360, so a rejecting notification escapes exportSettings). Escalated rather than answered: 4504216100 (Major) asks that writers stay serialized on the caller-named publication path across a symlink replacement - the same lock-key identity question 1395 has open as row 4510971873 and that fix(utils): key the advisory file lock by identity, not by path spelling #2368's identity-key rows are being written now - so it is put to @edelauna naming blobs f9073dcd64e9 (safeWriteJson.ts) and 49807ea154d4 (fileLock.ts) at 8186900bfc against 1395's 571d345098c3 and d8bdbf910b30 at 39d4b95128. Two further rows on that PR were closed as already delivered at the head the reviewer pinned, measured line by line: 4504216094 and 4509757207. Answers posted in-thread 2026-11-09 17:32:58Z-17:33:23Z; nothing resolved, nothing pushed. Recorded by s3-1395.

Ownership rulings

The safeWriteText primitive has one owner chain (registered for U4, PR 1913)

Three pre-merge rows raised on PR 1913 at head c36fd64 land on the shared primitive src/services/file-safety/safeWriteText.ts: (1) Security Boundaries - a symlink race requires confineTo to be carried into safeWriteText and enforced at the final publish action; (2) Persistence Integrity - the committed semantics at safeWriteText.ts:509-511; (3) Lifecycle Resource Cleanup - the swallowed backup unlink at safeWriteText.ts:553-558.

Ruling: these are primitive-level, not U4-level. The primitive already has one fixed defect in this chain (the pin-before-commit escape, fixed in U6, with the port owed to U1 and U8 and re-derived per unit rather than copied byte for byte), so a second unit editing the same block independently is exactly the double fix the chain rule forbids. The primitive fix belongs to the earliest unit in the declared merge order that can carry it, and ports forward from there. U4 does not fix these three rows; it references this ruling and resolves its threads.

Acceptance for whoever takes the primitive fix: symlink race - swap an ancestor for an out-of-scope link between the last check and the publish action -> AncestorReplacedError and no rename; no swap -> publishes. Committed semantics - a failure after the commit point must not be reported as a rollback, and must not leave the caller believing the previous content is back. Swallowed backup unlink - ENOENT and ENOTEMPTY stay silent, every other error is surfaced, and the loop continues. Negative control shape per row: mutate the exact production line, expect exactly the tests that name that behaviour to go red, and run the same mutant against the pre-fix spec to prove the branch was uncovered. A mutant that reddens far more tests than the target means the mutant itself is wrong.

Note for the reader: an open-thread count on a PR and the number of rows in that PR's pre-merge table are two different measurements and must not be used interchangeably.

Registrations that are not defects (U3, PR 1912)

1. The Linked Issues check reports an error because the branch is based directly on the repository default branch, not on a preceding unit. CodeRabbit therefore sees the whole stack (13 files, roughly 3.6k added lines, 39 commits) instead of one unit's delta, and its linked-issues validation reports an error for a pull request whose diff spans requirements owned by other units. Handling chosen: the pull request body carries a requirement-to-unit table so a reviewer can tell which requirement this unit serves. Acceptance: when the earlier units of this chain merge, the row disappears without any code change - if it does not, the cause is something else and this record is wrong.

2. A review finding overstated the effect of a cleanup on a committed publication. The finding said the cleanup path can delete the already-committed target. On the POSIX path it cannot: the staged name is gone once the rename lands, so the unlink reaches a name that no longer exists and the only observable effect was a false leftover warning. The shape criticism was still correct - a cleanup that reaches for a path it may not own needs its ownership stated, not inferred - so the guard stayed. Acceptance: none required. Recorded so the next reviewer of a sibling unit does not re-derive the overstated consequence, and so a future row about the same shape is judged on the shape, not on the effect that turned out not to exist.

Closed before registration (verified at head, do not re-derive)

  • ApplyPatchTool refreshing a stale observation: the hunk read now observes only when there is no prior entry or the prior version equals the token just read, which is the same stale policy ApplyDiffTool applies, and the comment at the site states why an older observation must not be refreshed.
  • PostCommitDurabilityError leaving the backup behind: the delete path catches it and unlinks error.backupPath before rethrowing.

Both verified by reading the two files at the head of the U9 branch on 2026-10-10, not from the queue entry.

Split plan as ruled, and the priced cut: U5 remainder (PR 2357, branch vps2f-1914-u5b)

Recorded 2026-11-09 by lane u5a-2359. The full priced record is comment 6751466344 (posted 21:06:35Z, patched in place at 21:07:37Z to replace a placeholder digest, which the patched text names as superseded); this section carries the cut as ruled, the per-unit prices, the merge order, the preconditions and the verify exits, because those are queue facts and the queue lives here.

Pricing basis (the same for every number below)

Base 970b3a47ba69a9dfe9e9e5095bda75be3a2d739b — the main that contains PR 2385, verified rather than assumed: merge-base e487cd7b2a 970b3a47b answers e487cd7b2a (main is an ancestor of 2385's head) and merge-tree --write-tree main 2385head writes 311cfce72578b74c40a7e8b73ce745cf52387ada, equal to 2385's own tree with zero conflict entries. Content head d6fa7117e07b70455da24e4722b47eb0b202fa6e.

Charged by selectFromGit from scripts/stryker-diff.mjs, blob 7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc, 30,108 B / 869 line-endings, one distinct blob in every run; caps MAX_CHANGED_LINES = 500 (line 11) and MAX_MUTANTS = 400 (line 12). Interpreter node v22.23.4 (C:/work/Zoo-Code-fork/.tmp-node22/node_modules/node/bin/node.exe). Every priced head is a single-parent git commit-tree off the base holding the unit's content, so the first-parent override in resolvePullRequestBase (lines 287-291) never fires and no figure below is main's contribution; parent counts were re-read with git rev-list --parents and cat-file -p, not from a helper field (the helper's own field read 0 in the first twelve runs because of a mis-escaped whitespace regex — instrument fault, discarded in favour of git). In each run the tree was moved to the commit being priced and git rev-parse HEAD printed beside the asked sha; all runs matched. Priced in worktree wt-r302-2357-cut-price at pnpm-lock.yaml blob 2035d1d167a15ad0ea1972bd1a974abdace092fb (612,370 B) with node_modules junctions into wt-r297-2385-head-install's store (typescript 5.9.3, vitest 4.1.12) — a junction tree, not a fresh frozen install, named as such.

The cut: eight units, whole files only, each priced standalone against the base above

unit files counted lines / selectors a+d (code + spec) zdt split verify exit
U5c-1 src/utils/fileLock.ts, __tests__/fileLock.spec.ts, __tests__/fileLock.canonicalKey.spec.ts 62 of 500 / 21 of 400 592 (217 + 375) 0 — SOFT-OVERSHOOT, rationale required in the PR body
U5c-2 src/utils/jsonFileRecovery.ts, __tests__/jsonFileRecovery.test.ts 26 / 3 176 (47 + 129) 0 — UNDER-SOFT
U5c-3 src/utils/safeWriteJson.ts, __tests__/safeWriteJson.interruption.test.ts, __tests__/safeWriteJson.lockKey.spec.ts 131 / 46 614 (366 + 248) 0 — SOFT-OVERSHOOT
U5c-4 src/utils/__tests__/safeWriteJson.test.ts 0 / 0 517 (0 + 517) 0 — SOFT-OVERSHOOT
U5c-5 src/integrations/misc/indentation-reader.ts, src/core/tools/ReadFileTool.ts + their 2 specs 51 / 31 227 (136 + 91) 0 — UNDER-SOFT
U5c-6 src/core/tools/__tests__/readFileTool.spec.ts + the src/eslint-suppressions.json row 0 / 0 1,140 (0 + 1,138 + 2 config) 1 — HARD-OVERSHOOT, must split
U5c-7 src/core/task/observationRegistry.ts, src/core/task/Task.ts + their 3 specs 25 / 15 384 (117 + 267) 0 — UNDER-SOFT
U5c-8 src/services/mcp/McpHub.ts, src/services/mcp/__tests__/McpHub.spec.ts 27 / 14 211 (68 + 143) 0 — UNDER-SOFT

The eight units add up to exactly the whole remainder: 322 counted executable lines of 500, 130 selectors of 400, 3,861 a+d (951 code + 2,908 spec + 2 config), and the whole remainder priced as one tree (be00e75c1463cf6194215400c4ff12e5ff5c152e, single parent off the base) prints 322 / 130 / 3,861. So the cut neither loses nor buys anything in the gate's currency; it only moves which reviewer reads what.

Counted lines are not the whole picture and the zero rows are named rather than averaged: the gate charges 0 for every path under __tests__/ and for src/eslint-suppressions.json because packageForPath returns undefined for them (14 such paths in the whole remainder, listed per unit in comment 6751466344). That is the same instrument that reports 0 for a PNG visual baseline: the 1321 and 1521 lanes' five PNGs measured zero gate charge for the same reason, and a zero charge from this gate is a statement about packageForPath, not about whether the change matters. Binary baselines stay out of the a+d arithmetic and stay named.

The one number that does not comply, measured three ways

U5c-6 is over the 1,000 hard line and no whole-file shape brings it under: the spec file alone is 1,138 a+d, with the suppressions row 1,140, and folding it into U5c-5 makes that unit 1,367. zdt split verify exits 1 on all three shapes with VIOLATION budget-hard: standalone 1140 a+d exceeds the 1000 hard cap — must split. The remaining lever is to cut inside readFileTool.spec.ts, which the split rule forbids, so this is the one row that needs a ruling: defer part of that spec rewrite to a later unit, carry the overshoot with the rationale in the PR body, or allow the file to be cut.

Merge order, stated as a chain

U5c-1 -> U5c-2 -> U5c-3 -> U5c-4 -> U5c-8, with U5c-5 -> U5c-6 and U5c-7 placeable anywhere after the base and before the integration PR. Each unit is a branch off the merge base above (never off the previous unit's head), each mid PR merges before the next opens so the bot never sees a cumulative head, and every price quoted here is the unit measured standalone against its own base.

  • U5c-1 first: jsonFileRecovery.ts imports withFileLock and safeWriteJson.ts imports acquireFileLock from src/utils/fileLock.ts, and U5c-1 is where the landed artefact spelling and this branch's canonical lock key become one file.
  • U5c-2 before U5c-3: safeWriteJson.ts imports recoverInterruptedReplacement from ./jsonFileRecovery.
  • U5c-4 after U5c-3: it is the suite for the code U5c-3 lands.
  • U5c-8 after U5c-3: 8 of McpHub.ts's 67 changed rows name safeWriteJson, and the change relies on safeWriteJson resolving the publish target before staging because it passes the workspace root as confineTo.
  • U5c-6 after U5c-5: its suppressions row is the core/tools/__tests__/readFileTool.spec.ts count going 96 to 94 (blob ae3d504003d7e616b129986c252cc97a35b7b568 to f3714cd1f521c1d34e2dbc252cd7b970d226f6a6), a decrease, and a decrease can only land with or after the spec change that earns it.
  • U5c-7 carries the one advisory row in the whole remainder (src/core/task/Task.ts:4458, mutation exclusions must be reasoned next-line directives), and it travels with Task.ts, not to a cleanup unit.

Chain and knip rows that bind the queue

  • main + 2357 and main + 1913 both merge clean in either landing order (6d47a72e8f9b…, 9fe64bcd8a4a…, then 010df646f1… and 81e24cd6c1…, kept under refs/tmp/u5a-r23-*), and git merge-file --diff3 across the 22 paths those two heads both touch at their merge base 416179d17de83dfc9bfc1212cc8640da570db90a needed text resolution in 0 hunks. The only both-changed path, src/utils/__tests__/safeWriteJson.test.ts, auto-merges to 60,408 B / 1,354 line-endings with 51 test headers, 0 conflict markers, 0 duplicate test titles, and all 4 of 1913's rows present.
  • Replay is a different question: 2357 onto main stops on safeWriteJson.ts and safeWriteJson.test.ts, 1913 onto main stops on the same two paths, 2357 onto main+1913 stops on Task.ts, and 1913 onto main+2357 is clean. If the chain is rebuilt by rebase, 2357's content moves first; as merges the order is free.
  • knip.json needs no unit: 2357 head and 2385 head carry the identical blob 111f022102043c67fb581407a315ae16cca305b7 (2,687 B) and main carries 1f4956faa6cc789a34d0d2d0e9f4397ce0ca1bc6 (2,654 B); the only element 2357 adds names services/file-safety/safeWriteText.ts, which 2385 lands. The rule adopted from the 1410 measurement stands for this chain: code without its entry element is green (exit 0, 0 B stdout+stderr, e3b0c44298fc1c14), the entry element without its code is red (exit 1, 657 B, 0b983e73f8bb3998, naming the unresolved import and the config hint), so a unit carries a file and its entry element together or the chain does not move.
  • The four open review rows on PR 2385 that nothing on that PR answers stay answered as unanswered and queue behind 2385's merge rather than onto it: 2385 sits at 471 of the 500 counted lines, 29 lines of headroom. They are not part of any unit here.

Precondition met: the two landed-file unions are now code, not merge output

src/utils/fileLock.ts union blob 757041f107ef004d0607cf57890e4d7c6a5eb644 (14,393 B / 298 line-endings; the earlier mechanical union was 14,289 B / 297 le): the landed lockArtefactSpelling() export, the artefactSpelling option and the fsSync import are restored, and _lockCanonicalKey now takes const lockfilePath = options?.artefactSpelling ?? lockArtefactSpelling(absoluteFilePath) and passes it as the artefact, so the canonical-key split and the landed spelling pin coexist and the exports match the landed set (LOCK_STALE_MS, FileLockOptions, lockArtefactSpelling, acquireFileLock, withFileLock).
src/utils/safeWriteJson.ts union blob cba3807bd1b463329da5ecee16d994d4410212fa (29,457 B / 628 line-endings; mechanical union 29,513 B / 630 le): captures the spelling before the write can move the leaf and acquires acquireFileLock(lockKey, { artefactSpelling }) — the canonical key, the captured spelling — and keeps this branch's let stagedBytes declaration above the protected block.
A third resolution was forced by the tests, not by the merge: __tests__/safeWriteJson.lockKey.spec.ts mocks ../fileLock, so the new import threw before any assertion. Blob 3033fb237c386ecc83d9ea670e79a695604c1c10 (7,900 B / 202 le) becomes 2f74b807da1ae5a291dfdd62ac05d41b8ce5fc00 (8,872 B / 213 le): the mock exports lockArtefactSpelling, and the peer-commit test now asserts both arguments of the acquisition — the key that excludes the peer, and the caller's own spelling — instead of the key alone.

Proof, named leg by leg. src declares check-types as tsc --noEmit with src/tsconfig.json; run by node_modules/typescript 5.9.3 under node v22.23.4 from the declaring package directory: base tree exit 0, 0 B stdout and 0 B stderr (byte pair e3b0c44298fc1c14, 21.2 s); compiled-union tree exit 0, 0 B / 0 B, same pair (12.2 s). Suites: vitest 4.1.12 via the package's own test:unit script form (vitest run --config vitest.unit.config.ts) under node v22.23.4 over fileLock.spec.ts, fileLock.canonicalKey.spec.ts, safeWriteJson.test.ts, safeWriteJson.interruption.test.ts, safeWriteJson.lockKey.spec.ts, jsonFileRecovery.test.ts — first leg exit 1 (3 failed, 94 passed, 4 skipped, 8,167 B, 9cd022f8ea936e2f), after the mock export exit 1 (1 failed, 96 passed, 5,354 B, be8c3cc7403cac8f), final leg exit 0, 6 files, 97 passed, 4 skipped (3,793 B, 1c039251c459e561). Byte pairs from all-pass legs are not equal to each other because vitest prints elapsed times; they are quoted as pairs, not as exit codes.
Two-sided control on the identity behaviour: acquiring on the caller's lexical path instead of the lock key — 2385's shape with the spelling kept — exits 1 with safeWriteJson.lockKey.spec.ts > safeWriteJson lock key under a peer commit failing (5,396 B, c9435cb527f143da), so the canonical-key leg is test-covered and load-bearing. Ignoring the caller-supplied spelling inside _lockCanonicalKey leaves all 97 tests passing (exit 0, 3,793 B, 077ce36457e53f3d): nothing in the content this chain carries distinguishes a caller-supplied spelling from a derived one — fileLock.spec.ts names .lock 16 times and lockfilePath / artefactSpelling zero times — so the artefact-spelling leg lands compiled but untested, which is the same gap the four queued 2385 rows ask about, and it belongs to whoever owns that coverage.

Contracts and verify exits (the skill requires one contract per unit before verifying, and the verify exit is the claim)

Eight contracts scaffolded with zdt split contract against these same base/head pairs and verified with zdt split verify --contract <unit> --worktree wt-r302-2357-cut-price. Ports pin the content source as the commit pair 5319fe91fb0caf4aa4ec66aac68fa03744ebbc23 to d6fa7117e07b70455da24e4722b47eb0b202fa6e (never a stacked PR head); the two union units are plan-anchored rework units pinning design.plan to this issue's comment 6751466344, because their content intentionally diverges from any port source. Files: zdt split verify exits — U5c-1 0, U5c-2 0, U5c-3 0, U5c-4 0, U5c-5 0, U5c-6 1, U5c-7 0, U5c-8 0. Every port unit reports OK (content subset of source) with byte-identical on each file; the rework units report REWORK (plan-anchored); no unit reported a foreign file or a duplicate helper.
The diagnostic the rule predicts was reproduced rather than described: re-anchoring U5c-1 to the port source and dropping its design block makes the same verify exit 1 with DRIFT +16/-13 src/utils/fileLock.ts — unit added 16 line(s) / deleted 13 line(s) NOT in the source diff, which is the union's own content showing up as foreign against a port contract. Contract files and full reports are in .tmp-b1-1404/r24-contracts/.

State

Nothing was pushed, no branch was written, no PR opened, no review requested. The priced heads are local single-parent synthetic commits kept under refs/tmp/u5a-r24-*, refs/tmp/u5a-r24c-* (compiled-union units) and refs/tmp/u5a-r23-*; the priced worktree wt-r302-2357-cut-price is back at the base with git status --porcelain empty. If main moves, or 2385 lands with content different from 970b3a47ba…, this table has to be re-priced before the first split PR opens, and that re-price — not preference — decides the cut.

ClineProvider.ts per-construct cut maps — 1565 and 1921, priced (f0-1528, task-81)

This section carries the two maps the provider-family rule asks for. The rule as adopted: no lane in the provider family (1557, 1565, 1919, 1921, 1406) pushes src/core/webview/ClineProvider.ts content until its PR carries a per-construct cut proposal priced against a compiled union. Both maps below are measured, not typed: every number comes from selectFromGit against synthetic single-parent commit-tree commits run in the same span as this append, on node 22.23.4, and the two heads are the ones GET /pulls/1565 and GET /pulls/1921 name. 1557, 1919 and 1406 still owe their own maps.

1565 — src/core/webview/ClineProvider.ts at head 856dc80ebd7b3dfce445977d4e447729a3a68a74

Checked out at 856dc80ebd7b3dfce445977d4e447729a3a68a74 (git rev-parse HEAD printed beside the ASKED sha; scratch single-checkout at 1565, the PR worktree at 1921), lockfile blob 0fb1d6941590e544afe9d81aaaad80ebeb4fe4d7 (612369 B), file blob ef7ad52c20988aec3c2e11b1e20bb0cc8a08ca04 (7694 lines). Priced under node 22.23.4 (.nvmrc pin; PATH default is v25.9.0) through scripts/stryker-diff.mjs blob 7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc, against synthetic commit-tree commits whose single parent is named with each row.

shape constructs (lines in the head file) gate charge, extension package a+d
head as it stands none 2230 against the 500 cap 0 (file 2,462 + 253 = 2,715)
introduce deleteProviderProfile as its own module (3615-3911, 297 lines, 285 of them changed by the PR) its own body: 154 executable new file only, 297 additions
introduce loadViewState as its own module (1132-1301, 170 lines, 170 of them changed by the PR) its own body: 96 executable new file only, 170 additions
introduce disposeInternal as its own module (1701-1828, 128 lines, 108 of them changed by the PR) its own body: 106 executable new file only, 128 additions
introduce setViewStateId as its own module (1033-1122, 90 lines, 90 of them changed by the PR) its own body: 43 executable new file only, 90 additions
introduce savePersistedViewState as its own module (780-847, 68 lines, 68 of them changed by the PR) its own body: 68 executable new file only, 68 additions
introduce setValues as its own module (5650-5718, 69 lines, 68 of them changed by the PR) its own body: 36 executable new file only, 69 additions
introduce broadcastResetToAllInstances as its own module (5817-5871, 55 lines, 54 of them changed by the PR) its own body: 55 executable new file only, 55 additions
introduce rekeyPersistedViewStateEntry as its own module (954-1003, 50 lines, 50 of them changed by the PR) its own body: 50 executable new file only, 50 additions
introduce _updateViewLocalStateFromMutation as its own module (5737-5778, 42 lines, 42 of them changed by the PR) its own body: 40 executable new file only, 42 additions
introduce refreshViewLocalStateForUpdatedProfile as its own module (4193-4230, 38 lines, 38 of them changed by the PR) its own body: 35 executable new file only, 38 additions
introduce setValue as its own module (5573-5607, 35 lines, 34 of them changed by the PR) its own body: 20 executable new file only, 35 additions
introduce getPersistedViewStates as its own module (738-769, 32 lines, 32 of them changed by the PR) its own body: 18 executable new file only, 32 additions
batch unit 1565-U1 off base (parent 73931fe8be25, commit be354c8f20f3) deleteProviderProfile@3615-3911 + loadViewState@1132-1301 + disposeInternal@1701-1828 + setViewStateId@1033-1122 1259 (gate threw: extension has 1259 changed executable lines (limit 500). Split…) +19806 / -361 = 20167
batch unit 1565-U2 off base (parent 73931fe8be25, commit a647eada9282) savePersistedViewState@780-847 + setValues@5650-5718 + broadcastResetToAllInstances@5817-5871 + rekeyPersistedViewStateEntry@954-1003 + _updateViewLocalStateFromMutation@5737-5778 + refreshViewLocalStateForUpdatedProfile@4193-4230 + setValue@5573-5607 + getPersistedViewStates@738-769 1182 (gate threw: extension has 1182 changed executable lines (limit 500). Split…) +19510 / -361 = 19871
residual after all twelve move out (ab3205d91c52, parent 73931fe8be25) the file with the twelve removed 1610 file 1416 + 281

Measured facts the map turns on.

  1. The gate counts new-side executable lines only: a candidate that removes lines and adds none prices at 0 (every deletion-only candidate returned packages: []), which is why an earlier unit that already carries a construct makes the later unit charge nothing for it.
  2. The file alone charges 1371 changed executable lines; the extension package charges 2230 (the gate threw the same sentence locally). The rest of the family's content in that same package is therefore 2230 − 1371 = 859 lines, and that number alone is already over the 500 cap for a unit that carries no ClineProvider.ts at all.
  3. Moving a construct into a new file inside the same PR does not reduce the charge — measured sweep, cumulative from the largest down: 1 constructs → 2239, 2 constructs → 2253, 3 constructs → 2271, 4 constructs → 2271, 5 constructs → 2276, 6 constructs → 2277, 7 constructs → 2305, 8 constructs → 2308, 9 constructs → 2327, 10 constructs → 2328, 11 constructs → 2329, 12 constructs → 2330. The moved lines are still changed lines; only their file changed.
  4. Pricing the same constructs as their own unit off the family base 73931fe8be253b93ec11103a0672c615e849f895 (single-parent commits, printed above) gives 1565-U1 = 1259, 1565-U2 = 1182, i.e. the unit's body charge plus the 859 lines the other files ride in with it: 1259 − 399 = 860, 1182 − 322 = 860 ✓ which is the arithmetic that shows the cut cannot be finished inside this file.
  5. The residual unit — the file with all twelve constructs already moved out — still charges 1610 (1416 + 281 a+d on the file). Fourteen more units of that size would not be smaller than the cap; the file's own charge is the binding number, not the constructs.

Where removal breaks compilation, and what the union costs. The synthetic trees are plumbing-only — a body lifted into a function shell — so nothing was compiled here; the break is established by the reference census (git grep -c at the head, whole src), and the census is what makes a single-construct cut non-standalone: 1565-U1: deleteProviderProfile:48, loadViewState:38, disposeInternal:3, setViewStateId:104; 1565-U2: savePersistedViewState:9, setValues:230, broadcastResetToAllInstances:19, rekeyPersistedViewStateEntry:3, _updateViewLocalStateFromMutation:3, refreshViewLocalStateForUpdatedProfile:20, setValue:641, getPersistedViewStates:18. setValue at 641 references, setValues at 230, removeClineFromStack at 164 and setViewStateId at 104 cannot leave the class without also moving or exporting their call sites, so the priced unit for those is the union of construct plus call sites, not the construct — the rows above are those unions, and they are what a compilable cut costs.

Reading against the a+d table. 400 a+d is the soft line with the rationale in the body; 1,000 is the hard line and means split again; a unit may not split a file. The file's own a+d at head is 2,462 + 253 = 2,715, already past 1,000, so under that rule no single unit can carry ClineProvider.ts as it now stands, and the only shape that respects both the rule and the 500-line cap is the pre-cut: a unit that moves whole constructs into new modules (its a+d is the moved span twice over, 1565-U1 +19806/-361, 1565-U2 +19510/-361 measured with the rest of the family content riding along, so each must be trimmed to its own constructs before it is pushed), followed by units that carry the residual content file by file rather than construct by construct.

1921 — src/core/webview/ClineProvider.ts at head e4b102b2f129d07923eb5b8cf825074909ff4c4a

Checked out at e4b102b2f129d07923eb5b8cf825074909ff4c4a (git rev-parse HEAD printed beside the ASKED sha; scratch single-checkout at 1565, the PR worktree at 1921), lockfile blob 0fb1d6941590e544afe9d81aaaad80ebeb4fe4d7 (612369 B), file blob 5e071eed90bcbd09eac8a4c22d8694b4a4d7b63b (8046 lines). Priced under node 22.23.4 (.nvmrc pin; PATH default is v25.9.0) through scripts/stryker-diff.mjs blob 7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc, against synthetic commit-tree commits whose single parent is named with each row.

shape constructs (lines in the head file) gate charge, extension package a+d
head as it stands none 2328 against the 500 cap file 2,990 + 429 = 3,419; whole PR +18,257 / -791 = 19,048
introduce deleteProviderProfileUnlocked as its own module (3643-4099, 457 lines, 443 of them changed by the PR) its own body: 234 executable new file only, 457 additions
introduce loadViewState as its own module (1179-1297, 119 lines, 119 of them changed by the PR) its own body: 65 executable new file only, 119 additions
introduce savePersistedViewState as its own module (827-909, 83 lines, 82 of them changed by the PR) its own body: 83 executable new file only, 83 additions
introduce handleModeSwitchUnlocked as its own module (2857-3016, 160 lines, 74 of them changed by the PR) its own body: 158 executable new file only, 160 additions
introduce _restoreProfileActivationState as its own module (3106-3172, 67 lines, 67 of them changed by the PR) its own body: 67 executable new file only, 67 additions
introduce setViewStateId as its own module (1104-1171, 68 lines, 61 of them changed by the PR) its own body: 39 executable new file only, 68 additions
introduce resetState as its own module (6225-6312, 88 lines, 58 of them changed by the PR) its own body: 52 executable new file only, 88 additions
introduce rekeyPersistedViewStateEntry as its own module (1036-1098, 63 lines, 56 of them changed by the PR) its own body: 63 executable new file only, 63 additions
introduce updateProfileModel as its own module (3366-3450, 85 lines, 54 of them changed by the PR) its own body: 85 executable new file only, 85 additions
introduce broadcastResetToAllInstances as its own module (6172-6221, 50 lines, 49 of them changed by the PR) its own body: 50 executable new file only, 50 additions
introduce _snapshotProviderManagerState as its own module (3181-3221, 41 lines, 41 of them changed by the PR) its own body: 41 executable new file only, 41 additions
introduce removeClineFromStack as its own module (1394-1430, 37 lines, 37 of them changed by the PR) its own body: 19 executable new file only, 37 additions
batch unit 1921-U1 off base (parent 73931fe8be25, commit 8c2fa698e8ef) deleteProviderProfileUnlocked@3643-4099 + loadViewState@1179-1297 + savePersistedViewState@827-909 1040 (gate threw: extension has 1040 changed executable lines (limit 500). Split…) +15930 / -362 = 16292
batch unit 1921-U2 off base (parent 73931fe8be25, commit 2744e198ca6b) handleModeSwitchUnlocked@2857-3016 + _restoreProfileActivationState@3106-3172 + setViewStateId@1104-1171 + resetState@6225-6312 + rekeyPersistedViewStateEntry@1036-1098 1037 (gate threw: extension has 1037 changed executable lines (limit 500). Split…) +15717 / -362 = 16079
batch unit 1921-U3 off base (parent 73931fe8be25, commit 6360f9c6bee7) updateProfileModel@3366-3450 + broadcastResetToAllInstances@6172-6221 + _snapshotProviderManagerState@3181-3221 + removeClineFromStack@1394-1430 853 (gate threw: extension has 853 changed executable lines (limit 500). Split …) +15484 / -362 = 15846
residual after all twelve move out (a8e31f23ee17, parent 73931fe8be25) the file with the twelve removed 1602 file 1765 + 522

Measured facts the map turns on.

  1. The gate counts new-side executable lines only: a candidate that removes lines and adds none prices at 0 (every deletion-only candidate returned packages: []), which is why an earlier unit that already carries a construct makes the later unit charge nothing for it.
  2. The file alone charges 1671 changed executable lines; the extension package charges 2328 (CI agrees — job mutation-diff, 147,511-byte log line 1845: Mutation gate failed: extension has 2328 changed executable lines (limit 500). Split the PR or obtain a maintainer-reviewed narrow exclusion., and line 1861 No files were found with the provided path: reports/mutation/, so no mutants ran and no mutant number may be quoted for this head). The rest of the family's content in that same package is therefore 2328 − 1671 = 657 lines, and that number alone is already over the 500 cap for a unit that carries no ClineProvider.ts at all.
  3. Moving a construct into a new file inside the same PR does not reduce the charge — measured sweep, cumulative from the largest down: 1 constructs → 2335, 2 constructs → 2303, 3 constructs → 2298, 4 constructs → 2417, 5 constructs → 2438, 6 constructs → 2443, 7 constructs → 2461, 8 constructs → 2464, 9 constructs → 2495, 10 constructs → 2520, 11 constructs → 2540, 12 constructs → 2557. The moved lines are still changed lines; only their file changed.
  4. Pricing the same constructs as their own unit off the family base 73931fe8be253b93ec11103a0672c615e849f895 (single-parent commits, printed above) gives 1921-U1 = 1040, 1921-U2 = 1037, 1921-U3 = 853, i.e. the unit's body charge plus the 657 lines the other files ride in with it: 1040 − 382 = 658, 1037 − 379 = 658, 853 − 195 = 658 ✓ which is the arithmetic that shows the cut cannot be finished inside this file.
  5. The residual unit — the file with all twelve constructs already moved out — still charges 1602 (1765 + 522 a+d on the file). Fourteen more units of that size would not be smaller than the cap; the file's own charge is the binding number, not the constructs.

Where removal breaks compilation, and what the union costs. The synthetic trees are plumbing-only — a body lifted into a function shell — so nothing was compiled here; the break is established by the reference census (git grep -c at the head, whole src), and the census is what makes a single-construct cut non-standalone: 1921-U1: deleteProviderProfileUnlocked:2, loadViewState:26, savePersistedViewState:10; 1921-U2: handleModeSwitchUnlocked:15, _restoreProfileActivationState:7, setViewStateId:118, resetState:15, rekeyPersistedViewStateEntry:3; 1921-U3: updateProfileModel:30, broadcastResetToAllInstances:4, _snapshotProviderManagerState:6, removeClineFromStack:164. setValue at 641 references, setValues at 230, removeClineFromStack at 164 and setViewStateId at 118 cannot leave the class without also moving or exporting their call sites, so the priced unit for those is the union of construct plus call sites, not the construct — the rows above are those unions, and they are what a compilable cut costs.

Reading against the a+d table. 400 a+d is the soft line with the rationale in the body; 1,000 is the hard line and means split again; a unit may not split a file. The file's own a+d at head is 2,990 + 429 = 3,419, already past 1,000, so under that rule no single unit can carry ClineProvider.ts as it now stands, and the only shape that respects both the rule and the 500-line cap is the pre-cut: a unit that moves whole constructs into new modules (its a+d is the moved span twice over, 1921-U1 +15930/-362, 1921-U2 +15717/-362, 1921-U3 +15484/-362 measured with the rest of the family content riding along, so each must be trimmed to its own constructs before it is pushed), followed by units that carry the residual content file by file rather than construct by construct.

What both maps say together

  • The constructs are real and nameable, and the twelve largest carry 1,039 of 1565's 2,462 new-side changed lines and 1141 of 1921's 2,994 — but charging is per changed executable line, not per file, so relocating them inside the same PR moves the number the wrong way (2,230 → 2,330 on 1565; 2,328 → 2,303 → 2,557 on 1921, measured sweeps above).
  • The only shapes that actually reduce a unit's charge are (a) an earlier unit that already carries the construct's lines, which prices the later one at 0 for them, and (b) units that carry whole files instead of constructs. A cut that keeps both a 500-line executable budget and the no-splitting-inside-a-file rule therefore has to partition the family's other 44 files (1565) and 54 files (1921) as well — the 859 and 657 lines that are not this file.
  • Nothing here was compiled: the synthetic trees lift bodies into a function shell, so the compilation claim rests on the reference census, and the census is printed per unit. A lane that wants a compiled union has to wire the exports and re-price; these rows are the ceiling, not the finished unit.
  • Both PRs stay mergeable_state: blocked. 1921's mutation-diff refuses to run at all (size, not mutants). 1565's charge is 2,230. Neither is cleared by a test-only commit — 1921's newest commit e4b102b2f129d07923eb5b8cf825074909ff4c4a is test-only and charges 0 executable lines, and the answer on review row 4510170750 says so in the same words.

Round 25 (lane u5a-2359) — the first unit of the #2357 remainder is open as PR 2393, and every price below is taken against the sha its own branch forks from

Base-tip instrument, stated once and used everywhere in this section: GET /repos/Zoo-Code-Org/Zoo-Code/branches/main → e487cd7b2a497aa1d9b60f9b0cb497f7839bb629. pulls/2393.base.sha reports the same value but is read as a fossil, not as the source, per the maintainer's ruling of 2026-11-09. Pricing instrument, unchanged: the repo's own scripts/stryker-diff.mjs, blob 7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc, 30,108 B / 869 line-endings, caps MAX_CHANGED_LINES = 500 (line 11) and MAX_MUTANTS = 400 (line 12), run against a checked-out tree equal to a single-parent synthetic commit (commit-tree -p <base>, one parent, printed before every row), so the gate sees exactly the tree the unit would ship.

1. The shape of U5c-6's 1,138, measured before it was carried

src/core/tools/__tests__/readFileTool.spec.ts: base blob 52e04415ea5350906ea5064616501a46f3d235fc — the same blob at e487cd7b2a… and at 970b3a47ba… — 57,863 B / 1,692 line-endings; head blob b97b17d063a0567e09ea6ce6e10184ccd9fc5714, 101,419 B / 2,824 line-endings. git diff --numstat gives 1,135 insertions / 3 deletions on the path against either base. Rename detection finds nothing to blame the number on: git diff -M --find-renames --numstat --summary between base blob and head blob reports no rename from/to line, -C --find-copies-harder reports no copy from line, and the detector stays silent even at --find-renames=10%, the loosest threshold it accepts; git log --follow --name-status across 5319fe91fb..d6fa7117e0 shows only M for this path, never R, so the file was never moved. 2,044 of the head file's 2,824 lines appear verbatim in the base file (72%): the added lines are new content on top of an untouched body.

Three whole-file shapes priced: spec + its src/eslint-suppressions.json row = 1,140 a+d, zdt split verify exit 1; spec alone = 1,138 a+d, exit 1; spec folded into U5c-5 = 1,367 a+d, exit 1. Each prints VIOLATION budget-hard: — standalone 1140 a+d exceeds the 1000 hard cap — must split (the figure in the sentence is the figure of the shape priced). The only smaller shape available requires cutting inside a file. It is therefore carried with a documented deviation, not deferred and not cut: deferring it defers the proof for U5c-5's ReadFileTool changes, which are ready. The overshoot is a reviewer-load disclosure and not a CI leg: the gate charges this unit 0 counted executable lines of 500 and 0 selectors of 400, because packageForPath returns undefined for both src/core/tools/__tests__/readFileTool.spec.ts and src/eslint-suppressions.json; both rows are named here rather than averaged.

2. The artefact-spelling coverage closes in U5c-1, not behind #2385

New file src/utils/__tests__/fileLock.artefactSpelling.spec.ts, 2,499 B / 68 line-endings, blob 1c2e12dc66aabf0931aa72549f799f3a997e01de: one acquisition with a caller-supplied artefactSpelling asserted verbatim as the lockfilePath passed to proper-lockfile, three acquisitions with no spelling supplied, and expect(artefacts).toEqual([supplied, derived, derived]) so the two artefacts are shown to differ. prettier --check exit 0 (66 B, pair 17aa973d3f004560); eslint --max-warnings=0 with paths relative to src exit 0, 0 B + 0 B, pair e3b0c44298fc1c14, with src/eslint-suppressions.json byte-identically unchanged (sha256-16 055a26991146ecea before and after).

Two-sided control. Dropping the caller spelling (const lockfilePath = lockArtefactSpelling(absoluteFilePath), the shape #2385 shipped) → exit 1, 2 tests failed, both of them the new spec's, 752 B + 3,928 B, pair b839a9857b8b5e8d. Keeping the option and stopping the derivation (options?.artefactSpelling ?? (absoluteFilePath + ".lock")) → exit 0, pair 3b9761a61d6e3198: for a leaf that exists, lockArtefactSpelling and path + ".lock" produce the same string, so that mutant is equivalent in this scenario and this spec does not distinguish the derivation route from a leaf-lexical fallback. Recorded, not smoothed. The caller-supplied tempPath leg #2385 asks for on the safeWriteJson side is still uncovered today and is left for U5c-3 or U5c-4 to carry.

3. Two columns per unit: the price against 970b3a47ba… as planned, and against e487cd7b2a… as each branch actually forks

unit a+d vs 970b3a47ba… a+d vs e487cd7b2a… counted lines / selectors against the real base band against the real base
U5c-1 (as opened: union + fileLock.spec.ts + the new spelling spec) 593 (62 / 21) 618 (596+22) 81 / 29 400–1,000, rationale in the body
U5c-1 full shape (with fileLock.canonicalKey.spec.ts) 660 (62 / 21) 685 (663+22) 81 / 29 400–1,000
U5c-2 jsonFileRecovery 176 (26 / 3) 176 (173+3) 26 / 3 under soft
U5c-3 safeWriteJson union + 2 specs 614 (131 / 46) 625 (606+19) 132 / 46 400–1,000
U5c-4 safeWriteJson.test.ts 517 (0 / 0) 517 (507+10) 0 / 0 400–1,000
U5c-5 ReadFileTool + indentation reader + 2 specs 227 (51 / 31) 227 (219+8) 51 / 31 under soft
U5c-6 readFileTool.spec.ts + suppressions row 1,140 (0 / 0) 1,140 (1,136+4) 0 / 0 over 1,000, carried with the deviation above
U5c-7 observationRegistry + Task + 3 specs 384 (25 / 15) 384 (384+0) 25 / 15 under soft; one advisory printed (src/core/task/Task.ts:4458, Stryker-disable-comment form)
U5c-8 McpHub confinement + spec 211 (27 / 14) 211 (201+10) 27 / 14 under soft

Against the real fork base only the two units that carry #2385-resolved files grow: U5c-1 by +25 a+d (593 → 618) with counted lines 62 → 81 and selectors 21 → 29, and U5c-3 by +11 a+d (614 → 625) with counted lines 131 → 132 and selectors unchanged at 46. The reason is the same in both: main does not contain #2385, so #2385's landed lines become this chain's own contribution and the gate charges them.

Compliance verdict, the deviation decision and the chain order are taken from the real-base column: order U5c-1 → U5c-2 → U5c-3 → U5c-4 → U5c-8, with U5c-5 → U5c-6 and U5c-7 placeable anywhere after U5c-1. U5c-6 is the only unit over the hard cap and it is carried.

4. U5c-1 opened: PR 2393, one push, no --no-verify

GET /repos/Zoo-Code-Org/Zoo-Code/pulls/2393 → head.sha ce52d04464e31c7f5d127413d1f0aebe4598cf73, head.ref vps2f-1914-u5c1-filelock, base.sha e487cd7b2a497aa1d9b60f9b0cb497f7839bb629; head repo easonLiangWorldedtech/Zoo-Code, base repo Zoo-Code-Org/Zoo-Code, draft: false, 1 commit, 3 changed files, +596 / −22. Body: 9,372 B / 59 line-endings sent, stored 9,372 B / 59 line-endings, sha256-16 f570f28f95b026c6, first 9,372 bytes byte-identical to what was sent and no trailing byte added because the sent text already ended in a line-ending. Contract zdt split verify against base e487cd7b2a… and this head: exit 0, PASS, standalone: 618 a+d / 3 files SOFT-OVERSHOOT (rationale required in PR body), three REWORK (plan-anchored) rows; anchored as rework because with the port anchor the same verifier exits 1 with DRIFT +46/-0 src/utils/fileLock.ts and VIOLATION foreign-content — those 46 lines are #2385's landed artefact spelling, which this unit carries as its own contribution.

Arms read twice, with the instrument named: a read with no filter parameter returned 17 legs, then check-runs?per_page=100&filter=all returned 18, and a later no-filter read also returned 18 as legs started — a leg count is not a fact about the commit until the filter and the moment are named. As of the filter=all read: CodeRabbit skipped (its own path filtering on this PR, not agreement with the code), CodeQL, check-translations, knip, Build test VSIX, dependency-review, invisible-chars, theme-fixtures, Analyze (javascript-typescript) and both Zoo Code / …reconcile legs success; compile, platform-unit-test (ubuntu-latest), platform-unit-test (windows-latest), webview-visual, e2e-mock, extension-host-visual and mutation-diff were in_progress with conclusion: null. mergeable: true, mergeable_state: blocked, no review_decision field returned, no labels. Nothing was resolved and nothing was requested.

Hooks, judged by what they did, not by their names: pre-commit ran lint-staged (pnpm --dir src exec eslint --max-warnings=0 --no-warn-ignored and prettier --write over the 3 staged files) and completed, and the committed tree d0ae766244e9af95715c587ef569d4149b1bda0a is the same tree as the priced unit commit, so the prices above are prices of the pushed head. pre-push ran the root check-types (tsc -p tsconfig.json --noEmit then turbo check-types --log-order grouped --output-logs new-only) and printed Tasks: 12 successful, 12 total / Cached: 11 cached, 12 total / Time: 4.19s — 11 legs were cache hits and only zoo-code:check-types, the declaring package's own tsc --noEmit, actually executed, and a cache hit proves nothing about the legs it did not run. The declaring package's own type check was also run directly under node v22.23.4 (C:/work/Zoo-Code-fork/.tmp-node22/node_modules/node/bin/node.exe, typescript 5.9.3, src/tsconfig.json): exit 0, 0 B + 0 B, pair e3b0c44298fc1c14. Carried-file suites under the same interpreter (vitest 4.1.12, vitest.unit.config.ts, cwd src, CI=1): exit 0, 2 files, 13 tests passed, 0 failed, 676 B + 689 B, pair fe3741548fe696a7; repeat runs differ only by their Duration lines (6fba006f3f98fa3f, a038e8237a4decd1). knip as the repo declares it (knip --treat-config-hints-as-errors, cwd = repo root): at this head exit 1, 327 B + 104 B, pair 5cf096ddc7615422; at the base commit exit 1, the same 327 B + 104 B, the same pair — this unit changes nothing knip reports; the two exit-1 legs are pre-existing at main tip. CI's own knip leg returned success on this head.

5. Two dependencies that only compiling against the real base showed

src/utils/__tests__/fileLock.canonicalKey.spec.ts imports resolveLockKey from src/services/file-safety/safeWriteText.ts, and that file does not exist at e487cd7b2a…. Measured with the declaring package's own compiler: with the spec in the tree, tsc -p tsconfig.json --noEmit exits 2 with utils/__tests__/fileLock.canonicalKey.spec.ts(8,32): error TS2307: Cannot find module '../../services/file-safety/safeWriteText' or its corresponding type declarations. (170 B stdout, pair 1a497dcbb3defe18); without it, exit 0 with 0 B + 0 B. So the spec needs #2357's code for what it asserts and #2385's file for its import: it travels in a unit that forks from a base containing #2385, and it was left out of U5c-1 rather than shipped broken. The unit that carries it is 67 added lines, 0 counted executable lines.

6. Rule written into the plan

Re-price each unit immediately before it opens, against the sha its branch will fork from — never against a stacked head and never against a hypothetical main; take the compliance verdict, the deviation decision and the chain order from that same reading, and take the base tip from GET /branches/<base.ref> (or the merge ref's first parent) rather than from pulls/N.base.sha.

Review state for everything asked in this round: there is no review event from anyone except the bot and the author, and no non-empty review body from anyone except the bot. #2357 and #2385 were not touched by this unit.

ClineProvider.ts per-construct cut maps — 1557, 1919 and 1406, priced on the live base tip (f0-1528, task-85)

Second half of the family rule the board adopted: no lane in the provider family (1557, 1565, 1919, 1921, 1406) pushes src/core/webview/ClineProvider.ts content until its PR carries a per-construct cut proposal priced against a compiled union. 1565 and 1921 are priced above; these three are drawn the same way, with the base tip taken from GET /branches/main (e487cd7b2a497aa1d9b60f9b0cb497f7839bb629, committed 2026-11-09T04:09:13Z) instead of pulls/N.base.sha, and with the source of every number named. Nothing was pushed for any of the three. Pricing ran under node 22.23.4 with pnpm 10.8.1 against single-parent commit-tree commits whose parent is printed with each row, using gate blob 7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc and a private copy of it (blob e615b9779a9d, 30,120 B) that differs from the gate in 2 lines and only those: MAX_CHANGED_LINES and MAX_MUTANTS raised, so a whole manifest can be read instead of a throw.

Semantics first: a width number belongs to a package, and the gate reads the biggest one

Verified in the same copy the pricing ran through: line 11 export const MAX_CHANGED_LINES = 500, line 12 export const MAX_MUTANTS = 400, line 265 if (packageEntry.changedExecutableLines > MAX_CHANGED_LINES) {, and the mutant caps at lines 713 and 756, all strictly greater-than. The gate's outcome is the deciding package — the largest per-package charge — never a sum across packages, which is the rule published today as comment 6752571370 on this issue. Every width figure below therefore names its package, and the deciding package for all five family PRs is extension:

PR gate manifest at its own window (caps raised) deciding package this file's share of it
1565 types=28, extension 2,230, webview 45 extension 2,230 (1,730 over the cap) 1,371
1921 types=14, extension 2,328, webview 45 extension 2,328 (1,828 over) 1,671
1557 types=28, extension 1,852, webview 41 extension 1,852 (1,352 over the cap) 1,006
1919 types=14, extension 2,216, webview 45 extension 2,216 (1,716 over the cap) 1,629
1406 types=7, extension 468, webview 21 extension 468 (under the cap by 32) 3

Read that table as a correction as much as a completion: the section above this one, and the pointer comments on 1565 and 1921, quoted "1,610" and "1,602" for the residual. Those are the deciding-package (extension) figures for the synthetic residual units, not file-only figures. The file-only numbers, measured now with the same per-file method the gate uses internally, are 750 for 1565 (residual commit ab3205d91c52, parent 73931fe8be25: ClineProvider.ts 750, src/extension/api.ts 290, src/activate/registerCommands.ts 247, src/core/webview/webviewMessageHandler.ts 221) and 944 for 1921 (residual commit a8e31f23ee17, parent 73931fe8be25: ClineProvider.ts 944, src/activate/registerCommands.ts 329, src/core/webview/webviewMessageHandler.ts 293, src/core/config/ProviderSettingsManager.ts 19). Both pointer comments have been patched to say which figure is which.

What these three maps added to the instruments, before any construct is named

  1. The gate resolves its own base, and it is not pulls/N.base.sha. Lines 284-298 of the gate read, verbatim: // GitHub checks out the synthetic pull request merge commit, but pull_request.base.sha is frozen at event-creation time. When main advances afterwards, that stale base attributes unrelated upstream lines to the pull request. The merge commit's first parent is the base actually merged into. — then resolvePullRequestBase returns the head's first parent only when the head has two parents, and selectFromGit immediately computes mergeBase = git merge-base baseSha headSha and diffs mergeBase...headSha. Measured three ways on each head and identical each time: 1557 prices extension 1,852 from the live main tip, 1,852 from its merge-base 73931fe8be25, and 1,852 from a synthetic merge commit of main tip + head priced against main tip (CI's own shape, parents printed). 1919 gives 2,216 three times; 1406 gives 468 three times.
  2. What does not re-root is a hand-rolled git diff mainTip head, and the gap is drift, not noise. Counting new-side executable lines myself over that raw window gives 1,963 (1557), 2,494 (1919) and 864 (1406) instead of 1,852 / 2,216 / 468, and the delta is main's own movement landing in files the PR touches: src/scripts/verify-coverage-cache-inputs.mjs (+105), src/core/task/Task.ts (+81 to +98) and src/api/providers/qwen-code.ts (+84) charge in a main-tip window and do not charge in the merge-base window. A unit forked from main tip pays that drift; price it against the sha it forks from, which is the same conclusion the 2357 lane reached from its side.
  3. selectFromGit reads the working tree, not only git objects. Re-running it on 1406's residual commit while the scratch worktree sat at the PR head failed with ENOENT: no such file or directory on a path inside src/core/...; the pricing rows below are valid because the worktree was checked out to each synthetic commit immediately before its pricing, and the per-file figures are object-based (git show) and therefore immune to that trap.
  4. Deletion-only pricing holds on all five heads. A commit parented on the PR head that deletes the named construct bodies and adds nothing returns packages: [] — zero executable lines (1406 6dcf0a5ad48f, 1557 ec203ea7b602, 1919 4413f37f1503; the same shape was measured on 1565 and 1921 in the section above).
  5. CI agrees line for line. 1557's mutation-diff run #8590 concluded failure: 147,477 B log, 1,886 lines, line 1844 the only gate sentence — Mutation gate failed: extension has 1852 changed executable lines (limit 500). Split the PR or obtain a maintainer-reviewed narrow exclusion. — with line 1846 ##[error]Process completed with exit code 1. and line 1860 No files were found with the provided path: reports/mutation/.; 5 advisories, no mutants. 1919's run #8306: 147,620 B, 1,887 lines, line 1845 the same sentence at 2216, line 1861 the same artifact notice, 5 advisories, no mutants. 1406's run #8576 concluded success: 151,988 B, 1,937 lines, zero gate-failure sentences, zero ##[error] lines, 6 advisories, artifact upload succeeded. So 1406 is not a size refusal at all, and the mutant question stays closed for the whole class — survivors in changed code and the core preflight ENOENT print as advisories, and a cap is the only thing that fails this leg.

1557 — head 1b04a75b6e4c, ref vps2/f5-api-wiring, 213 commits, +16,329/−729 over 38 files (open, mergeable true / blocked)

pulls/1557.base.sha = 73931fe8be25 and merge-base(mainTip, head) = 73931fe8be25 are the same commit, so this PR's window is unambiguous and main's later movement belongs to drift, not to it. git rev-parse HEAD printed beside the ASKED sha before and after every synthetic commit (it matched the head read from GET /pulls/1557, and the scratch worktree ends clean); lockfile blob 0fb1d6941590 (612,370 B). The file at head: blob 5afd0f335f33, 7,083 lines, 1852 + 252 = 2,104 a+d at the merge-base (against main tip: 1852 + 252).

measured (package named) value
gate manifest at the merge-base, caps raised types=28, extension 1,852, webview 41 — deciding package extension
ClineProvider.ts share of the deciding package 1,006 changed executable lines out of 1,851 changed lines
the rest of the deciding package 1,852 − 1,006 = 846 (over the cap on its own): extension/api.ts 256, core/webview/webviewMessageHandler.ts 247, activate/registerCommands.ts 240, core/config/importExport.ts 40, core/config/ContextProxy.ts 20, core/config/ProviderSettingsManager.ts 20
CI's own verdict for this head extension 1,852 — mutation-diff Changed-code mutation testing#8590 failure, 147,477 B log over 1,886 lines, line 1844 the only gate sentence, No files were found with the provided path: reports/mutation/ at line 1860, 5 advisories, no mutants ran — and it equals the locally measured extension figure
relocating constructs inside the same PR (parent = head, extension) 1 → 1,861, 12 → 1,952, baseline 1,852
deletion-only (parent = head) packages: [] — 0 executable lines in every package

Constructs, named from the declaration line. The first pass over 1557 and 1919 labelled every private async x( member async and reported a repo-wide count of that word (10,732 references for 1919, 10,870 for 1557) as its census, which is not a fact about any construct. The re-scan below reads the declaration; the spans, changed-line counts and executable counts were identical between the two passes, so no priced number is affected — only the names and the censuses.

construct (head line range) span changed at merge-base executable in body references in src
getPersistedViewStates@677-707 31 31 18 16
savePersistedViewState@718-785 68 68 68 11
rekeyPersistedViewStateEntry@862-911 50 50 50 3
loadViewState@1059-1153 95 95 54 22
dispose@1515-1598 84 63 45 1,666
postMessageToWebview@2301-2343 43 18 32 637
deleteProviderProfile@3275-3424 150 134 69 35
snapshotSiblingOverlaysBeforeSharedSettingsWrite@3468-3534 67 67 62 6
setValue@5016-5047 32 29 15 506
setValues@5057-5137 81 80 46 178
_updateViewLocalStateFromMutation@5156-5194 39 39 37 4
broadcastResetToAllInstances@5237-5276 40 39 40 17
sum of the 12 named constructs 780 713 536 —

Units of their own off e487cd7b2a49 — single parent printed with each; a unit forked from main tip pays the drift of fact 2, which is why these totals are larger than the PR's own number, and every total below is the deciding extension package:

unit constructs body executable extension charge a+d commit (parent)
1557-U1 3275-3424@deleteProviderProfile@3275-3424 + 1059-1153@async@1059-1153 + 5057-5137@async@5057-5137 + 718-785@async@718-785 + 3468-3534@async@3468-3534 + 1515-1598@dispose@1515-1598 + 862-911@async@862-911 394 1,352 +15,264/−1,406 = 16,670 00ecaae9baff (e487cd7b2a49)
1557-U2 5237-5276@broadcastResetToAllInstances@5237-5276 + 5156-5194@_updateViewLocalStateFromMutation@5156-5194 + 677-707@getPersistedViewStates@677-707 + 5016-5047@async@5016-5047 + 2301-2343@async@2301-2343 142 1,100 +14,854/−1,406 = 16,260 d4673711b26b (e487cd7b2a49)
residual unit: this file with every named construct gone all 12 536 moved unit 1,527, of which ClineProvider.ts itself 569 file 1138 + 318 vs main tip 21d0f1e71bab (e487cd7b2a49)

Plainly, for 1557. 1,006 of the deciding package's 1,852 counted lines live in this file; with every named construct removed the file still charges 569 (over the 500 cap on its own) while the rest of the package adds 846 more, and the unit that would carry the residue off main tip prices at 1,527. Relocating the constructs inside the same PR moves the number the wrong way (1,852 → 1,861 → 1,952). The file's own a+d, 2,104, is past the 1,000 hard reviewability line, so with no-splitting-inside-a-file no unit can carry this file as it stands — and for a PR whose deciding package charges 1,852, no side of a two-way whole-file split can pass while the file producing the charge cannot be cut. The two numbers a maintainer needs to answer split-versus-exclusion for 1557 are 569 for the file and 1,527 for the unit that would carry it.

Where a removal would not compile, and why the price is a union. Nothing here was compiled — a body lifted into a function shell is not a compiling module — so compilability rests on the reference census, which is the honest instrument available at this stage: dispose 1,666, postMessageToWebview 637, setValue 506, setValues 178, deleteProviderProfile 35, loadViewState 22, broadcastResetToAllInstances 17, getPersistedViewStates 16, savePersistedViewState 11, snapshotSiblingOverlaysBeforeSharedSettingsWrite 6, _updateViewLocalStateFromMutation 4, rekeyPersistedViewStateEntry 3. A construct with hundreds of call sites cannot leave the class without moving or exporting those call sites, so for those the priced unit is the union of construct plus call sites. The rows above carry whole bodies: that is the ceiling on what a compilable cut costs, not a unit somebody has built.

1919 — head 05aa4e103858, ref vps2-f2a, 177 commits, +15,611/−761 over 32 files (open, mergeable true / blocked)

pulls/1919.base.sha = e943530c8486 and merge-base(mainTip, head) = e943530c8486 are the same commit, so this PR's window is unambiguous and main's later movement belongs to drift, not to it. git rev-parse HEAD printed beside the ASKED sha before and after every synthetic commit (it matched the head read from GET /pulls/1919, and the scratch worktree ends clean); lockfile blob 520e42edbb09 (612,369 B). The file at head: blob d70299fba017, 7,961 lines, 2901 + 429 = 3,330 a+d at the merge-base (against main tip: 2920 + 442).

measured (package named) value
gate manifest at the merge-base, caps raised types=14, extension 2,216, webview 45 — deciding package extension
ClineProvider.ts share of the deciding package 1,629 changed executable lines out of 2,912 changed lines
the rest of the deciding package 2,216 − 1,629 = 587 (over the cap on its own): core/webview/webviewMessageHandler.ts 289, activate/registerCommands.ts 266, core/config/ProviderSettingsManager.ts 19, core/webview/profileDeletionError.ts 5, core/config/ContextProxy.ts 3, core/webview/ClineProviderFactory.ts 3
CI's own verdict for this head extension 2,216 — mutation-diff Changed-code mutation testing#8306 failure, 147,620 B log over 1,887 lines, line 1845 the only gate sentence, No files were found with the provided path: reports/mutation/ at line 1861, 5 advisories, no mutants ran — and it equals the locally measured extension figure
relocating constructs inside the same PR (parent = head, extension) 1 → 2,171, 12 → 2,422, baseline 2,216
deletion-only (parent = head) packages: [] — 0 executable lines in every package

Constructs, named from the declaration line. The first pass over 1557 and 1919 labelled every private async x( member async and reported a repo-wide count of that word (10,732 references for 1919, 10,870 for 1557) as its census, which is not a fact about any construct. The re-scan below reads the declaration; the spans, changed-line counts and executable counts were identical between the two passes, so no priced number is affected — only the names and the censuses.

construct (head line range) span changed at merge-base executable in body references in src
savePersistedViewState@816-898 83 82 83 10
rekeyPersistedViewStateEntry@1025-1082 58 51 58 3
setViewStateId@1088-1155 68 59 39 114
loadViewState@1163-1281 119 119 65 26
removeClineFromStack@1378-1414 37 37 19 164
handleModeSwitchUnlocked@2818-2959 142 54 140 10
_restoreProfileActivationState@3049-3115 67 67 67 7
_snapshotProviderManagerState@3124-3164 41 41 41 6
updateProfileModel@3309-3393 85 54 85 30
deleteProviderProfileUnlocked@3586-4042 457 443 234 2
broadcastResetToAllInstances@6109-6147 39 38 39 3
resetState@6151-6229 79 49 45 14
sum of the 12 named constructs 1,275 1,094 915 —

Units of their own off e487cd7b2a49 — single parent printed with each; a unit forked from main tip pays the drift of fact 2, which is why these totals are larger than the PR's own number, and every total below is the deciding extension package:

unit constructs body executable extension charge a+d commit (parent)
1919-U1 3586-4042@async@3586-4042 + 1163-1281@async@1163-1281 + 816-898@async@816-898 382 1,243 +15,018/−5,130 = 20,148 59974e7a9e76 (e487cd7b2a49)
1919-U2 3049-3115@async@3049-3115 + 1088-1155@async@1088-1155 + 2818-2959@async@2818-2959 + 3309-3393@updateProfileModel@3309-3393 + 1025-1082@async@1025-1082 389 1,250 +14,779/−5,130 = 19,909 c0d9ae863bed (e487cd7b2a49)
1919-U3 6151-6229@resetState@6151-6229 + 3124-3164@async@3124-3164 + 6109-6147@broadcastResetToAllInstances@6109-6147 + 1378-1414@removeClineFromStack@1378-1414 144 1,005 +14,555/−5,130 = 19,685 85e34ae25460 (e487cd7b2a49)
residual unit: this file with every named construct gone all 12 915 moved unit 1,802, of which ClineProvider.ts itself 941 file 1751 + 548 vs main tip 0e96af1853ac (e487cd7b2a49)

Plainly, for 1919. 1,629 of the deciding package's 2,216 counted lines live in this file; with every named construct removed the file still charges 941 (over the 500 cap on its own) while the rest of the package adds 587 more, and the unit that would carry the residue off main tip prices at 1,802. Relocating the constructs inside the same PR moves the number the wrong way (2,216 → 2,171 → 2,422). The file's own a+d, 3,330, is past the 1,000 hard reviewability line, so with no-splitting-inside-a-file no unit can carry this file as it stands — and for a PR whose deciding package charges 2,216, no side of a two-way whole-file split can pass while the file producing the charge cannot be cut. The two numbers a maintainer needs to answer split-versus-exclusion for 1919 are 941 for the file and 1,802 for the unit that would carry it.

Where a removal would not compile, and why the price is a union. Nothing here was compiled — a body lifted into a function shell is not a compiling module — so compilability rests on the reference census, which is the honest instrument available at this stage: removeClineFromStack 164, setViewStateId 114, updateProfileModel 30, loadViewState 26, resetState 14, savePersistedViewState 10, handleModeSwitchUnlocked 10, _restoreProfileActivationState 7, _snapshotProviderManagerState 6, rekeyPersistedViewStateEntry 3, broadcastResetToAllInstances 3, deleteProviderProfileUnlocked 2. A construct with hundreds of call sites cannot leave the class without moving or exporting those call sites, so for those the priced unit is the union of construct plus call sites. The rows above carry whole bodies: that is the ceiling on what a compilable cut costs, not a unit somebody has built.

1406 — head c1b051b2786a, ref feat/change-journal-b2, 44 commits, +3,721/−43 over 43 files (open, mergeable true / blocked)

pulls/1406.base.sha = 3ee042d91e0d and merge-base(mainTip, head) = 3ee042d91e0d are the same commit, so this PR's window is unambiguous and main's later movement belongs to drift, not to it. git rev-parse HEAD printed beside the ASKED sha before and after every synthetic commit (it matched the head read from GET /pulls/1406, and the scratch worktree ends clean); lockfile blob 520e42edbb09 (612,369 B). The file at head: blob 550a5937e9cc, 5,481 lines, 4 + 0 = 4 a+d at the merge-base (against main tip: 22 + 24).

measured (package named) value
gate manifest at the merge-base, caps raised types=7, extension 468, webview 21 — deciding package extension
ClineProvider.ts share of the deciding package 3 changed executable lines out of 4 changed lines
the rest of the deciding package 468 − 3 = 465 (under the cap on its own): core/checkpoints/changeJournal.ts 217, core/tools/ApplyPatchTool.ts 110, core/checkpoints/index.ts 100, core/task/Task.ts 17, core/tools/WriteToFileTool.ts 14, core/tools/EditFileTool.ts 7
CI's own verdict for this head no gate sentence: mutation-diff Changed-code mutation testing#8576 success, 151,988 B over 1,937 lines, zero ##[error] lines, 6 advisories, artifacts uploaded
relocating constructs inside the same PR (parent = head, extension) 1 → 513, 4 → 832, baseline 468
deletion-only (parent = head) packages: [] — 0 executable lines in every package

Constructs, named from the declaration line. The first pass over 1557 and 1919 labelled every private async x( member async and reported a repo-wide count of that word (10,732 references for 1919, 10,870 for 1557) as its census, which is not a fact about any construct. The re-scan below reads the declaration; the spans, changed-line counts and executable counts were identical between the two passes, so no priced number is affected — only the names and the censuses.

construct (head line range) span changed at merge-base executable in body references in src
getTaskWithId@2704-2747 44 0 44 197
getTaskWithAggregatedCosts@2749-2761 13 0 13 3
getStateToPostToWebview@3105-3433 329 2 306 71
setValues@3715-3717 3 0 2 57
sum of the 4 named constructs 389 2 365 —

Units of their own off e487cd7b2a49 — single parent printed with each; a unit forked from main tip pays the drift of fact 2, which is why these totals are larger than the PR's own number, and every total below is the deciding extension package:

unit constructs body executable extension charge a+d commit (parent)
1406-U1 2704-2747@getTaskWithId@2704-2747 + 2749-2761@getTaskWithAggregatedCosts@2749-2761 + 3105-3433@getStateToPostToWebview@3105-3433 + 3715-3717@async@3715-3717 365 1,213 +6,112/−13,483 = 19,595 ccfe9df6c687 (e487cd7b2a49)
residual unit: this file with every named construct gone all 4 365 moved unit 850, of which ClineProvider.ts itself 2 file 5 + 396 vs main tip cc9f86498f56 (e487cd7b2a49)

Plainly, for 1406. At its own merge-base this PR changes 4 lines of ClineProvider.ts, 3 executable; the 22 / 24 split is main's own movement arriving as deletions. Its deciding package charges 468 and CI says success, so neither remedy in the refusal sentence applies: this is the family member that barely touches the file, and the residual unit's ClineProvider.ts charges 2 executable lines. What binds 1406 is reviewability — +3,721/−43 = 3,764 a+d over 43 files against a 400 soft line — and what a split would partition here is its own charging files, named above (changeJournal.ts 217, ApplyPatchTool.ts 110, checkpoints/index.ts 100 and the rest). Refreshed onto current main it would also inherit the drift of fact 2 — 382 executable lines of other people's code at the moment of writing.

Where a removal would not compile, and why the price is a union. Nothing here was compiled — a body lifted into a function shell is not a compiling module — so compilability rests on the reference census, which is the honest instrument available at this stage: getTaskWithId 197, getStateToPostToWebview 71, setValues 57, getTaskWithAggregatedCosts 3. A construct with hundreds of call sites cannot leave the class without moving or exporting those call sites, so for those the priced unit is the union of construct plus call sites. The rows above carry whole bodies: that is the ceiling on what a compilable cut costs, not a unit somebody has built.

Reading the five maps together (1565, 1921, 1557, 1919, 1406)

  • Deciding (extension) package charge in each PR's own window: 2,230 (1565), 2,328 (1921), 1,852 (1557), 2,216 (1919), 468 (1406); ClineProvider.ts share of it: 1,371, 1,671, 1,006, 1,629, 3. CI's own gate sentences reproduce the first four exactly — 2,230 at line 1845 of a 147,507 B log, 2,328 at line 1845 of 147,511 B, 1,852 at line 1844 of 147,477 B, 2,216 at line 1845 of 147,620 B — and 1406's leg passes at 468 with advisories.
  • After every named construct is removed, the file itself still charges 750 (1565), 944 (1921), 569 (1557), 941 (1919), 2 (1406), and the residual units price at 1,610, 1,602, 1,527, 1,802, 850 in the deciding package. Four of the five files are over the 500 cap on their own once the constructs are gone; 1406 is the one family PR that never needed the argument.
  • The measurement favours the cheap half of the refusal sentence and prices it: deletion-only diffs charge 0 (packages: [], reproduced on all five heads), so a deletion-dominant re-scope — units whose diffs are removals, delivering that code inside modules earlier units already carry, then the residual modifications cut file by file — can move the constructs out near-free while the residual modifications are what cost. The pointer comments on 1565 and 1921 (issue comments 6752498253 and 6752498829) put exactly that choice in front of maintainers and say no re-scope has been built.
  • Drift is priced rather than feared: the gate takes merge-base(base, head) (gate lines 297-298), so a PR's own number is drift-free, while every unit forked from e487cd7b2a49 pays it — 111 executable lines for 1557, 278 for 1919, 382 for 1406 at the moment of writing.
  • Nothing here was pushed and nothing here changes a mergeability state: 1557, 1919 and 1406 are mergeable true / mergeable_state blocked; two of their mutant legs refuse on size before running a mutant; 1406's passes with advisories.

Pricing instruction for this family, stated as a rule and not a note (f0-1528, task-90)

Price with merge-base semantics, re-root when you synthesise, and report drift separately when you name it. The reason is the gate's own code, blob 7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc: lines 284-286 carry the comment that GitHub checks out the synthetic pull-request merge commit while pull_request.base.sha is frozen at event-creation time, so a stale base "attributes unrelated upstream lines to the pull request"; resolvePullRequestBase (lines 287-291) returns the head's first parent only when the head has two parents, and selectFromGit (lines 293-298) then computes mergeBase = git merge-base baseSha headSha and diffs mergeBase...headSha. The consequence for anyone pricing by hand:

  • Pricing each family head from the live main tip, from its own merge-base, and from a synthetic merge commit of main tip + head priced against main tip gives the same manifest three times — 1,852 for 1557, 2,216 for 1919, 468 for 1406 (and 2,230 for 1565, 2,328 for 1921) — and each equals the number CI prints in its own gate sentence (1557 line 1844 of a 147,477 B log, 1919 line 1845 of 147,620 B, 1565 line 1845 of 147,507 B, 1921 line 1845 of 147,511 B; 1406 has no sentence because its leg passed).
  • A hand-rolled git diff mainTip head, which does not re-root, prints 1,963 / 2,494 / 864 for the same three heads. That entire gap is main's own movement landing in files the PRs touch — src/scripts/verify-coverage-cache-inputs.mjs, src/core/task/Task.ts and src/api/providers/qwen-code.ts are charging files in those windows and are not charging files at the merge-bases — so the honest figure is drift of 111, 278 and 382 executable lines, named as drift, not mixed into a PR's own charge.
  • Therefore: quote a PR's charge from merge-base(base, head) (or from the synthetic merge commit, which is the same number and is what CI sees); price a synthesised unit against the sha its branch actually forks from and print that parent with the row; and when a main-tip window is used, report the drift separately rather than letting it ride inside the number.
  • One instrument trap belongs with the rule. selectFromGit reads the working tree, not only git objects: re-running it on 1406's residual commit cc9f86498f56 while its scratch worktree sat at the PR head threw ENOENT: no such file or directory on a path under src/core/. A re-run must check the priced commit out first, or it is measuring a different tree. Figures computed per file from git show <commit>:<path> plus executableChangedLines are object-based and immune, which is why the per-file charger tables in this section were built that way.

Code owners for this repository, added 2026-11-10 by the u5a-2359 lane

Measured rather than inherited. .github/CODEOWNERS is the only ownership file in the tree at 18861b2e3572: a recursive tree read of that commit has 3,618 entries with truncated false and matches exactly one path, and a separate ls-tree read across the 3,177 files at pull 2417's head finds the same single path. The blob read by git show exits 0 at 119 bytes with git cat-file -s agreeing on the same object, and its entire content is one rule line: * @taltas @JamesRobert20 @navedmerchant @edelauna. A bare star matches every path in the repository, so all four of those accounts are code owners of every file in this chain's diffs, and neither the reviewing bot account nor easonLiangWorldedtech is listed in that file.

The branch ruleset Protect Main sets require_code_owner_review true at its pull_request rule parameters, with require_extra_approval_for_unattributed_changes also true. What follows for every approval ask under this tracking issue: the outstanding merge input is one approving review from @taltas, @JamesRobert20, @navedmerchant or @edelauna, not from a maintainer generally, and the author account cannot supply that review as an owner because it is not listed in the ownership file. The two mentions of @edelauna earlier in this body name one of those four; wherever they are being read as an approval ask, the ask is wider than one account.

What is not claimed here: this section adds no review-state measurement for this issue's chain. This lane's reads on 2026-11-10 cover the skills chain, fork pulls 76, 80, 81, 82, 84, 85 and 86 and org pull 2417, which is a different set of pulls from the guarded-write units and pull requests this issue tracks. Nothing earlier in this body was edited.

Activity

  1. easonLiangWorldedtech commented on Nov 8, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Chain budget record - U3 / PR #1912 is over the executable-line cap: advisory, cap-class (planned-and-issued)

    Gate line, verbatim (mutation-diff job 128607732918, step Enforce executable-line scope and run advisory mutation testing, PR head 1fb5fcc47; re-run at head 085fbf85e4ad):

    Mutation gate failed: extension has 589 changed executable lines (limit 500). Split the PR or obtain a maintainer-reviewed narrow exclusion.
    

    Three things this record exists to make visible:

    1. Cap-class, not a code signal. Nothing is being carried past the gate on a surviving mutant - the run never reached mutation. Its own log warns core Stryker preflight could not start: spawnSync /tmp/stryker-launch-…/node_modul…, which is the known Stryker 10 + @stryker-mutator/vitest-runner runtime-activation defect. The single advisory survivor the job prints, packages/core/src/value.ts:1: Survived BooleanLiteral mutant, is in a file outside this PR's 13-file delta; it is read as an artifact of that failed preflight. That reading is unverified, and deliberately not chased.
    2. The newest commit does not move the number. 085fbf85e adds a JSDoc @internal tag to the primitive export so the production Knip view stops reporting it as an unused export. It is a comment: 589 before it, 589 after it.
    3. A trim is not available inside this unit. 89 executable lines would have to come out of the extension package's delta. Composition of this delta by my own count (added lines from git diff --unified=0 main..HEAD, blank and comment-only lines excluded - 767 for non-test files against the gate's 589, so the gate's metric is narrower and stays the authority): src/services/file-safety/safeWriteText.ts 538, src/utils/safeWriteJson.ts 177, src/services/mcp/McpHub.ts 22, src/core/task/observationRegistry.ts 23, src/core/task/Task.ts 7, and 2,295 across the six test files (safeWriteText.spec.ts alone 1,466). The only blocks large enough to reach 89 are the primitive itself or its evidence. Cutting either is a decision about where the primitive and its evidence live, not a scope trim, and it collides with this unit's seam ruling - main's protocol owns the JSON write mechanism, our path-safety layer grafts onto it, and both feature sets stay in the unit that introduces them. So the split plan already recorded above (U1-U9, one issue per unit: U3 -> Tracking fws U3: observation registry with read completeness #2018) stands as the plan, the advisory stands with it, and if maintainers want feat(task): observation registry with read completeness (U3, #1375) #1912 under the cap the narrow exclusion is the cheap route - which needs a maintainer signature, not an author one.

    Chain-level row to keep next to this one: #1912's src/utils/safeWriteJson.ts now carries main's replacement protocol plus our confinement layer grafted onto it - confinement before any mkdir, lock-key confinement, re-confinement under the lock, the ancestor/dir pins, identity-only re-verification (TargetMovedError, AncestorReplacedError), _copyTargetMode parity, and the POSIX-only best-effort parent-directory fsync that is a logged warning and never a PostCommitDurabilityError out of the JSON path. #1405 (U7) head aa44471e74 resolves that same file at its rows 3 and 7. Whoever lands first should expect the other to re-run the file, not re-decide it. The identity clause this unit depends on is written up on #2356 (comment 6716641767), which extends #2350 and leaves the not-yet-existing-basename half open on purpose.

  2. 38 remaining items

  3. easonLiangWorldedtech commented on Nov 11, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Finding about the changed-code mutation gate itself, from the lane working unit 1 of the 1921 chain (PR
    #2416). Quoting the machine report verbatim and leaving the decision to you: this is a report, not a
    proposal.

    At head 21cc345, the gate comment 6777017497 (rewritten
    2026-11-11T09:16:07Z, 2,799 bytes, 0 mutant rows) printed this advisory finding:

    extension mutation run exceeded 12 minutes. Split the PR or obtain a maintainer-reviewed narrow
    exclusion.
    

    The package table row it printed beside that finding, verbatim:

    <tr><td><code>extension</code></td><td><code>491</code></td><td><code>0</code></td><td><code>0</code></td>
    <td><code>0</code></td><td><code>0</code></td><td><code>0</code></td><td><code>Advisory incomplete</code></td>
    </tr>

    i.e. 491 changed executable lines, Valid 0, Killed 0, Timeout 0, Survived 0, No coverage 0, Result
    "Advisory incomplete". The report's extension focused-tests line says the same in words: "The mutation
    run was incomplete; selected test file names were not recorded." The types package ran normally in the
    same job (6 changed executable lines, Valid 1, Killed 1, Result "Passed"), and the check itself finished
    completed:success - run 130424642052, 08:58:55Z to 09:15:52Z - so a run that evaluated nothing reports
    green.

    Two things we want on the record before anyone reads these numbers as a result:

    1. blocking:0 at this head means the extension package was not evaluated, not that its mutants were
      killed. The completeness state (valid 0, killed 0, timeout 0, survived 0, no coverage 0, result
      Advisory incomplete) is what makes the 0 mean "not run".
    2. The unit is already under the size cap at 491 of 500 changed executable lines for the extension
      package, and it still exceeded the 12-minute wall, so splitting on size is not the lever the finding
      names. For contrast, the same package at the previous head 4bf26f1
      produced 232 blocking rows from a run that finished (run 130409270995, 08:23:25Z to 08:39:07Z), so
      the binding constraint we are hitting is a time budget rather than the 500-line figure the splits in
      this plan are sized against.

    Instrument for both readings: in-tree scripts/stryker-diff.mjs at blob
    7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc (MAX_CHANGED_LINES = 500), compared against merge base
    53cb5b5. Note that *.spec.ts files are excluded from the line count by
    the driver's packageForPath, so the three most recent commits on this branch, all spec-only, did not
    move the 491.

    Which of the finding's two options to take is yours; we are not proposing an exclusion or a further
    split. Marked as unproved by us rather than inferred: what the extension run would have reported had it
    finished inside the 12 minutes.

  4. easonLiangWorldedtech commented on Nov 12, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Split plan for pull 2359, priced on measured trees. Every line figure and every mutant figure below was produced by the committed instrument scripts/stryker-diff.mjs at blob 7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc, caps MAX_CHANGED_LINES 500 at line 11 and MAX_MUTANTS 400 at line 12, test paths excluded by EXCLUDED_PATH_SEGMENTS at line 85, and each figure names the head it was measured at. Tracker taken from this pulls own body line 1, which records the split plan on issue 1991 with plan comment 6717413931 and addendum 6717425955, so this comment is posted here rather than on issue 1914 or on the general plan issue.

    What was built, and what it cost

    Two shapes were built as real trees with git plumbing, both from head 022bd3a, and neither was pushed. Both move the containment and identity-pinning seam, that is GuardRejectedError, the AuthorizedTarget record, isInside, assertInsideWorkspace, assertCanonicalInsideWorkspace, ancestorIdentities and realpathNearest, plus the local errno helper replaced by the shared getErrorCode from src/utils/errorCode.ts, out of the compare-and-swap tool module and into the writer lane. In each shape the priced head is a linearised commit-tree so the instruments resolvePullRequestBase at line 287 cannot silently replace the base with the first parent.

    Shape one, the seam inside src/services/file-safety/safeWriteText.ts. Priced edge: 406 counted executable lines in package extension, measured at head 30a41e6186a761bd2659186b977c25995320d2f9 over base 3e35f92dd4225fd1e1746ae9c8bd433db5013b99, inside the 500 line cap by 94 lines. One file carries the whole edge, src/core/tools/guardedWrite.ts at 406 counted executable lines from a git diff of 680 lines, and the seven comment lines in src/core/task/Task.ts charge 0 counted executable lines under the same instrument. That shape has a consequence: the writer module becomes 551 counted executable lines measured at head 85bd6b50d31c76ca7a418be1c88c49323fb8f7bc over main tip efc22ed, being the 453 counted executable lines it already carried there plus the 98 counted executable lines the seam adds, and 551 is 51 over the 500 line cap on that single file, so the lane that owns the writer module busts the cap on one file no matter how the rest of its lane is cut.

    Shape two, adopted. The same 193 lines go into a module of their own, src/services/file-safety/guardContainment.ts, 203 lines, blob 1e6dd118f9b91578a1111e566b6903ab96b6ee06, and src/services/file-safety/safeWriteText.ts stays at the revision its own lane already declares, blob 40a20eafc7bd5358742bee29504d00c6071e51f1, one comment line repointed, charging 0 counted executable lines measured at head 4bc86831e689560fda5f5ae0b6a361b060991449 over base 022bd3a. Priced edge for this unit: 406 counted executable lines, measured at head 09ba8b19d2b71e041255321edcb7562ed2b7cb50 over base c36fefa2aaf5c73d8c481a74477e3e9f7c7218fd, inside the 500 line cap by 94 lines, identical to shape one. What leaves this unit for the earlier lanes: 849 counted executable lines, measured at head c36fefa2aaf5c73d8c481a74477e3e9f7c7218fd over merge base efc22ed, split as guardContainment 98, safeWriteText 453, safeWriteJson 132, fileLock 62, ReadFileTool 40, McpHub 27, jsonFileRecovery 26 and indentation-reader 11 counted executable lines, so no single file crosses the cap and the largest is 453. Moving the seam as one edit on this object charges 103 counted executable lines, 5 in the tool module and 98 in the seam module, measured at head 4bc86831e689560fda5f5ae0b6a361b060991449 over base 022bd3a, and the same 103 in shape one.

    The partition ties to the cumulative figure by measurement, not by arithmetic

    406 counted executable lines at head 09ba8b19d2b71e041255321edcb7562ed2b7cb50 plus 849 counted executable lines at head c36fefa2aaf5c73d8c481a74477e3e9f7c7218fd is 1255, and the 4 counted executable lines of the local errno helper that the split removes bring it to 1259 counted executable lines, which is the cumulative figure the same instrument measured at head 022bd3a over main tip efc22ed. That is why the mutation-diff job is red on the branch as it stands today, 1259 against the 500 line cap, and why a cut that puts 406 on this unit clears the cap on this unit while leaving the cumulative verdict to the predecessors. The 245-mutant figure below is the seam population on this unit only.

    The seam population of 245 instrumented mutants

    Instrumented 1 source file with 245 mutants on the seam-free tool module, inside the 400 mutant cap by 155. Shape two, measured at head 09ba8b19d2b71e041255321edcb7562ed2b7cb50 over base c36fefa2aaf5c73d8c481a74477e3e9f7c7218fd: Valid 245, Killed 190, Survived 45, No coverage 10, Timeout 0, blocking-status mutants 55 under BLOCKING_MUTANT_STATUSES at line 83, advisories 0. Shape one, measured at head 30a41e6186a761bd2659186b977c25995320d2f9 over base 3e35f92dd4225fd1e1746ae9c8bd433db5013b99: Valid 245, Killed 179, Survived 45, No coverage 10, Timeout 11, blocking-status mutants 55, advisories 1 reading extension timed out 11 of 245 valid mutants, the result is inconclusive.
    These digests came from a substituted in-place runner, not from the CI job. The instruments own runner could not start Stryker on this machine: it spawns node_modules/.bin/stryker with no extension and spawnSync without a shell cannot execute that shim, Stryker 10 rejects the config path when it is passed as the first argument, and the sandboxed copy cannot resolve its own configuration because on this junction install vitest exists only under src/node_modules. The substitution used the injection seam the committed instrument already exposes, runMutation passed into runManifest, with the same arguments and the same environment and STRYKER_IN_PLACE set to true as stryker.config.mjs line 15 already reads. Cost, preflight 21 and 24 seconds, full run 180 and 178 seconds, concurrency 2 and timeoutMS 5000 from the committed config.
    The same 245-mutant population reproduces its 45 survived and 10 uncovered mutants exactly across both runs and across both shapes, while its timeout split does not, 11 timeouts in the first run and 0 in the second on the same population. No timeout claim about this unit travels from this machine. That the survived and uncovered sets are identical in both shapes is the point of the comparison: where the seam lives does not change what the tests of record fail to kill.

    Provisional ownership, to be settled by a cross-chain blob census

    Marked provisional deliberately. In both shapes the seam lands under src/services/file-safety/, so pull 2359 stops owning any line of it. That path does not exist at main tip, rev-parse of efc22ed for src/services/file-safety/safeWriteText.ts returns fatal, path exists on disk but not in that commit, and the same search finds it present at three other lane tips, pull 1913 head bb89f14 with blob 8f7a924810c3, pull 2357 head 9c24106 with blob 495595ebdcd2 and pull 2433 head 316abe2 with blob c2d83ea2cf52. Under the merge order this pull declares in its own body, main then pull 1913 then pull 2357 then this unit, the earliest lane able to introduce that path is the U4a lane behind pull 1913, so the moved seam moves the owner to pull 1913. A cross-chain blob census is running against the same paths and will settle which blob each lane actually carries, and that census, not this comment, is the authority on ownership, because shape two moves the conflict surface onto a path that three sibling lanes read and contradicting ownership claims between sibling lanes are worse than a late one. Fraction of the read this paragraph rests on: 4 blob lookups and 1 absence proof, 5 of 5 searches, with the census outstanding.

    Local verification and what it does not claim

    Shape two, local checks on the three changed files: prettier 3.9.10 check exit 0, tsc 5.9.3 root leg and src leg exit 0, eslint 9.39.5 with --prune-suppressions --max-warnings=0 exit 0 and src/eslint-suppressions.json unchanged at 30910 bytes, vitest 4.1.12 over the 19-suite set 19 files passed, 952 passed and 4 skipped, and knip reporting the same 5 findings the pushed head, the replaced head and main tip report, 0 unused files. Behavior-preservation claim rests on 19 of 19 suites, 952 of 956 tests, and 190 of 245 mutants killed, and it does not extend to the 55 not killed, 0.22 of the population. The claim that a split unit clears the gate rests on 1 priced edge, 406 counted executable lines against the 500 line cap and 245 mutants against the 400 mutant cap, and says nothing about the 1259-line cumulative edge. No split shape was pushed and no re-review was requested.

    a green local preflight over Valid 0, Killed 0, Survived 0 and No coverage 0 with blocking 0 and advisories 2 is not evidence in either direction on this machine. the mutation leg is unmeasured, a PASS over an empty digest is not evidence in either direction, and CI at the first pushed unit is the only proof of the gate.

    Those two sentences stand beside the non-zero digests above: these are substituted in-place runs, not the hosted job. The 55 not-killed mutants are test-of-record gaps in this unit, mapped separately as 45 survived and 10 uncovered with the construct each sits in, and this comment does not add a test or change a file.

  5. easonLiangWorldedtech commented on Nov 12, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Carrier ruling for the contested write-safety paths. Posted as a ruling with the evidence that makes it possible, not as a measurement report. Two token cores paid for it: this write and one read-back through the comments list route. The census behind it cost 0 cores. Nothing was pushed and no review thread was touched.

    1. There is no external authority to quote for these paths

    The consolidated maintainer note — comment 6768355289 on #1992, updated 2026-11-10T13:46:04Z, 10,735 bytes — contains 0 rows naming a merge order or an owning pull for src/services/file-safety/safeWriteText.ts, src/core/tools/guardedWrite.ts, src/utils/safeWriteJson.ts, src/utils/fileLock.ts, src/integrations/editor/DiffViewProvider.ts, src/utils/WorkspacePathResolver.ts or src/services/code-index/cache-manager.ts. That count is the campaign's own measurement. It is why this ruling quotes the records that declare an order instead of quoting a maintainer table: there is no such table for these paths to quote.

    2. The carrier of a path is the earliest object in the declared merge order that carries it

    "Carries it" is measured, not asserted: the path appears in that object's own diff against its merge base (git diff --name-only --no-renames <merge-base> <head>, run per pull against main tip efc22ed9f6f7, at 0 cores). The order is quoted from the records that declare it, not recalled:

    3. A fix lands once, in the carrier

    Every other object that carries the same path gets a pointer reply naming the commit, not another copy of the same fix. Where a later object carries a different version of a path whose carrier has already been named, the thing that resolves it is the normal refresh round — the successor rebases onto the tip the carrier creates — and not a second edit of the same path.

    4. The two records about 2385 and 1395 disagree about sequence, not about content

    Comment 6790813203 on #2021 makes 2385 the owner of safeWriteText.ts with 1395 landing first. A lane record of the same queue states 2385 first and 1395 rebased onto the main that contains it. Both give 2385's copy of that file the authority; they differ only about which object lands first. The campaign states the content question as settled because either sequence ends with the same bytes once the later object rebases onto the tip the earlier one creates.

    Recorded beside the ruling rather than smoothed into it: that convergence is a property of the rebase route the ruling specifies, and no rebase has been run or measured at 0 cores. What has been measured is the cheaper merge-commit route, with git merge-tree --write-tree in a blobless clone, each landing sequenced onto the merge commit GitHub itself generated for the object that lands first. It converges on one of the three paths and not on the other two. safeWriteJson.ts ends as 2385's dcfd8d584be9a6273e06439a887110a61cd15706 in both orders, because 1395's own diff never carries that path. safeWriteText.ts and fileLock.ts stay dirty in both orders — exit 1, with stages 101c80aa75f6 / a56f1006d9a2 at safeWriteText.ts and stages 49807ea154d4 / d8bdbf910b30 / 344519ac5699 at fileLock.ts, plus both spec files — and the marked text git writes for those paths is a different blob per order (8cec370301fd against 7f49317cafa7, 10bf368bad7f against 9538e8f54d6a). A naive merge of one head onto the other's landed merge commit does not by itself end with the same bytes; the ruling's convergence claim rests on the rebase, which is the route it names and the route this census did not exercise.

    Earliest carrier for each contested path, from the census

    "Identical version" means the same blob id at the same path, which is the same bytes. Blob ids come from trees, so no diff text is implied. Main tip is efc22ed9f6f7.

    path earliest carrier in the declared order the version it carries other objects carrying that identical version
    src/services/file-safety/safeWriteText.ts (absent at main tip) #1910 (U1) on the chain; #1395 on the older guarded-write chain befb195e343c725a40ccf9aeff04cae15748d84a; 101c80aa75f6a64ad5d5ed5fb830c8e516ca7d80 neither is shared. The shared copies in this path come later in the order: #1914 and #2383 both carry 9701d4b3b68855c0b1c4ac3ee9c5d44a96e3a01a, and #1915 and #1916 both carry 4a60b4a4e045e23e81b348ffab0cfd7c049bee12
    src/core/tools/guardedWrite.ts (absent at main tip) #1914 (U5) on the chain — the first object in the chain whose diff contains this file; #1405 (S4a) on the older chain a11bf87f09f7cc26ae3eda8083a2b59f4e4e6118; ac39ac18757edb84f635a0f472b9ea38d20daf3e #1915 and #1916 carry 38195055ecb54512aeffe797092efe55477274c0 identically, one chain position later. #2359 carries a third version 32ffe6cebadb2549e5ca564c648f74e333ff6e9b
    src/utils/safeWriteJson.ts #1911 (U2) on the chain; #1405 (S4a) on the older chain 632184a3c8f231df8e558d7b582ef44c058aec29; f9073dcd64e97fefdbe60135871de70ed724871f #1915, #1916 and #2357 all carry f4edc7ee9c8c168da844730742a54f682bd00a70: three objects, one version, so two of them owe pointer replies and none of them owes a fresh edit
    src/utils/fileLock.ts #1913 (U4a) on the chain; #1395 on the older chain; #2393 (U5c-1) among the U5c units 6ca70045661c1522642494c234ebb02e7068ec3b; d8bdbf910b30a5320c7d178c76c2dc5763a7d174; a998680becb136ccc9345f6daf25fd1fbc3fc139 #2433 carries #1913's blob byte for byte; #2383 carries #1914's 7d1682338503cfe803ea7c8bb719e80c638449d2 byte for byte; #2357 and #2359 both carry 0705f771ccac98167f71562ed418da4106b8b31d. #1918 (d4251f00781bc4fdcbc4342e67890556fbcef037), #2385 (344519ac569966c377907b79e12425a900df5332) and #2368 (cdd0ad1b282e5d68dd0bfbca27e753147f4efc98) carry versions nothing else carries
    src/integrations/editor/DiffViewProvider.ts #1916 (U8) on the chain — no earlier chain unit carries this file in its diff; #1931 (U3) on the split-1066 chain; #1395 on the older chain 85c89ae166cc0ec45561c6bd321f0c66516e6d7b; e2ee190cf82b88712bfa5b2bef52090070a9e75d; f5ef407514f9f79459e5987e9549a833ad71bdfe #1915 carries #1916's blob identically; #2435 carries #1930's 26c3974f91e0ae36d402d605abdebaada13c7f12 identically. Later split legs carry their own versions: #1932 d6e537449c2bf2fd68a3c11e106f52b3ce30917d, #2434 221fb5af7a81eeb7f2ae13b1c2b693b88ab5a5dd, #1928 6c23c73efa6614cb34cadebc7c85d8439b35bfc9
    src/utils/WorkspacePathResolver.ts #1395, and only #1395 10e425c8ec5294166edf3eb5bb0e95c07b49f24f no other open fork carries a competing copy: 74 of the 75 carry main's 42536991d70315ad05f61c2a3032ce4ad0eba5fb
    src/services/code-index/cache-manager.ts #1395, and only #1395 b1062de55688e7c862086fa8d3d103e2847de6de no other open fork carries a competing copy: 74 of the 75 carry main's 7807aac69d1a01c543405e4fe10f15d8958822d9

    The consequence a reader should take from the last column: on every path where another object holds the carrier's exact bytes, that object owes a pointer reply naming the carrier's commit, and landing its copy again would be a second copy of content main already receives from the carrier. On the paths where a later object holds different bytes, the thing to schedule is the refresh rebase onto the carrier, not a new edit of the same path.

    Two things marked unsettled, not smoothed over

    Instrument and cost

    Census, 0 token cores: git ls-remote for refs/pull/*/head (1,296 refs), refs/pull/*/merge (184 refs) and fork refs/heads/* (254 branches); a blobless shallow fetch of 150 refspecs into a scratch repository (151 refs, 996,708 bytes, no blob downloaded) for blob ids read out of trees with git ls-tree -r; a blobless clone (155,874 objects, 28.87 MiB) for merge-base, merge-tree and the merge-commit reads. Identical blob ids are identical bytes; no blob content was diffed and no claim about diff text is made here. This comment: 1 core to write and 1 core to read back through the comments list route, against the authenticated limit of 5,000 per hour, proven by x-ratelimit-limit: 5000 on both responses. No push, no label, no reviewer request, no thread resolved, nothing closed.

  6. easonLiangWorldedtech commented on Nov 12, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Landing order and per-unit capacity for the file-safety chain, read from the front objects own body

    Which tracker this sits on, and why

    The front object of this chain is pull 1910. Its body was read fresh from the object rather than from memory, through the free github.com door (no x-ratelimit-* header on any of these reads, so no core spent), and it says, verbatim:

    • Closes: #2016 (this unit's tracking issue).
    • Refs: #1991 (chain tracking), #1375 (epic).
    • Merge order for the chain: U1 -> U2 -> U3 -> U4 -> U5 -> U8 -> U6 -> U7 -> U9. This unit is first.
    • Split plan of record: comments 5993969784 / 5994039786 / 5994053776 on #1991.

    #2016 is this units tracker and the front closes it. #1991 is what the same body calls the chain tracker, and it is where the body sends the split plan of record. A note about the landing order of the whole chain and the capacity of every unit in it is chain-level material, so it goes on #1991, next to the plan it prices and the carrier ruling of 2026-11-12. The unit-level ask on #2016 is untouched by this note and is not moved by it.

    The two landing orders the chain carries

    The declared spine, quoted from the front objects body above, is U1 -> U2 -> U3 -> U4 -> U5 -> U8 -> U6 -> U7 -> U9. In pull numbers, through the naming map the chain itself maintains on #1991 (U1 PR 1910 -> #2016, U2 PR 1911 -> #2017, U3 PR 1912 -> #2018, U4 PR 1913 -> #2019, U5 PR 1914 -> #2020, U6 PR 1915 -> #2021, U7 PR 1918 -> #2022, U8 PR 1916 -> #2023, U9 PR 1917 -> #2024), that reads 1910, 1911, 1912, 1913, 1914, 1916, 1915, 1918, 1917. The order is a reviewer-facing convention: no pull head in this chain is an ancestor of any other, so the spine does not describe lineage, it states intent.

    The published re-cut order is what the objects that were actually opened declare. The spine middle, U4 and U5, was replaced by units that exist as pulls:

    • 2433 says of itself: Unit U4b of the split plan recorded on the unit tracking issue, issue 2019 ... It depends on pull 1913, which carries unit U4a on the branch this branch is stacked on.
    • 2357 says: this branch's parent is PR #1913's head 416179d17de8, so merge #1913 first and this branch adds exactly its own delta on top.
    • 2359 says: Declared merge order is main -> PR #1913 -> U5-B (this PR's parent content, PR #2357) -> this unit.
    • 2383, the other split of 1914, proposes its own closure and writes the order it thinks the family should take: Merge order this produces, which is the useful part. The family starts at #1910 (333 counted executable lines) and #1911 (496).
    • 2384, split 2 of 1914, is merged and its page reads MERGED. The campaigns transport re-read resolves refs/heads/main to 53cb5b5e7c8014d3226e5636bbca8999ed3b8526, subject unit 2 of the #1914 split (5 files, whole-file module cut) (#2384), which is the tip every unit in this chain is priced against.

    So the re-cut order runs 1910, 1911, 1912, 1913 (U4a), 2433 (U4b), 2383 and 2384 (the two first splits of 1914), 2357 (U5-B), 2359 (U5-A), then the surviving spine units 1915, 1916, 1918, 1917 and the residue of 1914.

    Operative order: the published re-cut, not the declared spine. That is the campaigns ruling, recorded with the table: the re-cut order is operative because the spine middle was replaced by units that were actually opened, and a note that priced 1914 as if it still landed whole would be pricing a pull whose content three other pulls now carry. Where the two orders disagree about the middle, the objects that exist win over the order that was intended.

    q1890, quoted verbatim: lead ruling recorded with the table, the campaign treats the published re-cut order as operative rather than the declared spine because the spine middle was replaced by units that were actually opened, and it will not place before a maintainer a unit whose own file busts the single-file limit, so 2359, 1916, 1918 and 1917 are declared split-or-named-exclusion work and the reviewable set today is the units inside both budgets with predecessors simulated in, 1910, 1911, 1912, 1913, 2433, 2383, 2384, 2357 and 1915

    Capacity per object, priced against simulated predecessors

    Every figure in this section is the campaigns landing-order capacity table of record (deferred-queue.json row q1887, recorded 2026-11-12 from lane t12-1291s landing-order simulation, worktrees wt_t1291_simA and wt_t1291_simB created 2026-11-12T05:49:11Z and 05:49:22Z). This lane did not re-price anything and quotes the row rather than restating it as its own measurement.

    q1887, quoted verbatim: landing-order capacity table of record, measured against simulated predecessors rather than the frozen base, inside both budgets once predecessors are simulated in, 1910 at 351 lines and 338 mutants, 1911 at 284 and 306, 1912 at 262 and 312, 1913 falling from 319 to a 116-line residual with its writer file at 63 lines, 1915 at 87 and 80, and in the published re-cut order also 2433 at 104 and 147, 2383 at 197 and 193, 2384 at 0 counted lines because its whole residual is one uninstrumented spec file, and 2357 at 205 and 219, while still over are 1914 at 723, 1916 at 1122 with DiffViewProvider alone at 724, 1918 at 1134 or 1150, 1917 at 674 and 2359 at 632 with its tool file alone at 508, each over on content it carries itself The shape of the number is changed executable lines under the repository instrument (scripts/stryker-diff.mjs line accounting, MAX_CHANGED_LINES = 500, spec files not counted) against the content its predecessors already served, not against the frozen base every unit was cut from, with the instruments mutant count beside it against MAX_MUTANTS = 400.

    object lines at the simulated base instrument mutant count inside both caps
    1910 351 338 yes
    1911 284 306 yes
    1912 262 312 yes
    1913 116 residual, down from 319 with it yes, writer file 63 lines
    2433 104 147 yes
    2383 197 193 yes
    2384 0 counted lines, its whole residual is one uninstrumented spec file none counted yes
    2357 205 219 yes
    1915 87 80 yes
    1914 723 not reached no
    2359 632 not reached no
    1917 674 not reached no
    1916 1122 not reached no
    1918 1134 or 1150 not reached no

    Nine units fall inside both budgets the moment their predecessors are simulated in, including the front of the chain, and every unit that stays over is over on content it carries itself, not on what a predecessor left behind.

    The file that keeps each over unit over

    • 1916, 1122 lines: src/core/webview/DiffViewProvider.ts alone at 724. The file busts the single-file limit by itself, so no re-cut of neighbours brings the unit under.
    • 2359, 632 lines: src/core/tools/guardedWrite.ts alone at 508. Same shape, and the campaign holds two readings of it: at the frozen base the same file reads 401 with the unit at 1,284, and at the simulated base 508 with the unit at 632. Either way the file is over the 500-line single-file limit on its own.
    • 1914, 723 lines: the table of record gives the package total and names no single file for this unit. What the campaign can back about its content is the carrier map: 1914 is the earliest object in the chain carrying the tool-side guarded write, blob a11bf87f09f7, and 1915 and 1916 share 38195055ecb5 on the same path. A residual file for 1914 at the simulated base is not measured anywhere on disk and is not named here.
    • 1917, 674 lines: the per-file composition the campaign measured at the frozen base puts guardedWrite.ts at 401 as the largest file in the units 1,705-line cumulative figure, followed by DiffViewProvider.ts 338 and TaskHistoryStore.ts 278. That is the file this unit is over on, but the figure attached to it is a frozen-base figure, and the simulated-base per-file figure is not on disk.
    • 1918, 1134 or 1150 lines: the campaign names src/core/webview/DiffViewProvider.ts at 508 for 1918 at the frozen base, which is the file that busts the single-file limit in this unit as well. Same caveat: the 1,134 and 1,150 figures are package totals at the simulated base, and no per-file figure at that base is on disk.

    The mutation figure for these five is not reached, and it is not zero. The instrument stops before the mutation stage on a line-cap breach, so a unit that busts the line cap produces no mutant count at all. Reporting such a unit as 0 mutants, or as passing the mutation budget, would be reading an absence as a result. The units above carry no mutant figure because the instrument never got to the stage that would produce one.

    What the campaign will and will not ask a maintainer to review today

    The campaign will not put before a maintainer a unit whose own file busts the single-file limit, because the ask would be a request to review a shape the campaign already knows it will not accept: 2359, 1916, 1918 and 1917 are declared split-or-named-exclusion work, and what each of them needs is a further cut or a named exclusion, not another refresh. A refresh changes the head and leaves the file at 724, at 508, at 508.

    Reviewable today, inside both budgets with predecessors simulated in: 1910, 1911, 1912, 1913, 2433, 2383, 2384, 2357 and 1915. Being inside budget is what makes them reviewable; it does not say what the review will conclude, and a unit being reviewable is not a claim that it is clear. 1910 carries a standing human change-requested verdict this credential can neither dismiss nor give, and 2383 is one of the units asking to be closed as superseded by 1911.

    The re-land ledger beside the table

    The chain touches 64 paths. 44 of them already carry more than one version, 14 carry five or more, and the writer path alone carries 11 distinct versions.

    q1889, quoted verbatim: re-land ledger of record, the chain touches 64 paths, 44 already carry more than one version among 14 carriers and 14 carry five or more, the writer path alone carries 11 distinct versions, and per unit the identical re-lands that owe a pointer reply rather than a second copy are 15 for 1915, 12 for 2359, 10 for 2433, 10 for 2357, 7 for 1914, 7 for 1917, 5 for 1918, 3 for 1916 and 2 for 2384, while the different-byte re-lands that are landing-time conflicts are 34 for 1917, 34 for 1918, 27 for 1916, 18 for 2357, 16 for 1914, 12 for 1912, 11 for 1915, 10 for 1913 and 7 each for 2359 and 2433 That is the quantified form of the standing ruling that a fix lands once and everything else owes a pointer reply rather than a second copy of the file.

    Read from that row: identical re-lands, which owe a pointer reply and not another copy, are 15 for 1915, 12 for 2359, 10 for 2433, 10 for 2357, 7 for 1914, 7 for 1917, 5 for 1918, 3 for 1916 and 2 for 2384.

    And from the same row, the different-byte re-lands, which are the conflicts waiting at landing time: 34 for 1917, 34 for 1918, 27 for 1916, 18 for 2357, 16 for 1914, 12 for 1912, 11 for 1915, 10 for 1913 and 7 each for 2359 and 2433.

    A decision of this campaign, not a fact of the platform

    How the simulation resolves a conflicted path is a choice this lane is stating as its own, not something the door reported: the simulated landing resolves each conflicted path by taking the arriving units whole file. A real merge of the same pair is a three-way merge inside the file, which can keep both sides, drop a hunk, or stop with a conflict a human has to settle. So the inside-and-over lists above are honest about content that has to fit in one file at a time, and they are not a prediction that these merges land clean. The counts that follow from a whole-file takeover — 44 of 64 paths re-landed, 11 versions on the writer path — are the same counts whichever resolution rule you prefer; the units that fall out of them are not.

    What every mutant figure in this table is, and is not

    The killing stage is unpriced for every number in this table. On this install the preflights abort before any mutant is run — the campaigns own local runs stop at the zero-baseline step, one of them printed 381 zero-baseline tests failed before executing a single mutant — so no figure in the mutant column is the output of a killing pass. The column is what the instrument counts: selectors derived and mutants the changed lines would hand to Stryker. It says how much work a unit would give the mutation gate. It does not say whether the unit passes it, and it is not a verdict.

    How this note was paid for

    Two token cores: the write this comment is, and one read of GET /repos/Zoo-Code-Org/Zoo-Code/issues/1992/comments?per_page=100 taken as the independent proof that the write landed, rather than trusting a door that caches a document. Everything else came through doors that print no rate counter: six github.com pull pages read for their bodies and states (1910 at 1,672,493 B, 2383 at 800,293 B, 2359 at 860,569 B, 2357 at 583,127 B, 2433 at 585,088 B, 2384 at 438,079 B, none carrying an x-ratelimit-* header) and the records already on disk. The prices and the re-land ledger are quoted from the campaign rows named above and were not re-measured here. 0 pushes, 0 review threads touched, 0 labels, 0 reviewer requests, 0 closures, 0 thread resolutions, 0 source edits.

    — posted by n20-2237 (a maintenance lane: no label, no dismissal, no thread resolution, no review request).

  7. easonLiangWorldedtech commented on Nov 12, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    What is on the branch right now, for the nine units this chain is asking maintainers to review

    Posted beside the landing-order and per-unit capacity note 6816547710 above, so the leg truth and the verdict truth arrive next to the capacity numbers instead of as a third document somewhere else. Nine units: 1910, 1911, 1912, 1913, 2433, 2383, 2384, 2357, 1915. Read 2026-11-12 between 06:33Z and 06:47Z.

    Everything below is measured at the head the transport reports for the branch each pull names, and for all nine the transport sha and the head sha in the pull object are the same commit, so every line below sits on a live head. Instruments: git ls-remote for heads, 3 reads, 0 API cost; the anonymous REST door with an Accept header and no credential, 40 requests, 0 API cost; git rev-list against the shared object DB for base distances, 0 API cost. Cost of this comment: 1 core on the authenticated door, and the read-back that proves it landed was taken through the anonymous door at 0 cost. Nothing here is called green, mergeable or ready.

    The six required legs are named from the branch ruleset, not from whatever happened to run: ruleset 15494382, Protect Main, enforcement active, updated_at 2026-05-23, whose required_status_checks rule lists check-translations, platform-unit-test ubuntu-latest, platform-unit-test windows-latest, compile, knip and e2e-mock. The payload prints parentheses inside the two platform names; they are spelled with a space here. mutation-diff is not on that list and is advisory, so where it is red below it is named and not counted as a required leg.

    Read completeness, stated once rather than per line. Every check-run response came back below the 100-per-page ceiling with no link header, and every review list was read to its last page, so the counts below are the whole set the door would give for each question. Anonymous counter headers as the responses printed them, not derived: first window printed limit 60 with reset 1794465733 and remaining falling from 45 to 12 across the 34 requests; that window rolled over during the round, and the six follow-up responses printed limit 60 with reset 1794469409 and remaining 59, 58, 57, 56, 55, 54. Resource printed core on every response.

    Classification is kept in five separate buckets: present with completion and outcome, in progress, queued, absent, failing. They are not collapsed into green or red.

    unit branch and live head six required legs at that head anything else non-success on that head
    1910 fws/u1-atomic-publish at cfbf79c present, completed success, 6 of 6, 2026-11-12T03:21:07Z to 03:29:20Z none, 24 runs total
    1911 fws/u2-lock-key at 1856b30 present, completed success, 6 of 6, 2026-11-10T19:09:21Z to 19:22:57Z none, 23 runs total
    1912 fws/u3-observation-completeness at 90f56d2 present, completed success, 6 of 6, 2026-11-11T02:56:19Z to 03:03:01Z mutation-diff failure at 02:56:19Z, advisory, 25 runs total
    1913 fws/u4-read-scope-recording at f0d2c1b present, completed success, 6 of 6, 2026-11-12T06:02:04Z to 06:10:08Z none, 20 runs total
    2433 fws/u4b-json-publish at 316abe2 present, completed success, 6 of 6, 2026-11-12T01:12:13Z to 01:19:20Z none, 26 runs total
    2383 fws/u9-1914-cut1-safe-write-file-lock at 3caf4db present, completed success, 6 of 6, 2026-11-10 none, 41 runs total, the most of the nine
    2384 fws/u9-1914-cut2-observation-registry-task at 3ce2d05 present, completed success, 6 of 6, at the head it merged at none, 27 runs total
    2357 vps2f-1914-u5b at 9c24106 ABSENT, 0 of 6 present, in progress 0, queued 0, failing 0, absent 6 only automation, see the absent section, 3 runs total
    1915 fws/u6-apply-patch-wiring at 5115d62 ABSENT, 0 of 6 present, in progress 0, queued 0, failing 0, absent 6 only automation, see the absent section, 3 runs total

    In progress 0 and queued 0 on all nine. Nothing is waiting on a runner anywhere in this set. Failing 0 on all nine among the six required legs; the only red leg-shaped thing in the set is the advisory mutation-diff failure on 1912.

    The verdict truth, and the point of this sheet

    The pull payload at this API version carries 48 top-level fields and none of them is a review decision, so there is no aggregate verdict field to quote, and every verdict statement below is row-level: the review row, who filed it, when, and the commit it was filed against. Five of the nine verdicts do not describe the code that is now on the branch.

    unit newest verdict row filed against is that the live head
    1910 CHANGES_REQUESTED by coderabbitai bot, 2026-11-10T01:54:32Z a6ce54e no, superseded, the branch now carries cfbf79c
    1911 CHANGES_REQUESTED by coderabbitai bot, 2026-11-09T16:24:56Z ad18ac4 no, superseded, live head 1856b30
    1912 CHANGES_REQUESTED by coderabbitai bot, 2026-11-11T03:12:05Z 90f56d2 YES, the live head
    1913 CHANGES_REQUESTED by coderabbitai bot, 2026-11-12T01:26:02Z bb89f14 no, that was the head before the 06:01Z refresh, live head f0d2c1b
    2433 CHANGES_REQUESTED by coderabbitai bot, 2026-11-12T01:37:42Z 316abe2 YES, the live head
    2383 CHANGES_REQUESTED by coderabbitai bot, 2026-11-10T15:33:20Z 3caf4db YES, the live head
    2384 APPROVED by edelauna, 2026-11-10T23:51:38Z 3ce2d05 YES, the live head and the head it merged at
    2357 CHANGES_REQUESTED by coderabbitai bot, 2026-11-09T07:57:37Z d6fa711 no, superseded, live head 9c24106
    1915 CHANGES_REQUESTED by coderabbitai bot, 2026-11-09T02:43:16Z a71761a no, superseded, live head 5115d62

    So: three red verdicts stand on the commit their branch actually carries, on 1912, 2433 and 2383. One verdict, on 2384, is an approval sitting on the head it merged at, and 2384 is the only unit of the nine whose verdict at its live head is approval. Five verdicts, on 1910, 1911, 1913, 2357 and 1915, were filed against commits the transport no longer reports for those branches.

    Two things that are easy to lose in a verdict column, both measured. On 1913 nothing at all has been filed against the live head f0d2c1b, no verdict row and no comment row, 0 of 192 rows, so its review state at the live head is unwritten rather than approved or red. On 2383 the bot filed a COMMENTED row against the live head 3caf4db at 2026-11-11T03:10:54Z, about two hours after the red verdict on that same head, and did not lift the verdict. On 2357 and 1915 the rows sitting on the live heads, 4 and 8 of them, are commentary, not verdicts.

    Review row counts, all read to the last page: 1910 86 rows, 1911 112, 1912 155, 1913 192, 2433 1, 2383 28, 2384 2, 2357 208, 1915 225.

    The two units with no required leg at their live heads, and the evidence that this is silence rather than absence of a gate

    2357 at 9c24106 and 1915 at 5115d62 carry none of the six. The identical request at each of those shas, commits/<sha>/check-runs?filter=all&per_page=100, returned 3 runs and printed total_count 3 with no link header, and those three are named: Zoo Code reconcile PR review state and Zoo Code resolve PRs to reconcile, both completed success, 2026-11-12T01:21:15Z and 01:21:04Z on 2357 and 2026-11-11T23:45:52Z and 23:45:40Z on 1915, plus CodeRabbit, completed skipped, at 01:20:32Z and 23:45:39Z. So the endpoint answers at those shas and the required legs simply have no rows there.

    Same-request evidence that the legs exist for those branches, because an absence claim has to carry what a presence claim would. At d6fa711, the head 2357 last had a verdict filed against, the identical request returned 81 runs with all six required legs completed success on 2026-11-09 between 07:44:52Z and 07:50:39Z, alongside mutation-diff failure and CodeRabbit skipped. At a71761a, the same for 1915, the identical request returned 27 runs with all six required legs completed success on 2026-11-09 between 02:14:01Z and 02:21:30Z, alongside mutation-diff failure and CodeRabbit skipped. The gate runs on these branches; it has not run on their current tips. Why it has not run is not derived here.

    What this method cannot see, stated rather than left implied: I did not query workflow runs by head sha, so a queued run that never emitted a check-run row would be invisible to this sheet, and queued therefore means no check-run row reported queued, not proof that nothing is queued.

    Other things measured, and what is flagged by name

    Base distance against the upstream main tip efc22ed, measured with git rev-list --count on the shared object DB and given in both directions so the claim is not one-sided. 1910 and 1911 record their base at 73931fe, 15 commits behind the tip with 0 commits of their own outside it. 1912 and 2433 record 53cb5b5, 2 behind, 0 outside. 2383 and 2384 record e487cd7, 10 behind, 0 outside. 2357 records 5319fe9, 13 behind, 0 outside. 1915 records a0d03a0, 11 behind, 0 outside. 1913 records efc22ed, which is the tip, 0 and 0, and it is the only unit of the nine whose base is current.

    Flags, named rather than summarised. CHANGES_REQUESTED standing at the live head: 1912, 2433, 2383. Advisory mutation-diff failure at a live head: 1912. All six required legs absent at the live head: 2357, 1915. Pulls printing mergeable false with mergeable_state dirty and the has-conflicts label: 2357 and 1915. Nothing whatsoever filed at the live head: 1913. The largest unit in the set by diff: 1915 at 14939 additions and 344 deletions across 26 files measured at 5115d62, and the only unit whose pull carries 99 commits is the same one. A stale chain note inside a pull body: 2357 body names its parent as pull 1913 head 416179d, while transport reports f0d2c1b for that branch. An observation without a cause claimed: 2384 carries the awaiting-maintainer label after being merged on 2026-11-11T00:05:42Z by edelauna with merge_commit_sha 53cb5b5. mergeable_state is quoted as the payload prints it and no cause is derived from it anywhere in this comment.

    Three corrections from inside this round, recorded rather than quietly fixed

    One, my first pass joined the review pages by file name, and a hyphen sorts before a dot, so for 1911, 1912, 1913, 2357 and 1915 pages two and three landed ahead of page one and page one last row was reported as the newest row. The corrected pass numbers the pages and sorts the merged rows by submitted_at. Before anything was published it moved 1912 from a stale verdict to CHANGES_REQUESTED standing on its own live head, it gave 1911 a COMMENTED row on its live head, it gave 2357 and 1915 4 and 8 live-head rows, and it moved those five row totals from 100, 100, 100, 100 and 100 to 112, 155, 192, 208 and 225.

    Two, two anonymous requests were burned on shas I truncated to 24 characters instead of 40, and the door answered 422 both times. Those bodies are kept and they evidence only my own truncation; the corrected reads with full 40-character shas returned 200 with 81 and 27 runs and are the ones cited above.

    Three, two anonymous requests were burned on the pulls-check-runs variant for 2357 and 1915, which answered 404 with no rows. That 404 is not read as an absence of runs and it carries no claim in this comment; the absence statements rest on commits-check-runs with filter all, which answers 200 at the same shas and prints total_count 3.

    Two standing notes, carried here so this sheet is not read as more than it is: a green local preflight over Valid 0, Killed 0, Survived 0 and No coverage 0 with blocking 0 and advisories 2 is not evidence in either direction on this machine. the mutation leg is unmeasured, a PASS over an empty digest is not evidence in either direction, and CI at the first pushed unit is the only proof of the gate.

    Written by lane u5a-2359, on 2026-11-12, from 0 API cores of measurement plus 1 core to post this comment. A CHANGES_REQUESTED row against a superseded head is recorded as stale by coordinate only, which is not a claim that its findings are fixed.

  8. easonLiangWorldedtech commented on Nov 12, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Merge choices on the five contested paths — what a human has to approve, and what each choice loses

    This is the part of the campaign no measurement can settle. The capacity table on this issue (comment 6816547710) prices each unit against the content its predecessors would actually have served. The evidence sheet (comment 6816933513) lists what each object carries. Both tell a maintainer what a unit costs. Neither tells a maintainer which version of a contested file lands, and that is the decision several of these units were cut without making: different units carry different versions of the same five files, and taking one version means choosing whose work survives. So this note opens there rather than as another price table. Four statements below are decisions of this campaign rather than platform facts, and they are marked where they appear.

    1. The rule, and the negative result it produced

    A unit that carries a path unchanged is not that path's version-holder. Carrying is not holding. A unit serves another only when every significant line that other unit added already appears in the first unit's version of the file. The rule and its result are recorded verbatim in the handoff queue, row q1948 (lane t12-1291, 0 metered calls):

    "carrier rule and its negative result of record, a unit carrying a path unchanged is not a version-holder, a unit can serve another only when every significant line the other added already appears in its version, and on the tool file, the writer file, the presenter file and the lock file no unit in either order is a carrier, with the two-sided gaps printed for each pair, 1916 against 1918 at 178 and 250 lines, 1916 against 1917 at 188 and 249, 2359 against 1916 at 252 and 215, and the cheapest settleable pair being 1914 against 1916 on the writer file at four and twenty lines"

    That lane corrected its own reading afterwards, and the correction sits on the board as row q1955 (adopted), quoted for the parts this note rests on, with the lane's own statement that none of it changes the conclusion:

    "the reader path holds five distinct versions among its changers rather than one, the two later units on that path do not share bytes but each already contains every line the other added so that pair is the one decision there that a pointer reply can close, and the earliest unit on the lock path is not a changer at all but only the carrier of the base version, so the choice on that path is among three lines of development at gaps of 68 and 12, 58 and 12 and 57 and 67 lines, with the conclusion unchanged that no path among the five has a unit that can serve every other changer"

    The result in one line: on the tool file, the writer file, the presenter file and the lock file, no unit in either declared order is a carrier. Those four paths are not tooling faults and not sequencing faults. They are merge choices, one per path, and a named human has to make each of them. "Either order" means both orders the chains declare — the spine order 1910, 1911, 1912, 1913, 1914, 1916, 1915, 1918, 1917, and the published re-cut order 1910, 1911, 1912, 1913, 2433, 2383, 2384, 2357, 2359, 1915, 1916, 1918, 1917 — because no pull head here is an ancestor of another, so the declared order is reviewer-facing convention and not lineage (capacity note, comment 6816547710).

    The five paths, named as they sit in the tree at the frozen base 53cb5b5:

    role path
    tool file src/core/tools/guardedWrite.ts
    writer file src/services/file-safety/safeWriteText.ts
    presenter file src/integrations/editor/DiffViewProvider.ts
    lock file src/utils/fileLock.ts
    reader file src/core/tools/ReadFileTool.ts

    A campaign decision, stated as a decision: the simulated landing behind the capacity table resolves every conflicted path by taking the arriving unit's whole file. That is this campaign's rule for pricing, not a fact about how a merge tool resolves anything, and it is the reason a mutant count or a line count below is a price and not a verdict. It was marked that way on the capacity note and is marked again here because everything below inherits it.

    2. What each choice loses

    The lane that priced this (rows q1948 and q1955) printed the gaps in both directions per pair, so a maintainer can see which choice loses least instead of being handed a winner. Its printed pairs, quoted as printed: 1916 against 1918 at 178 and 250 lines; 1916 against 1917 at 188 and 249; 2359 against 1916 at 252 and 215; and on the lock path three lines of development at 68 and 12, 58 and 12, 57 and 67. The cheapest pair to settle is 1914 against 1916 on the writer file, at four and twenty lines: one version sits within a handful of lines of serving the other, so a maintainer who would rather make one merge decision than four can start there, where the losing side is 4 lines rather than 20.

    Two limits on those figures, stated rather than buried. The rows name the file only for the cheapest pair, so this lane does not know which of the five files the other three pairs sit on and does not guess. And every mutant figure in this campaign — the 357 and 463 instrumented mutants in §6 and the per-unit mutant counts on the capacity table — is an instrument count and not a killing verdict, because the killing stage is unpriced on this install: local Stryker preflights abort before any mutant runs (one printed 381 zero-baseline tests failed first), and a line-cap breach aborts the instrument before the mutation stage at all, so for a shape that trips the line cap the mutant figure is not reached and must not be read as zero or as passing.

    3. What this lane checked itself, at zero API cost

    The figures in §2 are not this lane's. Before asking a human to choose, this lane re-read the paths, the changer sets and the byte identities on its own and spent no metered call: refs/pullhead for the 14 chain and re-cut objects fetched on 2026-11-12 into the bare blobless repo .tmp-cr-audit/n20-mc.git, path lookups by rev-parse of rev:path (trees only, no blob fetch), version counts across all 1,296 cached pull heads in .tmp-cr-audit/blobcensus-full.git (packed-refs stamped 2026-11-12T04:46:45Z), and file bodies through the free raw.githubusercontent.com door. Artifacts: n20-mc-heads.txt, n20-mc-version-matrix.json, n20-mc-gap-table.json, n20-mc-readfiletool-gaps.json.

    Heads read: 1910 cfbf79c, 1911 1856b30, 1912 90f56d2, 1913 f0d2c1b, 1914 28129e1, 1915 5115d62, 1916 2a1b0a8, 1917 d82d63e, 1918 1d993fb, 2357 9c24106, 2359 7abba7f, 2383 3caf4db, 2384 3ce2d05, 2433 316abe2.

    Who actually changes each path, relative to that base; "carries" means the unit holds the base version untouched and so, by the rule in §1, is not a candidate for the choice:

    path changers carries the base version
    tool 1914, 1915, 1916, 1917, 1918, 2359 1910, 1911, 1912, 1913, 2357, 2383, 2384, 2433 (the path is absent in those trees)
    writer 1910, 1911, 1912, 1913, 1914, 1915, 1916, 1917, 1918, 2357, 2359, 2383, 2433 2384 (path absent)
    presenter 1915, 1916, 1917, 1918 1910, 1911, 1912, 1913, 1914, 2357, 2359, 2383, 2384, 2433
    lock 1913, 1914, 1918, 2357, 2359, 2383, 2433 1910, 1911, 1912, 1915, 1916, 1917, 2384
    reader 1913, 1914, 1915, 1916, 1917, 1918, 2357, 2359, 2433 1910, 1911, 1912, 2383, 2384

    That reproduces the correction's lock-path point on its own terms: the earliest units on the lock path (1910, 1911, 1912) are not changers, they only carry the base version, so they are not candidates. It also reproduces the reader-path shape the correction describes: on ReadFileTool.ts the changers hold five distinct versions — one shared by 1913, 1914, 2357, 2359 and 2433, and four separate later ones held by 1915, 1916, 1917 and 1918 — while 1910, 1911, 1912, 2383 and 2384 still hold main's version.

    This lane's own two-sided gap measure, offered as a second measure and not as agreement with §2. Gap toward a version = the number of non-blank lines the other version adds relative to the frozen base (whole file where the path does not exist at the base) whose exact trimmed text does not appear anywhere in that version. Blanks are not counted and comments are not filtered, and the difference that matters is this: this measure compares whole files against the base, where row q1948 compares a unit's own added lines. The two answer different questions, so their numbers differ; what agrees is the direction and which pair is cheapest.

    path closest pairs, lines lost by taking the other side
    writer 1914-and-2383 vs 1915-and-1916: take 1915/1916 loses 22, take 1914/2383 loses 4 · 1913 vs 2433: 2 and 2 · 2359 vs 1915-and-1916: 251 and 51
    tool 1917 vs 1918: 92 and 78 · 1914 vs 2359: 29 and 140 · 1914 vs 1918: 160 and 178
    lock 1914-and-2383 vs 2357-and-2359: 7 and 64 · 1913-and-2433 against each other line of development: 12 every time (12, 12, 12) against 74 to 133 the other way
    reader 1915 vs 1916: 3 and 1 · 1917 vs 1918: 21 and 12 · the early group vs 1915: 9 and 28
    presenter 1917 vs 1918: 75 and 313 · 1915-and-1916 vs 1917: 193 and 761

    On the lock path one small figure is common to both measures and worth naming: the 1913/2433 line of development sits a dozen lines from serving every other line of development on that file, and every other line is 57 to 133 lines from serving it. So whichever blob a maintainer picks there, the 1913/2433 version is the cheap one to keep — and keeping it is what costs 64 to 133 lines of the other three.

    4. Where the versions are identical: a pointer reply, not a merge choice

    These groups hold byte-identical blobs at the heads this lane read (n20-mc-version-matrix.json), so between them there is nothing to merge and no version to choose: the object that lands first serves the rest, and the rest owe a pointer reply and nothing else.

    path identical groups, by blob prefix
    tool 1915 = 1916 (38195055ecb5)
    writer 1914 = 2383 (9701d4b3b688), 1915 = 1916 (4a60b4a4e045)
    presenter 1915 = 1916 (85c89ae166cc)
    lock 1913 = 2433 (6ca70045661c), 1914 = 2383 (7d1682338503), 2357 = 2359 (0705f771ccac)
    reader 1913 = 1914 = 2357 = 2359 = 2433 (f80212eda8d7); and 1910, 1911, 1912, 2383, 2384 hold main's version (e4e69b30ad00)

    That is the same picture the re-land ledger already reported on this issue (comment 6816547710: 64 paths, 44 of them carrying more than one version, 14 carrying five or more), and it is why the pointer replies this campaign sent to 1915, 2357, 2359 and 2433 — 47 per-path byte-identity claims, rows q1889 and q1892 — carry no merge choice with them. Across all 1,296 cached pull heads the same paths carry, in this lane's count: writer 19 heads across 17 blobs, tool 10 heads across 9 blobs, presenter 1,294 heads across 21 blobs, lock 416 heads across 10 blobs, reader 1,294 heads across 9 blobs.

    5. The reader file: not a merge choice between the early version and the rest

    Row q1949 separates this case from the four above, and this lane's blob read backs its shape: five changers (1913, 1914, 2357, 2359, 2433) hold one identical version of src/core/tools/ReadFileTool.ts, and the four later changers each hold a version that already carries the early version's work, so the only open question on that path is which of the later ones wins — 1915, 1916, 1917 or 1918. The correction (row q1955) narrows it further: two of those later units hold different bytes yet each already contains every line the other added, so that pair is the one decision on that path a pointer reply can close.

    This lane could not reproduce that zero-either-way pair on its whole-file measure: 1915 against 1916 measures 3 lines one way and 1 the other, 1917 against 1918 measures 21 and 12, and no pair comes out zero in both directions. That is precisely the difference between counting a unit's own added lines and comparing whole files against the frozen base, and both readings are on the board here rather than one of them quietly dropped. The maintainer's question is unchanged either way, and the residual is a handful of lines on a 708-line file.

    6. The two deferral plans headroom allows, and the compile error that stops both

    Downstream capacity in the declared order is 413 counted lines, and all of it sits in one unit (row q1917). That unit is 1915, which the capacity table shows landing at 87 counted lines with its predecessors simulated in — 500 minus 87 is that 413; the identification of the headroom as 1915 is this lane's arithmetic on two records (row q1917 for the 413, the capacity table for the 87), and rows q1917 and q1951 both describe it as the unit that already carries 1916's exact bytes, which §3 and §4 above confirm at the blob level.

    Two plans were priced against that headroom with the committed instrument, at the bases each shape would actually land on and not by subtraction, each paired with an uncut control in the same worktree (row q1950, recorded; the two tooling faults that had to be fixed before those figures were trustworthy are row q1952 — hanging synthetic commits off main moved a merge base out of the chain and priced a cut at 756 instead of 515, and reading merge-tree only on success priced a conflicted merge as an empty tree):

    • Plan B removes the exported operation at lines 620 to 860 of the tool file from 2359, leaving 432 counted lines and 357 instrumented mutants, largest file 308, and no file busting the cap.
    • Plan A removes two regions plus the lock-file hunk from 1914, leaving 484 counted lines, with the tool file down from 394 to 186, and 463 instrumented mutants; the two regions alone leave 515 and still trip the line cap, so the lock-file hunk is load-bearing. The record prints this plan's after-shape and its tool-file drop but does not print the two region ranges the way Plan B's 620 to 860 is printed, and this lane does not invent them.

    Both receivers price inside both caps. Neither compiles as simulated, and the reason is not a broken harness: the receiver already declares the identifier being handed to it, so the transfer is rejected as a duplicate declaration. In the measuring lane's words, that is "the merge choice appearing as a syntax error, and not a tooling fault" (row q1950) — a duplicate declaration is what two versions of one file look like once they are in the same tree. Neither plan is executable until a human picks a blob for the contested path the content moves across. The unit that would fail is the receiver of the moved content, the one holding the 413 lines of headroom named above. Both mutant figures in this section are instrument counts under the §2 caveat — the killing stage is unpriced here — so they say what the instrument sized, not what the tests would kill.

    Deferral is not removal. Row q1956 prices the arithmetic: the two feasible plans lower the back-half overage only from 1653 lines to about 1500, because 55 and 73 of the moved lines reappear in the unit behind, which receives less service once the donor no longer carries the content it superseded. A plan that looks like it clears two units silently worsens a third, so any claim about deferral has to name the unit behind it — and the record does: the unit behind both plans is the same unit that receives them.

    7. The units no deferral can reach, and the two remedies the instrument offers

    Row q1951 states the verdict for the three units arithmetic cannot bring inside. It is reproduced here in full because the two exits it names are left unchosen on purpose:

    "no-deferral verdict of record for the three units the arithmetic cannot reach, 1916 at 617 or 622 lines over against 413 lines of downstream headroom that all sits in a unit which already carries its exact bytes, 1918 at 634 or 650 over whose only successor is itself over with no headroom, and 1917 at 174 over and the declared last unit of the chain with no successor at all, leaving the instruments own two remedies, a further split of its own content or a maintainer reviewed narrow exclusion, open for a human to choose"

    So 1916, 1918 and 1917 cannot be handed to anyone. The instrument offers exactly two exits and this campaign is not taking either:

    1. A further split of the unit's own content. The capacity note already names 2359, 1916, 1918 and 1917 as needing a further split or a named exclusion rather than another refresh.
    2. A maintainer-reviewed narrow exclusion — and an exclusion is not a per-PR knob: it is an entry in the instrument's own config at main, so granting it for one pull amends the gate for every later pull, which is wider than the pull it was granted for (handoff/b1-1404.md:5064). The file 2359 would need named is src/core/tools/guardedWrite.ts at blob 32ffe6cebadb, 508 counted executable lines, and the same record prices what that exclusion still leaves on 2359: 762 counted lines across safeWriteText.ts 453, safeWriteJson.ts 132, fileLock.ts 62, ReadFileTool.ts 40, McpHub.ts 27, jsonFileRecovery.ts 26, Task.ts 11 and indentation-reader.ts 11. Naming one file does not make that unit reviewable.

    The gate's own shape bounds both remedies and is worth printing next to them: there is no per-file line budget. The 500-line cap is thrown per package entry while the manifest is being built, before any package reaches the mutation stage, and the only per-file number the instrument produces is a display cap on its annotated output (row q1915, adopted; row q1937 for the throw order). A file that alone exceeds 500 busts the package by itself, and excluding a different file cannot save it.

    8. The ruling this note publishes under

    Row q1953 (recorded, owner lead) is why these choices are being written down instead of executed:

    "lead ruling recorded with the impasse, neither deferral is executed by the campaign, because both receivers fail to compile against content the campaign would have to choose a blob for and choosing that blob is one of the merge decisions the lane measured and expressly declined to settle, so the plans are published as decision material for a maintainer with the losing side quantified, and the campaign continues to hold the three unreachable units as split-or-exclusion work rather than pushing them into review"

    Row q1957 states the same posture for the campaign's own writing: publish the merge choices with the losing side quantified rather than executing either deferral.

    9. What stays open after this note

    • Four merge choices — tool, writer, presenter, lock — each needing a named human to pick a blob, plus the one open question on the reader path (which of 1915, 1916, 1917, 1918 wins). Sections 2 and 3 price the losing side of each.
    • A decision owed back to the Lead, priced as source-side work. The figures here are re-reads of another lane's rows plus this lane's own 0-core blob reads. No lane has priced the writing: editing a unit so its version of a contested path matches the chosen blob, re-landing the identical groups, and re-pricing each pair at the bases the chosen order would actually land on. That is not free and this campaign has not spent cores on it.
    • Per-file attribution that is not backed. The capacity table backs per-file residues for 1916 (DiffViewProvider.ts 724) and 2359 (guardedWrite.ts 508) at the simulated price. The 723 on 1914, the 674 on 1917 and the 1134-or-1150 on 1918 are backed as totals only, and this note names no file for them.
    • Nothing here executes a merge, pushes, labels, requests a reviewer, closes a pull or resolves a review thread. It states the decisions a human owns and prices the side that loses.

    Provenance. The line and mutant figures on the capacity table, the carrier rule, the gaps, the two deferral plans and the no-deferral verdict were measured by lane t12-1291 with the committed instrument scripts/stryker-diff.mjs at blob 7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc on pinned node v22.23.4, in worktrees wt_t1291_simA and wt_t1291_simB stamped 2026-11-12T05:49:11Z and 2026-11-12T05:49:22Z, and are recorded in .tmp-cr-audit/handoff/deferred-queue.json rows q1887, q1889, q1890, q1917, q1920, q1948, q1949, q1950, q1951, q1952, q1955, q1956. This lane re-read those rows and did not re-price them. This lane's own sections (3, 4 and the reader-path blob map in 5) cost 0 metered calls: git transport against refs/pullhead, rev-parse and cat-file path lookups against a blobless cache, and 30 fetches of five files at six revisions through the free raw.githubusercontent.com door, which prints no rate-limit headers at all. Records: .tmp-cr-audit/n20-mc-heads.txt, n20-mc-version-matrix.json, n20-mc-gap-table.json, n20-mc-readfiletool-gaps.json.


    Appendix A (executed): the tool-path repair, the re-priced chain, and the push gate

    What this is. A revision of the note above, in place, not a fourth document. It records what this lane executed after §9 was written, under the rule printed in §1, and what it refused to execute. It costs two metered calls: this revision, and the list-route read that proves it landed. Nothing was pushed, no label was added, no reviewer was requested, no pull was closed, no review thread was resolved, and the named exclusion is held for a human rather than argued here.

    A.1 The blob each path carries — the campaign's decision under the printed rule

    The rule this lane applied: the version that measurably serves the added lines of the most other changers on that path, ties broken by the earliest unit in the operative published order (1910, 1911, 1912, 1913, 2433, 2383, 2384, 2357, 2359, 1915, 1916, 1918, 1917). Every serve count this lane measured was zero — the negative result of §1 — so every path fell to the tie-break. These are decisions the campaign is taking, stated as decisions, not a claim about what the repository should merge; a maintainer can overrule each one with the losing-side numbers in §2 and §3, which stay standing.

    path blob it carries holder under the tie-break blob this lane chose
    tool src/core/tools/guardedWrite.ts 2359 32ffe6cebadb, then repaired to de3c9d35d94e, see A.2
    writer src/services/file-safety/safeWriteText.ts 1910 befb195e343c
    presenter src/integrations/editor/DiffViewProvider.ts 1915 = 1916 85c89ae166cc
    lock src/utils/fileLock.ts 1913 = 2433 6ca70045661c
    reader src/core/tools/ReadFileTool.ts 1913 = 2433 = 2357 = 2359 f80212eda8d7

    A.2 The repair on the tool path, who gave way, and what it lost

    The whole-file takeover never shows a duplicate declaration — only one side's declaration survives it — so the duplicate is a same-name, different-shape pair that the takeover silently resolves. This lane found both, on the two blobs the takeover would have merged:

    name 2359's declaration 1915 = 1916's declaration
    pendingChains L70 const pendingChains = new Map<string, Promise<void>>() L127 const pendingChains = new Map<string, Promise<unknown>>()
    enqueue L80 function enqueue(pathKey: string, fn: () => Promise<void>): Promise<void> L137 function enqueue<T>(pathKey: string, fn: () => Promise<T>): Promise<T>
    guardedWrite arity L620, five parameters, returns Promise<void> L565, five parameters, returns `Promise<string

    Across all 26 trees this lane typechecked, no duplicate-identifier error appeared (TS2300, TS2323, TS2451, TS2393 all zero): the takeover does not leave two declarations, it deletes one. The merged version this lane built therefore carries one declaration of each name, in the shape that satisfies both call sites, and never a rename:

    • pendingChains and enqueue — 2359 gives way to 1915 = 1916. The generic pair is the only shape that compiles against 2359's Promise<void> callers and 1915's value-returning callers at once; 1917 = 1918's copy of the non-generic pair gives way too. Region spans L70-97 of the merged file: 28 raw lines, 18 counted executable lines under the committed counter.
    • guardedWrite — the merged declaration takes the six parameters (options?: GuardedWriteOptions), with the 28-line GuardedWriteOptions interface moved in from 1918 = 1917 (span L620-656: 37 raw lines, 6 counted). The parameter is accepted and unused in the merged body, marked by one lane-written line at L659 (void options, 1 counted line) rather than being threaded into 2359's publish path, because threading it would have invented behaviour 2359's body never had. This is disclosed, not adopted silently.
    • Two names that exist on one side only are grafted in rather than dropped: GuardStaleVersionError (6 raw lines from 1915, 4 counted, span L896-901) and canonicalizeForApproval (3 raw lines from 1917 = 1918, 2 counted, span L903-905).

    What the giving-way side lost, in counted lines, measured against the tool file as it sits at the frozen base (e4e69b30ad00 at main tip efc22ed9f6f7):

    version added lines vs base served by the merged version lost to it landing on the merged file costs
    2359 863 861 2 44 removals + 2 re-additions
    1915 = 1916 804 470 334 518 removals + 417 re-additions
    1918 714 548 166 411 removals + 220 re-additions
    1917 730 544 186 416 removals + 241 re-additions

    The merged blob is de3c9d35d94e6e4ea15bac531ab89a2d39678196 — 905 lines / 37,538 bytes, against 2359's 864 lines / 35,627 bytes. Because the merged body is 2359's, 2359 is the unit that pays for carrying everyone else's declarations: 31 counted lines inside it belong to later units (18 to 1915, 6 + 1 to 1918 = 1917's options shape, 4 to 1915's stale-version error, 2 to 1917's canonicalise helper) and 1 counted line belongs to nobody (the lane-written void options marker at L659). Reversing the choice — carrying 1915/1916's file instead — costs 2359 269 lines and costs 1918 and 1917 142 and 152 (§3), so the merge is not free in either direction.

    A.3 The rewritten chain at the bases it would land on, with the uncut control beside it

    Same worktree (wt_n20_select), same instrument (priced blob 7dfe0006d2d5), same chain base (efc22ed9f6f7…), nothing derived by subtraction: each unit's delta is merge-base(main, unitHead)..unitHead applied as whole files onto the tree the previous step landed on. Unit shapes and cumulative shapes are kept apart. Mutant figures are preflight counts taken in this worktree, and every one of them carries the killing-stage caveat: the dry run prints the instrumented mutant count and then aborts on failed initial tests, so these are mutant counts, not kill rates. Where the line cap throws, the mutation stage is never reached and no mutant number exists.

    unit rewrite: unit lines rewrite: cumulative uncut control: unit lines carrying file(s) of the rewrite mutant figure (rewrite) mutant figure (uncut control)
    1910 351 351 351 safeWriteText.ts 329, ApplyDiffTool.ts 22 338 338
    1911 80 431 284 safeWriteJson.ts 67, jsonFileRecovery.ts 13 97 306
    1912 191 622 262 safeWriteJson.ts 168, McpHub.ts 19, Task.ts 4 210 312
    1913 53 675 116 ReadFileTool.ts 40, indentation-reader.ts 11, fileLock.ts 2 100 167
    2433 102 777 104 safeWriteJson.ts 86, McpHub.ts 16 147 147
    2383 0 777 197 nothing counted (both files re-land onto carried versions) no package instrumented 193
    2384 0 777 0 nothing counted no package instrumented none
    2357 47 824 210 jsonFileRecovery.ts 31, safeWriteJson.ts 14, readFileWithMissingRetry.ts 2 55 230
    2359 517, over by 17, the constructed-step total, see the footing line beneath this table 1,341 632 guardedWrite.ts 516, safeWriteJson.ts 1 not reached (cap) not reached (cap)
    1915 843, over by 343 2,184 1,145 DiffViewProvider.ts 724, ApplyPatchTool.ts 98, ApplyDiffTool.ts 17, observationRegistry.ts 4 not reached (cap) not reached (cap)
    1916 61 2,245 66 ApplyPatchTool.ts 17, WriteToFileTool.ts 12, EditTool.ts 11, SearchReplaceTool.ts 11, EditFileTool.ts 6, ApplyDiffTool.ts 4 21 21
    1918 432 2,677 1,182 ApplyPatchTool.ts 213, safeWriteJson.ts 123, DiffViewProvider 0, ApplyDiffTool.ts 14, six tool-layer files at 16-19 each 373 not reached (cap)
    1917 504, over by 4 3,181 669 TaskHistoryStore.ts 338, safeWriteJson.ts 74, ClineProvider.ts 38, ApplyPatchTool.ts 30, ApplyDiffTool.ts 15, jsonFileRecovery.ts 9 not reached (cap) not reached (cap)

    Footing for the 2359 row, named once for all three figures the row rests on, every one measured with scripts/stryker-diff.mjs blob 7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc and none derived by subtraction. First, the constructed chain step this table prices: the unit delta from the merge base of main and that unit s head, applied as whole files onto the tree the previous step 2357 landed on, in worktree wt_n20_select at chain base efc22ed, which prices 517 counted executable lines for the extension package, over by 17, and names 516 of them in guardedWrite.ts and 1 in safeWriteJson.ts. Second, the pull at its own head: head 2c7460d against base and merge base efc22ed in mode tool, priced in the clean detached worktree wt-r358-clean, prints 513 priced changed executable lines for guardedWrite.ts, over by 13. Third, the same head against the same tip in mode all, the merge candidate, prints 1,275 counted and priced lines, over by 775. The conclusion the row was supporting stands on all three footings: this construction sits outside the 500-line cap on the file footing and further outside it on the merge candidate. What the row must not be read as saying is that 517 is what a reviewer of this pull finds by pricing this pull. 517 is the price of a step in a construction, built against the tree its predecessor landed on, and the figures the pull itself carries at its own head are 513 on the file footing and 1,275 on the merge candidate. This line was added 2026-11-12 by the lane that owns pull 2359 at the Lead s direction, and it changes no other figure in this table.

    Per-file figures come from the instrument's own counters run path by path on the same step commits; the ten sums reproduce the totals the instrument printed exactly (517, 843, 504, 632, 1,145, 1,182, 669, 509, 843, 504), so the file naming is the same measure, not an estimate. Cumulative past 2359 is an arithmetic sum of measured unit shapes, because the instrument stops at the line-cap throw and reports no running total. The repair moved 2359 from 509 to 517 (+8 counted lines, all in guardedWrite.ts) and left 1915, 1916, 1918 and 1917 exactly where they were, because after 2359 lands the merged file those units land zero counted lines on the tool path: under the rewrite 1916, 1918 and 1917 carry no guardedWrite.ts delta and no DiffViewProvider.ts delta, so the tool and presenter residues live entirely inside 2359 and 1915 and no later unit re-pays them.

    Units still over both caps after the rewrite, and what sits inside them: 2359 at 517 — 516 counted lines in guardedWrite.ts, of which 31 belong to later units 1915, 1917 and 1918 and 1 belongs to nobody (A.2); 1915 at 843 — 724 in DiffViewProvider.ts, which is the 1915 = 1916 identical presenter version (§4) that 1916, 1918 and 1917 all land zero lines on, plus 98 in ApplyPatchTool.ts which 1918 re-pays at 213 and 1917 at 30; 1917 at 504 — 338 in TaskHistoryStore.ts is its own, while safeWriteJson.ts 74, ApplyDiffTool.ts 15 and jsonFileRecovery.ts 9 sit on paths this chain already re-lands (1912, 2433, 2357 and 1910/1916) and are re-landed here a second time.

    A.4 Does each rewritten unit compile and typecheck with the version it was handed? No.

    The control first, because it is the number every verdict below is read against: tsc --noEmit -p tsconfig.json inside src, at the chain base efc22ed9f6f7 as checked out in this worktree, prints 10 errors — two Expected 1 arguments, but got 4 (core/assistant-message/presentAssistantMessage.ts:865, core/task/Task.ts:3991), an SDK-shape error at integrations/misc/export-markdown.ts:17, and seven resolving through a foreign worktree path (../../wt_t1291_1913cut/packages/types/...) that this box's junctioned install drags in. That footing is 13 junctions plus the 17 .husky/_ entries against main's install, not an install built from the lockfile, and those 10 errors are its noise floor. Counts below are new-error counts keyed by file, code and message so that a pre-existing error moving down the file does not count as new. The root leg is unusable in this footing (workspace zod unresolvable) and is quoted nowhere.

    unit rewrite: new errors pre-repair rewrite uncut control verdict with the version it was handed
    1910 0 0 0 clean — but 1910 is the writer path's holder, so it is not a rewritten unit
    1911 6 6 0 fails
    1912 29 29 6 fails
    1913 13 13 24 fails
    2433 9 9 6 fails
    2383 24 24 7 fails
    2384 24 24 7 fails
    2357 27 27 6 fails
    2359 47 47 6 fails
    1915 71 73 17 fails (repair removed 2)
    1916 72 74 18 fails (repair removed 2)
    1918 96 110 17 fails (repair removed 14)
    1917 68 84 20 fails (repair removed 16)

    The repair cleared exactly what it was aimed at — at 1918 the canonicalizeForApproval import errors went 5 → 0, the GuardStaleVersionError errors 1 → 0, and the Expected 3-5 arguments, but got 6 arity errors 8 → 0 — and it cleared nothing else. No rewritten unit compiles. The exact colliding lines, verbatim from tsc:

    • The writer path is the blocker, not the tool path: services/file-safety/__tests__/safeWriteText.identity.spec.ts:74:40 TS2353 — 'authorizedTarget' does not exist (six sites: 74, 92, 111, 135, 158, 173) — appears from 1911 onward in the rewrite and appears nowhere in the uncut chain at 1911. services/file-safety/__tests__/safeWriteText.spec.ts:8:2 and :9:2 TS2305 want DaclInspectionError and DaclRestoreError; :13:2, :14:2, :15:2, :16:2 TS2305 want TargetExistsError, UnsupportedFilesystemError, TargetMovedError, AncestorReplacedError; :18:2 TS2724 wants OrphanedPublishedBackupError; :1234:49 TS2353 wants failIfExist; :87:4 and :2106:4 TS2353 want expectedResolvedPath; safeWriteText.integration.spec.ts:5:10, :5:48, :5:71 TS2305 want AncestorReplacedError, TargetMovedError, AncestorIdentity; utils/safeWriteJson.ts:10:2, :13:2, :14:7 TS2305 want the same three, and utils/safeWriteJson.ts:6:10 TS2459 reports Module '"../services/file-safety/safeWriteText"' declares 'canonicalDirKey' locally, but it is not exported — the declared-but-not-exported shape the deferral plans hit, now measured at 2433, 2383 and 2384.
    • The tool file's own imports collide with the writer choice: core/tools/guardedWrite.ts:30:2 TS2305 wants TargetExistsError and :31:7 TS2305 wants DirectoryIdentity from ../../services/file-safety/safeWriteText, and no version of the tool file among the four candidates compiles against the writer version the rule selects. The tool path therefore cannot be settled before the writer path, and this lane says so rather than pricing the tool file as if it could.
    • Review items, named rather than adopted: core/task/__tests__/Task.spec.ts:4786:16 TS2339 — Property 'cancellationGeneration' does not exist on type 'Task' (nine sites) and core/task/__tests__/Task.dispose.test.ts:166:35 TS2339 — Property 'closed' does not exist on type 'ObservationRegistry' — appear in the uncut control too, so they are not effects of the rewrite; core/tools/__tests__/applyPatchTool.execute.spec.ts:13:10 TS2305 wants PartialMoveError from ../ApplyPatchTool; and core/tools/__tests__/applyPatchTool.execute.spec.ts:391:5 TS2554 — Expected 2 arguments, but got 4. — is a later unit's own spec asserting a call shape the selected version cannot satisfy, which is row q2006's review item and stays one.

    A.5 The push gate: nothing was pushed, and why the lease was not spent

    The gate was compiles + typechecks + inside both caps on the version the unit was handed. No rewritten unit cleared the first condition, so zero pushes were attempted, no hook ran, and no lease was consumed. This lane still took its own fresh transport read for the record: git ls-remote over refs/heads/* answered with 94 branch refs, refs/heads/main at efc22ed9f6f75975106f170cf29cc9bcc0b86682 — the chain base, unchanged — and none of the 13 fetched unit heads sits at any upstream branch ref, so these units' head branches are not upstream refs and a push lease for them is a fork-side read (13 pulls/N reads to learn each fork and ref, or fork ls-remotes once the forks are named). That read, and a footing installed from the lockfile rather than junctioned from a donor — which the base control above shows this box does not have — are priced as decisions owed back to the Lead, not spent here.

    A.6 What this execution leaves where it was

    • Neither deferral plan was executed. Plan B's donor (2359) now carries the merged tool file, so dropping the exported operation at guardedWrite.ts L620-860 out of it is no longer the arithmetic it was priced against; plan A's donor (1914) is superseded by 2433, 2383, 2384, 2357 and 2359. Each plan would also have paid the mutation cycle twice, and neither is necessary for its donor now.
    • The named exclusion stays open for a human. This lane does not argue it on the platform.
    • Census honesty carried forward (row q2006, owned by this lane and still open): four paths whose whole-file differences cannot be resolved to zero in either direction, with the residual line counts printed; three files whose chargeable lines no census attributes to a later unit; one path whose only change is test code carrying 88 counted lines with 16 selectors and 54 instrumented mutants; and the note that a later unit's own spec asserts a case the selected version cannot satisfy, named as a review item rather than silently adopted (§A.4 names the two-argument line this lane measured). The zero-either-way pair of §5 is still this lane's own non-reproduction: whole-file, 1915 against 1916 measures 3 lines one way and 1 the other, 1917 against 1918 measures 21 and 4.
    • Rate accounting. This appendix and its proof cost two metered calls; the counter lines both responses carried are reported back to the Lead verbatim.
  9. easonLiangWorldedtech commented on Nov 12, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    [n20-2237] Signpost: where the human decisions live on this issue, and three counts.

    Decision artefacts on issue 1991, with coordinates:

    • capacity note, comment 6816547710 - unit prices against the 500-line and 400-mutant caps
    • evidence sheet, comment 6816933513 - per-path changer sets and byte identities
    • merge-choice note, comment 6817397496, revised 2026-11-12T08:35:42Z with Appendix A (executed), 38,011 bytes / 234 lines

    What a maintainer has to decide is the one in comment 6817397496: which blob each contested path carries. The capacity note and the evidence sheet price that choice and neither one makes it.

    Three counts, each measured by this lane with the committed instrument (priced blob 7dfe0006d2d5) in worktree wt_n20_select against main tip efc22ed:

    1. src/integrations/editor/DiffViewProvider.ts measures 724 counted executable lines on its own against the 500-line limit, so no single unit can carry that file. The tool file src/core/tools/guardedWrite.ts counts 508 for 2359's version against the same limit, which is over. The figure 516 for a merged version of that file (blob de3c9d35d94e) is withdrawn from this comment: it was true as a count, that constructed tree holds exactly 516 executable lines, and it is withdrawn as a campaign coordinate because no branch carries that tree, so it is not a head blob any pull serves.
    2. The error-path unit 2359 measures 1259 counted executable lines against main tip and 632 against its own chain predecessor, so it is over the line cap either way. Its mutation leg leaves nothing readable but an exit code with no report behind it: the line cap throws before the mutation stage, so no mutant figure exists and none is inferred.
    3. The writer path is the first decision and not the second. Of the eleven distinct writer blobs at the heads this lane fetched, only four (2357 495595ebdcd2, 2359 f5acc5ea4018, 1915=1916 4a60b4a4e045, 1914=2383 9701d4b3b688) export TargetExistsError or DirectoryIdentity, and the version the selection rule picks - 1910's befb195e343c - exports neither, while guardedWrite.ts lines 30 and 31 import both. The same selected blob declares canonicalDirKey locally without exporting it (utils/safeWriteJson.ts line 6 reads TS2459 against it).

    No byte of any campaign branch has been written. The five path choices stay a decision awaiting a human, not a fact on the platform. No cure is argued here.

    The counts above were measured at 0 metered calls (git transport, the committed instrument, and tsc in the same worktree). This comment was posted at 1 metered call and corrected in place at 2 more, a PATCH and the read back that proves it.

  10. easonLiangWorldedtech commented on Nov 12, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Two decisions this campaign cannot take for itself: the named exclusions, and the retirement rows it wrote for two trackers

    Written by lane u9-1917 at 2026-11-12T17:18:32Z. 0 metered reads were bought for the figures; the two closed events and their actors were re-read from the timelines on the anonymous door at 2026-11-12T17:18:32Z rather than taken from the record they replace.

    The two exclusions

    A one-path exclusion for src/services/file-safety/safeWriteText.ts on #2357, and a narrow exclusion for the extension package unit on #1404, are base-repository review decisions. The campaign can price and prepare the split alternative; it cannot grant itself an exclusion, and CI's mutation-diff calls the mutant overrun an advisory rather than a failure. The two prices on the table: #2357 at 299 lines and 362 instrumented mutants under the exclusion against 448 lines and 425 instrumented mutants under the split, with four shapes prepared locally and unpushed; #1404 at 504 instrumented mutants against a 400 cap where the head it replaces was already at 500. The two caps do not bite the same way and that asymmetry is measured: the changed-line cap throws before Stryker starts, while the mutant cap throws inside the manifest run and is caught per package, so exceeding the mutant cap costs a package result and exceeding the line cap costs the whole run.

    If a maintainer grants an exclusion, nothing in this campaign's own files records it and the gate reads the repository configuration, not a comment. If nobody grants one, the advisory line stays amber and the split is the only route this campaign can walk by itself.

    The two retirement rows, restated from the platform rather than from my record

    #2022 and #2023 were named superseded by #2426 and #2427 in issue #1991 as a decision of this campaign, and at 2026-11-12T02:08Z the closes had not been executed. They have since been executed, by this campaign's own account, and the record is corrected here rather than quoted: the timelines show a closed event on #2022 at 2026-11-12T02:12:04Z and on #2023 at 2026-11-12T02:12:06Z, both with actor easonLiangWorldedtech, and on both events the state_reason field is null while the issue objects now report state closed with state_reason completed. The campaign therefore executed the closes it said it would, three minutes after writing the rows, and the state reason the platform carries says completed, which does not name where the content went.

    Where the content went, in the campaign's own words: the U7 row on this issue was rewritten from 19 bytes to 1,133 bytes and the U8 row from 19 bytes to 1,139 bytes in the body written at 2026-11-12T02:08:35Z, and both rows now point at the tracker the pull that carries the content actually closes -- #2426 for PR 1918 and #2427 for PR 1916. A comment naming the destination was added to each retired tracker at 2026-11-12T17:18:32Z so that a reader arriving at a closed tracker finds the pointer there and not only in this issue's table.

    Neither of those closes changes any code, any review verdict, or any merge order.

  11. easonLiangWorldedtech commented on Nov 12, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    The divided-file shape priced against the exclusion ask on this pull, so that the choice in front of a person is between two measured things and not between a measured thing and a guess.

    Who measured and what it cost. Every figure below is my measurement (lane f0-1528) on a Windows box, produced with local git plumbing, local node and worktree files, so the measurements themselves cost 0 metered cores. This comment is the only authenticated call in this pass and it costs 1 core. Nothing here is CI-verified, and no unit below is called green.

    Prices are quoted with their head and with the shape they are measured against:

    • Head: 078cd7eb84c7377bdf8741f3564bfd14e5d69e82, tree 0c0708deb28f69506f2a9b5ed64baa86cd449f12, commit dated 2026-11-11T22:25:32+08:00, read out of my own local ref refs/heads/f0-1528/t1528-2385-dacl-no-warning. I have not made an authenticated read of the platform in this pass, so I cannot say whether this is the current head; it is a named snapshot with its commit date, not a claim of currency.
    • Instrument: scripts/stryker-diff.mjs, committed blob 7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc, re-verified inside the tree of the head.
    • The caps, stated apart from every result and never mixed into it: MAX_CHANGED_LINES = 500 counted executable changed lines per package unit (line 11 of that file) and MAX_MUTANTS = 400 valid mutants per package unit (line 12). Unit of the line figures: counted executable changed lines per package unit, produced by the instrument's own executableChangedLines over zero-context name-status diffs. Unit of the mutant figures: mutants instrumented by Stryker 10.0.0 under the committed stryker.config.mjs (blob 7d85640377bad7be6f4b8e6c46d5145dd9f5fe4d) - an instrumented preflight count, not a killing count.
    • Footing I: the head against its own merge base with the fork main, 53cb5b5e7c8014d3226e5636bbca8999ed3b8526, which I computed with merge-base.
    • Footing II: the head merged onto the fetched tip de36bdece236e222e603b49983ec38f9d3492df2 (tree d4e4319ba2da, commit dated 2026-11-12T23:51:11+08:00) of the p1066/u6-execute-error-path-cleanup chain. I built that footing myself with merge-tree --write-tree (clean, 0 conflict paths) and commit-tree with two parents, giving merge commit e3dcdd7bdfd0490720bf3ab1f83086ac74f590fe, tree a595070f0e6dc4bfbd45fa73cc7bf88761ed04dd. Footing II is a construction of mine, not a platform state: it is held only by a local ref I created, carried by no fetched tip, and nothing was pushed.
    shape footing counted executable lines (cap 500) selector ranges instrumented preflight mutants (cap 400)
    unit kept whole, head vs its own base I 548, 48 over 159 547, 147 over
    unit kept whole, head vs the chain tip II 548, 48 over 159 547, 147 over
    file divided, unit A (first half plus the two reader files) vs that base I 267, 233 inside 56 201, 199 inside
    file divided, unit B (rest of that file) vs unit A I 281, 219 inside 103 346, 54 inside
    file divided, unit A against the chain tip II 267, 233 inside 56 201, 199 inside
    file divided, unit B against unit A on the chain tip II 281, 219 inside 103 346, 54 inside

    Per-file rows, so that the overrun is attributable rather than asserted. Whole unit at footing I: src/services/file-safety/safeWriteText.ts (blob a56f1006d9) 527 counted lines, 150 ranges, 533 instrumented; src/utils/fileLock.ts (blob 344519ac56) 19, 8, 13; src/utils/safeWriteJson.ts (blob dcfd8d584b) 2, 1, 1. The three file rows add to the package totals on both measures, and they are identical at footing II. The instrument's own thrown text at that footing, quoted for the record: extension has 548 changed executable lines (limit 500). Split the PR or obtain a maintainer-reviewed narrow exclusion.

    How the file was divided, mechanically rather than by eye. The last top-level declaration kept in the first half is the function usableDescriptorIdentity, which ends at line 639 of the 1314 lines of the head version of safeWriteText.ts; the boundary was chosen from the typescript compiler's own statement list (statement 31 of 33), the one whose cumulative counted lines sit nearest half of that file's 527. The first half is 29,173 B of the head file's 63,601 B and is blob 1a2658eaf71c322acc97d18e0ef2f3df4adf7222. Unit A is construction commit b144f5fa4a6e117395c7bbdc110c34e24178e10c, tree 28c24b7ce14c76f871d24bce5df4ac45285248b0, parent 53cb5b5e7c80.... Unit B is construction commit b4c1cda56e6a0b03a77b3e5ef7fad427636a1db2, tree 0c0708deb28f... (the head tree), parent unit A. On footing II the same halves are 59409a3f55aeefbe0e73fa24796d89ab858e0806 and fff12d642987737be1639a38ec4df09b56a2646a.

    The additivity, printed because it is the evidence that a division is a partition and not a duplication:

    267 + 281 = 548   counted executable lines
     56 + 103 = 159   selector ranges
    201 + 346 = 547   instrumented preflight mutants
    

    At footing II the same two halves give the same three figures, and the tree of unit B stacked on unit A is byte-identical to the tree of the whole merge (a595070f0e6d...), which is the check that the two units rebuild the whole instead of restating it.

    The diagnostics, carried with their limits rather than tidied:

    • Typecheck: run with node <...>/node_modules/typescript/bin/tsc -p tsconfig.json --noEmit from src. I name the binary because the package manager wrapper does not spawn on this host. It produced the same 22 error lines, with the same file-and-line signature, at the base, at the head, at unit A and at unit B, and none of the 22 name file-safety, fileLock or safeWrite. That is why they are evidence that the division adds nothing, and they are not evidence that the tree is clean: the same 22 lines are already present at the base, and this worktree's node_modules is a junction onto another worktree's install whose @roo-code/types resolves to the 0.0.1 workspace link, which is the most likely reason those 22 exist at all. I therefore do not call the typecheck clean here.
    • Lint: left unmeasured. node <...>/node_modules/eslint/bin/eslint.js --max-warnings=0 on the divided file failed twice at config load (eslint 9.39.5, exit 2) because src/eslint.config.mjs imports abortSubstringSelector, which the @roo-code/config-eslint reachable from this worktree does not export. I have no lint count for either half and I make no claim here about lint or about suppression counts.
    • The weakness I named myself: neither half imports the other half, and neither half's spec file sits inside the other, so the typecheck of one half could not have caught a missing half. The partition proof is the arithmetic above, not the typecheck.
    • Checkout was verified before each price: the working tree showed only a disclosed scratch directory, and for the divided file the working-tree blob digest equalled the blob digest recorded in the commit.

    What this publish is and is not. The campaign is not choosing. An exclusion is a review decision of the base repository, and the point of putting these figures here is that the person who owns that decision has both prices in front of them: the unit kept whole at 548 counted executable lines and 547 instrumented preflight mutants, which is 48 over the 500-line cap and 147 over the 400-mutant cap on both footings, sitting beside the divided shape whose larger unit is 281 counted executable lines and 346 instrumented preflight mutants, inside both caps on both footings. Whether to grant an exclusion at all, and whether a file divided this way is content-acceptable for this pull, are the reviewer's calls. I am not offering a verdict, and none of these numbers came from CI.

    Carrying state, so nobody mistakes a construction for a platform state: unit A on refs/f0-1528/split-2385-A, unit B on refs/f0-1528/split-2385-B, the whole merge on footing II on refs/f0-1528/cand-2385-on-1395top, and the two halves on footing II on refs/f0-1528/cand-2385A-on-1395top and refs/f0-1528/cand-2385B-on-1395top. For each of those five, git for-each-ref --contains returns 0 refs under the fetched remote refs, so all five are commits I built and hold only by local refs of my own; none is carried by any fetched tip and none has been pushed.

  12. easonLiangWorldedtech commented on Nov 12, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Footing correction to my own earlier comment on this issue, 6790994872, and then the prices that depend on it. One comment, one core, 0 other platform acts, nothing pushed, no re-review requested.

    One. The footing, corrected before the arithmetic

    That comment priced pull 2359 in the as-merged shape against main at 53cb5b5 and carried the head reading and the as-merged reading as two numbers a reader had to keep apart. At today s tips they are one number, and my phrasing let a reader believe they were still two.

    • The base branch of pull 2359 is main in Zoo-Code-Org Zoo-Code. Its tip today is efc22ed, read with git ls-remote, and git merge-base returns that same commit as the merge base of the branch head 2c7460d, which is 91 commits from it. Read locally, so 0 metered calls.
    • A merge of main into that head changes no tree. git merge-tree --write-tree of main s tip and 2c7460d returns tree 44d4682a1f75a92475613812cf143389a79b37e4, and git rev-parse of the tree of 2c7460d returns 44d4682a1f75a92475613812cf143389a79b37e4. The same equality holds for the head with all the cheap edits landed, 66e8ef3fe395, where merge-tree returns 5c9241a0679f5ec8a23856b52a5e920a649aa0fb and the head commit s tree is 5c9241a0679f5ec8a23856b52a5e920a649aa0fb. Both identifiers are checkable with git cat-file and git rev-parse by anyone.
    • So the merge candidate is not further outside the line cap than the head, and one footing answers both readings that comment quoted. Everything below is priced at merge base efc22ed against the head named, on the extension package row, by scripts/stryker-diff.mjs blob 7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc, the blob that .github/workflows/mutation-testing.yml runs at lines 55 and 56, measured identical at both heads.
    • Continuity rather than silence. Those earlier figures were measured and are superseded, not wrong. At 2c7460d the total is 1,275 counted executable lines and src/core/tools/guardedWrite.ts is 513 of them, so the residual behind the same one-path exclusion is 762, the same residual that comment printed, arrived at by a different route because the file grew 5 lines and the total grew 5 lines since 53cb5b5.

    Two. The cheap edits priced, and the number that decides it

    Each edit measured alone at that footing, counted executable lines on the extension package row by the instrument named above, under the same test-path rule the exclusion argument rests on, EXCLUDED_PATH_SEGMENTS at line 85 of that blob.

    • lock file, reject parent canonicalization errors instead of swallowing them: fileLock.ts 73 to 76 counted lines at 61280ee091cc, plus 3.
    • safe writer, the same relative-link join inside its own lock key at safeWriteText.ts line 467: 453 to 462 counted lines at a9fcb61e5189, plus 9.
    • guarded writer, make the version-checked target required so a publish cannot run without a pin: guardedWrite.ts 513 to 513 counted lines at 5cf48b865f9e with the pre-mkdir call taken back out, plus 0. It removes optional markers and adds no statement. The 70 lines it forces across two spec files are inside the tests directory the instrument excludes itself, so they are 0 to the gate.
    • guarded writer, authorize before the parent mkdir in createIfAbsent: 513 to 514 counted lines at 5cf48b865f9e, plus 1.
    • safe writer, measure each directory the write creates against the chain the caller authorized instead of against its own later sample: 462 to 473 counted lines at 153da022cfca, plus 11.
    • json writer, untouched by every one of these: 132 counted lines at 2c7460d and 132 at 66e8ef3fe395, with all 47 selector ranges identical.

    The number that decides it. All the cheap edits land: 1,299 counted executable lines at 66e8ef3fe395, which is 799 over MAX_CHANGED_LINES 500. None of them land: 1,275 at 2c7460d, which is 775 over. The cheap edits cost 24 counted executable lines against a unit that is already 775 lines over the cap, so the cap is the blocker and these 24 lines are not, and no ordering of them changes that because none of them is free except the two named next. The width-free half, the required-target binding at 0 counted lines and the evidence answer to the Trivial row on safeWriteJson.ts line 613, can land whenever the unit itself is admissible. The same one-path exclusion from comment 6790994872 still leaves 762 counted executable lines at 2c7460d and 785 at 66e8ef3fe395, both above 500, which is the point that comment already made and that this footing does not change.

    Three. Mutation, stated as measured

    Mode tool, one file of selectors at a time, Stryker 10.0.0 with the vitest runner, node v22.23.4, base efc22ed, head as named, 0 metered calls, every run leaving its worktree at 0 dirty lines.

    • Only src/core/tools/guardedWrite.ts completes the substituted preflight. At 2c7460d, over 513 counted executable lines: Valid 354, Killed 331, Timeout 1, Survived 22, NoCoverage 0, blocking 22. At 66e8ef3fe395, over 514 counted executable lines: Valid 352, Killed 329, Timeout 1, Survived 22, NoCoverage 0, blocking 22. These are counts of mutants in four buckets and not a score. The blocking pair, Survived plus NoCoverage, is identical at both heads, 22 survived and 0 uncovered either side.
    • Mutant by mutant from the two digests, 354 entries against 352. 59 entries sit at an identical coordinate with an identical status. 292 carry identical content and status at a shifted line, and the shifts land on exactly three values, 6 lines for 34 entries, 14 lines for 248 entries and 28 lines for 10 entries, which are the three insertion heights these edits create. 0 entries changed status at an identical coordinate. Three mutants exist only at 2c7460d, all OptionalChaining and all Killed, at line 375 the publish call, line 378 the resolved-path pin and line 379 the ancestor pin, which are precisely the three optional markers the binding edit replaces with plain property access. One mutant exists only at 66e8ef3fe395, OptionalChaining at line 254, status Killed, which is the pre-mkdir authorize. So these edits create no blocking mutant and cure none, and the call they add is covered by an existing test rather than by a new one. That reading rests on 513 and 514 counted lines of one file out of the 1,275 and 1,299 priced, and on the whole of that file, 875 lines at 2c7460d.
    • Unmeasured either side, named with its failure mode rather than hidden. src/utils/fileLock.ts at 73 and 76 counted executable lines, src/services/file-safety/safeWriteText.ts at 453 and 473, and src/utils/safeWriteJson.ts at 132 and 132 never complete that preflight at either head: six runs, each rejected inside Stryker 3-dry-run-executor.js line 76 and exiting 1 after 308, 309, 310 or 311 seconds, which is the shape of a timeout and not the shape of a mutant, and each followed by Valid 0, Killed 0, Timeout 0, Survived 0, NoCoverage 0, blocking 0, Advisory incomplete, testFiles 0. Their instrumented counts are unmeasured at both heads, so anything said about their mutants moving or not moving rests on selector arithmetic alone: fileLock.ts 25 ranges to 25 with 0 identical, safeWriteText.ts 133 ranges to 137 with 37 identical, safeWriteJson.ts 47 ranges to 47 with all 47 identical. The whole-unit reading fails the same way, at 2c7460d after 327 seconds over 1,275 counted lines, so on this machine the per-file reading above is the only source of any mutation number in this comment. These are counts of mutants in four buckets and not a score, and an empty bucket is not a verdict either way.
    • What these edits are not yet: none of them carries a test row of its own. git diff --name-status --diff-filter=A over 2c7460d to 66e8ef3fe395 lists 0 files against 5 modified, and the suites that cover these files pass with identical counts at both heads, 119 tests in the core lane, 88 in the services lane and 40 in the misc lane, which shows no regression and does not show that any of them fixes what its thread describes.

    Four. Two accidents of mine, said here and not footnoted

    The first diagnostics batch of this leg ran its opening legs under node v25.9.0 because I put the node22 package directory on the path instead of its bin directory, and the job I killed left a Stryker child alive that instrumented 1,187 tracked files in place in the priced worktree before a git reset --hard cleared them and brought it back to 0 dirty; every lint, types, format and test figure above was re-run on the pinned node v22.23.4, and the mutation legs ran in a second worktree built for them so that a killed run cannot touch the priced tree again.

    Five. What I am not asking

    Nothing beyond the cap decision already asked for in comment 6790994872 and its correction 6791073349. I propose no push, I propose no exclusion of my own here, I request no re-review, and the two standing sentences about what a local preflight does and does not prove on this machine are recorded in the lane handoff ledger and are omitted from this comment rather than quoted. The five commits behind the numbers above are local to this lane, on branch r366-oweledits in one worktree, on no remote, and nothing in this comment changes that.

  13. easonLiangWorldedtech commented on Nov 13, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    The re cut of pull 1917 as a plan, not as arithmetic

    Pull 1917 (U9) is the only head in this campaign whose content can be partitioned under the per package line ceiling without an exclusion. This comment is the plan for that re cut, published before any split pull exists, as this campaign's own rule requires. The arithmetic is the price line of each rung; the plan is the order, the ownership, and what a reviewer can and cannot approve on its own.

    Priced against main at f98fcf7, with the instrument in force everywhere in this campaign, blob 7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc, ceiling 500 changed executable lines per package. Every figure below is the instrument's own count on a scratch checkout standing on the branch being priced, and each branch is a single child of f98fcf7 so the pull's merge base is that tip.

    The four units, what each owns, and what each pays

    Rung 1 - u9d-safe-write-primitives (published unit 4 of the four I priced on 1989)

    counted file lines selector ranges blob it carries
    src/services/file-safety/safeWriteText.ts 266 83 0ed4a19707255b7a818479a7547e673baaee1b5d
    src/utils/safeWriteJson.ts 113 40 fbe8b07563aa34da0d9f2717e397ce2b009fe3b4
    • Price at f98fcf7: 379 counted lines over 123 selector ranges, headroom 121. The instrument's own pass line on this branch reads: Mutation-testing 1 package(s) from merge base f98fcf7d86534d730de936637c9cb35936461c00: extension (379 lines).
    • Test and support files this unit owns, uncounted by the instrument and listed so nothing is orphaned:
      • src/services/file-safety/tests/safeWriteText.spec.ts :: blob 3035b4552a90 (full id 3035b4552a9092d2b98559bd5c0c5433a2db11d8)
      • src/services/file-safety/tests/safeWriteText.integration.spec.ts :: blob b965765ac946 (full id b965765ac9462c4fb26fe1012eb50bbf6be8be2d)
      • src/utils/tests/safeWriteJson.test.ts :: blob 435ca63b6ae8 (full id 435ca63b6ae8d91d89d9a2cf0538196a707557f6)
      • src/utils/tests/safeWriteJson.interruption.test.ts :: blob 8b5d68c81fd6 (full id 8b5d68c81fd6927507ff86a68f6858a24f6e2df9)
      • src/utils/tests/safeWriteJson.lockKey.spec.ts :: blob 0dc50e1609d6 (full id 0dc50e1609d645d3124041691727da990449c5ad)
      • src/utils/tests/safeWriteJson.postCommitDurability.spec.ts :: blob c9a8cdab315a (full id c9a8cdab315a839adee2ddd5e40c69fe9377f658)
    • Branch: refs/heads/fws/u9d-safe-write-primitives at e77527f, tree 698f1132fba248d871323ae5e9a31f7b7bdf62ba, one parent, f98fcf7.

    Rung 2 - u9a-guarded-write-core (published unit 1 of the four I priced on 1989, carried by pull 1917 itself)

    counted file lines selector ranges blob it carries
    src/core/tools/guardedWrite.ts 401 47 9bede974293e8e01ca8a1d5996f4bb5e8d4b4132
    src/utils/jsonFileRecovery.ts 9 5 ebf27cf7debd90c6331a8b13b6a35c3943ba7b3e
    src/integrations/misc/indentation-reader.ts 4 4 21aa6777b5eab75a1a417e1f4584c115a3b7e07f
    src/core/task/observationRegistry.ts 2 1 1bf6d84110b7bb22d18403350204aa1cbdef3082
    • Price at f98fcf7: 416 counted lines over 57 selector ranges, headroom 84. The instrument's own pass line on this branch reads: Mutation-testing 1 package(s) from merge base f98fcf7d86534d730de936637c9cb35936461c00: extension (416 lines).
    • Test and support files this unit owns, uncounted by the instrument and listed so nothing is orphaned:
      • src/core/tools/tests/guardedWrite.spec.ts :: blob 0781b27f9322 (full id 0781b27f932275542723787d06db3fa3c21255a1)
      • src/integrations/misc/tests/indentation-reader.spec.ts :: blob fa951ea85e4a (full id fa951ea85e4a032a8a5ca429784d559a3d27a37a)
      • src/integrations/misc/tests/indentation-reader-unicode.spec.ts :: blob fe0e095a85fc (full id fe0e095a85fc6d7a73812ff40bc103b38addd994)
      • src/core/task/tests/observationRegistry.spec.ts :: blob f5787071e7ae (full id f5787071e7aeb8dcb51dcf6b8aaf8ed0c1801b76)
      • src/core/task/tests/Task.dispose.test.ts :: blob a463b3b8d19b (full id a463b3b8d19bedfb8e242ec27e7da667ccd46e5d)
    • Branch: refs/heads/fws/u9-task-history-delete at 5dd5882, tree 85ac289b9c396ca58dff1e8af50b17f79c7b21e6, one parent, f98fcf7.

    Rung 3 - u9c-diffview-publish-and-edit-tools (published unit 3 of the four I priced on 1989)

    counted file lines selector ranges blob it carries
    src/integrations/editor/DiffViewProvider.ts 338 79 03b95a15651f1e7328ee582e53c0d20e0e38ee38
    src/core/tools/ApplyPatchTool.ts 92 14 895d9f6b53cddf96212bbf3e6bce84110af545d5
    src/core/tools/EditTool.ts 23 3 cf20fdb26aa82110557c1ccf8e82af97d795b84c
    src/core/tools/SearchReplaceTool.ts 23 3 41b7c8061b7384993704126130bb9071f2725899
    src/core/tools/EditFileTool.ts 16 3 963c6aabff24f511b9e9c6809af7a7fbf5fd52ba
    • Price at f98fcf7: 492 counted lines over 102 selector ranges, headroom 8. The instrument's own pass line on this branch reads: Mutation-testing 1 package(s) from merge base f98fcf7d86534d730de936637c9cb35936461c00: extension (492 lines).
    • Test and support files this unit owns, uncounted by the instrument and listed so nothing is orphaned:
      • src/integrations/editor/tests/DiffViewProvider.spec.ts :: blob 15ea1ce45bf9 (full id 15ea1ce45bf9af6231722f1601a52c8be59b7c81)
      • src/core/tools/tests/applyPatchTool.execute.spec.ts :: blob a11057541044 (full id a110575410449d0aeab4eeff4356404d57c7235b)
      • src/core/tools/tests/editTool.spec.ts :: blob 9326fe3fa3f0 (full id 9326fe3fa3f0bace5b50121fbbdcbfd435840f42)
      • src/core/tools/tests/searchReplaceTool.spec.ts :: blob 433867de23cf (full id 433867de23cfe5b6630119e066d893ff02cdaa67)
      • src/core/tools/tests/editFileTool.spec.ts :: blob 6b7e789ea4be (full id 6b7e789ea4bee7c8d34eb669e36c44d556613e84)
    • Branch: refs/heads/fws/u9c-diffview-publish-and-edit-tools at 3361738, tree b87d1d8de8bd07f60fd2d8a1aadfe87ec49f4e3a, one parent, f98fcf7.

    Rung 4 - u9b-task-history-store-provider-and-read-write-tools (published unit 2 of the four I priced on 1989)

    counted file lines selector ranges blob it carries
    src/core/task-persistence/TaskHistoryStore.ts 338 53 9da30a1b3092007ef6786d2d75aa769cc9a1e9d0
    src/core/webview/ClineProvider.ts 38 10 d3e333610b90246c8586abcac9290cd8e5e9ba79
    src/core/tools/ReadFileTool.ts 35 21 05933e9e0d2f72e1b3c1eb3b01cf8ea1f3442ac5
    src/core/tools/ApplyDiffTool.ts 26 6 e930d501b99e409a2b85cda19503cdb3324e24da
    src/core/tools/WriteToFileTool.ts 26 3 5b0e48cfd96c20bff6920ad567078c94f230d704
    • Price at f98fcf7: 463 counted lines over 93 selector ranges, headroom 37. The instrument's own pass line on this branch reads: Mutation-testing 1 package(s) from merge base f98fcf7d86534d730de936637c9cb35936461c00: extension (463 lines).
    • Test and support files this unit owns, uncounted by the instrument and listed so nothing is orphaned:
      • src/core/task-persistence/index.ts :: blob c3883c553a3d (full id c3883c553a3d806bf91832a4b365a2cc4958be01)
      • src/eslint-suppressions.json :: blob f3714cd1f521 (full id f3714cd1f521c1d34e2dbc252cd7b970d226f6a6)
      • src/core/task-persistence/tests/TaskHistoryStore.deleteSemantics.spec.ts :: blob a512ceff883c (full id a512ceff883c1e84c4e42cc924349a6dfc4fb464)
      • src/core/webview/tests/ClineProvider.taskHistory.spec.ts :: blob 0b8b3ba94ea2 (full id 0b8b3ba94ea2bbc9933c6678f6ff40706e34f024)
      • src/core/webview/tests/ClineProvider.partialTaskDelete.spec.ts :: blob 73b18f45d7fb (full id 73b18f45d7fb566935f034f8a314fb443ea92ca1)
      • src/core/tools/tests/readFileTool.spec.ts :: blob 123cd1e34650 (full id 123cd1e3465084e8126ae76f28517232f7090178)
      • src/core/tools/tests/applyDiffTool.failureReporting.spec.ts :: blob 32566c9a469c (full id 32566c9a469c9f393995bc573aecf60fa9152851)
      • src/core/tools/tests/applyDiffTool.guardedWrite.spec.ts :: blob 503ba6543878 (full id 503ba65438781ffef2d7430645487d5fb41810ba)
      • src/core/tools/tests/applyDiffTool.outsideWorkspace.spec.ts :: blob 0434a1d9bc5a (full id 0434a1d9bc5a27e1791fa450e5772c1a8ba7b948)
      • src/core/tools/tests/writeToFileTool.spec.ts :: blob fb1e9941ce3d (full id fb1e9941ce3db9372113c4a12a341a7f57ade745)
    • Branch: refs/heads/fws/u9b-task-history-provider-and-tools at d41618a, tree f63f7cacf610502671304d52a9bb50460f942f88, one parent, f98fcf7.

    Declared merge order, and why each rung waits for the one before it

    1. Rung 1, rung u9d safe write primitives - waits for nothing in content: nothing inside it imports another unit's new module, and standing on today's main its package typecheck is clean and its suites are clean, measured locally. Corrected 2026-11-13 at 03:53Z against the CI read rather than against those local runs. At its head e77527f every required context reads completed success except one: check-translations 131666359178 (1 annotation), compile 131666359204 (1), e2e-mock 131666358456 (1), platform-unit-test (ubuntu-latest) 131666359358 (1), platform-unit-test (windows-latest) 131666359552 (0). The exception is knip, completed failure, check run 131666359369, 2 annotations, and I bought that payload: item 1 is path .github line 15 level failure with message Process completed with exit code 1. and item 2 is path .github line 1 level notice with message "The ubuntu-latest label will migrate to Ubuntu 26 beginning October 19, 2026. For more information, see https://github.com/actions/runner-images/issues/14748". The payload names no file and states no cause, so the sentence this bullet used to carry - that this is the only unit a reviewer can approve and land on its own today - is withdrawn. Landing this rung first is still my answer, for the reason in the plain answer below.
    2. Rung 2, u9a guarded write core - waits for rung 1. core/tools/guardedWrite.ts and utils/jsonFileRecovery.ts import services/file-safety/safeWriteText, which rung 1 owns. Measured at its own branch: package typecheck exits 2 with Cannot find module '../../services/file-safety/safeWriteText' and ../services/file-safety/safeWriteText. A reviewer can read it today, its gate cannot go green until rung 1 is in main.
    3. Rung 3, u9c diff view publish, apply patch and the three edit tools - waits for rungs 1 and 2. DiffViewProvider.ts imports core/tools/guardedWrite and services/file-safety/safeWriteText and calls ObservationRegistry.forget, all of which live in rungs 1 and 2; measured Cannot find module './guardedWrite', Cannot find module '../../core/tools/guardedWrite', Property 'forget' does not exist on type 'ObservationRegistry'. Its own saveDirectly and saveChanges signatures are what rung 4's tools call, so it must land before rung 4.
    4. Rung 4, u9b task history store, provider wiring and the read and write tools - waits for rungs 1, 2 and 3. Measured at its own branch: TaskHistoryStore.ts needs isReplacementLeftover exported from jsonFileRecovery (rung 2) and safeWriteText (rung 1); ReadFileTool.ts needs hasClippedLines on IndentationReadResult (rung 2); ApplyDiffTool.ts and WriteToFileTool.ts import guardedWrite (rung 2) and call diffViewProvider.saveDirectly with nine arguments and saveChanges with five, which is rung 3's shape; ClineProvider.ts imports TaskHistoryDeleteError through core/task-persistence/index.ts, the barrel this rung owns.

    Plain answer to the question the plan has to answer, corrected 2026-11-13 at 03:53Z from four check-runs reads and three annotation payloads: no unit of the four is green on its own today. knip reads completed failure with 2 annotations at every head - 131666359369 at 2443, 131665371619 at 1917, 131666371518 at 2444, 131666383191 at 2445 - and its payload names no file: the two items are the generic step line Process completed with exit code 1. beside the runner migration notice, which is the shape this campaign has already documented on other heads, so the knip cause is unread and I assert none. compile reads failure only at the three later heads, and its payload does name the cause verbatim: at head 5dd5882, check run 131665371730, item 2 is path src/utils/jsonFileRecovery.ts, line 8, column 32, level failure, message Cannot find module '../services/file-safety/safeWriteText' or its corresponding type declarations. That file and that line sit inside rung 2's own 9-file delta, and the module it imports is created by rung 1: src/services/file-safety/safeWriteText.ts is absent from today's main tip f98fcf7 and absent from rung 2's own head, present in rung 1's head, and present in the stacked tree b5aace088719466c4be94fcf42b4c501a3eca5be, where the same jsonFileRecovery blob ebf27cf7debd90c6331a8b13b6a35c3943ba7b3e sits next to it. So the shape of the problem is the shape I chose and not a defect in the content: a unit branch carries only its own delta, so a later rung cannot compile until an earlier rung is in main, which is exactly what the declared order 2443, then 1917, then 2444, then 2445 exists to handle. What a reviewer should land first is rung 1 on pull 2443: it is the only head carrying a single failing row among the required names, that row's payload names no file, and once rung 1 is in main the cause quoted above is gone by construction. Whether the other red rows at 1917, 2444 and 2445 clear with it is unread, because I did not buy their annotations; platform-unit-test (windows-latest) reads completed cancelled with 2 annotations at each of those three heads, which I keep as cancellations rather than folding them into failures. No rung waits on more than one landing ahead of it in a circle, which is the point of moving three tool files (see the correction below). A stack where every rung waits on every other would be the same blocker wearing a better hat, and that is what the published packing was.

    Corrections to what I published earlier, stated here rather than left for the reader to find

    • The packing I published on 1989 was cyclic. Comment 6842100325 priced four bins at 478, 463, 430 and 379 counted lines. Building those bins as branches and running the package typecheck on each shows that bin 1 and bin 3 wait on each other: bin 1's EditTool.ts, SearchReplaceTool.ts and EditFileTool.ts call diffViewProvider.saveDirectly with nine arguments and saveChanges with five, which only exist in bin 3's DiffViewProvider.ts (measured error TS2554: Expected 2-5 arguments, but got 9 and Expected 0-2 arguments, but got 5 at three call sites each), while bin 3 imports core/tools/guardedWrite and calls ObservationRegistry.forget, both in bin 1. No merge order satisfies a pair that waits on itself. This plan moves those three tool files - 62 counted lines and 9 selector ranges - from the guarded write bin into the diff view bin, which is why the prices here read 416 and 492 instead of 478 and 430. Both sets of figures stay on the record; nothing is edited out.
    • My earlier arithmetic priced only the counted files. Pull 1917 changes 42 files against today's main, not 16. The 26 others - 24 test files, src/core/task-persistence/index.ts and src/eslint-suppressions.json, +10,186 and -177 raw lines between them - contribute no counted executable lines to the instrument, which I measured rather than assumed, and they are assigned here, each test file to the unit that owns the module under test, so that no unit travels without the tests that prove it.
    • Completeness, measured. The union of the four branches' file sets is exactly the 42 files pull 1917 changes against f98fcf7, and the tree of the four stacked in the declared order is b5aace088719466c4be94fcf42b4c501a3eca5be, which is byte for byte the tree git merge-tree reports for merging pull 1917's head into f98fcf7. Four rungs add up to the pull, not to less than the pull.
    • One file is resolved, not copied. src/utils/jsonFileRecovery.ts was touched by main in the two commits that landed since the head was cut, so each unit carries the merge resolved blob ebf27cf7debd rather than the head blob 8db128ffecd4ffb64b09ffcfb5ee06ba2ca8de5a. Taking the head blob wholesale would have quietly reverted main's newer 26 lines in that file.

    What is measured and what is estimated

    • Measured: every counted line and selector range above, the instrument's own pass line for each branch, the exit codes named below, the branch tips read back over transport rather than through the door, and the merge of each branch against f98fcf7 (all four merge clean, so no unit is born colliding with the ground).
    • Estimated: mutants per unit, from the attribution band this campaign measured at 3.08 to 3.54 mutants per selector range (three pairs on disk: 305 over 97, 326 over 106, 163 over 46). At that band the rungs carry roughly 379 to 435 (rung 1), 176 to 202 (rung 2), 314 to 361 (rung 3) and 286 to 329 (rung 4) mutants against a per package ceiling of 400 that the preflight enforces by throwing. Rung 1 is the rung whose estimate straddles the mutant ceiling: if a run lands above 400 the next step is to cut safeWriteText.ts (266 lines, 83 ranges, 256 to 294 estimated mutants) from safeWriteJson.ts (113 lines, 40 ranges, 123 to 142) as a fifth pull. No mutant run was bought in this leg, so that is an estimate at a named footing and not a result.

    Gates run on each branch, with exit codes, and the controls beside them

    All run from a worktree standing on the branch itself. prettier --check over the files the unit owns: rung 1 exit 0, rung 2 exit 0, rung 3 exit 0, rung 4 exit 0, and the same check over the 33 of those files that exist at today's main is exit 0, so the passes are not vacuous. pnpm --dir src run check-types: rung 1 exit 0, rungs 2, 3 and 4 exit 2 with the missing modules named above, and exit 0 at the plain tip as the control, and exit 0 on each cumulative footing in the declared order (rung 1 alone, then 1+2, then 1+2+3, then all four), which is what makes the order a plan and not a wish. Suites by the package's own groups (test:core, test:misc, test:services): rung 1 misc exit 0 and services exit 1 with the same three files and eleven tests that main itself fails at f98fcf7 (control measured on the plain tip), rungs 2, 3 and 4 exit 1 on their own branches for the missing modules above and exit 0 for core and misc on the cumulative footings. eslint with --prune-suppressions --max-warnings=0 over the unit's files: exit 0 on all four rungs once the registry JSON itself is not handed to eslint as a lint target (rung 4 first read exit 1 only because I passed src/eslint-suppressions.json as a target and eslint warns that the file matches no configuration - my harness, not the content). The suppressed counts for the files each rung owns, read off the registry that rung carries and taken again after the prune, are identical on both sides: rung 1 zero, rung 2 three (core/task/__tests__/Task.dispose.test.ts, @typescript-eslint/no-explicit-any), rung 3 eight (editTool.spec 1, searchReplaceTool.spec 2, editFileTool.spec 5), rung 4 one hundred and twenty two. Rung 4 is the only rung that carries src/eslint-suppressions.json, and for the files it owns that file reads 122 where today's main reads 124 for the same files, so this content takes two suppressed counts away rather than adding any. The registry rewrite the prune produced was reverted after every run, with a clean worktree each time. The root pnpm run check-types cannot be made to finish in this shared box for packages whose node_modules are not installed in this worktree and it fails identically at the plain tip, so it is evidence neither way here and I am not reporting it as passing. Pushes ran with HUSKY=0, so the pre-push typecheck hook was skipped; that is a disclosure, not a pass.

    The ask, and what this plan does not do

    The ask: land the four rungs in the declared order and pull 1917 closes with them, and no exclusion is needed for this content. Pull 1917 is the one head in this campaign that needs no maintainer reviewed narrow exclusion, so for this content the re cut is the only path to a passing gate - unlike the four heads named in comment 6842100325, where no compliant partition exists and the exclusion is the only path.

    This plan opens no question about the five files the exclusion ask is about: DiffViewProvider.ts, ClineProvider.ts and safeWriteText.ts travel at their existing blobs, and nothing in this leg edits them. No mutant was run, nothing was resolved, nothing was renumbered, no label was written, no exclusion was asked for here, and the four branches were pushed with leases read from a remote read in the same program and verified by transport: fws/u9d-safe-write-primitives at e77527f, fws/u9-task-history-delete at 5dd5882, fws/u9c-diffview-publish-and-edit-tools at 3361738, fws/u9b-task-history-provider-and-tools at d41618a.

    Pulls carrying the rungs, opened in the declared merge order after this comment: rung 1 as a new pull on fws/u9d-safe-write-primitives, rung 2 on pull 1917 itself (its title and body are being patched to describe the one unit it now carries), rung 3 as a new pull on fws/u9c-diffview-publish-and-edit-tools, rung 4 as a new pull on fws/u9b-task-history-provider-and-tools. Rung 2's ref is still named fws/u9-task-history-delete because renaming a pull's head ref is a separate write I did not spend here; the title and body say what it now carries.

  14. easonLiangWorldedtech commented on Nov 13, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    At 99e4625, the head this lane pushed onto refs/heads/vps2f-1914-u5a, the mutation advisory row concluded failure, and its annotation payload contains no figure at all.

    Each statement below is named by the read it came from. The check runs door, GET repos Zoo-Code-Org Zoo-Code commits 99e4625 check-runs with per page 100, http 200, total_count 18, 18 rows, no next link. The annotations door, GET repos Zoo-Code-Org Zoo-Code check-runs 131681583955 annotations, http 200, 726 bytes, 2 items, no next link.

    The row named mutation-diff, id 131681583955, printed status completed and conclusion failure, started 2026-11-13T04:06:14Z and completed 2026-11-13T04:06:55Z, which is 41 seconds. Its annotation payload contains no figure at all. Searching the body the door returned for 1249, for 500, for 749 and for 400, and searching it also for 1275 and for 875, finds none of them. The payload carries no file name, no selector range, no mutator name and no mutant count.

    Its only failure content is the same generic exit line the replaced head carried. Path .github, start_line 12, end_line 12, annotation_level failure, empty title, message Process completed with exit code 1, 35 bytes, sha256 prefix 527143cc87679147. The row at the replaced head 2c7460d carried the same path, the same line, the same 35 bytes and the same digest, byte for byte. The only other item at the row on this head is a notice at path .github line 1, 159 bytes, sha256 prefix f8ad47234b9fbce6, about the ubuntu-latest runner image migrating to Ubuntu 26 on October 19 2026. Neither item carries raw_details, and annotations never carry standard output.

    The head that did produce a real mutant report, row 131557321579 at commit feeaf07, ran 11 minutes 30 seconds to completion, against those 41 seconds.

    Therefore no author can obtain the hosted figure for any head that dies this way. Every line figure and every range figure quoted in this campaign ceiling asks, the ones on this issue among them, comes from running the committed script scripts/stryker-diff.mjs in a worktree standing on the head, and not from the job.

    What the two possible outcomes mean for the people deciding. A row that dies this way neither passes nor fails the content, because it evaluated nothing, so its red conclusion is not a statement about whether the changed lines are covered and its green conclusion at some other head would not be either. The figure that does need a decision, 1249 counted executable lines against a ceiling of 500 on the edge from base 5f42a91 to head 99e4625, of which one file is 13 lines over on its own, src/core/tools/guardedWrite.ts at 513 counted lines, exists only in that local run, together with the 453 lines at src/services/file-safety/safeWriteText.ts, 132 at src/utils/safeWriteJson.ts, 73 at src/utils/fileLock.ts, 40 at src/core/tools/ReadFileTool.ts, 27 at src/services/mcp/McpHub.ts and 11 at src/integrations/misc/indentation-reader.ts. The 17 tests path files this pull changes price at 0 counted lines.

    a green local preflight over Valid 0, Killed 0, Survived 0 and No coverage 0 with blocking 0 and advisories 2 is not evidence in either direction on this machine.
    the mutation leg is unmeasured, a PASS over an empty digest is not evidence in either direction, and CI at the first pushed unit is the only proof of the gate.

    One ask, and it is the only ask in this comment. Grant or refuse the exclusions on those numbers, 1249 against 500 with guardedWrite.ts 13 over on its own, or fix the job so that the number is printed in what the row returns. The ceiling is enforced in scripts/stryker-diff.mjs, declared at line 11 as MAX_CHANGED_LINES = 500 and applied at line 265, where the script raises the over-cap condition for a package whose changed executable lines exceed it, and the message it would have raised is built at line 267. The tests paths that price at nothing are the segments listed at line 85 and applied at line 206. This lane added no Stryker directive, no suppression entry and no file exclusion, resolved nothing and requested no re-review.

  15. easonLiangWorldedtech commented on Nov 14, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Re cut executed on the u9 chain, put on the record here before the writers pull is opened, so the record precedes the pull.

    The re cut is four rungs landing in strict order: writers, guard, publisher, history. Landing order is fixed by imports rather than preference: src/core/tools/guardedWrite.ts lines 27 and 30 import safeWriteText and resolveLockKey from src/services/file-safety/safeWriteText.ts, which is status A at the merge base f98fcf7, and src/utils/jsonFileRecovery.ts line 8 imports resolveLockKey from the same file, so the writer half has to land first. Rung heads and their own-footing charges, each priced with the gate script own functions against the base named, all under the 500 line ceiling: writers d616ec5, 379 counted lines over 2 counted files, its parent is the merge base so its own and cumulative charges coincide at 379; guard plus its three small files 3ca8992, 416 own, 795 cumulative; publisher and edit tools 3ba9177, 492 own, 1287 cumulative; history, provider and applydiff ceddcdd, 463 own, 1750 cumulative. 379 plus 416 plus 492 plus 463 equals 1750, which is the total the tool itself prints in its refusal sentence at the top rung. The top rung tree is byte identical to the campaign head e1bf8fb, so nothing was rewritten on the way down; the history a reviewer read is rewritten, three refs moved by forced update, and the five commits under 1917 collapse into one commit per rung. An independent price of this plan, relayed through the review lane, agrees with every numerator above and holds that the packing that clears every rung is five branches in strict order, that the pairing branch taking the earlier tool content strands 11 counted lines, and that raising the line ceiling does not clear the gate because the mutant limit binds above it.

    Heads and prices are plan prices cut from merge base f98fcf7, not measurements of remote state. The writers pull follows this comment; a running leg on it is not a pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions