v0.0.2 phases 7-13: OS-invariant cross-OS cache sharing, Windows CI reuse, read-only Actions-cache backend - #16
Open
LayZeeDK wants to merge 519 commits into
Open
v0.0.2 phases 7-13: OS-invariant cross-OS cache sharing, Windows CI reuse, read-only Actions-cache backend#16LayZeeDK wants to merge 519 commits into
LayZeeDK wants to merge 519 commits into
Conversation
Settles U-01 empirically in all three parts: Nx 23.1.0's hash_runtime concatenates trimmed stderr onto trimmed stdout before hashing (quoted from source), the current command's stderr is empty on both CI legs, and node's warning channel carries a PID -- so a warning degrades the discriminator to a permanent MISS rather than a one-off rotation. Recommends `node --no-warnings -p process.platform`. Records three corrections to CONTEXT with replacement reasons: D-18's fork-PR read-only premise is false (the conclusion survives because ci.yml is on: push: branches: [main]), D-08's workflow_dispatch cannot prove anything pre-merge, and D-21's graph-premise reading is confirmed.
XOS-04/XOS-08: three windows-11-arm legs, each needs: its one ubuntu counterpart, guarded RED-first by per-job-block describes. XOS-05: the scheduled --skip-nx-cache detector, the forced write decision, and the O4 pre-registration (1/2/1 occurrences, all three ubuntu legs MISS-and-save). DOCS-07: node --no-warnings -p process.platform single-sourced into nx.json and docs/cross-os.md, with CORR-04's byte-identical invariant superseded rather than silently violated. ROADMAP: filled the Plans placeholder, and corrected the Phase 12 requirement count from 3 to 4 -- the XOS-08 traceability row was missing while the section's own Requirements line and SC1 both named it.
- three per-leg describes in dogfood-cross-os.spec.ts (build-windows, typecheck-windows, test-windows), seven independently-named clauses each: presence control, runs-on, needs:, timeout-minutes, own-target, sidecar, and the absence of a job-level if: - every clause is indent-anchored (job keys at four spaces, step children at six) and scoped to jobBlock(<leg>). windows-11-arm occurs 19 times in ci.yml, so no file-wide clause is permitted; and an unanchored token on build-windows's needs: would be satisfied by its own npm run build step, which is the tautology this file records having shipped once - the positive control is asserted FIRST in each describe: jobBlock THROWS on an absent job key, and that throw IS the presence guard that stops a new CI job from being a silently deletable gate - no comment-phrase assertion here -- codeLines strips every # line, so a comment lock in this file is vacuous by construction OBSERVED RED (jobs land in plan 12-02): each of -t "build-windows", -t "typecheck-windows" and -t "test-windows" exits 1 with 7 failed | 870 skipped, every failure being the jobBlock THROW naming the absent job key.
…x.json Three edits, ONE commit, and the commit boundary IS the point (D-09 / PARITY-08): a guard that lands before its nx.json registration can replay a cached PASS computed before its subject existed, which is the exact defect PARITY-08 recorded when test.inputs listed cleanup.yml and not ci.yml. - new packages/github-cache/src/windows-regression-detector.spec.ts: an eight-clause shape guard over .github/workflows/windows-regression-detector .yml -- triggers (schedule plus workflow_dispatch, never push or pull_request), an off-the-top-of-the-hour cron, exactly one windows-11-arm job, --skip-nx-cache, NO_COLOR plus the multi-target success line, no sidecar and no remote cache tier, and no write permission or actions scope - the read is existsSync-guarded so an absent file yields '' rather than a module-load ENOENT, and a NAMED existence it() is asserted FIRST. The two absence clauses pass trivially against '', so that control is what makes them mean anything - the success needle names all THREE targets in -t argument order. The short form is vacuous for a multi-target run: Nx filters the printed target list down to targets that resolved a task (formatting-utils.js:37), so a two-of-three run still matches the prefix - nx.json: registers the detector workflow as a test input, immediately after the ci.yml entry so the two workflow entries stay adjacent. Explicit path, never a workflows/** glob, which would silently adopt whatever lands there next - nx-target-inputs.spec.ts: one literal pin pinning that registration. No second merged-configuration clause -- the existing mergedTest cases already discharge it for the whole test list OBSERVED RED (the workflow file lands in plan 12-03): -t "detector" exits 1 with 6 failed | 3 passed, the first failure being the named existence it() with "expected false to be true", not an ENOENT. The 3 passes are the two absence clauses and the nx.json registration pin, which correctly passes because the registration landed in this commit. Full suite: 27 failed | 859 passed across 41 files, and all 27 are the new assertions (21 Windows-leg clauses plus 6 detector clauses). No pre-existing test changed state. lint and format:check green.
… they falsify - ci.yml: build-windows, typecheck-windows and test-windows, each needs: its ONE ubuntu producer as a bare scalar, runs-on: windows-11-arm, timeout-minutes: 15, a verbatim copy of the test job's sidecar block, and no job-level if: - ci.yml: the sidecar invariant now governs SEVEN wired jobs and names them, with the eight-copies-vs-seven-wired distinction stated (consumer-smoke is outside it) - ci.yml: the graph-premise comment corrected under XOS-04 -- the step, the mode and both matrix legs are byte-unchanged and the assertion still passes; only its evidentiary claim moves, with the replacement reason supplied - capture-hashes.mjs: the same correction at BOTH attribution sites (the FORBIDDEN_TARGETS docblock and assertion 2's failure message). Behaviour, the constants and all six assertions are unchanged One commit by decision (D-21): the instant the legs exist the old producer attribution is false, and a comment carrying a false reason is a documented argument for undoing the work.
10-SECURITY.md's Q1 section promised "re-priced by Phase 12" and was written to be appended to. This keeps that promise, with BOTH halves stated: - Leg A: D-02's needs: edge removes the concurrent race, and only the race - Leg B: it does NOT remove the second producer, so O1's Linux-produced attribution is permanently false and the record is frozen at 11-EVIDENCE.md - The write decision recorded as FORCED with its measurement (select-backend.ts :32-59 has no read-only-Actions-cache branch; TRUST-05 and D2-02 foreclose adding one) - The replay nuance marked [DERIVED], not measured, so a later audit re-checks it - TRUST-12: the exposure delta is WHICH OS's output, never WHETHER it crosses - No C19: the second writer sits under C1, its arbitration under C3, stated as a decision so nobody hunts for a missing ledger row
12-02-SUMMARY.md records the measured before/after counts, the verbatim wording chosen at all three graph-premise correction sites, and the seven bold lead sentences of the 10-SECURITY.md append, so a later checker can re-grep every claim without guessing. State: plan pointer 2 -> 3, two decisions recorded, metrics row added, ROADMAP 12-02 checked off. requirements.mark-complete deliberately NOT run -- XOS-05 is live-CI-only and closes in 12-06; REQUIREMENTS.md is byte-identical to 0251bd3.
- One `detect` job on windows-11-arm, daily at '23 4 * * *' (off the top of the hour, and both fields distinct from cleanup.yml's) plus workflow_dispatch, whose comment states its real justification -- on-demand re-run AFTER merge -- and disclaims the pre-merge-proof claim GitHub's default-branch rule forbids. - Runs `npm exec -- nx run-many -t build typecheck test --skip-nx-cache` and demands the PLURAL three-target success line, because Nx filters the printed target list down to targets that resolved a task, so the singular prefix still passes a two-of-three run. - No sidecar, no remote-cache client variable, no concurrency group, no write permission: the job executes three targets and writes nothing. - Header records XOS-05's identical-observation sentence at the point the detector is added, plus why it hard-fails by design. - Turns the six plan 12-01 detector clauses GREEN (9 passed, 0 failed).
Task 2 changes no file -- the measurements ARE the deliverable, so they land here rather than in an empty commit. - Measurement A: `nx run-many -t build typecheck test --skip-nx-cache` on this Windows arm64 box, exit 0, 886/886 tests, and the needle matched the printed line character for character on the first run. The 69bd1b7 test flake did NOT surface, so nothing was appended to Phase 8's deferred-items. - Measurement B: the same command with `typecheck` dropped exits 0, FAILS the plural needle, and PASSES the naive short one -- the vacuity PATTERNS M-2 predicted, now measured rather than argued. Also repairs three GSD state-handler defects before committing: the duplicate bare plan list and mangled progress cell from roadmap.update-plan-progress, and the em dash plus `[Phase ?]` marker from state.add-decision. requirements mark-complete is deliberately NOT called -- XOS-05 needs the live-CI proof plan 12-06 gathers, and REQUIREMENTS.md is byte-identical to 0251bd3.
- move both exact-equality pins in nx-target-inputs.spec.ts to `node --no-warnings -p process.platform` FIRST (observed RED: both pins failed naming the old value while the one-runtime-input guard stayed green), then move nx.json's integration runtime value to match (GREEN) - rewrite the rationale block above the first pin so the file no longer argues against its own contents: the byte-identity requirement is kept GOING FORWARD at the new value, the old argument is kept visible as SUPERSEDED for exactly one re-spelling, and the four replacement components are recorded -- stderr IS hashed (Nx 23.1.0 hash_runtime.rs:33-35 concatenates trimmed stdout and trimmed stderr with no separator), the node warning channel is PID-bearing so a warning is a permanent 100% MISS rather than a one-time rotation, --no-warnings is a node flag and not a shell construct (hash_runtime runs the string through %COMSPEC% /C or sh -c, so a redirect would break one OS), and the residual startup-error channel fails loud instead of silently re-partitioning the cache - runtime entry stays the LAST element of the integration inputs array and the only runtime entry in the workspace; toEqual kept over toContain so a SECOND runtime entry stays a CORR-04 event - capture-hashes.mjs needs no change: readDiscriminatorCommand reads the string out of nx.json, so per-leg verification of the new command is free on the next hash-parity run DOCS-07, D-15, U-01
…tree Aligns the remaining spelling sites with nx.json so the doc plan 12-05 renders cannot drift from the config (D-15). All six are comments or a test fixture; nothing executable changes. - hash-parity/compare.spec.ts: the record-factory fixture's `command:` value, and the comment quoting the input. The fixture's empty-string `stderr` is LEFT AS IS -- that is the value measured on both real CI legs - hash-parity/compare.ts: the contract comment quoting the declared runtime input - eslint.config.mjs: the comment naming the accepted form in the P8 ambient-platform-read block. The rule, its selectors, its message and its files/ignores keys are untouched - .github/workflows/ci.yml: BOTH comments quoting the input -- the integration job's leading block and the XOS-08 wiring block plan 12-02 added. The plan budgeted five sites against a tree that had one ci.yml occurrence; 12-02 made it two, so this is six Sweep, run in the corrected form and paired with a positive control (PATTERNS M-1): the documented flagless command reports 7 sites for the new literal, the `--hidden` command reports 9 -- the delta is exactly the two `.github/` sites the flagless command silently drops. The old spelling returns a genuine exit 1 (not exit 2) under the same `--hidden` command. The supersession note added in the previous commit no longer reproduces the old literal verbatim; it states the change as inserting `--no-warnings` after `node`, so the completeness sweep is not made permanently non-zero by the file that records the retirement. Same reason docs-same-os-claims.spec.ts writes its forbidden phrases with a single-character character class. ROBUST-04: `npm run check:action` run FROM THE MAIN WORKING TREE, exit 0, no drift -- compare.ts is not serve()-reachable and the edit is comment-only, so start-cache-server/index.js does not move.
08-ROOT-CAUSE.md's bounding-constraint table forbids re-spelling the integration discriminator "in any way". Phase 12 re-spells it once, so the constraint is SUPERSEDED with a replacement reason rather than silently violated -- otherwise a future reader is left holding a documented argument for reverting the change, which is the failure mode S-1 exists to prevent and the one that shipped a regression in Phase 9. The supersession is attached to the constraint (immediately under the paragraph that elaborates it, seven lines below the row), not filed in a new document. The row itself, the prose above it, and the struck checklist item 6 in the Hand-off to Phase 12 section are all byte-unchanged; this commit is 55 added lines and zero deletions. Six components, each separately greppable: 1. superseded for exactly ONE re-spelling, in force going forward at the new value 2. the mechanism -- Nx 23.1.0 hash_runtime.rs:33-35 hashes trimmed stdout concatenated with trimmed stderr, no separator, so stderr IS hashed. This is the citation the constraint's own era lacked 3. the bounded failure mode -- the node warning channel is PID-bearing, so a warning varies the hash on every invocation and yields a permanent 100% MISS rather than a one-time rotation 4. the measurement -- stderr EMPTY on both CI legs (hazard LATENT), a four-cell shell-by-flag matrix showing byte-neutral stdout under both cmd /C and sh -c, and an emitWarning positive control closing 100 stderr bytes to 0 5. the scope reading -- the constraint's own prose says "Phase 8 does not re-spell it", so this is a phase-scoped constraint retired with cause, not a project-level lock overridden. RESEARCH Open Question 1; the cost of the competing reading is stated so the trade is visible 6. the two exact-equality spec pins moved WITH it, so the invariant is still mechanically enforced at its new value DOCS-07, D-15, CORR-04, T-12-16
…plan Records the Step A RED verbatim, both sweep counts (7 flagless vs 9 with --hidden) and the old literal's exit 1, the check:action verdict (exit 0, no drift, start-cache-server/index.js did not move), and the supersession's shape, so plan 12-05's doc can echo the reasoning without re-deriving it. Two deviations recorded rather than smoothed over: SIX spelling sites instead of the planned five (12-02 added a second ci.yml occurrence), and the Task 1 supersession note initially defeating its own completeness sweep by reproducing the retired literal. State: plan pointer 4 -> 5, metrics row, and the D-15 decision. ROADMAP repaired after roadmap.update-plan-progress injected a duplicate plan list and mangled the progress-table cell. REQUIREMENTS.md deliberately untouched -- traceability is closed once by the orchestrator's phase.complete step after the verifier runs.
…n pin
- new docs-cross-os.spec.ts: existence control first, then the D-15
single-sourced discriminator equality read out of nx.json (never re-spelled
in the spec), the D-11 section-order index comparison, the D-13 anchored
same-sentence arch/libc/arm64 clause, the D-12 five-item count and both nav
clauses
- the discriminator clause uses an occurrence COUNT, not a bare toContain: a
phrase occurring twice is only half locked
- nx-target-inputs.spec.ts pins {workspaceRoot}/docs/cross-os.md as a test
input, explicit path rather than a docs/** glob
- OBSERVED RED: 9 failed, first failure is the named existence control
- new docs/cross-os.md: safe default FIRST (declare the discriminator on all
cacheable targets, remove per target only after proving portability), the
five-item earn-a-removal checklist SECOND, the arm64-only limit on
architecture and libc THIRD, and the trim-before-concat hazard Nx already
closes FOURTH
- the discriminator snippet carries its three trap comments inside the fence:
why a node flag and not a shell redirect, why stderr is hashed and why a
PID-bearing warning is a permanent MISS, and what an adopter must VERIFY
because they have no two-leg comparison gate
- item 6 of the inherited hand-off is STRUCK and measured false, so nothing is
documented for it -- not even a reassurance
- nx.json declares {workspaceRoot}/docs/cross-os.md as a test input, explicit
path immediately after docs/configuration.md, so the guard cannot replay a
stale cached PASS; the registration lands in this same commit as the doc
- README Documentation nav and docs/advanced.md publish/sync section link it
… floor Measured, not predicted: `node --no-warnings -p process.platform` renders TWICE in docs/cross-os.md -- once in the copy-pasteable nx.json snippet and once as the bare command in the verification fence. A `>= 1` floor is exactly as half-locking as the `toContain` it replaced (WR-09), so deleting the verification fence -- the half that closes T-12-09 -- would have left the guard green. Proven by mutation: altering ONE character at the verification site alone now fails with `expected 1 to be 2`; under the floor it passed. Spec-only change. No doc prose was edited to satisfy a poorly-chosen phrase.
Records the observed Task 1 RED (no doc-reading clause passed trivially against the empty string), the four section headings as written, the phrase-count table with its one required floor-to-exact-count conversion, the five measured mutation reds, and the nx.json insertion index (12, adjacent to docs/configuration.md). Three deviations recorded: the plan's five-item acceptance one-liner is off by one (its section chunk includes its own numbered heading), the >= 1 discriminator floor was half-locking and was converted to an exact count, and the trap comments live in the bash verification fence because nx.json is strict JSON and a commented config snippet would not parse where an adopter pastes it. requirements.mark-complete deliberately NOT called; REQUIREMENTS.md is byte-identical to 0251bd3.
…pre-registration - fill the O4 section IN PLACE (D-22), converting the reservation rather than deleting it: no 12-EVIDENCE.md, not relocated, not duplicated - pre-register the per-target [remote cache] occurrence counts before any run exists -- build 1, typecheck 2, test 1, total 4 -- with the resolved-task-set derivation re-measured at pre-flight on this commit - pre-register the per-target ubuntu verdict as MISS-and-save on all three, each traced to the edit that rotates it; compare.ts is why build rotates - name the ubuntu typecheck-vs-build race ambiguity in advance so neither outcome is later read as a deviation - record all five anti-requirements at the point the observation will be made - correct the vehicle reason with a replacement rather than a bare deletion: ci.yml is on: push for main plus pull_request, so a phase-branch push does not trigger CI at all and a same-repo PR is the only vehicle; the fork-PR read-only-cache premise is FALSE per the 2026-06-26 changelog - state what this proof does NOT need: no temporary main push, no warm mirror, no mirror row, and the refs/pull/N/merge entry is ephemeral and isolated - record the write decision as FORCED, cross-referencing 10-SECURITY.md Q1's Leg A / Leg B append, with the capability-versus-observation sharpening - leave the verdict slot at PENDING -- live-CI, first run of the proving PR - reconcile the file's other O4 references so the record does not contradict itself: the headline status row, the intro paragraph, the what-remains-unobservable bullet, and the calibrated-instruments row for the graph-premise flag
…rocedure handed over No proving run exists, so no observation is recorded. Not a partial one, not an inferred one, not one reconstructed from an earlier run. PENDING is an allowed terminal outcome for this section and this is it. - record WHY there is no run, measured rather than assumed: the branch's remote tip is still 38f9aea (the Phase 11 proving head), the local tree is 55 unpushed commits ahead of it, there are zero open pull requests, and the newest workflow run in the repository is a schedule run on main at fe25a3f - state that the three Windows legs landed in f5dd429 and exist nowhere on the remote, so no run can carry them - state that opening the pull request is a carried OPERATOR decision: this plan opens no PR, pushes no branch and triggers no workflow - hand over the six-step observation procedure with the exact rg -o -F "[remote cache]" <log> | wc -l command, the per-leg pre-registered counts (1 / 2 / 1, total 4), the exit-code discipline, the Cache: n/m hit marking rule, the do-not-adjust-the-pre-registration rule, and the capture-output-before-any-re-run rule for the 69bd1b7 test flake - record RESEARCH assumption A1 as OPEN and name the artifact that closes it for free on the same first run, with both artifact names and the discriminator block's shape; a non-empty stderr on either leg is a FINDING deferred-items.md is deliberately untouched: no run executed the test target on any leg, so there is nothing to append to item 1.
- 12-06-SUMMARY.md: the four pre-flight results (battery 42 files / 896 tests, check:action exit 0 no drift from the MAIN tree, resolved task sets re-measured at build 1 / typecheck 2 / test 1 matching the pre-registration), A1 recorded as OPEN with the instrument calibrated but the reading not taken, the O4 verdict as written (PENDING), and all four open human-verify items with their exact reproduction commands - STATE.md: plan 6 of 6, status verifying, two decisions recorded, session recorded; the em dash injected by state.advance-plan replaced with ASCII and both [Phase ?] markers corrected to [Phase 12] - ROADMAP.md: 12-06 checked off, plan count 6/6; the duplicate bare plan list injected by roadmap.update-plan-progress deleted and the mangled progress-table cell restored requirements.mark-complete deliberately NOT run: XOS-05 is not closeable without the live observation this plan did not obtain. REQUIREMENTS.md is byte-identical to 0251bd3.
…red and unopened Paused immediately before opening the window, deliberately. The plan and its check are committed and pushed; pre-flight has not run. Nothing outward-facing has been touched, and that is asserted rather than assumed: main is still at the restore point, the pull request is open with a null merged timestamp, the CI workflow is active, the tree is clean. The window is the only stateful operation in this milestone. Everything before it was append-only commits, which are safe to abandon halfway; a window is not. It involves waiting on two live CI runs and has to be closed in the same sitting, so it should begin with a full context budget rather than the tail of a long session. That is the whole reason for pausing here rather than pushing on. The handoff carries four blocking constraints, and two of them exist because this session nearly got them wrong. The pull request must be closed before the open push. Its base is main and its head is the branch being pushed there, so leaving it open lets GitHub auto-close it as merged -- a de-facto merge of the milestone, arrived at by an action nobody would describe as merging, in direct violation of the one prohibition that has been restated three times. And the window runs as three hops rather than two, because a push event runs the workflow at the pushed tip. The restore lands on a commit that predates the new gate, so it runs the ungated workflow. The gate is real but dormant for restores until main itself carries it, which happens at merge, which is last. The anti-pattern table records what this session actually cost. Three of the vacuous gates found were introduced by fixes. Five independent review passes shared one blind spot and none of them could have found it by being more careful, because they were all reading the same file and none asked which commit's copy executes. And the window plan's own pre-registered observation was impossible by construction -- it asked a job that enforces reader equals producer to report them differing -- inherited unexamined from a stale row. Everything needed to run the window is on the remote, including the recovery procedure, which detects an open window by the one condition that matters and closes it from any of seven points where a session could die.
…umber that disagreed Ran the three-hop temporary main window end to end in one sitting. Window open 65m25s (22:19:01Z to 23:24:26Z), closed and asserted: origin/main back at fe25a3f, CI active, PR #16 reopened with mergedAt still null, the five pre-existing backup refs untouched, nothing merged. Hops 1 and 2 form a controlled experiment rather than two adjacent observations. Same branch, same event, executable ci.yml held constant at 823/823 identical lines, so github.event.forced is the only variable: hop 1 non-forced, publish RAN; hop 2 forced, publish SKIPPED inside a populated 24-job run with the census unchanged. That is the 260808-u2q gate's first behavioural evidence in the skip direction, with its run direction supplied by the same workflow text. All four observations sampled from run 31281406708. O-A, O-C and O-D close fully. O-A's source row was superseded in place: it asked for a 'linux' producer on the Windows leg, which assertPublishedByThisLeg exists to reject, so the row's expectation was impossible by construction and the log says so verbatim. O-B did not close clean, and that is the useful part. readMisses is 63, not the pre-registered 0. Not novel and not a regression: 09-VALIDATION's OBS-04 section already recorded 41/41 on run 30400231720, so this expectation contradicted a measured number in the same milestone when it was written. The symmetry still matches that section's VER-01 fingerprint. What is unexplained is that the count grew rather than draining to the predicted all-HIT steady state, so it is carried forward as an open sub-item, not closed. Noted alongside it: L3's own falsifier was better drafted than its prose expectation and was NOT triggered -- a real run landed between the two halves of one row. The publish counts live only in the job summary, which reaches neither the log nor the REST API, so two of the four observations were unreadable until the maintainer directed the use of a signed-in browser. Recorded as a deviation, because an observation that exists only in a job summary will otherwise be silently filed as unobservable.
HANDOFF.json and .continue-here.md are one-shot artifacts and their content is now actively misleading -- both still say item 3 is not started. Deleted, with their substance moved into STATE.md. Two lessons recorded that no other artifact would carry: gh workflow disable is denied to the agent by the auto-mode classifier, so every future window needs the maintainer for hop 3's suppression; and an observable that lives only in a job summary is invisible to gh and to any unauthenticated fetch, so it gets filed as unobservable unless someone reaches for a signed-in browser. The merge prohibition is restated rather than relaxed. Item 3 being complete is the argument for STARTING the maintainer's code and security reviews, not for merging.
The `dogfood-seed` / `dogfood-verify` round-trip keyed on the bare
`${{ github.run_id }}`. That value is all-decimal, which is exactly the
character class of a real Nx task hash, so the publish mirror could not tell
the two apart -- and 28 of the 48 stale seed entries it re-enumerates on every
run belong to this one family.
Both jobs now pass `hash: bead${{ github.run_id }}`. The marker word is hex
LETTERS, so the key stays inside the server's `^[a-f0-9]{1,512}$` validator
while becoming structurally separable from an all-decimal task hash. This is
the third instance of a convention the repo already uses twice -- `cafe<run_id>`
for consumer-smoke and `feed<i><run_id>` for the publish leg -- and not a new
prefix or a namespace switch: keys stay under `nx-cache-`.
`bead` rather than the obvious `dead`: ci.yml already ships the literal
`deadbeef` as the consumer-smoke sidecar readiness probe, and a `dead`-marked
seed family would read as related to it while being entirely unrelated.
The two jobs move together because they are one round-trip -- the seed PUTs the
key and the verify GETs it back -- and a one-sided edit produces no local signal
at all, only a live-CI MISS. A new spec case pins each value WHOLE and anchored
plus their equality, so a one-sided revert, a both-sided revert, and a reduction
to a constant key each redden it. Proven by mutation rather than assumed.
Prose swept everywhere it asserted the superseded shape: the concurrency-safety
rationale, dogfood-seed's own all-decimal justification (now false), the
vacuity-condition block, the consumer-smoke marker rationale, the publish job's
key-disjointness paragraph -- whose probabilistic 1-in-1.8e19 argument is
superseded now that every seed family is structurally separable -- the
verify-branch comment in action/index.ts, and mirror-seed.ts's
distinct-marker-word paragraph, which named two families where three now exist.
No action-bundle rebuild: none of these files is reachable from serve(), and
`npm run check:action` reports no drift.
Two independent cost fixes to the publish mirror's enumeration loop. D1 -- the prior-run seed filter. CI writes three families of single-use seed into the same Actions-cache scope this engine enumerates. A seed from a PRIOR run can never restore here, so it can never be mirrored, so it is never in the shard, so it is enumerated and re-restored on every future run forever. The set is self-perpetuating: 48 of the 63 restore MISSes on run 31281406708 were prior runs' seeds, and zero of the 63 were present in the shard. The admission rule is family-agnostic -- marker-prefix AND ends-with-this-run-id -- with no per-family parse and nothing derived from the OS tuple. Ending with the run id is what admits every `feed<i>` index and not only the running leg's, which C1 requires: `max-parallel: 1` runs ubuntu first, so the windows leg enumerates the ubuntu leg's seed and publish-verify reads its own leg's seed back out of the shard. It fails OPEN when the run id is unavailable, and its residual errs the same way -- it can only ever ADMIT an entry it could have skipped, which is the status quo, never DROP one. `runPublish` now reads `process.env.GITHUB_RUN_ID` and forwards it as `PublishOptions.runId`. Without that edge the filter has no run id, fails open, and the entire change is silently unachieved in production while every engine-level spec stays green -- so a spec pins the forwarded VALUE, not merely its presence. Proven by mutation. D3 -- shard membership is tested BEFORE the restore, which needs no bytes since the asset name is a function of the hash alone. Measured on run 31281406708: 78 of 149 restores per leg were fetched and then discarded by the first-write-wins branch. The guard is undefined-safe because the shard resolves LAZILY on the first restorable entry, so an all-MISS leg never creates an empty release. TWO AGGREGATE OUTCOMES CHANGE, and review should not have to discover them. An oversized-but-already-present asset now returns before the D-12 size check, so it no longer counts as `failed` -- nothing is uploaded either way, but the aggregate differs. And a present-but-unrestorable entry now counts as an already-present skip rather than a read MISS, so `readMisses` moves down in partial runs. D1 also shrinks `scanned`, which is the denominator of every ratio read off the OBS-01 summary, so the pre-fix 149/150 figures are not comparable across this commit. The reorder does NOT affect the total-case all-MISS gate. That gate needs `readMisses === hashes.length && mirrored === 0`, while the membership guard needs a resolved shard, and the shard resolves only after a restore HIT -- which already falsifies the gate's condition. The two are mutually exclusive, so the set of runs on which the gate fires is identical before and after. The three marker literals are duplicated from ci.yml and mirrorSeedHash with no gate tying them together. Accepted, and recorded where the array is: the drift direction is fail-open, and deriving the one literal that could be derived would cover one family of three while coupling the filter to a helper whose own docblock says its encoding must change at a tenth OS. No permission added, the listCacheEntries ref scoping untouched, and no key prefix or namespace change. `npm run check:action` reports no bundle drift.
…al case D4 -- the split. `restore-MISS (of skipped)` meant two things at once: unrestorable entries, and entries skipped because their asset name was already in the shard. That conflation is the direct reason a 42% restore-MISS rate went unread for 11 days across two windows -- the number was large and nobody could tell which half it was. `PublishResult` gains `alreadyPresent`, incremented from BOTH membership branches: the pre-restore one and the post-restore one the first entry of a run still reaches. It is documented and labelled the way `readMisses` already is -- a strict SUBSET of `skipped`, never a sibling -- so a reader summing the column still cannot arrive at more than `scanned`. Every loop iteration still increments exactly one of mirrored / skipped / failed, so the reconciliation is unchanged. The duplicate-upload race is deliberately NOT counted into it. That is a write-race outcome, not an enumeration-cost signal, and folding it in would make the number stop answering the one question it exists for. The summary gains a row rather than renaming the existing one. Renaming is forbidden: writeCountSummary takes [string, number] pairs only, which makes the miss row's label the sole place the subset relation can be stated at all, and that label is pinned byte-for-byte by a spec. All eleven whole-result assertions carry a case-correct value rather than a blanket zero -- one of them exercises the already-present path and a zero there would assert the opposite of what this counter reports. D5 -- the partial guard. The existing gate fires only on the total case (`readMisses === hashes.length && mirrored === 0`), which is why it stayed correctly silent through both windows. This adds a lower-threshold sibling as an `else if`, so exactly one of the two can fire and the total case still reaches its own more specific message first. THE THRESHOLD IS ONE HALF, and the obvious quarter is wrong. D1 keys on a marker prefix, so it removes only the 20 cafe/feed seeds; the 28 already written under the superseded bare-run-id shape stay unfilterable until they evict. The immediate post-fix window is therefore roughly 43 misses of roughly 129, or 33% -- a quarter would fire on every single run, and a tripwire that fires on correct work gets disabled (D-28b, which this file already records). The intermediate band was considered and REJECTED, and the branch says so: 40% would stay silent through the immediate window and still fire on a return to the pre-fix condition, which is strictly more coverage. It is rejected on provenance rather than principle -- the 33% figure is DERIVED, not measured, and a threshold placed in a nine-point window above an unmeasured estimate is one estimation error away from firing on every correct run. The cost of that choice is recorded rather than left for review to find: one half does NOT fire at 42%, so this guard does not cover its own motivating case. What covers that case is D1 and D2, which remove the accrual that produced it. The revisit trigger is recorded as a RATIO -- readMisses / scanned on the first live post-fix run on the default branch -- because a bare count would be consistent only at the assumed denominator, and D1 shrinks it. It is a warning, never a failure: `failed > 0` is this file's only red signal and stays reserved for per-item upload faults. The boundary, the exclusivity and the reclassification are each pinned by a case, and the exclusivity case asserts on the MESSAGE rather than the call count -- a count of one is equally satisfied by the wrong branch firing. Both the `>=` boundary and the `else if` ordering were proven by mutation.
…e lacks The code review found comments in this task's diff claiming a constraint or a purpose the code does not have. In every case the code is right and the comment is wrong, so only prose moves here. TWO WERE LOAD-BEARING and would have misled the next reader into changing code: - The D1 filter's comment called its POSITION before the `Set` load-bearing. It is not: the predicate is pure and dedup commutes with it, so filtering before or after the `Set` yields an identical array in identical first-occurrence order. A reader needing to reorder the pipeline -- to filter on the entry key rather than the parsed hash, say -- would have believed they were breaking the all-MISS gate and would not have been. The invented invariant had also been promoted into the plan's key_links, so it is corrected there in this same commit; otherwise a future reader defends a constraint that does not exist. - The D3 comment said the D-11 asset-cap branch is now reached only by names that are ABSENT. The FIRST entry of a run falsifies it: it resolves the shard AFTER the pre-restore guard has already been skipped, so it can reach the cap branch carrying a name that is present. The `!shard.names.has(name)` clause is therefore still live, and deleting it on that advice makes a shard at the cap emit a spurious cap warning for an entry that is already mirrored and count it as a plain skip rather than an alreadyPresent. The comment now states that the clause remains reachable and why. THE OTHER FALSE CLAIMS. The exported threshold's docblock said a spec drives the boundary from the constant, while its only consumer pins the VALUE and hand-builds fixtures for one half -- the exact drift the docblock claimed to avoid. The dogfood round-trip gate described three shipped assertions as each closing a hole the other two leave open, when the third is entailed by the first two and can never fail first; the list is correct as three REJECTED alternative designs, so it is relabelled as that and the redundant assertion is kept as documentation. And the D5 pair was presented as covering the total and partial cases respectively, when in this workflow the total-case gate cannot fire on the rotation it names: ci.yml runs mirror-seed immediately before publish in the same job, so this leg's own seed always restores and `mirrored >= 1`. ALSO SWEPT, stale rather than false by construction: `scanned`'s field doc never said D1 subtracts other runs' seeds from it, and that field doc is where a reader of the OBS-01 summary arrives; the engine docblock had no bullet for either D1 or D3 and its return sentence omitted `alreadyPresent`; consumer-smoke's round-trip step still called its key all-decimal 23 lines below the rationale block this task rewrote; and action.yml still described the `hash` input as the bare run id, which is wrong for two of the four operations since D2. ONE REAL BEHAVIOURAL CONSEQUENCE IS NOW RECORDED rather than left for review to find. D3 means publish no longer restores an entry already present in the shard, and a restore is an access, so publish no longer refreshes the Actions cache's 7-day-unaccessed eviction clock for already-mirrored entries. Accepted: a hash still in use is fetched by the sidecar every run and refreshed that way, and a mirrored entry stays readable from the shard until retention prunes it. The case worth watching is the month-shard rollover -- a hash that evicts before the month rolls over cannot be re-mirrored into the new month's shard, where publish's own restore previously kept it alive indefinitely. No runtime behaviour changes in this commit.
…ests on The structural-disjointness mitigation that D1 and D2 both rest on depends on Nx rendering task hashes as ALL-DECIMAL, and until now nothing in the repo failed if that stopped being true. It holds for the pinned Nx, but the evidence behind the three sites calling the disjointness STRUCTURAL is itself empirical -- "verified over 153 local cache entries, zero containing a-f". This task upgraded the COST of that assumption failing without upgrading its verification. Before D1, a hex-lettered task hash cost a cosmetic collision in a shard listing. After D1 it can be misclassified as another run's seed and dropped from the mirror SILENTLY -- no error, no red, just a permanent consumer cache MISS for that hash. So an Nx change now has to go red rather than quiet. THE PIN EXERCISES VALUES WHOSE SHAPE COMES FROM NX, not a literal restating the regex -- that weaker form would pass unchanged after a renderer switch, which is the one event it exists to catch. `hashArray` is the function Nx's own task hasher composes the final task-hash value with, and it delegates straight to the native hasher, so the probes are Nx-rendered hashes. A hex-rendered u64 avoids a-f only about once in 6500 strings, so 32 draws make the shape clause certain to redden. BOTH CLAUSES ARE LOAD-BEARING and neither subsumes the other. The shape clause is what actually detects the renderer change. The enumeration clause -- every one of those hashes still reaches the restore under a foreign run id -- ties the shape to the CONSEQUENCE, so a failure names the mirror rather than a regex. On its own it would be weak: a hex hash collides with one of the three marker words only about three times in 65536. The failure message names the installed Nx version and what has to change before the pin is relaxed. `nx/src/*` is an internal subpath with no semver guarantee, so an Nx major could move it and break this file at IMPORT time. That is the desired failure mode here too, and the same posture nx-target-inputs.spec.ts already records.
… trail Planning artifacts for the change that stops the publish mirror re-enumerating prior runs' seed entries, plus the two STATE.md quick-task rows (260808-wxg was never recorded either) and a forward pointer on 10-VERIFICATION.md's L3. The audit trail is the point of these files rather than a side effect. Four plan-check iterations found three blockers, and none of them would have gone red on its own: a succession premise that was false and would have shipped as a code comment, a commit-body claim and a threat-model row; a threshold that would have fired on every correct run, which is the documented route to someone disabling the tripwire; and a run-id plumb with no gate at all, where writing nothing passed and the task would have silently no-opped in production while every test stayed green. Verification then re-applied six mutations rather than trusting the summary, and code review found eleven warnings verification structurally could not -- five comments asserting constraints the code does not have, one of which would have led a future reader to delete a live clause. 10-VERIFICATION.md's L3 keeps its stale `readMisses 0` expectation deliberately. The correct post-fix value is itself unobservable until a real push to main, and substituting a derived figure into a measured-evidence record would swap one unverified expectation for another -- the exact defect this milestone has spent three quick tasks removing. The row now points here instead, and closes from the same live window as the three unobserved items.
The existing pair is hand-built for a raw one half on a 4-entry enumeration
and cannot express the rule that replaces it: under a Wilson lower bound, 4
entries with 2 misses gives 0.150 and even 3 gives 0.301, so neither can fire
and the "at the threshold" fixture would silently become a second
below-threshold case that passes for the wrong reason.
Four fixtures, each computed for the bound at a target rate of one half:
10 entries / 9 misses -> 0.5958, WARNS
10 entries / 8 misses -> 0.4902, SILENT
4 entries / 3 misses -> 0.3006, SILENT (IN-03, no floor constant)
112 entries / 43 misses -> 0.2991, SILENT (MEASURED, run 31305961054)
The just-below case sits 0.0098 under the target rate, which is what makes
the pair able to catch an off-by-one or a mis-transcribed z; a looser pair
would pass against a wrong formula.
RED against the raw ratio: the two below-threshold cases warn. The
just-above and measured-baseline cases pass under both rules, which is
correct -- they agree wherever the two rules agree.
The 10- and 112-entry enumerations are generated rather than spelled out. A
hand-authored 112-row array is a transcription hazard in the one place the
arithmetic has to be exact, and the misses are ordered first so every one
stays on the pre-shard path and out of the D3 already-present guard, which
would otherwise reclassify them and move the number under test.
The branch read the raw miss proportion against one half, so its behaviour depended on the enumeration size in a way no constant in the file expressed: 2 misses of 4 fired, 43 of 112 did not, and the only thing separating them was arithmetic a reader had to redo. Gating on the Wilson score interval's lower endpoint instead makes the rule scale-invariant -- it needs no constant beyond the target rate and z, and it is silent at small N by construction rather than by a floor. The bound is a SMALL-SAMPLE REGULARISER here, and the helper's docblock says so rather than calling it a confidence bound. A restore MISS is deterministic given (entry cohort, leg platform), so there is no superpopulation to cover and no estimand to bound; the formula still does the useful thing, but naming it a confidence bound would ship a false justification. IN-03 closes without a minimum-N floor: the bound cannot reach one half below four trials at all, and at four only a 4/4 miss clears it -- which the total-case gate already owns. Mutation-checked against the four rebuilt fixtures, each mutation applied and reverted: z -> 1.0 reddens 10/8 and 4/3 drop the z2/(2n) term reddens 10/9 back to the raw ratio reddens 10/8 and 4/3 The condition keeps `readMisses > 0` as its first clause, so a zero-miss run cannot reach the branch, and the helper stays module-local: a spec that called it would pin the arithmetic without proving the branch reads it.
…measurement The branch justified its constant with a figure it derived rather than observed -- 33%, arithmetic over one run's counts plus an assumption about which cohort D1 removes. A review then corrected it to 41%. Both were wrong, in opposite directions and by the DENOMINATOR each time; the miss COUNT of 43 was right in both. Run 31305961054 at head e3bf98b settles it: 43 of 112 enumerated on ubuntu-24.04-arm (38.4%) and 43 of 113 on windows-11-arm (38.1%), with the bound at 0.299 -- silent, as the healthy steady state must be. Deleted, because all three exist only to defend a constant against an estimate that has since been measured: the 33%/41% derivation, the intermediate-band paragraph whose premise WAS that estimate, and the "one half survives being wrong about the estimate" argument. The revisit-trigger paragraph is rewritten rather than deleted -- its trigger has fired, and the run that fired it is the one now quoted. Added, because it is the non-obvious choice a future reader will otherwise "fix": the denominator is deliberately the full enumeration. Attempted-only (`scanned - alreadyPresent`) measures 43/53 = 0.811 on ubuntu and 43/44 = 0.977 on windows on the same run, so it would fire on BOTH legs of a healthy run. `max-parallel: 1` runs ubuntu first, so the second leg's attempted-miss rate is dominated by leg order rather than by cache health. The spec's own prose is swept in the same commit, because the fixture rebuild falsified five things it asserted about itself -- a 4-entry enumeration that is now 10-entry, a pointer to an intermediate band this commit deletes, a revisit trigger that has since fired, a title calling the value a threshold when it is now a target rate a bound must reach, and a message citing the derived baseline. Leaving them would re-create the false-comment class in the very commit that removes it. Also folds in a one-line prettier reflow of the condition introduced by the preceding commit.
…n history The message closed by instructing its reader to compare the figure against a specific later reading of THIS repository rather than an earlier one, on the grounds that a seed filter had moved the denominator between them. A consumer has that filter in no version of their history and has neither reading -- the sentence was our own incident record rendered as a stranger's CI log, which PROJECT.md's distribution constraint forbids. What replaces it names only what a reader can act on inside their own repository: the miss count, the enumeration size, which denominator that proportion is over, and the two candidate causes the sibling gate above already names. The denominator clause is the one addition rather than a subtraction -- it is the single thing about the number a reader cannot infer, and the engine comment records that the alternative reading fires on both legs of a healthy run. The leak cannot return silently: a spec pins the call count and asserts over the FLATTENED calls. `not.toHaveBeenCalledWith(...)` was not an option -- it passes when any ONE call fails to match, so it states "some warning lacks the phrase" rather than "no warning carries it", negating the predicate instead of the quantifier. Verified non-vacuous by reinstating the removed sentence, which reddens it. The three phrases proved absent are split with a single-character class so they are not planted in the file that proves them gone. The total-vs-partial branch discriminator moves to the new denominator clause. The two messages now share both candidate causes by design, so a needle from the shared half would pass whichever branch fired.
…hold Plan 02 and its record. The panel that produced this option set was worth running and was also wrong twice: both of its numeric estimates missed, and its consensus rested on a premise a measurement falsified. One 17-minute window resolved what seven agents could not. The reversal worth reading is the denominator. Thresholding on attempted-only is the "coherent" choice and I argued for it; measured, it fires on both legs of a healthy run at 0.811 and 0.977, because the second matrix leg finds nearly everything already mirrored by the first. That is leg ordering, not cache health. The enumerated denominator that looked incoherent is the one that is stable, so it stays -- decided by observation rather than by argument. Recorded but deliberately NOT actioned: the OBS-04 total gate is effectively unreachable, since it needs every enumerated entry to miss and any hit either mirrors or is already present. 09-VALIDATION already measured this without recognising it, and docs/advanced.md still promises consumers a warning that cannot fire. That is a pre-existing consumer-facing defect larger than the one this task chased, and it belongs in its own task rather than in a frozen milestone's diff.
…duces The rotation paragraph promised an adopter that the first publish after a cache version bump "restores everything as a MISS and mirrors nothing", and that "the warning it emits names the axis". The total gate needs mirrored == 0. A deliberate bump moves the sidecar and publish together, so the entries written during that same run restore and mirror, and the gate this paragraph describes stays silent. The counterexample was already in the project's own validation record for the one real rotation -- mirrored 6, restore-MISS 41 of 47 scanned -- and went unread because nobody was looking for it. What replaces it is the CONDITION rather than a second confident sentence: whether the publish run also wrote entries of its own at the new cache version. A run that did gets the proportional warning; a publish-only or scheduled run with an entirely historical enumeration is what the all-MISS warning is reserved for. The proportional one is described as proportional and NOT promised, because it is weighed against a lower bound on the miss proportion -- around 90% of ten entries against around 60% of a hundred -- so whether it fires depends on a ratio this document cannot know in advance. Both correction and guard land in one commit, which is the rule docs-same-os-claims.spec.ts states about itself: a reworded site updates its ROW in the same commit. Splitting them would ship corrected prose with no guard, which is the drift the table exists to prevent. The three forbidden patterns were proven against the pre-edit file before being written -- the first stops at "mirrors" because the original sentence wrapped there, and the third stops at "publish" because the longer phrase matches only the lesser of that sentence's two occurrences and would leave the bolded lead sentence free to return green. The shared harness's failure message is made claim-neutral in the same commit. It was hardcoded to the same-OS claim, so a reader who reintroduced the all-MISS promise would have been told they reintroduced a same-OS one -- which defeats the reason this correction gets its own row. No behaviour change: the gate, both warning branches and their thresholds are untouched.
…nt it falsifies The panel that surfaced this defect was wrong about both its location and its fix. The gate it wanted repaired is correct; the documentation was not. Six of the eleven assets the measurement run mirrored are real task hashes rather than seeds, which is what falsified the proposed repair before any code was written. Two of the orchestrator's own claims were falsified inside this task -- the inherited framing, and then a bundle-drift rationale written into the locked-decisions file itself. The planner caught the second by reading action.yml rather than the prose. Recording that here because the pattern is the point: every one of these was caught by an agent re-deriving a claim instead of inheriting it, and none by the agent that wrote it. ROBUST-04 still states that drift surfaces only as the all-restore-MISS warning, which the same finding disproves. Filed as a capture rather than an edit -- it is a milestone artifact and the milestone is frozen mid-review, so amending it now would push an unreviewed requirement change into a diff the maintainer is already reading. The requirement's mitigation is unaffected; only its account of how the failure surfaces is wrong.
…on recorded The handoff carries a review verdict rather than a plan. A two-agent review rejected the ROBUST-04 correction this session proposed, upheld the maintainer's objection against the revised version as well as the original, and left exactly one docs-only edit to an unfrozen capture file. What the next session most needs is not the task, which is four sentences. It is the correction record: six of this session's own claims in that one area were falsified, every one of them by an agent re-deriving from source, and none by the agent that wrote it. Two survived as long as they did because a filtered rg view was treated as the artifact. The handoff says to distrust its own claims on the same grounds. Recorded so it is not re-litigated: REQUIREMENTS.md stays untouched, ROBUST-04's checkbox stays ticked, and the drift gap is NOT closed by the branch shipped today -- silent mid-month, misattributed at rollover.
…ved evidence - state the runtime silence in the past tense, over the requirement's whole life - reword the literally false "stops receiving anything" as prose, leaving the quotation intact - record that no drift shipped, on the byte-identity build measurement - record the rollover misattribution as an open, unfiled defect
… it was taken on "Forty-seven commits later ... a fresh build at HEAD" was true of 23d9207 and became false at the moment this amendment committed -- the count is 48 now and grows with every commit, and "at HEAD" floats forever. Name the tree instead, and put the measurement in the past tense. The evidence is unchanged; only its anchor is. A capture written to remove aging claims should not ship one.
…iewed corrections Closes the one task the previous session left standing after a two-agent review rejected the REQUIREMENTS.md amendment. That rejection holds: REQUIREMENTS.md is untouched and ROBUST-04's checkbox stays ticked. The research pass falsified most of the argument the handoff had specified verbatim for edit 2. Eight of the nine bundle rebuilds paired with a source change, not all nine -- db577db was lockfile-driven. Not one of the seven unpaired commits is a spec file; all seven are comment-only. And "no action-bundle-drift catch is recorded" is refuted outright: Phase 7's Q10 catch is recorded five times and sits one day inside the window. That argument was broken on its own terms anyway, since gh run list returns no runs for any of the eight commits, so the gate never evaluated them. Replaced by a measurement instead: a fresh esbuild build at 23d9207 reproduces the committed bundle byte for byte, which needs no commit-counting. Removes HANDOFF.json and .continue-here.md -- one-shot artifacts describing the task this commit completes.
…list The partial-miss warning and the all-MISS gate both assert a fixed count of causes, and under a version skew between the sidecar and this publish step they fire naming a cache-version rotation the reader never made while never naming the cause that occurred. The closed enumeration is the defect rather than the missing item: this same message already shed a true cause once with nothing reddening. These guards land failing, before the messages are touched, so the next commit has to earn them. - Both branch fixtures retract the closed-enumeration phrase and require the version-skew cause. The phrase lives in both messages and only one branch fires per run, so one fixture cannot cover it. - The partial fixture requires the month-shard rollover cause and the total-gate fixture forbids it. That gate needs `mirrored === 0`, so its shard never resolves, the pre-restore membership skip never runs, and rollover cannot move its number -- naming it there would be a cause the branch cannot have. Pinned from both sides in one commit: alone, either half is satisfied by putting the clause on neither branch. - The total gate had no positive pin on its cause list at all and no distribution-constraint guard; both are added here. The rollover needle is the discriminating long form deliberately. A bare `month shard` already occurs three times in this engine, in the asset-cap warning and the shard machinery, so a pin on that bigram is green before any edit.
…se list Both messages now say the list is not exhaustive and keep every cause they already named, so dropping the false completeness claim costs the reader no specificity. Two causes are added. The version-skew cause goes on BOTH branches, in the form a stranger can act on: the sidecar that wrote the entries and this publish step running at different versions of this action, which computes two cache versions in one repository. That is the cause the warning misattributes today -- it sends the reader looking for a rotation in a commit range where none happened. It is named in consumer-general terms because this repository's own instance of the class is our incident record, and a stranger cannot act on that. The month-shard rollover cause goes on the PARTIAL branch alone. The total gate requires `mirrored === 0`, so its shard never resolves, the pre-restore membership skip never runs, and rollover cannot move its number. The asymmetry is enforced from both sides by the guards that landed red in the previous commit, not narrated. The comments do the work the messages cannot. The rotation-signal instruction now says the branch fires on a version skew as well, and names action-bundle drift as our own instance of that class. The stranger's-CI-log block records that `54677af` dropped a true cause -- the self-perpetuating cohort -- as collateral of a leak removal, stated as what the diff shows rather than as that commit's intent, and says why it is not restored as a numbered item. That is the answer to the silent-deletion failure mode without paying the consumer-log cost. Also drops a stale cardinality and two stale ordinals from the sibling total-gate case's title and comments: the list is no longer closed, so a position in it is not a stable identifier for a cause. Still a warning, never a failure. `setFailed` is untouched.
Two sentences in the version-bump paragraph asserted a cardinality the messages no longer carry -- "two candidate causes" for the partial warning and "the same two causes worth checking" for the all-MISS one. Neither was pinned by any guard row, which is why the edit is made here deliberately rather than found stale later. The OBS-04 row's docstring records why the count left, and why it is not re-pinned as a `required` phrase: guarding a cardinality is the defect this change removes, and such a guard would redden the moment a fifth cause is found. All three of the row's required phrases still match, each on its own line, and none of its forbidden patterns is reintroduced.
… overclaim Review round on the three preceding commits. The two blocking findings were both in the change's own subject matter. THE RETRACTION SHIPPED UNGUARDED. "and this list is not exhaustive" is the one behaviour this change exists to add, and deleting it from both messages left all 1079 tests green -- the only committed pin was a phrase-level absence row that catches a verbatim relapse of the retired wording and nothing else. The `rg` that appeared to cover it lived in a verify block and proved the state of one tree at one moment. Both branch fixtures now require the clause through `publishMirror`; measured red on both when it is removed. THE SKEW COMMENT DESCRIBED A CONDITION THAT ROUTES TO THE SIBLING. It said the partial branch fires where "every enumerated entry misses", which is `readMisses === hashes.length` with nothing mirrored -- the total gate, making this `else if` unreachable. It contradicted its own neighbouring paragraph. It now says what is true and why it lands here: entries written this run through the publish-side artifact still restore, so `mirrored >= 1`. THE ASYMMETRY'S STATED REASON WAS A FALSE IMPLICATION. `mirrored === 0` does not imply an unresolved shard -- already-present, cap, burned-tag and upload-fault runs all resolve one. The working conjunct is `readMisses === hashes.length`: every hash took the miss branch, so none reached the lazy shard resolve. The conclusion and the assertions were already right; only the premise was wrong, and the engine's own comment at the reorder had it correct all along. Also: - Cause (4) claimed "a one-time rise" for what the code produces as a step. An entry that misses is never mirrored, so it is never in the shard, so it is re-attempted and misses again every run of that month. The message now says the affected entries become visible at once and stay counted until they evict, which is what the comment twenty lines above it already said. - The all-MISS paragraph in `docs/advanced.md` still claimed the two warnings name "the same causes" after the change made them differ by exactly one. It now names the difference, with the corrected premise rather than the `mirrored === 0` one. - The guard-row docstring spelled both retired phrases verbatim, against the rule that file states about itself three times; the relapse search is clean again. It also said the warnings carry no count, which overstates -- they still enumerate, what left is the completeness claim. - Cause (2)'s relative clause bound to "this action" rather than to the skew. - The forbidden-artifact pattern is one shared constant instead of two copies, no longer requires a trailing slash after the planning path, and records the two gaps it does not cover. - The retitled sibling case now pins the skew cause it names.
…that recorded it open The capture said the defect was "open, unfiled" and that the maintainer had chosen the edit alone. Both were true when written and neither is now. Also records what 260809-og2's research refuted: the reachability argument this capture inherited. npm pack ships dist/publish/publish-mirror.js in the consumer tarball, so the misattribution was reachable in a stranger's CI log, not only in ours.
…blish mirror warning Both read-miss warnings asserted a complete set of causes, so any cause outside the named two was structurally unnameable -- including the one that actually occurred. The completeness claim is gone from both, a consumer-general version-skew cause is named in both, and the month-shard rollover on the partial branch only. Research answered the task's parked question and reversed it. Bundle drift is not dogfood-only: npm pack ships dist/publish/publish-mirror.js in the consumer tarball, and docs/advanced.md sanctions an adopter wiring publish. So the cause could be named in terms a stranger can act on, without putting our artifacts in their job log. It also found that 54677af had silently dropped a true cause while removing a separate leak. That is the argument for retracting the completeness claim rather than raising the count.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Milestone: v0.0.2 OS-invariant cross-OS sharing (Phases 7 through 13, 45 plans)
Goal (ROADMAP.md): A Windows developer reuses Linux CI's portable task artifacts, and Windows
CI reuses them too, with the OS-sensitive target still provably separated. Proven by dogfooding
this repo, then documented as a recipe consumers can copy.
Status: Open for review. All seven phases verify
passed, all seven carrythreats_open: 0,all seven are Nyquist compliant, and the test suite at HEAD (
c7793c4) is 1079 tests across 43files, exit 0. The milestone audit records
status: tech_debt, notpassed: every requirement issatisfied, but the audit carried bookkeeping drift and open follow-ups, most since closed in place
by named quick tasks. Nothing here is merged or approved; the maintainer's own code and security
reviews govern that decision.
v0.0.2 supersedes one decision that v0.0.1 shipped.
CORR-01was an either/or, "OS-namespace thestore by default OR document consumer OS-discrimination"; v0.0.1 took the first branch, and this
milestone takes the second (D2-01). The store therefore becomes OS-INVARIANT end to end and OS
discrimination lives in exactly one place, the declared platform discriminator on the
integrationNx target. Getting there needed four independent things to be true at once, which is why they are
separate phases: the Nx task hash had to stop depending on the OS (Phase 8, root-caused node by
node before it was fixed), the
@actions/cachecache version had to stop depending on the OS(Phase 9), the Releases mirror asset name had to lose its OS component without losing prunability
or incident-response attribution (Phase 10), and the result had to be PROVEN on real runners rather
than argued from config (Phases 11 and 12, in a mandated order, because enabling Windows CI reuse
permanently destroys the producer attribution the local-developer proof depends on). Phase 7 comes
first for a hashing reason:
@nx/eslintis an Nx inference plugin, so adopting it changeshash_project_configand would have invalidated Phase 8's record if it had landed later. Phase 13closes the last soft spot the PR #12 review found: a gate over writable legs is launderable by a
re-run, so a read-only Actions-cache backend makes the count soundly gateable by making a
Windows-produced entry for those hashes structurally impossible.
Changes
By phase; per-plan detail is in each phase's
.planning/phases/<phase>/*-SUMMARY.md.Phase 7: Lint Toolchain and the Ambient-Platform-Read Ban (4 plans, completed 2026-07-27)
Goal: A developer who writes a unit spec that derives an expectation from the running machine
gets a build failure naming the rule, and cannot silence it without writing down why.
ESLint 9 flat config was adopted (v9 is forced, not preferred: Nx 23.1 dropped ESLint v8), a
linttarget was wired into the CI battery, and the ambient-platform-read ban was implemented asTWO core rules behind one shared message. Two rules are required rather than one because
no-restricted-syntaxis an AST-selector matcher that cannot see a destructured named import,while
no-restricted-importscannot ban a member of a namespace import, and the extant violationsites include both shapes. The rules are scoped by the partition that already existed in the vitest
configs, so identical code passes in an
*.integration.spec.ts. The rule set was proven RED beforeGREEN against evasion shapes, not only the sites that already existed, and
lint's Nx inputs weredeclared explicitly so a rule edit cannot replay a cached PASS, closed by differential rather than
by reading the config.
reportUnusedDisableDirectives: erroris the mechanism that later forceseach described disable out of the tree with its violation in Phases 9 and 10.
Key areas:
eslint.config.mjs,nx.json(targetDefaults.lint),.github/workflows/ci.yml, andunder
packages/github-cache/src/:lint-rules.spec.ts,lint-scope-drift.spec.ts,nx-target-inputs.spec.ts,pinned-deps.spec.ts.Phase 8: Nx Task-Hash Parity (6 plans, completed 2026-07-28)
Goal:
build,typecheckandtestcompute one hash on every machine that matters, andintegrationis the only target that diverges, with a CI job that keeps it that way instead of ameasurement taken once.
A root-level dev-only ESM capture instrument (
capture-hashes.mjs) drives Nx's owncreateTaskHasher().hashTask()and emits the per-NODE hashdetailsmap plus the mergedProjectConfigurationnode. That last part is what made the question answerable at all, sincenx show target inputsskipsProjectConfigurationand reports file paths rather than contenthashes. The instrument was proven byte-identical to what
nx runwrites into.nx/cache/run.jsonbefore any measurement was trusted. The record separates two axes that masquerade as each other: a
real OS axis, and a
staleness-of-persisted-inference axis under which a long-lived
.nx/workspace-dataon Windowsreproduces the Linux result exactly. Every prior cross-OS measurement in this repo, including the
pair previously recorded in
STATE.md, read a confounded variable, and that is written down.Measured root cause at anchor commit
a9a3895: exactly ONE hash node differed cross-OS,@op-nx/github-cache:ProjectConfiguration, and the differing field wastargets.typecheck.outputs(seven entries on linux, one on win32). The fix is an
nx.jsontargetDefaultschange pinningtypecheck.outputsto the seven-entry list, taken only after the route was written down in commitsthat provably predate every
nx.jsonedit. A build-gatinghash-paritymatrix job plus ahash-parity-comparejob now assert, over both real legs at one commit, that fewer than twoplatform records is a FAILURE rather than a skip, that the
integrationhashes DIFFER, and thatbuild,typecheck,testandlintare IDENTICAL; the gate was proven able to fail on a realleg, not only on a fixture.
Key areas:
capture-hashes.mjs,nx.json,.github/workflows/ci.yml,packages/github-cache/src/hash-parity/,08-ROOT-CAUSE.md.Phase 9: OS-Invariant Actions-Cache Version (8 plans, completed 2026-07-28)
Goal: The
@actions/cacheversion stops depending on which OS computed it, proven by aWindows runner reading back an entry a Linux runner wrote.
@actions/cachesha256s the raw archive path strings into the cache version, so the inheritedos.tmpdir()value was accidental correctness that also over-partitioned invisibly on any runnerwith a different
TMPDIR, username or container. The path is now a hardcoded, workspace-relative,forward-slash literal under
.nx/cache/, byte-identical onwin32andlinux, built with nonode:pathcall and derived from neitheros.tmpdir(),RUNNER_TEMPnor~. A construction-timeconjunction guard asserts that
cwdandGITHUB_WORKSPACEagree, at construction rather than perrequest precisely because a per-request check would be swallowed into a silent MISS.
enableCrossOsArchive: trueis hardcoded at all three call sites, with a spec asserting theargument LIST and the call COUNT at each, because the flag sits at a different positional index in
each function and upstream's JSDoc documents the wrong order. The
all-restore-MISS warning drops the now-false "different OS" explanation and names cache-version
rotation as a candidate cause, with the tripwire gated on two consecutive all-miss pushes with no
version-affecting change in between, because this milestone contains three legitimate rotation
windows and a tripwire that fires on correct work gets disabled. (That warning's cause list was
corrected again later on this branch by quick
260809-og2; the Post-phase corrections subsectionbelow is the current state.)
{workspaceRoot}/.github/workflows/ci.ymlwas registered as atestinput first.
Closed behaviourally:
dogfood-verifyonwindows-11-armread back the entrydogfood-seedwroteon
ubuntu-24.04-armand asserted the body was LINUX-produced, on run30400231720. That assertsprovenance rather than presence, which matters because the seed key is one per RUN, not per OS.
Key areas (all under
packages/github-cache/src/unless noted):lib/cache-archive-path.ts,backend/actions-cache-backend.ts,lib/compression-method.ts,lib/dogfood-body.ts,publish/publish-mirror.ts; plusnx.json,.github/workflows/ci.yml, andstart-cache-server/index.js(regenerated in the same commits as theserve()-reachable edits,per ROBUST-04).
Phase 10: OS-Invariant Releases Mirror (8 plans, completed 2026-07-29)
Goal: One asset name per hash with no OS component, still prunable, still attributable to its
producer, with the trust consequences classified by an auditor rather than assumed away.
Every mirrored asset is now named
nx-cache-<hash>, single-sourced from the existingCACHE_KEY_PREFIXand derived by BOTH reader and publisher from onereleaseAssetNamehelper. Thecleanup filter admits the new name and the legacy
<hash>-<os>names in the SAME COMMIT, so legacyassets age out through
CACHE_MIRROR_MAX_AGE_DAYSinstead of accumulating, andCACHE_KEY_PREFIXis comment-locked as now governing four things. The PoC-era
<hash>.tar.gzassets that match nofilter got an explicit disposition, accepted as dead weight with a measured count (50 of 122 in
shard
cache-mirror-202607) rather than left unasked.Collapsing two namespaces into one destroys attribution, so
uploadReleaseAssetwas widened with alabelparameter throughaction/index.tsand every fake, and every asset now recordsmirrored-by: <os>in metadata that is NOT part of the lookup name. The label is deliberatelymirrored-byand not "producing OS":listCacheEntriesreturns{ key }only, and Phase 9 brokethe publisher-equals-producer identity, so a producing-OS claim would be wrong in exactly the
cross-OS case the label exists to serve. That retraction is comment-locked at the construction site
with both of its independent grounds.
publishnow depends on every job producing a mirrored entry rather than onbuildalone, backedby the repo's first
needs:VALUE guard; and eachpublishmatrix leg seeds a leg-distinguishablehash while each
publish-verifyleg reads back its OWN leg's asset, so a dead Windows publish pathnow FAILS instead of passing on the ubuntu leg's asset. TRUST-11 and TRUST-12 were classified by
gsd-security-auditorin10-SECURITY.mdrather than self-certified.Key areas (all under
packages/github-cache/src/unless noted):lib/release-asset-name.ts,lib/cache-key.ts,cleanup/cleanup.ts,publish/publish-mirror.ts,lib/mirror-seed.ts,roundtrip/read-back.ts,action/index.ts; plus.github/workflows/ci.yml.Phase 11: Live Proofs, O1 / O2 / O3 (7 plans, completed 2026-07-30)
Goal: Three of the four target outcomes proven live with defined evidence, including the
producer attribution that Phase 12 destroys permanently.
O1: from a cleared local Nx cache, a native Windows workstation logged the literal
[remote cache]label for
build,typecheckANDtestagainst Linux-CI-produced artifacts, one occurrence each,corroborated by
run.jsoncacheStatus: remote-cache-hit. The reset is mandatory because a localcache hit short-circuits before the remote is ever queried. A soundness probe ran BEFORE the
measurement and is timestamped as such, and each
Cache: n/m hitline is marked NON-DISCRIMINATINGin both directions, since a
0%prints identically with no sidecar at all. O2: the same run HITintegrationfrom a Windows-CI-produced artifact, against the pre-rename baseline as anon-regression.
O1's producer attribution was captured at proof time, per hit hash, by four independent means, each
hash naming which means carried it AND that means' limit; three weaknesses are accepted as STATED
LIMITS rather than smoothed over. The premise that Windows CI produces no
build/typecheck/testhash was asserted MECHANICALLY against the resolved Nx task graph for the Windows leg's actual
command, not assumed from the job list.
O3 was proven as an Nx-HASH property and explicitly not as a storage probe: after Phase 9 a
storage-level probe for the Linux hash would now HIT, so asserting a 404 would assert a property
this milestone deliberately destroyed. The proof cites Phase 8's record that
H_linux != H_winforintegration, shows the Windowsintegrationtask EXECUTED carrying no[remote cache]label in arun where
nx-cache-<H_linux>demonstrably existed, and carries a POSITIVE CONTROL in the same jobso the result is not an artifact of a dead sidecar. New
ci.ymlprobe steps and ano3-witnessjobwere built for it.
Key areas:
capture-hashes.mjs(graph-premise mode),read-integration-hash.mjs,.github/workflows/ci.yml(o3-witness),packages/github-cache/src/dogfood-cross-os.spec.ts,packages/github-cache/src/docs-same-os-claims.spec.ts,11-EVIDENCE.md.Phase 12: Windows CI Reuse (O4) + Consumer Recipe (6 plans, completed 2026-07-31)
Goal: Windows CI reuses Linux CI's portable artifacts, and an outside project can copy the
recipe without inheriting a wrong-result risk.
ci.ymlgainedbuild,typecheckandtestlegs onwindows-11-arm, each declaringneeds:onits corresponding ubuntu job. The
integrationmatrix was explicitly NOT the wiring precedent: itstwo legs compute DIFFERENT hashes so parallelism is harmless, whereas the new legs compute the SAME
hash and in parallel would both MISS, execute, and race
saveCache. O4 was observed live on theFIRST run of same-repo PR #12 (run
30586177358), per-leg[remote cache]counts 1 / 2 / 1,matching counts pre-registered in commit
f5d03b0before the run existed, with every ubuntu legMISS-and-saved in the same run. Because those legs also wrote at this point, a scheduled
--skip-nx-cachewindows-11-armregression detector became required rather than optional, and itscommand was proven on Windows arm64.
docs/cross-os.mdships the consumer recipe, leading with the SAFE default (declare thediscriminator across all cacheable targets first, then remove it per target only after proving that
target's output is portable), with the portability checklist second, framed as how to EARN a
removal, its items derived from Phase 8's root-cause record. It names architecture and libc as axes
process.platformdoes not cover and states that this repo cannot exercise them, since everymachine here is arm64. The discriminator command is now
node --no-warnings -p process.platformatall nine tree sites, one string rather than two that happen to match.
--no-warningsrather thannothing because Nx hashes trimmed stdout concatenated with trimmed stderr and node warnings carry
the PID, so an emitted warning would be a permanent 100% MISS rather than a one-time rotation; and
rather than a redirect because
hash_runtimeruns the string through exactly one shell per OS, so aredirect breaks one OS. The doc is registered in
nx.json'stestinputs and drift-guarded by anexact occurrence count at both rendering sites, not a floor.
Key areas:
.github/workflows/ci.yml,.github/workflows/windows-regression-detector.yml,docs/cross-os.md,nx.json, and underpackages/github-cache/src/:docs-cross-os.spec.ts,windows-regression-detector.spec.ts.Phase 13: Read-Only Actions-Cache Backend (6 plans, completed 2026-08-02)
Goal: Make "read the Actions cache, never write it" representable, so the three Windows reuse
legs can be GATED on a genuine cross-OS HIT instead of merely recording one.
The PR #12 round-3 review (CR-18) found the three Windows reuse legs recorded but never gated, and
the obvious gate is launderable: those legs wrote through a writable sidecar, so a broken cross-OS
restore makes them MISS, execute and SAVE their own entry, and a re-run then HITs that self-produced
entry and takes a
count >= 1check green with cross-OS reuse dead.The named risk was that two Actions-cache backends means two places for the cache-version
computation to drift, which is precisely the bug Phase 9 existed to fix; the phase carried an
explicit escape hatch to shrink to a documented decision if no shape made drift unrepresentable, and
the hatch was not needed.
createActionsCacheBackend()is now{ ...createReadOnlyActionsCacheBackend(), put }, so exactly ONEcache.restoreCache(...)READcall site survives in the package and there is one version computation, not two. The file-scoped
@actions/cachedrift guard was widened to PACKAGE scope, because a sibling module is structurallyinvisible to a file-scoped scan and would have shipped a second version computation past a green
guard; the widening was mutation-proven.
put'slookupOnlyexistence probe stays a secondrestoreCacheon the write path only and deliberately is not unified with the read path, becauseprobing at a different cache version reports absent for a present entry.
CACHE_READ_ONLYis a strictly-narrowing env knob read byselectBackendas its LAST branch. Theguarantee is BRANCH ORDER, not validation: every branch above has already returned a read-only
backend or thrown, so the knob cannot resurrect the Releases branch, the fail-closed throw, or the
memory-degrade branch, and
selectBackend.lengthstays 0. All three Windows legs construct theread-only backend and their
[remote cache]counts are gated at a floor of 1 per leg, with afailure message naming BOTH causes a zero can have. The soundness argument is INDUCTIVE rather than
per-run: once the consumer legs cannot write, no Windows-produced entry for those hashes can ever
exist, so any HIT is necessarily Linux-produced regardless of run ordering or re-runs.
Key areas (all under
packages/github-cache/src/unless noted):backend/actions-cache-backend.ts,lib/select-backend.ts,test/consumer-contract.ts,public-surface.spec.ts,dogfood-cross-os.spec.ts; plus.github/workflows/ci.yml,docs/configuration.md,docs/advanced.md,docs/versioning.md.Post-phase corrections on this branch (2026-08-09)
Three quick tasks landed after Phase 13 and are in this diff; two touch shipped files.
260809-og2(source, and the current state of the publish warnings). Bothcore.warningmessages in
publish/publish-mirror.tsasserted a COMPLETE set of causes ("Two candidate causes"),so any cause outside the named two was structurally unnameable, including the one that actually
occurs: under an action-version skew the warning fired naming a cache-version rotation that had not
happened. Both branches now drop the completeness claim, keep every cause they already named, and
gain a consumer-general version-skew cause (the sidecar that wrote the entries and the publish step
running at different versions of this action). The PARTIAL branch alone gains a month-shard rollover
cause: the total gate requires
readMisses === hashes.length, so no hash reaches the lazy shardresolve and rollover cannot move its number. Both halves landed in one commit, since either alone is
satisfied by putting the clause on neither branch. Each branch is guarded through the emitted
message via
mock.calls.flat()rather than through a file read, after review found the headlineclause had shipped unguarded; the fix was proven RED at 2 failed of 1079, one per fixture. Two
cardinality sentences in
docs/advanced.mdwere retired with it. Research refuted the"dogfood-only" framing:
npm pack --dry-runshipsdist/publish/publish-mirror.jsin the consumertarball, so the misattribution was reachable in an adopter's CI log. Commits
51dadac,5a5ed82,e94c649,845b9cc,538e167; filespublish/publish-mirror.ts,publish/publish-mirror.spec.ts,docs-same-os-claims.spec.ts,docs/advanced.md.260809-hcr(docs plus its guard).docs/advanced.mdpromised adopters that the first publishafter a cache-version bump "restores everything as a MISS and mirrors nothing", and that the warning
names the axis. Both halves were false for the path described: on a bump the sidecar and publish
move to the new cache version together, so the run writes entries of its own and the all-MISS branch
cannot fire. The paragraph now states the CONDITION selecting between publish's two warnings and
promises neither, with a
docs-same-os-claims.spec.tsrow that reddens if either false promisereturns, in the correction's own commit (
909a88a). It also filed the ROBUST-04 follow-up.260809-iqe(planning only). Amended that ROBUST-04 capture under.planning/todos/pending/with four reviewed corrections. No source change.
Requirements Addressed
57 requirement IDs, all traced, all
[x]in.planning/REQUIREMENTS.md.The IDs below were cross-checked against
REQUIREMENTS.mditself, not against ROADMAP traceabilityrows. That matters here: ROADMAP requirement lists are parsed only up to the first newline, so a
wrapped line silently drops every ID on the continuation, and the Phase 8 rows were additionally
ID-shifted for part of this milestone. Two totals are both correct and are not the same number:
REQUIREMENTS.md(authoritative) counts the full DEFINEDset (57) and
ROADMAP.mdcounts the ROADMAPPED subset (53); the difference is exactly four IDs thatpredate Phase 13 and have a row in the former but none in the latter (
PARITY-08,VER-07,ROBUST-04,RETAIN-05). The audit's51/51is the as-audited value at2df3af5, superseded inplace when quick task
260808-lptclosed the Phase 8 shift.Phase 7 (7 IDs), lint toolchain and the ambient-platform-read ban.
LINT-01ESLint 9 flatconfig plus a
linttarget in the CI battery, devDeps exact-pinned and named in the pinned-depsguard.
LINT-02two rules ban ambient platform reads in unit specs and allow them in integrationspecs, scoped to the full extension set.
LINT-03proven RED before GREEN over evasion shapes, eachextant violation confirmed CAUGHT while it still existed.
LINT-04declaredlintinputs, so arule edit cannot serve a stale-cache false PASS.
LINT-05bareeslint-disableand bare@ts-expect-errorare errors; an opt-out needs a described reason.LINT-06a stale disable failsrather than pre-authorising a future violation.
CORR-06the platform-read strategy is mechanicallyenforced, not documented.
Phase 8 (9 IDs), Nx task-hash parity.
PARITY-01divergence root-caused node by node andrecorded BEFORE any fix, controlling for the OS axis and the staleness axis.
PARITY-02instrumentemits the per-NODE hash
detailsmap, withnx show target inputsrecorded as insufficient.PARITY-03build,typecheck,testbyte-identical at all three observation points, theworkstation measured in both graph states.
PARITY-04warm-local versus cold-CI kept as its ownnamed question rather than absorbed by
nx reset.PARITY-05integrationbyte-identical betweenthe Windows workstation and
windows-11-arm.PARITY-06every measurement records Nx and Nodeversion, install mode and graph state.
PARITY-07public-surface guard unchanged.CORR-03build-gating two-leg job asserting the record count, the
integrationdifference and thethree-target identity.
CORR-04integrationis the only target declaring a discriminator.Phase 9 (11 IDs), cache-version hardening.
PARITY-08ci.ymlregistered as atestinput,list comment-locked, landing before any spec asserts on it.
VER-01hardcoded workspace-relativeforward-slash archive path, byte-identical cross-OS.
VER-02both version-determining inputs pinnedby spec.
VER-03enableCrossOsArchive: trueat all three call sites, argument list and call countasserted.
VER-04construction-time cwd /GITHUB_WORKSPACEconjunction guard.VER-05resolvedcompression method surfaced in the publish summary, never gated.
VER-06a Windows leg reads back aLinux-written entry and asserts provenance.
VER-07archive directory created before the firstwrite, the literal gitignored and comment-locked as such.
ROBUST-04npm run build:actionin thesame commit as every
serve()-reachable edit.OBS-04all-restore-MISS warning corrected, two-pushtripwire.
DOCS-08four same-OS-restore assertions corrected, two additive preconditions keptdistinct from corrections.
Phase 10 (12 IDs), OS-invariant Releases mirror.
CORR-02asset namenx-cache-<hash>,single-sourced, no OS component.
CORR-05no cross-OS-shared target has a spec deriving anexpectation from the running machine.
RETAIN-04cleanup filter admits both name families, in therename commit.
RETAIN-05PoC-era asset disposition recorded, accept branches mutually exclusive,CACHE_KEY_PREFIXpinned as quadruply load-bearing.OBS-03mirrored-by: <os>in metadataoutside the lookup name.
OBS-05per-leg seeds and per-leg read-back.XOS-06max-parallel: 1retained, comment-locked as NOT a correctness control.
XOS-07publishdepends on every jobproducing a mirrored entry.
TRUST-10C1, C2 and C16's enumeration-side filter verified unchanged,refscoping pinned.TRUST-11first-write-wins arbitration recorded atsaveCache, residual riskmoved into the XOS-05 write decision.
TRUST-12public-repo exposure delta recorded.TRUST-13both classified by
gsd-security-auditor.Phase 11 (7 IDs), live proofs O1 / O2 / O3.
XOS-01(O1) local Windows HIT forbuild,typecheck,testfrom Linux-CI artifacts.XOS-02(O2) local Windows HIT forintegrationfromWindows-CI artifacts, against a pre-rename baseline.
XOS-03(O3) Windows CI MISSESintegrationproduced by Linux CI, as an Nx-hash property.
TEST-08live proofs in the mandated order withproducer attribution captured at proof time.
TEST-09the O3 proof shape: cited inequality,executed-without-label observation, positive control in the same job.
TEST-10proofs begin fromnx reset, reset before sidecar, soundness probe first.OBS-02evidence is a non-zero per-target[remote cache]count.Phase 12 (4 IDs), Windows CI reuse and the consumer recipe.
XOS-04threewindows-11-armlegsfor
build,typecheck,test.XOS-05(O4) those legs HIT on ubuntu-saved entries, writedecision recorded with its scheduled-detector consequence.
XOS-08producer-to-consumerneeds:ordering per leg.
DOCS-07safe-by-default adoption recipe, stderr-immune discriminator, registeredand drift-guarded.
Phase 13 (7 IDs), read-only Actions-cache backend.
VER-08the read-only backend IS thewritable one's read path, one read call site surviving.
VER-09importer guard widened from fileto package scope.
TRUST-14CACHE_READ_ONLYas a strictly-narrowing last-branch env knob, provenbehaviourally.
XOS-09all three Windows legs read-only, counts gated at a floor of 1, soundnessargued inductively.
TEST-11per-leg semantic clauses, non-vacuity mutation-proven, bareexit 1forbidden.
DOCS-09all seven ungated-count rationale sites corrected in the gating commit, the twoout-of-scope sites untouched.
DOCS-10ninth env knob documented, outcome count corrected from fourto five at all five sites.
Verification
Test suite at HEAD (
c7793c4): 1079 tests, 43 files, exit 0, independently re-run.threats_opennyquist_compliantpassed(22/22 must-haves, 5/5 SC)passed(7/7 must-haves)passed(11/11 code-verified)passed(13/13 must-haves)passed(12/12 must-haves)passed(13/13 after four human-verification items closed downstream)passed(7/7 must-haves)status: passedthreats_open: 0at thehighblock thresholdnyquist_compliantandwave_0_completeboth truestatus: tech_debtf5dd429(2026-07-30)Items the artifacts record as open, unproven or not observed
These are recorded here because omitting them would misrepresent the artifacts. None is a
requirement gap; all 57 requirements are satisfied.
10-VERIFICATION.md's firstlive-CI-only item expected
readMisses 0and MEASURED 63 on run31281406708(sampled2026-08-08 under a temporary
mainwindow, quick260808-wxg); four of five clauses were met.Triaged and fixed 2026-08-09 by quick
260809-2s6: 48 of the 63 were prior runs' single-use CIseed entries that the mirror enumerated as if they were cache content, and only 15 were real Nx
task hashes, all in the pre-
47597a6rotation cohort. The accrual is stopped at the source, butthe row's
expectedis DELIBERATELY not rewritten to a post-fix number, because the correct valueis unobservable until a real push to
main. That closure, and260809-2s6's own three unobserveditems, need a live window.
publish-verify (windows-11-arm). Carried as open by designthroughout the milestone and CLOSED LIVE on 2026-08-08 (quick
260808-wxg, job93164047226),with the row's ORIGINAL expectation superseded in place as impossible by construction rather than
met:
publish-verifyis a per-leg SELF round-trip, so alinuxproducer on the Windows leg isthe condition its assertion refuses. The audit's
open_by_designlist predates this closure.low. Below thehighblock threshold, so non-blockingand excluded from
threats_open.11-SECURITY.md's verdict isSECURED.XOS-09gate floor has a stated blind spot.typecheck-windowsresolves twotasks, so a drop from 2 to 1 clears the floor and stays green. The floor was kept deliberately
(the counts follow Nx's task graph and legitimately vary); the per-target counts in
13-EVIDENCE.mdare what make such a drop legible.quick
260803-0rr. Scope is explicitly local; no CI run has directly observed a PUT arriving, andthe CI inference rests on the client-side property being environment-independent. Deliberately not
converted to a standing test.
function that ran is not pinned by spec, because pinning a function-name prefix would mean the
guard has to be edited by the very change it guards; the two VER-04 clauses assert substance.
Deferred Idea, deliberately not added; the audit records it was not reopened.
testfailure at69bd1b7.npm run testexited 1 once and theoutput was destroyed by the battery loop's redirect, so no diagnosis was possible. Nx classified
it flaky (a FAILURE and a SUCCESS at the same task hash); seven consecutive local runs passed at
that hash and CI's
testjob at the same tree issuccess. Actionable only on a secondoccurrence WITH output captured. Triaged again under quick
260808-u2q; still open.main-window restore force-push used to fire production CI. Therestore re-pushes an existing commit whose message cannot carry a skip marker, so both
publishlegs ran for real. Quick
260808-u2qfixed it with one clause (publishnow carries&& !github.event.forced). Five stalerefs/backups/*on origin suggest prior occurrences.tech_debt, notpassed. The bookkeeping cluster it named (9requirement checkboxes, 12 traceability status cells, 20 plan and phase checkboxes, one
VERIFICATION body disagreeing with its own frontmatter) was closed by quick
260808-lptinc3fe74dandd5841c2and superseded in place per that file's own convention. The audit'sstatusline and as-audited counts are left as written; closing the cluster was not a re-audit.Key Decisions
CORR-01's second branch: the store is OS-INVARIANT (D2-01). OS discrimination livesexclusively in the declared Nx input on
integration. This supersedes a shipped v0.0.1 decision;it is a design change, not a bug fix. The first branch cost a Windows developer every cross-OS
hit; the ecosystem norm is trust-the-hash.
ordering argument (ubuntu-first wins the first-write-wins race) was proposed and REJECTED as
brittle: it would rest a wrong-result guarantee on CI job scheduling, the third
accidental-correctness dependency in a milestone whose premise is removing two.
max-parallel: 1is retained for its existing reasons and comment-locked as not a correctness control.
before the first fix commit, and every prior cross-OS measurement in this repo is declared
confounded.
os.tmpdir()value (D2-04).
tmpdir()in the version-hashed path was accidental correctness that alsoover-partitioned invisibly. Its guard is at construction rather than per request, because a
per-request check is swallowed into a silent MISS.
nx-cache-<hash>, single-sourced fromCACHE_KEY_PREFIX(D2-03). A prefixsatisfies the "distinguishing namespace" control literally, whereas a suffix accept-list on a
DELETE filter grows per scheme revision.
mirrored-by, never "producing OS". The label can only derive from thepublishing leg, and Phase 9 is what broke publisher-equals-producer, so a producing-OS claim
would be wrong in exactly the cross-OS case the label exists to serve.
reuse makes Windows a second producer of those hashes and permanently destroys the attribution.
The window closes at Phase 9, not Phase 12, which is why
created_atand the label are capturedper asset.
from a Windows runner would HIT, so asserting a 404 would assert a property this milestone
deliberately destroyed. Reframed, the proof is stronger.
exists, and why all three Windows legs were converted rather than a subset.
createActionsCacheBackendcomposes the read-only factory rather than duplicating it, so there is one read call site and
one version computation. The documented escape hatch was live and was not taken.
CACHE_READ_ONLYis legitimate because the SELECTOR reads it from the env bag as its LASTbranch; a construction-time
readOnlyfactory argument stays rejected. The guarantee is branchORDER, proven behaviourally, because a comment cannot fail when someone moves the check earlier.
adopter count: every comparator puts the portability knob in the task declaration, never in the
cache backend. (Phase 13's
CACHE_READ_ONLYis a separate narrowing role signal.)framed as how to EARN a removal.
hypothesis, named in the plan rather than after the run. Pre-registration is structural where
possible: Phase 13 pushed the prediction commit AS the proving run's head, so the ordering needs
no clock.
rather than deleting it. A bare deletion leaves a future reader holding a documented argument
for undoing the work. Applied to the same-OS-restore prose, the ungated-count rationales, the
byte-identical under CORR-01comment, and (post-phase) both publish warning messages.User Stories & Acceptance Criteria
Neither configured source heading exists in
REQUIREMENTS.md, so the fallback above is renderedverbatim. For context, not as a substitute, the maintainer-stated acceptance frame in
REQUIREMENTS.md ## Framing: the four target outcomesis:build/typecheck/testproduced by Linux CIintegrationproduced by Windows CIintegrationproduced by Linux CIbuild/typecheck/testproduced by Linux CIRisks & Dependencies
No phase
PLAN.mdcarries a## Risksor## Dependenciesheading, so the configured fallback is- No known high-risk rollout dependencies.It is not rendered alone, because the plans do carryreal risk content in a different shape (one
<threat_model>block per plan, 45 of them, auditedinto the seven SECURITY.md files) and the ROADMAP carries a
Depends on:line per phase.Synthesised from those:
Sequencing dependencies that were load-bearing, all honoured.
LINT-01beforePARITY-01:@nx/eslintis an inference plugin, so an inferredlinttargetchanges
hash_project_configand would have invalidated Phase 8's root-cause record.PARITY-08before any spec asserting onci.yml, or the spec serves a stale cached PASS.VER-07beforeVER-01: the archive directory must exist before the first write at the new path.RETAIN-04andRETAIN-05in the SAME COMMIT asCORR-02: a new asset name against anunextended cleanup filter silently stops pruning.
OBS-05beforeCORR-02, orpublish-verifygoes vacuous the moment the rename lands.
VER-01andVER-03beforeTEST-09: otherwise the O3 proof passes for the pre-change reason.XOS-01PROVEN beforeXOS-04andXOS-05: enabling O4 destroys O1's attribution permanently.ROBUST-04:npm run build:actionin the same commit as everyserve()-reachable edit, or thesidecar and the publish action compute different cache versions and the mirror silently stops
receiving.
Residual risks recorded rather than eliminated.
violation at serve time, because every detector that exists re-executes the task, and a cache that
re-runs tasks is not a cache. The Nx hash is the classification only GIVEN the
DOCS-07declaration; the residual is in
TRUST-11. "O4's green CI is the portability evidence" isexplicitly rejected as circular, since a restored task does not execute.
TRUST-12). With the incidental within-OS partitioning removed, asingle-OS publish leg can restore and mirror every OS's entries, so the captured terminal output
of every CI job on every OS crosses into the anonymously-readable Releases mirror. Classified by
gsd-security-auditor.listCacheEntries'refscoping is now the sole in-repo control keepingnon-default-branch trusted writes out of that mirror, since
TRUSTED_EVENTSincludespushwithno ref check; it is pinned by spec in three distinct cases.
saveCacheand not at the Releaseupload, and only once Windows legs compute the same hash as ubuntu legs.
XOS-06is satisfiedbecause no requirement depends on the winner, not because the race does not exist.
them and states this repo cannot test them. Relatedly,
zstdonwindows-11-armis arunner-image provisioning choice rather than a bundled guarantee; the compression method is
surfaced in the publish summary so a move is visible.
linttarget, the archive-path change, the asset rename), so an all-MISS push is expected rather than a
fault; the tripwire needs two consecutive all-miss pushes with no version-affecting change in
between.
readMissesfollow-up and260809-2s6's three unobserved items need a livemainwindow.Opening one is a maintainer decision.
Success Metrics & Release Criteria
Neither configured source heading exists (
REQUIREMENTS.md ## Definition of Done,VERIFICATION.md ## Release Criteria), so the fallback above is rendered verbatim. The criteriaactually applied live in
.planning/v0.0.2-MILESTONE-AUDIT.md ## Definition of Done, reproducedhere as measured results rather than as a merge recommendation:
REQUIREMENTS.md; audit's 51/51 is as-audited at2df3af5, superseded to the 53-ID roadmapped subsetstatus: passedthreats_open: 0at thehighblock threshold; 2 non-blockinglowopen in Phase 11nyquist_compliant: true,wave_0_complete: truestatus: tech_debtfor bookkeeping drift and open follow-ups, not because any requirement is unmetRemaining before this is releasable, recorded rather than claimed: the maintainer's code review and
security review, and a live
mainwindow to close the Phase 10readMissesrow and quick260809-2s6's three unobserved items.TDD Audit
Result: no commit in this range carries a parsed
gate_status:trailer. 0 of 511.Method. The range is
fe25a3f..HEADwith--no-merges, which is 511 commits. Trailer presencewas determined with Git's own trailer machinery rather than by matching
%B, so a commit thatmerely mentions the token in prose is not counted. That distinction is not hypothetical here:
exactly ONE commit,
2e98d91("docs(12): ship milestone v0.0.2, PR #12"), contains the literalstring
gate_statusin its body, and its PARSED trailer value is EMPTY. A raw grep would havereported 1 of 511 and been wrong, which is the false positive the ship spec warns about.
Closest available proxy: commit-type histogram over the 511.
No per-commit rows are rendered: 511 rows would be noise, and inventing them would imply a gate
record that does not exist. The histogram is a proxy for where the work went, not evidence of gate
coverage.
What this asserts. Only that the trailer is absent, which is a property of how these commits
were authored rather than a finding about the code. It does NOT assert that no test-driven work
happened: the RED-before-GREEN records, mutation proofs and non-vacuity measurements live in the
phase artifacts, not in commit trailers. This section is INFORMATIONAL and never blocks the ship.
gate_status: skill=0, fallback=0, exempt=0, missing=511