Files
felhom.eu/documentation/design/SPEC-orphan-card-copy-2026-08-10.md
T
admin c04f933d0b
gates / gates (push) Successful in 13s
The census answers no, three receipts found, and the prune was on file all along
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.
2026-08-10 11:15:37 +02:00

5.6 KiB

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.