Stage 4 close-out: fix dc-mirror.sh check false-greening the mirror gate
Operator directed closing Stage 4 before resuming the DC1 Stage-5 chain. Verifying the DoD's
fifth bullet ("per-DC mirror reachable") found that the check the gate would close on COULD
NOT FAIL.

scripts/dc-mirror.sh:271-273 tested `[ -f last-sync.status ]` and printed the file's contents
behind an unconditional "OK" -- the status word was displayed but never asserted on, so the
check exited PASS regardless. MEASURED on both racks:

- dc0 read "FAIL 2026-07-27T00:54:54Z ubuntu=255 uca=0"      -> printed OK, PASS
- dc1 read "RUNNING 2026-07-23T21:49:35Z", four days stale,
  left by the debmirror the D-135 amendment killed by signal -> printed OK, PASS

GA-R6 requires a stage to close on a NAMED executable check whose captured output the closing
commit cites. A check that cannot distinguish "synced" from "failed" or "died mid-run" is not
that check.

FIXED: the status word is case-analysed. OK* passes. FAIL* misses. An ABSENT file misses (it
was a passing `note`, but nothing has attested the content). An unrecognised word REFUSES
instead of defaulting to success. RUNNING* is deliberately neither pass nor plain fail --
in-flight and died-mid-run are indistinguishable from the file alone, so it reports an explicit
UNKNOWN and cross-checks the unit, naming a RUNNING marker with no active unit as a corpse.

Harness 19 -> 24; T20-T24 lock each status word and T24 locks out the return of the exact
defective shape.

PROOF, re-run against both live racks -- the gate is now honestly NOT MET for the first time:
dc-mirror check (dc0): FAIL / dc-mirror check (dc1): FAIL. Capture
docs/audit/stage4-mirror-gate-20260727.txt.

SUBSTANCE (measured, not inferred):
- dc0's mirror CONTENT is complete: 949G ubuntu + 342M cloud-archive, all three dists plus
  pool. Only the overnight INCREMENTAL failed -- "Download of dists/jammy/Release failed: 500
  read timeout" aborted the ubuntu leg with 255; the UCA leg succeeded. Transient upstream
  fetch failure, not mirror damage. Timer re-arms 2026-07-28 00:14:48.
- dc1's PROXY -- its RULED artifact path per the D-135 amendment -- checks PASS genuinely
  (apt-cacher-ng on .4:3142 serving archive + UCA Release 200). Its dormant fallback debmirror
  is dormant only ACCIDENTALLY: the timer is `enabled` with an EMPTY next-elapse because the
  unit sits in failed/Result=signal, so the ruling is enforced by nothing and a reset-failed
  would re-arm a 330G -> ~949G pull.

ALSO RECORDED, not yet actioned: DoD bullet 6 ("NTP from the DC's own OPNsense edge") is STALE
and unsatisfiable -- superseded by D-129(iv) 2026-07-21 ("Keep MAAS hierarchy", no NTP role on
the edge) -- and survives in four surfaces needing a DOCFIX before it can be checked at all.
The node-side half of bullet 5 needs an operator decision (gated rescue-boot check vs its own
gate row per GA-R6 E3); nodes are powered off by the READY-handoff ruling, so no node-side
probe can run as things stand. There is no conditional close.

No live state was altered: both racks were read only. repo-lint 0-fail, dc-mirror 24/24.

Revert: git revert the scripts/dc-mirror.sh + tests/dc-mirror/run-tests.sh changes; the check
returns to passing unconditionally on last-sync.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
1 parent 4b192a1 commit 5c9b4b5792efd512dba7fa4ccfd5fb1e9dfca0d5
@JANeumatrix JANeumatrix authored 4 hours ago
Showing 5 changed files
View
docs/CURRENT-STATE.md
View
docs/audit/stage4-mirror-gate-20260727.txt 0 → 100644
View
docs/changelog-20260727-creds-consolidation.md
View
scripts/dc-mirror.sh
View
tests/dc-mirror/run-tests.sh