The census answers no, three receipts found, and the prune was on file all along
gates / gates (push) Successful in 13s

CENSUS (read-only, hub store, tester's machine not contacted): no machine that is
not ours can be in the state that cost demo-felhom its history. The hub holds
escrow for three hosts; both demo boxes lost their pre-fix key in the same four
hours on 2026-08-04; peti-felhom and david have no host row and no escrow at all.
A control ran FIRST and had to pass -- the query returned "present (572 bytes)"
for a host known to have material and "absent (NULL)" for one known not to.
Corrected my own instrument on the way: a date-only comparison mislabelled both
losses as after the fix, so the in-force moment is now pinned from the hub's first
post-fix escrow row (11:11:37Z), which independently agrees with the register.

PART 1 ESTABLISHED. The prune is recorded inside R-267 -- the row about the
Configuration page being slow -- because pruning artifacts is what made that page
fast. Arithmetic checks (23+7=30, plus three versions that only surfaced after the
first thirty moved them onto page one = 33) and the PAGINATED listing shows both
generics at exactly ten. R-291's blocking condition is released: the operator was
being asked to establish something already written down. And my counter-argument
yesterday was wrong in exactly the way R-267 warns about -- "containers hold 19"
came from an unpaginated query; paginated they hold 270 and 169.

RECEIPTS: three restored (drives.enrol, backup.tier1, fail.lost-recovery-code),
each citing the document that walked it; the map already read PROVEN-LIVE for all
three, so this follows the map rather than raising a status in the view. NINE
HONEST GREYS. fault.selfheal's best hit argues against it -- an incident recording
self-heal's absence through a 1h15m outage.

THE DECAY RULE FIRED FOR THE FIRST TIME. backup.restore-proof has a receipt from
28 July and is superseded anyway: demo-hp's restore-test failed 5 August and the
box has since been rebuilt. A claim about a continuing behaviour cannot rest on an
old observation. The capability map still reads PROVEN-LIVE and is now the thing
out of step -- recorded, not silently rewritten.

PART 4 specified, not implemented. The orphan card promises restorability the box
rendering it cannot evaluate: the discriminator is on the hub and no wire field
carries it. A conditional promise the system cannot evaluate is the same defect as
an unconditional false one, so the copy stops promising, says what happens, and
names a route. Ships with the next controller change so one bake covers both.
This commit is contained in:
2026-08-10 11:15:37 +02:00
parent 67eced8fbf
commit c04f933d0b
8 changed files with 344 additions and 240 deletions
File diff suppressed because one or more lines are too long
@@ -240,17 +240,20 @@ claims:
band: journey
stage: 4
title: "A new drive is found, offered, formatted, mounted and enrolled — including on awkward older boot layouts"
status: built
status: walked
note: "Applies to a NEW drive. Re-attaching an existing one after a reinstall is R-280 and fails."
sources:
- capability-map: "Drive wizard: scan/format/mount/enroll, incl. legacy-boot LVM-root hosts"
- register: "R-220"
- evidence: "audits/SPIKE-raw-drive-enroll-2026-06-15.md"
changed:
from: walked
reason: "the 2026-08-09 walk exercised RE-attach (which failed, R-280); first-enrolment of a NEW drive has no walk on file"
from: built
also_moved: 2026-08-09 walked -> built
reason_superseded: "the 2026-08-09 walk exercised RE-attach (which failed, R-280); first-enrolment of a NEW drive has no walk on file"
reason: "receipt found 2026-08-10: a live throwaway drive on felhom-pve walked scan -> format -> mount -> PVE dir storage -> one-click Regisztralas, with the resulting storage.cfg entry and systemd mount unit recorded. The map already read PROVEN-LIVE; the dataset was behind it. Scope unchanged: this is a NEW drive. Re-attaching an existing one after a reinstall is R-280 and still fails."
verified:
date: 2026-08-09
verdict: downgraded
verdict: upgraded
depth: source-read
- id: drives.migrate
band: journey
@@ -297,16 +300,19 @@ claims:
band: journey
stage: 5
title: "App data on the machine, nightly database dumps, a copy on a second drive"
status: built
status: walked
sources:
- capability-map: "Tier-2 secondary-drive copy: class-driven legs"
- evidence: "audits/CAMPAIGN-8-backup-restore-2026-07-27.md"
changed:
from: walked
reason: "no walk document cited"
from: built
also_moved: 2026-08-09 walked -> built
reason_superseded: "no walk document cited"
reason: "receipt found 2026-08-10: an adversarial, destructive, unattended overnight campaign across both boxes and ep0, with A2 (one quiesce, two tiers) proven end to end. Map already read PROVEN-LIVE."
verified:
date: 2026-08-09
verdict: downgraded
depth: register+map
verdict: upgraded
depth: source-read
- id: backup.whole-machine
band: journey
stage: 5
@@ -348,6 +354,7 @@ claims:
changed:
from: walked
reason: "no walk document cited, and the last recorded restore-test on demo-hp FAILED (2026-08-05)"
decay: "PROOF-DECAY RULE FIRED (first time it has). A receipt EXISTS - architecture/_recovery-inventory-2026-07-28.md carries live journal lines for scheduled restore-tests on both boxes and both tiers - but it is superseded by later observation: demo-hp logged restore_test_failed on 2026-08-05, and the box has since been wiped and reinstalled (2026-08-09). The claim is about a CONTINUING scheduled behaviour, so a 2026-07-28 observation cannot carry it. Stays grey until a scheduled restore-test is seen passing on the rebuilt box. THE CAPABILITY MAP STILL READS PROVEN-LIVE (2026-08-03) AND IS NOW THE THING OUT OF STEP."
verified:
date: 2026-08-09
verdict: downgraded
@@ -621,17 +628,20 @@ claims:
- id: fail.lost-recovery-code
band: failures
title: "The customer loses their recovery code — by design, the data is unrecoverable"
status: built
status: walked
note: "The recovery screen states it in Hungarian."
sources:
- capability-map: "Escrow ceremony: customer-facing wizard, one-shot R claim, operator zero-knowledge"
- register: "R-198"
- evidence: "audits/REHEARSAL-byo-reinstall-2026-08-09.md"
changed:
from: walked
reason: "a by-design refusal; no walk document cited"
from: built
also_moved: 2026-08-09 walked -> built
reason_superseded: "a by-design refusal; no walk document cited"
reason: "receipt found 2026-08-10, and then proven the hard way: the recovery screen states in Hungarian that nobody can replace a lost code, and on 2026-08-10 demo-felhom's pre-fix key was confirmed unrecoverable by construction (identity_blob NULL; the restic password lives only in that bundle). The by-design refusal is real."
verified:
date: 2026-08-09
verdict: downgraded
verdict: upgraded
depth: source-read
- id: fail.moves-house
band: failures
File diff suppressed because one or more lines are too long
@@ -0,0 +1,90 @@
# SPEC — the orphan card must stop promising what it cannot know (2026-08-10)
**Specification only. Nothing here is implemented, deliberately** — see §5.
**Why this exists.** When a machine's off-site history is set aside, the card tells the customer — in
Hungarian, at the moment they have just lost that history — that the old copies *may be restorable
later with their recovery code*. **For anything set aside before 2026-08-04 that is false and
unfixable**, and on 2026-08-10 it was said to a real machine in exactly that state (`demo-felhom`).
Register: **R-202**.
---
## 1. The surfaces, at `file:line`
All in `felhom-controller`:
| # | file:line | what it says today |
|---|---|---|
| 1 | `controller/internal/web/templates/backups_remote.html:98` | *"A távoli tárhelyen lévő mentések egy korábbi, már nem elérhető kulccsal készültek…"* — the orphan explanation. **Accurate; keep.** |
| 2 | `controller/internal/web/templates/backups_remote.html:101` | *"A régi előzmény **félretéve marad** (nem törlődik), és a hozzá tartozó helyreállítási kóddal később visszaállítható lehet."* — **the false promise.** |
| 3 | `controller/internal/web/templates/layout.html:143` | the 14-day abandon countdown — *"a korábbi távoli mentéseidet {{.RecoveryAbandonDays}} nap múlva véglegesen töröljük"*. **Accurate; keep**, but see §3. |
| 4 | `controller/internal/settings/settings.go:337-338` | schema comment on `OrphanedRenamedTo`: *"so the card/log can name where the old history was set aside."* **Accurate; keep.** |
The `00-capability-map.md` restorability wording was searched for and **not found** — the map does not
make this claim, so there is nothing to correct there. (Stated because the task expected one.)
## 2. What is actually true, in three cases
| case | truth |
|---|---|
| material set aside **before** hub v0.93.0 (in force 2026-08-04 ~11:11Z) | **Not recoverable, by construction.** The restic password lives only in the identity bundle (`escrow/identity.go:39`, read by `escrow/recover.go:91`); for these rows `identity_blob` is NULL. No code and no recovery code can produce it. |
| material set aside **after** the fix | **Recoverable in principle** — the blob is retained and the customer's code unwraps it. Never yet demonstrated end to end on a superseded row. |
| **today's population** | Both cases that exist are the first one. Census 2026-08-10: two hosts hold a supersession, both `identity_blob` ABSENT, both superseded ~4 h before the fix. |
## 3. THE DECIDING FACT: neither the box nor the customer can tell which case they are in
- **The hub can tell** — it holds `host_escrow_superseded.identity_blob` (`store/store.go:393`).
- **The box cannot.** It caches only `HubEscrowIdentityPresent`
(`controller/internal/settings/settings.go:71`), which is about the **current** escrow, not a
superseded one. **No field on the report or ACK wire carries superseded-blob retention.**
- **The box is what renders the card.**
**So the card is making a conditional promise its renderer cannot evaluate. That is the same defect
as an unconditional false promise, in a longer sentence.** The wording below therefore stops
promising and starts stating.
## 4. The replacement copy
**Surface 2 — `backups_remote.html:101`. Replace:**
> A régi előzmény **félretéve marad** (nem törlődik), és a hozzá tartozó helyreállítási kóddal később
> visszaállítható lehet.
**with:**
> A régi előzmény **félretéve marad a tárhelyen — nem töröljük**. Új, üres tárolót hozunk létre, és a
> következő mentés oda készül.
>
> **A félretett mentések megnyithatóságát itt nem tudjuk megígérni.** Ez attól függ, megvan-e még a
> hozzájuk tartozó kulcs, és ezt ez a gép nem tudja megállapítani. Ha szeretnéd, hogy utánanézzünk,
> **írj nekünk** — a félretett másolat addig is a helyén marad.
Three properties, each deliberate: it **states** what happens (set aside, not deleted, fresh store);
it **declines** the claim it cannot evaluate, and says *why* rather than going vague; and it **names a
route the customer can take** — writing in — instead of leaving them nowhere.
*(Surface 3's countdown stays, but the two must be read together: the card must not simultaneously say
"we cannot promise these are openable" and "we will delete them in N days" without the second making
clear it is the set-aside copy being deleted. Recommend re-reading the pair once implemented.)*
## 5. The test that should pin it
`controller/internal/web/` render test, one per branch of the orphan card:
1. **Orphan card rendered → the string `visszaállítható lehet` does not appear anywhere in the
output.** That is the regression guard: it fails if the promise returns in any form.
2. **Orphan card rendered → the "write to us" route is present.** A refusal that names no route is a
defect in this project; pin the route, not only the absence.
3. A render test **per branch of the gate** that shows the card at all, per the seam-wiring rule —
a handler test that POSTs proves nothing about reachability.
## 6. Why it is not implemented tonight, and what it should ship with
The change lands in `felhom-controller`, and **a controller release is not delivered until a golden
carries it** — which costs one image bake and one operator approval (R-242 is the row about exactly
that gap). **The next session changes the controller anyway.** One bake and one approval should cover
this copy change together with that work, rather than spending two of each.
**Do not let this decouple:** if the next controller release ships without this, the promise is still
being made to the next customer who loses a history.