v0.231.0 - the box proves its own off-site copy still holds something (R-87)
gates / gates (push) Successful in 11s
gates / gates (push) Successful in 11s
R-87 re-scoped by its own spike and built as Option C. MinAgent 0.129.0 unchanged. THE QUESTION NOTHING ASKED. The weekly check proves the stored bytes are the bytes we stored; it cannot tell us we stored the WRONG thing. A hollow recovery unit backs up cleanly, checks cleanly at 100 percent depth, restores cleanly and gives the customer nothing back - measured on demo-hp 2026-08-31, 120082104 B to 7036 B in one nightly run recorded as a success (R-403). No tier and no cadence asked it. Now offsite-proof does, nightly, on one app. IT DOES NOT prove a restore puts data back into a running app. That stays drill work and 07 section 8 matrix row 4 is NOT moved. THE ACCEPTANCE RULE HAS TWO PARTS AND THE OBVIOUS ONE IS A TRAP. "Check the unit against its own packing list" PASSES a hollow unit, because a hollow unit declares nothing. So: (1) everything declared is present, AND (2) the manifest declares what the app is supposed to have. Part 2 is the whole value. RED-PROOFED: the naive rule makes the hollow-unit test read verdict "pass". THE EXPECTATION COMES FROM INSIDE THE UNIT, never the live box - the snapshot may predate the app's shape, and GetDockerVolumes describes the running app. Database half is DBServiceNames, the same discriminator RestoreFromRecoveryUnit uses. Volume half is ParseComposeNamedVolumes as an EXISTENCE check, not a name match: tars are <project>_<volume>.tar and ResolveDockerVolumeNames derives the project from the compose file's parent dir, which inside a unit is the literal string "compose". Measured on all eight real units on demo-hp the counts match exactly and the naming held every time - but "held on eight" is not "derivable" (R-355). Half a rule that is true beats a whole rule that is invented. THREE OUTCOMES: pass, fail (readable and empty), cannot judge. An app that legitimately has neither a database nor volumes PASSES. RED-PROOFED: alarming on any empty unit makes that test read verdict "fail". IT NEVER WRITES TO THE REPOSITORY and that is asserted on the ARGV as a non-effect: --no-lock, no unlockStale, and m.runner() rather than resticStep so the unlock --remove-all escalation is unreachable. RED-PROOFED: routing it the customer path's way makes the test fail on "unlock" appearing in the argv. IT TAKES acquireRunning ITSELF and skips rather than waits, because RestoreOffboxScratch does not take it (R-408) while offbox_integrity.go states that invariant as universal. DUE-NESS IS PER SNAPSHOT (R-86's model), never per clock. RED-PROOFED: recording a timestamp fails the stored-value test AND breaks the rotation - night 2 re-picks night 1's app. ITS SCRATCH IS A SEPARATE ROOT (backups/offsite-proof) and that is a safety decision, not tidiness: the job deletes its copy on every path, and sharing backups/offsite-restore/<app> would mean a nightly background job deleting the verification copy a CUSTOMER is looking at. It is also invisible to placement, so a proof copy can never be pushed into a live app. SHARED RATHER THAN FORKED: offboxScratchDirIn parameterises the scratch resolver on its ROOT builder, and unitOnlyHeadroom extracts the free-space gate, so the customer path and the proof refuse at the same floor with the same Hungarian sentence. RestoreOffboxScratch's behaviour is unchanged. NEW EVENT offsite_proof_empty, severity error, operator-only - deliberately NOT backup_integrity_failed, whose hub template says the store is DAMAGED. Here the store is sound and the content is absent: different cause, different action. The hub half shipped FIRST, in felhom.eu 1aeaa30 (hub v0.110.0, live and verified), because an unallowlisted type is 400'd and vanishes. 33 new tests, all groups green; full suite 1689 tests, 28 packages, rc=0. All 13 controller gates OK. Five red-proofs run and recorded in REPORT.md. A golden carrying 0.231.0 is OWED - the fleet is on 0.230.0. Viktor's call (R-242).
This commit is contained in:
+64
-1
@@ -7,7 +7,70 @@
|
||||
>
|
||||
> Ask Claude Code: "Please update CONTEXT.md with what we did today"
|
||||
|
||||
Last updated: 2026-08-31 (v0.230.0 — R-403: a poorer copy must never delete a richer one)
|
||||
Last updated: 2026-08-31 (v0.231.0 — R-87: the box proves its own off-site copy still holds something)
|
||||
|
||||
> **2026-08-31 — v0.231.0. FOUR RULINGS, recorded so none is re-litigated.**
|
||||
>
|
||||
> **1. THE ACCEPTANCE RULE IS NOT "EVERY DECLARED FILE IS PRESENT".** That is the spike's own one-line
|
||||
> summary and taken literally it is worthless: a hollow unit declares nothing, so everything it
|
||||
> declares is present, and the check passes on exactly the shape it exists to catch. The rule has TWO
|
||||
> parts and needs both — (1) everything declared is present, AND (2) the manifest declares what the app
|
||||
> is SUPPOSED to have. **Part 2 is the whole value; part 1 alone is the trap.**
|
||||
> `TestR87_HollowUnitForAnAppWithADatabaseFAILS` is the fence and its red-proof models the naive rule.
|
||||
>
|
||||
> **2. THE EXPECTATION COMES FROM INSIDE THE UNIT, NEVER FROM THE LIVE BOX.** R-403's guard could tell
|
||||
> hollow from legitimately-empty because it had TWO copies to compare; this has ONE. The snapshot may
|
||||
> predate the app's current shape, and the point is to judge the snapshot on its own terms — so the
|
||||
> source is the unit's own `compose/docker-compose.yml`, and `GetDockerVolumes` (live Docker,
|
||||
> `backup.go`) is explicitly NOT it. Database half: `DBServiceNames`, the same discriminator
|
||||
> `RestoreFromRecoveryUnit` already uses, so this cannot disagree with the restore path about what an
|
||||
> app is. Volume half: `ParseComposeNamedVolumes`.
|
||||
>
|
||||
> **THE VOLUME HALF IS AN EXISTENCE CHECK AND NOT A NAME MATCH, and that half-rule is deliberate.**
|
||||
> Volume tars are `<project>_<volume>.tar`; `ResolveDockerVolumeNames` derives the project from
|
||||
> `filepath.Base(filepath.Dir(composePath))`, which inside a unit is the literal string `compose`, not
|
||||
> the stack. Measured 2026-08-31 on all eight real units on demo-hp: the counts match exactly
|
||||
> (bookstack 2/2, docmost 3/3, kimai 2/2, opengist 1/1, privatebin 1/1, calibre-web 1/1, paperless-ngx
|
||||
> 3/3, romm 3/3) and `<stack>_<volume>.tar` held in every case. **"Held on eight" is not "derivable"**
|
||||
> — R-355's standing rule is that a claim about the app must never be inferred from a counter. Half a
|
||||
> rule that is true beats a whole rule that is invented. Name-level matching is available the moment
|
||||
> the capture records the project, and is not worth inventing before then.
|
||||
>
|
||||
> **3. THE PROOF'S RESTORE IS A READ-ONLY VARIANT, NOT A CHANGE TO THE CUSTOMER'S PATH.**
|
||||
> `RestoreOffboxScratch` is the customer's restore, it is proven, and a customer restore taking a lock
|
||||
> is correct — so it was NOT changed. What was shared instead of forked: the scratch-dir resolver is
|
||||
> now parameterised on its ROOT builder (`offboxScratchDirIn`), so the drive-preference rules, the
|
||||
> network-storage refusal and the R-252 wording have exactly one implementation; and the unit-only
|
||||
> headroom gate is extracted to `unitOnlyHeadroom` so both paths refuse at the same floor with the same
|
||||
> Hungarian sentence. The proof's own three differences are the ones that must differ: `--no-lock`, no
|
||||
> `unlockStale`, and `m.runner()` instead of `resticStep` so the `unlock --remove-all` escalation is
|
||||
> unreachable rather than unlikely (REUSE.md's rule: replacing `resticStep` would hide the escalation
|
||||
> from the assertion that must see it).
|
||||
>
|
||||
> **THE PROOF SCRATCH IS A SEPARATE ROOT (`backups/offsite-proof`) AND THAT IS A SAFETY DECISION, NOT
|
||||
> TIDINESS.** The proof deletes its copy on every path including failure. Sharing
|
||||
> `backups/offsite-restore/<app>` would mean a nightly background job deleting the verification copy a
|
||||
> CUSTOMER made and is looking at — a poorer actor destroying a richer one, R-403's shape in different
|
||||
> clothes. The separate root also keeps the proof copy invisible to `DeleteOffsiteRestoreCopy`, the
|
||||
> copy listing and `OffboxFullScratchReady`, so it can never be offered for placement into a live app.
|
||||
>
|
||||
> **4. THE ALARM IS A NEW EVENT TYPE, AND REUSING `backup_integrity_failed` WOULD HAVE BEEN WRONG.**
|
||||
> That type is the nearest existing one and it carries a hub-side Hungarian template saying the
|
||||
> integrity check found an error — i.e. **the store is damaged**. Here the store is sound and the
|
||||
> CONTENT is missing: a different fact, a different cause, a different customer action, and telling
|
||||
> someone their backups are damaged when they are not is the more expensive mistake (the same asymmetry
|
||||
> `looksLikeRepositoryDamage` is shaped around). So `offsite_proof_empty` was minted, severity `error`,
|
||||
> with NO `customerMessages` entry so the controller's dynamic Hungarian survives, and **the hub half —
|
||||
> `allowedEventTypes` plus `operatorOnlyEvents` — ships in the same commit**, because an unallowlisted
|
||||
> type is answered 400 and vanishes, and a missing customerMessages entry is not a routing block.
|
||||
> **This widened the task's stated scope to `felhom.eu/hub/`** and the reason is recorded here rather
|
||||
> than left as an unexplained diff.
|
||||
>
|
||||
> **DELIBERATELY NOT DONE:** `07` §8 matrix row 4 was NOT moved. This proves the snapshot CONTAINS a
|
||||
> recoverable unit; it does not prove a restore puts data back into a running app. R-408 (the missing
|
||||
> `acquireRunning` on `RestoreOffboxScratch`) was NOT fixed — the job takes the flag itself and the row
|
||||
> stays open.
|
||||
|
||||
|
||||
> **2026-08-31 — v0.230.0. THREE RULINGS, recorded so none is re-litigated.**
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user