Skip to content

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
mainfrom
gsd/v0.0.2-os-invariant-cross-os-sharing
Open

v0.0.2 phases 7-13: OS-invariant cross-OS cache sharing, Windows CI reuse, read-only Actions-cache backend#16
LayZeeDK wants to merge 519 commits into
mainfrom
gsd/v0.0.2-os-invariant-cross-os-sharing

Conversation

@LayZeeDK

@LayZeeDK LayZeeDK commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

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 carry threats_open: 0,
all seven are Nyquist compliant, and the test suite at HEAD (c7793c4) is 1079 tests across 43
files, exit 0. The milestone audit records status: tech_debt, not passed: every requirement is
satisfied, 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-01 was an either/or, "OS-namespace the
store 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 integration
Nx 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/cache cache 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/eslint is an Nx inference plugin, so adopting it changes
hash_project_config and would have invalidated Phase 8's record if it had landed later. Phase 13
closes 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
lint target was wired into the CI battery, and the ambient-platform-read ban was implemented as
TWO core rules behind one shared message. Two rules are required rather than one because
no-restricted-syntax is an AST-selector matcher that cannot see a destructured named import,
while no-restricted-imports cannot ban a member of a namespace import, and the extant violation
sites 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 before
GREEN against evasion shapes, not only the sites that already existed, and lint's Nx inputs were
declared explicitly so a rule edit cannot replay a cached PASS, closed by differential rather than
by reading the config. reportUnusedDisableDirectives: error is the mechanism that later forces
each 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, and
under 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, typecheck and test compute one hash on every machine that matters, and
integration is the only target that diverges, with a CI job that keeps it that way instead of a
measurement taken once.

A root-level dev-only ESM capture instrument (capture-hashes.mjs) drives Nx's own
createTaskHasher().hashTask() and emits the per-NODE hash details map plus the merged
ProjectConfiguration node. That last part is what made the question answerable at all, since
nx show target inputs skips ProjectConfiguration and reports file paths rather than content
hashes. The instrument was proven byte-identical to what nx run writes into .nx/cache/run.json
before 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-data on Windows
reproduces 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 was targets.typecheck.outputs
(seven entries on linux, one on win32). The fix is an nx.json targetDefaults change pinning
typecheck.outputs to the seven-entry list, taken only after the route was written down in commits
that provably predate every nx.json edit. A build-gating hash-parity matrix job plus a
hash-parity-compare job now assert, over both real legs at one commit, that fewer than two
platform records is a FAILURE rather than a skip, that the integration hashes DIFFER, and that
build, typecheck, test and lint are IDENTICAL; the gate was proven able to fail on a real
leg, 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/cache version stops depending on which OS computed it, proven by a
Windows runner reading back an entry a Linux runner wrote.

@actions/cache sha256s the raw archive path strings into the cache version, so the inherited
os.tmpdir() value was accidental correctness that also over-partitioned invisibly on any runner
with a different TMPDIR, username or container. The path is now a hardcoded, workspace-relative,
forward-slash literal under .nx/cache/, byte-identical on win32 and linux, built with no
node:path call and derived from neither os.tmpdir(), RUNNER_TEMP nor ~. A construction-time
conjunction guard asserts that cwd and GITHUB_WORKSPACE agree, at construction rather than per
request precisely because a per-request check would be swallowed into a silent MISS.
enableCrossOsArchive: true is hardcoded at all three call sites, with a spec asserting the
argument 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 subsection
below is the current state.) {workspaceRoot}/.github/workflows/ci.yml was registered as a test
input first.

Closed behaviourally: dogfood-verify on windows-11-arm read back the entry dogfood-seed wrote
on ubuntu-24.04-arm and asserted the body was LINUX-produced, on run 30400231720. That asserts
provenance 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; plus nx.json, .github/workflows/ci.yml, and
start-cache-server/index.js (regenerated in the same commits as the serve()-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 existing
CACHE_KEY_PREFIX and derived by BOTH reader and publisher from one releaseAssetName helper. The
cleanup filter admits the new name and the legacy <hash>-<os> names in the SAME COMMIT, so legacy
assets age out through CACHE_MIRROR_MAX_AGE_DAYS instead of accumulating, and CACHE_KEY_PREFIX
is comment-locked as now governing four things. The PoC-era <hash>.tar.gz assets that match no
filter 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 uploadReleaseAsset was widened with a
label parameter through action/index.ts and every fake, and every asset now records
mirrored-by: <os> in metadata that is NOT part of the lookup name. The label is deliberately
mirrored-by and not "producing OS": listCacheEntries returns { key } only, and Phase 9 broke
the 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.

publish now depends on every job producing a mirrored entry rather than on build alone, backed
by the repo's first needs: VALUE guard; and each publish matrix leg seeds a leg-distinguishable
hash while each publish-verify leg reads back its OWN leg's asset, so a dead Windows publish path
now FAILS instead of passing on the ubuntu leg's asset. TRUST-11 and TRUST-12 were classified by
gsd-security-auditor in 10-SECURITY.md rather 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, typecheck AND test against Linux-CI-produced artifacts, one occurrence each,
corroborated by run.json cacheStatus: remote-cache-hit. The reset is mandatory because a local
cache 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 hit line is marked NON-DISCRIMINATING
in both directions, since a 0% prints identically with no sidecar at all. O2: the same run HIT
integration from a Windows-CI-produced artifact, against the pre-rename baseline as a
non-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/test
hash 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_win for
integration, shows the Windows integration task EXECUTED carrying no [remote cache] label in a
run where nx-cache-<H_linux> demonstrably existed, and carries a POSITIVE CONTROL in the same job
so the result is not an artifact of a dead sidecar. New ci.yml probe steps and an o3-witness job
were 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.yml gained build, typecheck and test legs on windows-11-arm, each declaring needs: on
its corresponding ubuntu job. The integration matrix was explicitly NOT the wiring precedent: its
two 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 the
FIRST run of same-repo PR #12 (run 30586177358), per-leg [remote cache] counts 1 / 2 / 1,
matching counts pre-registered in commit f5d03b0 before the run existed, with every ubuntu leg
MISS-and-saved in the same run. Because those legs also wrote at this point, a scheduled
--skip-nx-cache windows-11-arm regression detector became required rather than optional, and its
command was proven on Windows arm64.

docs/cross-os.md ships the consumer recipe, leading with the SAFE default (declare the
discriminator 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.platform does not cover and states that this repo cannot exercise them, since every
machine here is arm64. The discriminator command is now node --no-warnings -p process.platform at
all nine tree sites, one string rather than two that happen to match. --no-warnings rather than
nothing 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_runtime runs the string through exactly one shell per OS, so a
redirect breaks one OS. The doc is registered in nx.json's test inputs and drift-guarded by an
exact 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 under packages/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 >= 1 check 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 ONE cache.restoreCache(...) READ
call site survives in the package and there is one version computation, not two. The file-scoped
@actions/cache drift guard was widened to PACKAGE scope, because a sibling module is structurally
invisible to a file-scoped scan and would have shipped a second version computation past a green
guard; the widening was mutation-proven. put's lookupOnly existence probe stays a second
restoreCache on the write path only and deliberately is not unified with the read path, because
probing at a different cache version reports absent for a present entry.

CACHE_READ_ONLY is a strictly-narrowing env knob read by selectBackend as its LAST branch. The
guarantee 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.length stays 0. All three Windows legs construct the
read-only backend and their [remote cache] counts are gated at a floor of 1 per leg, with a
failure 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). Both core.warning
messages in publish/publish-mirror.ts asserted 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 shard
resolve 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 headline
clause had shipped unguarded; the fix was proven RED at 2 failed of 1079, one per fixture. Two
cardinality sentences in docs/advanced.md were retired with it. Research refuted the
"dogfood-only" framing: npm pack --dry-run ships dist/publish/publish-mirror.js in the consumer
tarball, so the misattribution was reachable in an adopter's CI log. Commits 51dadac, 5a5ed82,
e94c649, 845b9cc, 538e167; files publish/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.md promised adopters that the first publish
after 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.ts row that reddens if either false promise
returns, 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.md itself, not against ROADMAP traceability
rows. 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 DEFINED
set (57) and ROADMAP.md counts the ROADMAPPED subset (53); the difference is exactly four IDs that
predate Phase 13 and have a row in the former but none in the latter (PARITY-08, VER-07,
ROBUST-04, RETAIN-05). The audit's 51/51 is the as-audited value at 2df3af5, superseded in
place when quick task 260808-lpt closed the Phase 8 shift.

Phase 7 (7 IDs), lint toolchain and the ambient-platform-read ban. LINT-01 ESLint 9 flat
config plus a lint target in the CI battery, devDeps exact-pinned and named in the pinned-deps
guard. LINT-02 two rules ban ambient platform reads in unit specs and allow them in integration
specs, scoped to the full extension set. LINT-03 proven RED before GREEN over evasion shapes, each
extant violation confirmed CAUGHT while it still existed. LINT-04 declared lint inputs, so a
rule edit cannot serve a stale-cache false PASS. LINT-05 bare eslint-disable and bare
@ts-expect-error are errors; an opt-out needs a described reason. LINT-06 a stale disable fails
rather than pre-authorising a future violation. CORR-06 the platform-read strategy is mechanically
enforced, not documented.

Phase 8 (9 IDs), Nx task-hash parity. PARITY-01 divergence root-caused node by node and
recorded BEFORE any fix, controlling for the OS axis and the staleness axis. PARITY-02 instrument
emits the per-NODE hash details map, with nx show target inputs recorded as insufficient.
PARITY-03 build, typecheck, test byte-identical at all three observation points, the
workstation measured in both graph states. PARITY-04 warm-local versus cold-CI kept as its own
named question rather than absorbed by nx reset. PARITY-05 integration byte-identical between
the Windows workstation and windows-11-arm. PARITY-06 every measurement records Nx and Node
version, install mode and graph state. PARITY-07 public-surface guard unchanged. CORR-03
build-gating two-leg job asserting the record count, the integration difference and the
three-target identity. CORR-04 integration is the only target declaring a discriminator.

Phase 9 (11 IDs), cache-version hardening. PARITY-08 ci.yml registered as a test input,
list comment-locked, landing before any spec asserts on it. VER-01 hardcoded workspace-relative
forward-slash archive path, byte-identical cross-OS. VER-02 both version-determining inputs pinned
by spec. VER-03 enableCrossOsArchive: true at all three call sites, argument list and call count
asserted. VER-04 construction-time cwd / GITHUB_WORKSPACE conjunction guard. VER-05 resolved
compression method surfaced in the publish summary, never gated. VER-06 a Windows leg reads back a
Linux-written entry and asserts provenance. VER-07 archive directory created before the first
write, the literal gitignored and comment-locked as such. ROBUST-04 npm run build:action in the
same commit as every serve()-reachable edit. OBS-04 all-restore-MISS warning corrected, two-push
tripwire. DOCS-08 four same-OS-restore assertions corrected, two additive preconditions kept
distinct from corrections.

Phase 10 (12 IDs), OS-invariant Releases mirror. CORR-02 asset name nx-cache-<hash>,
single-sourced, no OS component. CORR-05 no cross-OS-shared target has a spec deriving an
expectation from the running machine. RETAIN-04 cleanup filter admits both name families, in the
rename commit. RETAIN-05 PoC-era asset disposition recorded, accept branches mutually exclusive,
CACHE_KEY_PREFIX pinned as quadruply load-bearing. OBS-03 mirrored-by: <os> in metadata
outside the lookup name. OBS-05 per-leg seeds and per-leg read-back. XOS-06 max-parallel: 1
retained, comment-locked as NOT a correctness control. XOS-07 publish depends on every job
producing a mirrored entry. TRUST-10 C1, C2 and C16's enumeration-side filter verified unchanged,
ref scoping pinned. TRUST-11 first-write-wins arbitration recorded at saveCache, residual risk
moved into the XOS-05 write decision. TRUST-12 public-repo exposure delta recorded. TRUST-13
both classified by gsd-security-auditor.

Phase 11 (7 IDs), live proofs O1 / O2 / O3. XOS-01 (O1) local Windows HIT for build,
typecheck, test from Linux-CI artifacts. XOS-02 (O2) local Windows HIT for integration from
Windows-CI artifacts, against a pre-rename baseline. XOS-03 (O3) Windows CI MISSES integration
produced by Linux CI, as an Nx-hash property. TEST-08 live proofs in the mandated order with
producer attribution captured at proof time. TEST-09 the O3 proof shape: cited inequality,
executed-without-label observation, positive control in the same job. TEST-10 proofs begin from
nx reset, reset before sidecar, soundness probe first. OBS-02 evidence is a non-zero per-target
[remote cache] count.

Phase 12 (4 IDs), Windows CI reuse and the consumer recipe. XOS-04 three windows-11-arm legs
for build, typecheck, test. XOS-05 (O4) those legs HIT on ubuntu-saved entries, write
decision recorded with its scheduled-detector consequence. XOS-08 producer-to-consumer needs:
ordering per leg. DOCS-07 safe-by-default adoption recipe, stderr-immune discriminator, registered
and drift-guarded.

Phase 13 (7 IDs), read-only Actions-cache backend. VER-08 the read-only backend IS the
writable one's read path, one read call site surviving. VER-09 importer guard widened from file
to package scope. TRUST-14 CACHE_READ_ONLY as a strictly-narrowing last-branch env knob, proven
behaviourally. XOS-09 all three Windows legs read-only, counts gated at a floor of 1, soundness
argued inductively. TEST-11 per-leg semantic clauses, non-vacuity mutation-proven, bare exit 1
forbidden. DOCS-09 all seven ungated-count rationale sites corrected in the gating commit, the two
out-of-scope sites untouched. DOCS-10 ninth env knob documented, outcome count corrected from four
to five at all five sites.

Verification

Test suite at HEAD (c7793c4): 1079 tests, 43 files, exit 0, independently re-run.

Phase VERIFICATION threats_open nyquist_compliant
7. Lint Toolchain + Ambient-Platform-Read Ban passed (22/22 must-haves, 5/5 SC) 0 (22/22 closed) true
8. Nx Task-Hash Parity passed (7/7 must-haves) 0 (34/34 closed) true
9. OS-Invariant Actions-Cache Version passed (11/11 code-verified) 0 true
10. OS-Invariant Releases Mirror passed (13/13 must-haves) 0 true
11. Live Proofs O1/O2/O3 passed (12/12 must-haves) 0 true
12. Windows CI Reuse (O4) + Consumer Recipe passed (13/13 after four human-verification items closed downstream) 0 (21/21 closed) true
13. Read-Only Actions-Cache Backend passed (7/7 must-haves) 0 (27/27 closed) true
  • 7/7 phases carry a VERIFICATION.md with status: passed
  • 7/7 phases carry a SECURITY.md with threats_open: 0 at the high block threshold
  • 7/7 phases carry a VALIDATION.md with nyquist_compliant and wave_0_complete both true
  • Milestone audit present, verdict "achieved its definition of done", status: tech_debt
  • All four target outcomes O1 through O4 proven live with pre-registered evidence
  • The O1-before-O4 ordering held by the commit graph: the attribution capture (2026-07-29) predates the Windows-leg commit f5dd429 (2026-07-30)
  • 45 plans, 45 SUMMARY files

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.

  • Phase 10, live-CI clause FALSIFIED and not yet re-closed. 10-VERIFICATION.md's first
    live-CI-only item expected readMisses 0 and MEASURED 63 on run 31281406708 (sampled
    2026-08-08 under a temporary main window, quick 260808-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 CI
    seed entries that the mirror enumerated as if they were cache content, and only 15 were real Nx
    task hashes, all in the pre-47597a6 rotation cohort. The accrual is stopped at the source, but
    the row's expected is DELIBERATELY not rewritten to a post-fix number, because the correct value
    is unobservable until a real push to main. That closure, and 260809-2s6's own three unobserved
    items, need a live window.
  • Phase 9, first post-merge publish-verify (windows-11-arm). Carried as open by design
    throughout the milestone and CLOSED LIVE on 2026-08-08 (quick 260808-wxg, job 93164047226),
    with the row's ORIGINAL expectation superseded in place as impossible by construction rather than
    met: publish-verify is a per-leg SELF round-trip, so a linux producer on the Windows leg is
    the condition its assertion refuses. The audit's open_by_design list predates this closure.
  • Phase 11 security: 2 threats OPEN at low. Below the high block threshold, so non-blocking
    and excluded from threats_open. 11-SECURITY.md's verdict is SECURED.
  • Phase 13, XOS-09 gate floor has a stated blind spot. typecheck-windows resolves two
    tasks, 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.md are what make such a drop legible.
  • Phase 13, RESEARCH assumption A1 closed by LOCAL measurement only. Answered affirmatively by
    quick 260803-0rr. Scope is explicitly local; no CI run has directly observed a PUT arriving, and
    the CI inference rests on the client-side property being environment-independent. Deliberately not
    converted to a standing test.
  • Phase 13, one manual-only row ACCEPTED as manual by design. The VER-04 message naming the
    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.
  • Phase 12, one validation row DEFERRED by CONTEXT. The sidecar block drift guard is an explicit
    Deferred Idea, deliberately not added; the audit records it was not reopened.
  • Phase 8, one unattributed test failure at 69bd1b7. npm run test exited 1 once and the
    output 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 test job at the same tree is success. Actionable only on a second
    occurrence WITH output captured. Triaged again under quick 260808-u2q; still open.
  • Process: the temporary-main-window restore force-push used to fire production CI. The
    restore re-pushes an existing commit whose message cannot carry a skip marker, so both publish
    legs ran for real. Quick 260808-u2q fixed it with one clause (publish now carries
    && !github.event.forced). Five stale refs/backups/* on origin suggest prior occurrences.
  • Milestone audit status is tech_debt, not passed. The bookkeeping cluster it named (9
    requirement checkboxes, 12 traceability status cells, 20 plan and phase checkboxes, one
    VERIFICATION body disagreeing with its own frontmatter) was closed by quick 260808-lpt in
    c3fe74d and d5841c2 and superseded in place per that file's own convention. The audit's
    status line and as-audited counts are left as written; closing the cluster was not a re-audit.

Key Decisions

  1. Take CORR-01's second branch: the store is OS-INVARIANT (D2-01). OS discrimination lives
    exclusively 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.
  2. Cross-OS sharing rests on target platform-agnosticism, NEVER on publish-leg ordering. An
    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: 1
    is retained for its existing reasons and comment-locked as not a correctness control.
  3. Root-cause before fix, with the fix route written down first. Phase 8's record is dated
    before the first fix commit, and every prior cross-OS measurement in this repo is declared
    confounded.
  4. The archive path becomes a deliberate OS-invariant constant, not an inherited os.tmpdir()
    value (D2-04).
    tmpdir() in the version-hashed path was accidental correctness that also
    over-partitioned invisibly. Its guard is at construction rather than per request, because a
    per-request check is swallowed into a silent MISS.
  5. The asset name is nx-cache-<hash>, single-sourced from CACHE_KEY_PREFIX (D2-03). A prefix
    satisfies the "distinguishing namespace" control literally, whereas a suffix accept-list on a
    DELETE filter grows per scheme revision.
  6. Attribution is mirrored-by, never "producing OS". The label can only derive from the
    publishing 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.
  7. O1 must be PROVEN before O4 is ENABLED, so they never share a phase. Enabling Windows CI
    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_at and the label are captured
    per asset.
  8. O3 is an Nx-HASH proof, not a storage probe. After Phase 9 a storage probe for the Linux hash
    from a Windows runner would HIT, so asserting a 404 would assert a property this milestone
    deliberately destroyed. Reframed, the proof is stronger.
  9. A gate a re-run can launder is worse than no gate, because it reads as coverage. Why Phase 13
    exists, and why all three Windows legs were converted rather than a subset.
  10. Cache-version drift must be unrepresentable, not merely guarded. createActionsCacheBackend
    composes 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.
  11. Read-write versus read-only is which factory constructs, never a caller-facing mode flag.
    CACHE_READ_ONLY is legitimate because the SELECTOR reads it from the env bag as its LAST
    branch; a construction-time readOnly factory argument stays rejected. The guarantee is branch
    ORDER, proven behaviourally, because a comment cannot fail when someone moves the check earlier.
  12. No new env knob and no new action input for OS separation (D2-02). The reason is layer, not
    adopter count: every comparator puts the portability knob in the task declaration, never in the
    cache backend. (Phase 13's CACHE_READ_ONLY is a separate narrowing role signal.)
  13. The consumer recipe leads with the safe default, with the portability checklist second,
    framed as how to EARN a removal.
  14. Every "the job was green" claim is paired with a count that would differ under the failure
    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.
  15. A claim nothing enforces is the recurring defect here, and corrections replace the old reason
    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-01 comment, and (post-phase) both publish warning messages.

User Stories & Acceptance Criteria

  • Acceptance criteria are covered by the linked requirements and verification evidence.

Neither configured source heading exists in REQUIREMENTS.md, so the fallback above is rendered
verbatim. For context, not as a substitute, the maintainer-stated acceptance frame in
REQUIREMENTS.md ## Framing: the four target outcomes is:

# Outcome Layer Verdict
O1 Local Windows dev HITs build/typecheck/test produced by Linux CI Releases mirror ACHIEVED (Phase 11)
O2 Local Windows dev HITs integration produced by Windows CI Releases mirror ACHIEVED (Phase 11)
O3 Windows CI MISSES integration produced by Linux CI Actions cache ACHIEVED (Phase 11)
O4 Windows CI HITs build/typecheck/test produced by Linux CI Actions cache ACHIEVED (Phase 12)

Risks & Dependencies

No phase PLAN.md carries a ## Risks or ## Dependencies heading, so the configured fallback is
- No known high-risk rollout dependencies. It is not rendered alone, because the plans do carry
real risk content in a different shape (one <threat_model> block per plan, 45 of them, audited
into 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-01 before PARITY-01: @nx/eslint is an inference plugin, so an inferred lint target
    changes hash_project_config and would have invalidated Phase 8's root-cause record.
  • PARITY-08 before any spec asserting on ci.yml, or the spec serves a stale cached PASS.
    VER-07 before VER-01: the archive directory must exist before the first write at the new path.
  • RETAIN-04 and RETAIN-05 in the SAME COMMIT as CORR-02: a new asset name against an
    unextended cleanup filter silently stops pruning. OBS-05 before CORR-02, or publish-verify
    goes vacuous the moment the rename lands.
  • VER-01 and VER-03 before TEST-09: otherwise the O3 proof passes for the pre-change reason.
  • XOS-01 PROVEN before XOS-04 and XOS-05: enabling O4 destroys O1's attribution permanently.
  • ROBUST-04: npm run build:action in the same commit as every serve()-reachable edit, or the
    sidecar and the publish action compute different cache versions and the mirror silently stops
    receiving.

Residual risks recorded rather than eliminated.

  • Portability is a declaration, not a detection. No surveyed build cache detects a portability
    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-07
    declaration; the residual is in TRUST-11. "O4's green CI is the portability evidence" is
    explicitly rejected as circular, since a restored task does not execute.
  • Public-repo exposure delta (TRUST-12). With the incidental within-OS partitioning removed, a
    single-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' ref scoping is now the sole in-repo control keeping
    non-default-branch trusted writes out of that mirror, since TRUSTED_EVENTS includes push with
    no ref check; it is pinned by spec in three distinct cases.
  • First-write-wins has a real differing-payload race, at saveCache and not at the Release
    upload, and only once Windows legs compute the same hash as ubuntu legs. XOS-06 is satisfied
    because no requirement depends on the winner, not because the race does not exist.
  • Architecture and libc are unexercised axes. Every machine here is arm64, so the recipe names
    them and states this repo cannot test them. Relatedly, zstd on windows-11-arm is a
    runner-image provisioning choice rather than a bundled guarantee; the compression method is
    surfaced in the publish summary so a move is visible.
  • Rollout expectation. Three legitimate cache-rotation windows exist here (the inferred lint
    target, 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.
  • The readMisses follow-up and 260809-2s6's three unobserved items need a live main window.
    Opening one is a maintainer decision.

Success Metrics & Release Criteria

  • Release when automated verification and required manual checks pass.

Neither configured source heading exists (REQUIREMENTS.md ## Definition of Done,
VERIFICATION.md ## Release Criteria), so the fallback above is rendered verbatim. The criteria
actually applied live in .planning/v0.0.2-MILESTONE-AUDIT.md ## Definition of Done, reproduced
here as measured results rather than as a merge recommendation:

Metric Recorded value
Requirements satisfied 57/57 traced and ticked in REQUIREMENTS.md; audit's 51/51 is as-audited at 2df3af5, superseded to the 53-ID roadmapped subset
Phases verified 7/7 status: passed
Security 7/7 threats_open: 0 at the high block threshold; 2 non-blocking low open in Phase 11
Nyquist validation 7/7 nyquist_compliant: true, wave_0_complete: true
Cross-phase integration 8/8 seams WIRED, 4/4 E2E flows COMPLETE, 0 BLOCKERs, 1 WARNING resolved at HEAD
Target outcomes O1 through O4 all ACHIEVED, each with pre-registered evidence and a named run
Test suite at HEAD 1079 tests, 43 files, exit 0
Audit verdict "achieved its definition of done"; status: tech_debt for bookkeeping drift and open follow-ups, not because any requirement is unmet

Remaining before this is releasable, recorded rather than claimed: the maintainer's code review and
security review, and a live main window to close the Phase 10 readMisses row and quick
260809-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..HEAD with --no-merges, which is 511 commits. Trailer presence
was determined with Git's own trailer machinery rather than by matching %B, so a commit that
merely 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 literal
string gate_status in its body, and its PARSED trailer value is EMPTY. A raw grep would have
reported 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.

Type Commits
docs 293
fix 93
test 61
feat 36
wip 8
refactor 6
ci 6
chore 6
style 2

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

LayZeeDK added 30 commits July 30, 2026 09:05
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.
@LayZeeDK LayZeeDK closed this Aug 8, 2026
@LayZeeDK LayZeeDK reopened this Aug 8, 2026
…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.
@LayZeeDK LayZeeDK closed this Aug 9, 2026
@LayZeeDK LayZeeDK reopened this Aug 9, 2026
LayZeeDK added 17 commits August 9, 2026 12:04
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant