# Run 2a — the first two violations were HARNESS defects, not product defects (2026-08-01) Recorded in full, because **a check that fails for the wrong reason is as corrosive as one that passes for the wrong reason** — and this arc already has six of the latter. ## What fired cycle 10 HARD-RESET VIOLATION VM returned=True canaries_intact=False cycle 10 I7 VIOLATION want=C10-C010-A-194530 got=C10-C009-A-192929 snap=helyi restore_ok=True rto=41.0s I7 reporting a **stale restore** is the campaign's headline invariant failing. It was not real. ## Root cause — evidenced, not reasoned Cycle 10's permutation was `detach_target → detach_nontarget → hard_reset_mid_write → redeploy_app → restore_verify → …`, so the hard reset ran **before** the restore in the same cycle. It left the app container `rallly | Exited (255)` while `rallly-postgres` stayed healthy. The decisive evidence is the canary table itself — every generation the campaign ever wrote, in order: … 39|C10-C008-A-185905 40|C10-C009-A-192929 <-- highest row **There is no `C10-C010-A` row at all.** The seed never landed, because `_psql` runs `docker run --network container:rallly …` and there was no `rallly` container to attach to. The restore then correctly returned the newest generation that *had* been written — C009-A — and the harness called that "stale". Two harness defects, both mine: 1. `atom_restore_verify` called `L.seed(genA)` and **never checked the return value**, so an unwritten generation was indistinguishable from a stale restore. 2. `atom_hard_reset_mid_write` waited only for `felhom-controller` and then read canaries out of app containers that were still starting — it measured startup latency, not corruption. ## Why this justified stopping a 10-cycle run A **real** stale restore would have produced a byte-identical journal row. Leaving the flaw in place would have made I7 — the one invariant the whole campaign is built around — unable to tell a genuine data-loss event from a slow container start. Depth is worthless if the verdict is ambiguous. ## The fixes, red-proofed in both directions before restarting - `L.apps_ready()` waits for the **app** containers, health-aware, not the controller. - `L.seed(verify=True)` **reads the value back**; a write that reported success but did not land now returns False. - `atom_restore_verify` gates on both and records **`I7-SKIP`** — a PASS row carrying the reason — when preconditions fail. A skipped check is never silently a green one. - `atom_hard_reset_mid_write` judges canaries only once `apps_ready`; **apps failing to return within 10 minutes is itself the violation**, which is the thing actually worth alarming on. | Control | Result | |---|---| | positive — all apps up | `apps_ready=True` in 2 s; all four seeds land | | negative — grafana stopped | `seed → {grafana: False, …}`; `apps_ready=False` | ## Product observations from the same window — NOT filed as findings Both are recorded so a later session does not rediscover them as novel, and neither is claimed as a defect: the app recovered on its own and the behaviour was transient. - **The health probe named the wrong container.** During recovery the controller logged `Health probe rallly: HTTP GET :3000/ → Get "http://rallly-postgres:3000/"` — the **database** container's hostname on the **app's** port. **5 occurrences inside a ~70 s window**, then correct. rallly's own log showed `▲ Next.js 16.2.6 ✓ Ready` throughout. An operator reading only this log would chase a DNS fault on postgres. Transient mis-resolution while the app container was absent. - **`ValidateDump` WARN:** *"structurally valid (32 tables) but its accounts table has NO rows — the dump may predate the customer's data"* — the guard firing as designed, on a dump taken while the app was down. ## Disposition Run 2a stopped at cycle 10. Its evidence is preserved as `journal-run2a.tsv`, `rto-run2a.tsv`, `runner-run2a.log`. **The 10 cycles' other invariant rows remain valid** — I1, I2, I3, I4, I5, I6, I10 and I11 are independent of the seed path. Run 2b restarted from cycle 1 with the fixed harness.