Repository navigation
Description
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
@mentionin 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. Insidedescribe("saveDirectly method")insrc/integrations/editor/__tests__/DiffViewProvider.spec.tsat head39d4b95128d3485ff5b9f8eac37f7432da490b11, the only injectedsafeWriteTextrejections areDaclRestoreErrorat :886 andPublishNotDurableErrorat :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, becausepackageForPathinscripts/stryker-diff.mjsexcludes__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 comment6746153431on 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(refvps2f-filelock-identity-key, blobbd54acfe41fcofsrc/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 head39d4b95128,src/utils/fileLock.ts(blobd8bdbf910b30, 121 line-endings / 4,985 bytes) contains nostatIdentity, nocanonicalLockKey, nolockArtefactPath, nocanonicalLeafSpelling, nonearestExistingDirectoryand nolockfilePath; it callslockfile.lock(absoluteFilePath, {at line 48 withrealpath: falseat line 51; andsrc/utils/lockLeafSpelling.tsis absent from that tree. Under that implementation the library derives<file>.lockbeside 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 row4510971873and 1405's escalated row4504216100- 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(blob92637fe7a0df, 34 line-endings / 1,088 bytes / 2 rows, the file that exercisesacquireFileLockandwithFileLock, 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 issrc/services/file-safety/__tests__/safeWriteText.spec.tsblob5b07bffb1181at 1395's current head39d4b95128(110,036 bytes, 2,398 line-endings), while the package was measured against89384b314acd, where the same path is blob86a53b388acbat 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 at39d4b95128. - 1405 owes five accepted test-only rows out of its own review, named so they land rather than drift. Rows
4509757197(POSIX0o664/umask 022 and new-target/umask 077 published-permission assertions atsrc/utils/__tests__/safeWriteJson.test.ts:1081-1104, gated to non-win32 with the mask restored infinally),4509757204(Windows junction cleanup byfs.rmdirwith mock removal infinally),4509757233(the error-stream mocks must close thestagedFdthey are handed;stagedFdoccurs 0 times in that 1,626-line test file while the writer creates it atsafeWriteJson.ts:487),4510030249(real-writer export test through the public entry point, platform-gated for symlink privilege) and4510030255(catalog-loaded test asserting the renderedcommon:errors.settings_export_failednotification, the key already used atsrc/core/config/importExport.ts:360). Cost: 0 counted executable lines each, becausepackageForPathinscripts/stryker-diff.mjsexcludes__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 (replies4515845286,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 atsafeWriteJson.ts:411-436unlinksnewFileToCleanupWithinCatchwithout recording whether this writer created the stage, so an EEXIST collision deletes another writer's file),4509757220(_flushFileToDiskat :531-542 reopens withr+and only logs on failure, so a read-only stage is published unsynchronised; synchronise the descriptor from :487 instead),4509757226(fchmodSyncfailure is swallowed at :493-497, so a0o664target can publish0o644under umask 022), and4510030261(showErrorMessageis awaited inside the export catch atimportExport.ts:360, so a rejecting notification escapesexportSettings). 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 row4510971873and 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 blobsf9073dcd64e9(safeWriteJson.ts) and49807ea154d4(fileLock.ts) at8186900bfcagainst 1395's571d345098c3andd8bdbf910b30at39d4b95128. Two further rows on that PR were closed as already delivered at the head the reviewer pinned, measured line by line:4504216094and4509757207. 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)
ApplyPatchToolrefreshing 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 policyApplyDiffToolapplies, and the comment at the site states why an older observation must not be refreshed.PostCommitDurabilityErrorleaving the backup behind: the delete path catches it and unlinkserror.backupPathbefore 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.tsimportswithFileLockandsafeWriteJson.tsimportsacquireFileLockfromsrc/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.tsimportsrecoverInterruptedReplacementfrom./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 namesafeWriteJson, and the change relies on safeWriteJson resolving the publish target before staging because it passes the workspace root asconfineTo. - U5c-6 after U5c-5: its suppressions row is the
core/tools/__tests__/readFileTool.spec.tscount going 96 to 94 (blobae3d504003d7e616b129986c252cc97a35b7b568tof3714cd1f521c1d34e2dbc252cd7b970d226f6a6), 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 withTask.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…, then010df646f1…and81e24cd6c1…, kept underrefs/tmp/u5a-r23-*), andgit merge-file --diff3across the 22 paths those two heads both touch at their merge base416179d17de83dfc9bfc1212cc8640da570db90aneeded 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.tsandsafeWriteJson.test.ts, 1913 onto main stops on the same two paths, 2357 onto main+1913 stops onTask.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.jsonneeds no unit: 2357 head and 2385 head carry the identical blob111f022102043c67fb581407a315ae16cca305b7(2,687 B) and main carries1f4956faa6cc789a34d0d2d0e9f4397ce0ca1bc6(2,654 B); the only element 2357 adds namesservices/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.
- 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. - 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.
- 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.
- 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. - 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.
- 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. - 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 1861No 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. - 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.
- 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. - 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'smutation-diffrefuses to run at all (size, not mutants). 1565's charge is 2,230. Neither is cleared by a test-only commit — 1921's newest commite4b102b2f129d07923eb5b8cf825074909ff4c4ais 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
- 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.— thenresolvePullRequestBasereturns the head's first parent only when the head has two parents, andselectFromGitimmediately computesmergeBase = git merge-base baseSha headShaand diffsmergeBase...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-base73931fe8be25, 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. - 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) andsrc/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. selectFromGitreads 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 withENOENT: no such file or directoryon a path insidesrc/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.- 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 (14066dcf0a5ad48f, 1557ec203ea7b602, 19194413f37f1503; the same shape was measured on 1565 and 1921 in the section above). - CI agrees line for line. 1557's
mutation-diffrun #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 1860No 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.tsshare 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 frome487cd7b2a49pays 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.tsandsrc/api/providers/qwen-code.tsare 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.
selectFromGitreads the working tree, not only git objects: re-running it on 1406's residual commitcc9f86498f56while its scratch worktree sat at the PR head threwENOENT: no such file or directoryon a path undersrc/core/. A re-run must check the priced commit out first, or it is measuring a different tree. Figures computed per file fromgit show <commit>:<path>plusexecutableChangedLinesare 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
fix: order cancellation against the guarded publish and session restart
test(diffview): keep the open() serialization test inside the lint su…
fix: order cancellation against the guarded publish and session restart
test(diffview): keep the open() serialization test inside the lint su…
fix(file-safety): pin the authorized ancestry before the commit rename
fix(safety, task): cover the directory-failure paths and release the …
easonLiangWorldedtech commentedon Nov 8, 2026
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 head1fb5fcc47; re-run at head085fbf85e4ad):Three things this record exists to make visible:
core Stryker preflight could not start: spawnSync /tmp/stryker-launch-…/node_modul…, which is the known Stryker 10 +@stryker-mutator/vitest-runnerruntime-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.085fbf85eadds a JSDoc@internaltag 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.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.ts538,src/utils/safeWriteJson.ts177,src/services/mcp/McpHub.ts22,src/core/task/observationRegistry.ts23,src/core/task/Task.ts7, and 2,295 across the six test files (safeWriteText.spec.tsalone 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.tsnow carries main's replacement protocol plus our confinement layer grafted onto it - confinement before anymkdir, lock-key confinement, re-confinement under the lock, the ancestor/dir pins, identity-only re-verification (TargetMovedError,AncestorReplacedError),_copyTargetModeparity, and the POSIX-only best-effort parent-directory fsync that is a logged warning and never aPostCommitDurabilityErrorout of the JSON path. #1405 (U7) headaa44471e74resolves 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.Merge upstream/main (73931fe) into fws/u4-read-scope-recording
Reconcile the carried lock-key spec with the replacement protocol
Mark safeWriteText internal now that nothing in production calls it
Add the notice-separator row the review asked for on the clipped full…
Stage the confined write's bytes after staging, and copy the target m…
Read the staged bytes after staging, where the escape undo can use them
Copy the target's mode onto the staged file so the publish cannot wid…
Compare the interruption injection against identity so main's rows fi…
38 remaining items
easonLiangWorldedtech commentedon Nov 11, 2026
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:
The package table row it printed beside that finding, verbatim:
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:
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".
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.
easonLiangWorldedtech commentedon Nov 12, 2026
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.
easonLiangWorldedtech commentedon Nov 12, 2026
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
6768355289on #1992, updated 2026-11-10T13:46:04Z, 10,735 bytes — contains 0 rows naming a merge order or an owning pull forsrc/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.tsorsrc/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 tipefc22ed9f6f7, at 0 cores). The order is quoted from the records that declare it, not recalled:fws/u8-diffview-guarded-saveandfws/u9-task-history-delete; that step is labelled inference, not quotation.6811350087on Tracking fws U4: read-scope recording and separate clipping reports #2019, put U4a on the branch 1913 tracks and pushed U4b stacked before 2433 was opened.416179d17de8, which is U4's head.vps2f-1914-u5c1-filelockand the U5c chain statement places U5c-1 before U5c-2 onward.feat/guarded-write-s4a), then 1408 (unit S4b, branchfeat/guarded-write-wiring-s4b). 1403 declares itself built on 1395 under tracking issue 1396; 1404 carries tracking issue 1397 and leads the b-series whose labels run b1 (1404) -> b2 (1406) -> b3a (1411) -> b3b (1412) -> b3c (1410).6811832404on [split-1066] U6 - fix(write-to-file): run the diff cleanup when handleError rejects #1937) and the chain map on [Tracking] split-1066 chain: rules, port sources, chain-level rows #1989 (comment6811844117).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
6790813203on #2021 makes 2385 the owner ofsafeWriteText.tswith 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-treein 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.tsends as 2385'sdcfd8d584be9a6273e06439a887110a61cd15706in both orders, because 1395's own diff never carries that path.safeWriteText.tsandfileLock.tsstay dirty in both orders — exit 1, with stages101c80aa75f6/a56f1006d9a2atsafeWriteText.tsand stages49807ea154d4/d8bdbf910b30/344519ac5699atfileLock.ts, plus both spec files — and the marked text git writes for those paths is a different blob per order (8cec370301fdagainst7f49317cafa7,10bf368bad7fagainst9538e8f54d6a). 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.src/services/file-safety/safeWriteText.ts(absent at main tip)befb195e343c725a40ccf9aeff04cae15748d84a;101c80aa75f6a64ad5d5ed5fb830c8e516ca7d809701d4b3b68855c0b1c4ac3ee9c5d44a96e3a01a, and #1915 and #1916 both carry4a60b4a4e045e23e81b348ffab0cfd7c049bee12src/core/tools/guardedWrite.ts(absent at main tip)a11bf87f09f7cc26ae3eda8083a2b59f4e4e6118;ac39ac18757edb84f635a0f472b9ea38d20daf3e38195055ecb54512aeffe797092efe55477274c0identically, one chain position later. #2359 carries a third version32ffe6cebadb2549e5ca564c648f74e333ff6e9bsrc/utils/safeWriteJson.ts632184a3c8f231df8e558d7b582ef44c058aec29;f9073dcd64e97fefdbe60135871de70ed724871ff4edc7ee9c8c168da844730742a54f682bd00a70: three objects, one version, so two of them owe pointer replies and none of them owes a fresh editsrc/utils/fileLock.ts6ca70045661c1522642494c234ebb02e7068ec3b;d8bdbf910b30a5320c7d178c76c2dc5763a7d174;a998680becb136ccc9345f6daf25fd1fbc3fc1397d1682338503cfe803ea7c8bb719e80c638449d2byte for byte; #2357 and #2359 both carry0705f771ccac98167f71562ed418da4106b8b31d. #1918 (d4251f00781bc4fdcbc4342e67890556fbcef037), #2385 (344519ac569966c377907b79e12425a900df5332) and #2368 (cdd0ad1b282e5d68dd0bfbca27e753147f4efc98) carry versions nothing else carriessrc/integrations/editor/DiffViewProvider.ts85c89ae166cc0ec45561c6bd321f0c66516e6d7b;e2ee190cf82b88712bfa5b2bef52090070a9e75d;f5ef407514f9f79459e5987e9549a833ad71bdfe26c3974f91e0ae36d402d605abdebaada13c7f12identically. Later split legs carry their own versions: #1932d6e537449c2bf2fd68a3c11e106f52b3ce30917d, #2434221fb5af7a81eeb7f2ae13b1c2b693b88ab5a5dd, #19286c23c73efa6614cb34cadebc7c85d8439b35bfc9src/utils/WorkspacePathResolver.ts10e425c8ec5294166edf3eb5bb0e95c07b49f24f42536991d70315ad05f61c2a3032ce4ad0eba5fbsrc/services/code-index/cache-manager.tsb1062de55688e7c862086fa8d3d103e2847de6de7807aac69d1a01c543405e4fe10f15d8958822d9The 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
refs/pull/N/mergeis a sound openness signal in this repository. A closed-unmerged pull whose fork branch is still live and whose merge ref survived would read exactly the same way in refs, so un-closed is an inference and reading the state field would cost a core that was not spent.safeWriteJson.ts,fileLock.tsandDiffViewProvider.ts, a copy equal to main as it stood on 2026-11-06:773ba57c06e0c0503c9b8e2215b913999543881band9f7cad7653c38f035811fccc47355248e1f5fe9fas they stood at main commit457bed3cada1, andbb3368f063a3d25a14bbf6f6a712c5bd9f07c2c1as it stood at main commitbb6cbee59e7a. Measured against GitHub's own regenerated merge commits for those pulls — two-parent commits dated 2026-11-12T01:31:13Z through 01:35:31Z, every one with main-side parent631374617052— the merge is clean at those paths and it keeps main's current copy in 30 readings out of 30: in every case the merge base already holds the blob the head holds, which is precisely why the pull's older copy does not reach main. So this is staleness that a refresh rebase clears, and it is not a carrier of a fix on these three paths. What stays unsettled, and is left unsettled here, is the reading a reviewer gets from the pull's own Files tab, where the stale copy is what is displayed, and from any landing that adopts a branch tree wholesale instead of merging it; that route was not measured.Instrument and cost
Census, 0 token cores:
git ls-remoteforrefs/pull/*/head(1,296 refs),refs/pull/*/merge(184 refs) and forkrefs/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 withgit ls-tree -r; a blobless clone (155,874 objects, 28.87 MiB) formerge-base,merge-treeand 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 byx-ratelimit-limit: 5000on both responses. No push, no label, no reviewer request, no thread resolved, nothing closed.easonLiangWorldedtech commentedon Nov 12, 2026
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.comdoor (nox-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:
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.this branch's parent is PR #1913's head 416179d17de8, so merge #1913 first and this branch adds exactly its own delta on top.Declared merge order is main -> PR #1913 -> U5-B (this PR's parent content, PR #2357) -> this unit.Merge order this produces, which is the useful part. The family starts at #1910 (333 counted executable lines) and #1911 (496).refs/heads/mainto53cb5b5e7c8014d3226e5636bbca8999ed3b8526, subjectunit 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.
Capacity per object, priced against simulated predecessors
Every figure in this section is the campaigns landing-order capacity table of record (
deferred-queue.jsonrow q1887, recorded 2026-11-12 from lane t12-1291s landing-order simulation, worktreeswt_t1291_simAandwt_t1291_simBcreated 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.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
src/core/webview/DiffViewProvider.tsalone at 724. The file busts the single-file limit by itself, so no re-cut of neighbours brings the unit under.src/core/tools/guardedWrite.tsalone 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.a11bf87f09f7, and 1915 and 1916 share38195055ecb5on the same path. A residual file for 1914 at the simulated base is not measured anywhere on disk and is not named here.guardedWrite.tsat 401 as the largest file in the units 1,705-line cumulative figure, followed byDiffViewProvider.ts338 andTaskHistoryStore.ts278. 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.src/core/webview/DiffViewProvider.tsat 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.
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 failedbefore 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=100taken 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: sixgithub.compull 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 anx-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).
easonLiangWorldedtech commentedon Nov 12, 2026
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-remotefor heads, 3 reads, 0 API cost; the anonymous REST door with an Accept header and no credential, 40 requests, 0 API cost;git rev-listagainst 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.
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.
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.
easonLiangWorldedtech commentedon Nov 12, 2026
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):
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 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:
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:
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.
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.
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):
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:
So 1916, 1918 and 1917 cannot be handed to anyone. The instrument offers exactly two exits and this campaign is not taking either:
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:
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
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.
src/core/tools/guardedWrite.ts32ffe6cebadb, then repaired tode3c9d35d94e, see A.2src/services/file-safety/safeWriteText.tsbefb195e343csrc/integrations/editor/DiffViewProvider.ts85c89ae166ccsrc/utils/fileLock.ts6ca70045661csrc/core/tools/ReadFileTool.tsf80212eda8d7A.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:
pendingChainsconst pendingChains = new Map<string, Promise<void>>()const pendingChains = new Map<string, Promise<unknown>>()enqueuefunction enqueue(pathKey: string, fn: () => Promise<void>): Promise<void>function enqueue<T>(pathKey: string, fn: () => Promise<T>): Promise<T>guardedWritearityPromise<void>Across all 26 trees this lane typechecked, no duplicate-identifier error appeared (
TS2300,TS2323,TS2451,TS2393all 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:pendingChainsandenqueue— 2359 gives way to 1915 = 1916. The generic pair is the only shape that compiles against 2359'sPromise<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-lineGuardedWriteOptionsinterface 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.GuardStaleVersionError(6 raw lines from 1915, 4 counted, span L896-901) andcanonicalizeForApproval(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 (
e4e69b30ad00at main tipefc22ed9f6f7):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-writtenvoid optionsmarker 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 blob7dfe0006d2d5), same chain base (efc22ed9f6f7…), nothing derived by subtraction: each unit's delta ismerge-base(main, unitHead)..unitHeadapplied 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.safeWriteText.ts329,ApplyDiffTool.ts22safeWriteJson.ts67,jsonFileRecovery.ts13safeWriteJson.ts168,McpHub.ts19,Task.ts4ReadFileTool.ts40,indentation-reader.ts11,fileLock.ts2safeWriteJson.ts86,McpHub.ts16jsonFileRecovery.ts31,safeWriteJson.ts14,readFileWithMissingRetry.ts2guardedWrite.ts516,safeWriteJson.ts1DiffViewProvider.ts724,ApplyPatchTool.ts98,ApplyDiffTool.ts17,observationRegistry.ts4ApplyPatchTool.ts17,WriteToFileTool.ts12,EditTool.ts11,SearchReplaceTool.ts11,EditFileTool.ts6,ApplyDiffTool.ts4ApplyPatchTool.ts213,safeWriteJson.ts123,DiffViewProvider0,ApplyDiffTool.ts14, six tool-layer files at 16-19 eachTaskHistoryStore.ts338,safeWriteJson.ts74,ClineProvider.ts38,ApplyPatchTool.ts30,ApplyDiffTool.ts15,jsonFileRecovery.ts9Footing 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.tsand 1 insafeWriteJson.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 forguardedWrite.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 noguardedWrite.tsdelta and noDiffViewProvider.tsdelta, 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 inDiffViewProvider.ts, which is the 1915 = 1916 identical presenter version (§4) that 1916, 1918 and 1917 all land zero lines on, plus 98 inApplyPatchTool.tswhich 1918 re-pays at 213 and 1917 at 30; 1917 at 504 — 338 inTaskHistoryStore.tsis its own, whilesafeWriteJson.ts74,ApplyDiffTool.ts15 andjsonFileRecovery.ts9 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.jsoninsidesrc, at the chain baseefc22ed9f6f7as checked out in this worktree, prints 10 errors — twoExpected 1 arguments, but got 4(core/assistant-message/presentAssistantMessage.ts:865,core/task/Task.ts:3991), an SDK-shape error atintegrations/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 againstmain'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 (workspacezodunresolvable) and is quoted nowhere.The repair cleared exactly what it was aimed at — at 1918 the
canonicalizeForApprovalimport errors went 5 → 0, theGuardStaleVersionErrorerrors 1 → 0, and theExpected 3-5 arguments, but got 6arity errors 8 → 0 — and it cleared nothing else. No rewritten unit compiles. The exact colliding lines, verbatim fromtsc: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:2and:9:2 TS2305wantDaclInspectionErrorandDaclRestoreError;:13:2,:14:2,:15:2,:16:2 TS2305wantTargetExistsError,UnsupportedFilesystemError,TargetMovedError,AncestorReplacedError;:18:2 TS2724wantsOrphanedPublishedBackupError;:1234:49 TS2353wantsfailIfExist;:87:4and:2106:4 TS2353wantexpectedResolvedPath;safeWriteText.integration.spec.ts:5:10,:5:48,:5:71 TS2305wantAncestorReplacedError,TargetMovedError,AncestorIdentity;utils/safeWriteJson.ts:10:2,:13:2,:14:7 TS2305want the same three, andutils/safeWriteJson.ts:6:10 TS2459reportsModule '"../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.core/tools/guardedWrite.ts:30:2 TS2305wantsTargetExistsErrorand:31:7 TS2305wantsDirectoryIdentityfrom../../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.core/task/__tests__/Task.spec.ts:4786:16 TS2339—Property 'cancellationGeneration' does not exist on type 'Task'(nine sites) andcore/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 TS2305wantsPartialMoveErrorfrom../ApplyPatchTool; andcore/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-remoteoverrefs/heads/*answered with 94 branch refs,refs/heads/mainatefc22ed9f6f75975106f170cf29cc9bcc0b86682— 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 (13pulls/Nreads to learn each fork and ref, or forkls-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
guardedWrite.tsL620-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.easonLiangWorldedtech commentedon Nov 12, 2026
[n20-2237] Signpost: where the human decisions live on this issue, and three counts.
Decision artefacts on issue 1991, with coordinates:
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:
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.
easonLiangWorldedtech commentedon Nov 12, 2026
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.tson #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 thestate_reasonfield is null while the issue objects now reportstateclosed withstate_reasoncompleted. The campaign therefore executed the closes it said it would, three minutes after writing the rows, and the state reason the platform carries sayscompleted, 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.
easonLiangWorldedtech commentedon Nov 12, 2026
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:
078cd7eb84c7377bdf8741f3564bfd14e5d69e82, tree0c0708deb28f69506f2a9b5ed64baa86cd449f12, commit dated 2026-11-11T22:25:32+08:00, read out of my own local refrefs/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.scripts/stryker-diff.mjs, committed blob7dfe0006d2d5c7e4d2282f65f68b8ed5f16866bc, re-verified inside the tree of the head.MAX_CHANGED_LINES= 500 counted executable changed lines per package unit (line 11 of that file) andMAX_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 ownexecutableChangedLinesover zero-context name-status diffs. Unit of the mutant figures: mutants instrumented by Stryker 10.0.0 under the committedstryker.config.mjs(blob7d85640377bad7be6f4b8e6c46d5145dd9f5fe4d) - an instrumented preflight count, not a killing count.53cb5b5e7c8014d3226e5636bbca8999ed3b8526, which I computed with merge-base.de36bdece236e222e603b49983ec38f9d3492df2(treed4e4319ba2da, commit dated 2026-11-12T23:51:11+08:00) of thep1066/u6-execute-error-path-cleanupchain. I built that footing myself withmerge-tree --write-tree(clean, 0 conflict paths) andcommit-treewith two parents, giving merge commite3dcdd7bdfd0490720bf3ab1f83086ac74f590fe, treea595070f0e6dc4bfbd45fa73cc7bf88761ed04dd. 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.Per-file rows, so that the overrun is attributable rather than asserted. Whole unit at footing I:
src/services/file-safety/safeWriteText.ts(bloba56f1006d9) 527 counted lines, 150 ranges, 533 instrumented;src/utils/fileLock.ts(blob344519ac56) 19, 8, 13;src/utils/safeWriteJson.ts(blobdcfd8d584b) 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 ofsafeWriteText.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 blob1a2658eaf71c322acc97d18e0ef2f3df4adf7222. Unit A is construction commitb144f5fa4a6e117395c7bbdc110c34e24178e10c, tree28c24b7ce14c76f871d24bce5df4ac45285248b0, parent53cb5b5e7c80.... Unit B is construction commitb4c1cda56e6a0b03a77b3e5ef7fad427636a1db2, tree0c0708deb28f...(the head tree), parent unit A. On footing II the same halves are59409a3f55aeefbe0e73fa24796d89ab858e0806andfff12d642987737be1639a38ec4df09b56a2646a.The additivity, printed because it is the evidence that a division is a partition and not a duplication:
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:
node <...>/node_modules/typescript/bin/tsc -p tsconfig.json --noEmitfromsrc. 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 namefile-safety,fileLockorsafeWrite. 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'snode_modulesis a junction onto another worktree's install whose@roo-code/typesresolves to the0.0.1workspace link, which is the most likely reason those 22 exist at all. I therefore do not call the typecheck clean here.node <...>/node_modules/eslint/bin/eslint.js --max-warnings=0on the divided file failed twice at config load (eslint 9.39.5, exit 2) becausesrc/eslint.config.mjsimportsabortSubstringSelector, which the@roo-code/config-eslintreachable 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.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 onrefs/f0-1528/split-2385-B, the whole merge on footing II onrefs/f0-1528/cand-2385-on-1395top, and the two halves on footing II onrefs/f0-1528/cand-2385A-on-1395topandrefs/f0-1528/cand-2385B-on-1395top. For each of those five,git for-each-ref --containsreturns 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.easonLiangWorldedtech commentedon Nov 12, 2026
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.
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.
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.
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.
easonLiangWorldedtech commentedon Nov 13, 2026
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)
Mutation-testing 1 package(s) from merge base f98fcf7d86534d730de936637c9cb35936461c00: extension (379 lines).refs/heads/fws/u9d-safe-write-primitivesat 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)
Mutation-testing 1 package(s) from merge base f98fcf7d86534d730de936637c9cb35936461c00: extension (416 lines).refs/heads/fws/u9-task-history-deleteat 5dd5882, tree 85ac289b9c396ca58dff1e8af50b17f79c7b21e6, one parent, f98fcf7.Rung 3 - u9c-diffview-publish-and-edit-tools (published unit 3 of the four I priced on 1989)
Mutation-testing 1 package(s) from merge base f98fcf7d86534d730de936637c9cb35936461c00: extension (492 lines).refs/heads/fws/u9c-diffview-publish-and-edit-toolsat 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)
Mutation-testing 1 package(s) from merge base f98fcf7d86534d730de936637c9cb35936461c00: extension (463 lines).refs/heads/fws/u9b-task-history-provider-and-toolsat d41618a, tree f63f7cacf610502671304d52a9bb50460f942f88, one parent, f98fcf7.Declared merge order, and why each rung waits for the one before it
check-translations131666359178 (1 annotation),compile131666359204 (1),e2e-mock131666358456 (1),platform-unit-test (ubuntu-latest)131666359358 (1),platform-unit-test (windows-latest)131666359552 (0). The exception isknip, completed failure, check run 131666359369, 2 annotations, and I bought that payload: item 1 is path.githubline 15 level failure with messageProcess completed with exit code 1.and item 2 is path.githubline 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.core/tools/guardedWrite.tsandutils/jsonFileRecovery.tsimportservices/file-safety/safeWriteText, which rung 1 owns. Measured at its own branch: package typecheck exits 2 withCannot 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.DiffViewProvider.tsimportscore/tools/guardedWriteandservices/file-safety/safeWriteTextand callsObservationRegistry.forget, all of which live in rungs 1 and 2; measuredCannot find module './guardedWrite',Cannot find module '../../core/tools/guardedWrite',Property 'forget' does not exist on type 'ObservationRegistry'. Its ownsaveDirectlyandsaveChangessignatures are what rung 4's tools call, so it must land before rung 4.TaskHistoryStore.tsneedsisReplacementLeftoverexported fromjsonFileRecovery(rung 2) andsafeWriteText(rung 1);ReadFileTool.tsneedshasClippedLinesonIndentationReadResult(rung 2);ApplyDiffTool.tsandWriteToFileTool.tsimportguardedWrite(rung 2) and calldiffViewProvider.saveDirectlywith nine arguments andsaveChangeswith five, which is rung 3's shape;ClineProvider.tsimportsTaskHistoryDeleteErrorthroughcore/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.
knipreads 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 lineProcess 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.compilereads failure only at the three later heads, and its payload does name the cause verbatim: at head 5dd5882, check run 131665371730, item 2 is pathsrc/utils/jsonFileRecovery.ts, line 8, column 32, level failure, messageCannot 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.tsis 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
EditTool.ts,SearchReplaceTool.tsandEditFileTool.tscalldiffViewProvider.saveDirectlywith nine arguments andsaveChangeswith five, which only exist in bin 3'sDiffViewProvider.ts(measurederror TS2554: Expected 2-5 arguments, but got 9andExpected 0-2 arguments, but got 5at three call sites each), while bin 3 importscore/tools/guardedWriteand callsObservationRegistry.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.src/core/task-persistence/index.tsandsrc/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.b5aace088719466c4be94fcf42b4c501a3eca5be, which is byte for byte the treegit merge-treereports for merging pull 1917's head into f98fcf7. Four rungs add up to the pull, not to less than the pull.src/utils/jsonFileRecovery.tswas touched by main in the two commits that landed since the head was cut, so each unit carries the merge resolved blobebf27cf7debdrather than the head blob8db128ffecd4ffb64b09ffcfb5ee06ba2ca8de5a. Taking the head blob wholesale would have quietly reverted main's newer 26 lines in that file.What is measured and what is estimated
safeWriteText.ts(266 lines, 83 ranges, 256 to 294 estimated mutants) fromsafeWriteJson.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 --checkover 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.eslintwith--prune-suppressions --max-warnings=0over 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 passedsrc/eslint-suppressions.jsonas 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 carriessrc/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 rootpnpm run check-typescannot be made to finish in this shared box for packages whosenode_modulesare 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 withHUSKY=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.tsandsafeWriteText.tstravel 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 onfws/u9c-diffview-publish-and-edit-tools, rung 4 as a new pull onfws/u9b-task-history-provider-and-tools. Rung 2's ref is still namedfws/u9-task-history-deletebecause renaming a pull's head ref is a separate write I did not spend here; the title and body say what it now carries.easonLiangWorldedtech commentedon Nov 13, 2026
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.
fix(utils): refuse ambiguous backup recency in jsonFileRecovery (Zoo-…
easonLiangWorldedtech commentedon Nov 14, 2026
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.
feat(core): add ObservationRegistry with completeness scope and close…