v0.217.0: prefill from the app's own backup, where-the-data-goes on deploy, bounded inventory fan-out
gates / gates (push) Successful in 10s

Completes R-351 and ships R-352's visibility half. Gates 11/11 OK, suite 28 packages ok,
go vet clean, -race clean on the changed package - all run and read BEFORE this commit.

PART 2 SCENARIO A - the deploy page prefills the address and data folder from the app's OWN
backup. backup.RecordedUnitForStack scans every readable namespace root (the app is NOT
installed in this case, so there is no own drive to ask) and reads manifest.json plus the
captured compose/app.yaml. Local file reads only: no network, no restic, no restore.
RecordedAddress.Known() requires BOTH halves on purpose - an absent SUBDOMAIN makes the live
deploy path substitute the CATALOG default (stacks/deploy.go:88-90), and offering that back as
"what your backup says" would be a fabricated fact. The prefill is labelled as coming from the
backup and stays editable: a memory, not a lock.

PART 1 VISIBILITY (R-352) - the deploy page now states where the app's data will live before
the button is pressed. Measured 2026-08-21: 13 of 53 catalogue templates declare a storage
field; the other 40 have none and their data goes to the system drive, which no screen said.
Metadata.HasDeployField answers "does this app have somewhere to PUT a recorded value?" - for
the 40-class a recorded placement is a fact to state, never a value written into a field that
does not exist. NO PLACEMENT CHANGED. NOTHING MIGRATED. The rest is a filed specification.

PART 4 - measured before theorising, on the live off-site target:
  snapshots --json 2605 ms once; stats 2697 ms PER APP, sequential, 5 app tags
  => 2605 + 5*2697 = ~16.1 s, matching the reported ten-to-fifteen seconds.
The cause is the shape already on file, so the per-app size calls now run concurrently,
BOUNDED TO 4. The bound is the safety property, not the speed one: the repository is a Hetzner
Storage Box with a session cap, and a refused size call returns SizeBytes 0 - a silent
UNDER-REPORT of the customer's data rather than a visible failure. Peak-in-flight is asserted.
OffsiteInventoryList had no test at all before this.

TEMPLATE SAFETY - every Restore* key is set UNCONDITIONALLY in the deploy handler, because a
template doing index/eq against an undefined key errors at RENDER time: green build, green vet,
green suite, 500 on the page. Four render tests, one per branch, because the existing deploy
render test only renders AutoFields and never reaches these blocks.

RED-PROOFS, mutation asserted applied then reverted to 0:
  A   three template guards dropped (count asserted 3) -> the blank form returned
  P4  inventorySizeConcurrency = 1 -> "peak in flight was 1", elapsed 282ms = sequential

DOCS: CHANGELOG v0.217.0 (MinAgent 0.129.0 unchanged), CONTEXT (the restore's own memory +
what is next), controller/README.md (Backup System), REUSE.md (4 new rows), REPORT.md
overwritten - the previous REPORT preserved to audits/REPORT-v0.216.0-2026-08-14.md first.

NOT fixed here, filed as R-353 and named the next session's first item: a restore whose unit
carries no db_dumps and no volume_dumps still reports a bare completion.
This commit is contained in:
2026-08-21 21:29:01 +02:00
parent 985388c6e9
commit f94543ee5c
14 changed files with 1508 additions and 396 deletions
+38
View File
@@ -1950,6 +1950,44 @@ Last updated: 2026-06-13 (v0.60.0 backlog-Medium cleanup)
---
## THE RESTORE'S OWN MEMORY (v0.217.0, 2026-08-21) — R-351 / R-352 / R-353
> **Both sides of a written fact must be checked, not just the writing side.** Every recovery unit
> has recorded `drive` and `namespace_root` since schema 1. **No non-test code in the repository ever
> read either back.** A restore into a different destination than the backup recorded therefore
> succeeded silently under a green message. This is the same shape as several defects closed this
> month, and the cheap test for it is one grep: *who reads this field?*
>
> **`IsRunning()` is still the wrong flag, in one more place than we knew.** v0.154.0 fixed the wizard
> and left a comment explaining why. The **seven handlers** were never moved over, so a second press
> genuinely started a second run and reported „…elindult". A comment explaining a trap does not fix
> the other call sites — grep for them.
>
> **A result nobody can see is the same defect as no result.** The banner gated its terminal state on
> a page-local `sawRunning`. The 8.666 s OpenGist restore finished before any poll saw it, so no
> screen said it had completed. Fixed with `RestoreOpStatus.LastRecent` — and the window now lives in
> `internal/backup` as ONE expression that both surfaces read.
**State, and what is next.**
- **Shipped:** placement comparison + named mismatch + `ack_placement`; not-installed refusal names
the recorded drive; deploy prefill from the app's own backup; `restoreOpBlocked()`; `LastRecent`;
off-site listing bounded-concurrent (measured 16.1 s → two waves).
- **R-352 partly closed.** 40 of 53 catalogue templates declare no data path, and
`GetDefaultStoragePath()` is read by nothing that places data — its comment `// new apps use this by
default` has never been true. **Only visibility shipped**; the deploy page now states where the data
will live. **No placement changed, nothing migrated.** Specification:
`felhom.eu/documentation/backlog/SPEC-app-data-placement-2026-08-21.md`.
- **R-353 is the next session's first item.** A restore whose unit carries no `db_dumps` and no
`volume_dumps` reports a bare completion. OpenGist's unit held configuration and nothing else, and
the restore said only that it had finished. Fix the outcome first; *then* prove the off-site
coverage of a named-volume app by running a dump cycle — do not close the first on the second.
- **Open and unproven:** whether the 40-class reaches the off-site tier at all has **not been
observed**. `runVolumeDumps` covers them on paper; every unit on the box read `volume_dumps: None`
because no nightly run had happened yet.
---
## THE TWO RULES THE RECOVERY JOURNEY LEANS ON (v0.203.0, 2026-08-06)
> **1. A credential the hub stages is collected by the box, not waited for.** The reconcile that