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
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:
@@ -466,6 +466,50 @@ func (s *Server) deployHandler(w http.ResponseWriter, r *http.Request, name stri
|
||||
if alreadyDeployed && decryptedEnv != nil {
|
||||
data["DeployedFieldValues"] = decryptedEnv
|
||||
}
|
||||
// R-351 SCENARIO A — an app being reinstalled so its data can come back should not ask the
|
||||
// customer to remember what their own backup already recorded. The address and the data folder
|
||||
// are read from the most readable recovery unit (local file reads; no network, no restore) and
|
||||
// offered as a PREFILL the customer may change — a memory, not a lock.
|
||||
//
|
||||
// Only for a NOT-deployed app: on the ordinary path (installed, unchanged) nothing here runs and
|
||||
// the page is byte-identical to before. Scenario D is protected by that condition, not by luck.
|
||||
//
|
||||
// An UNKNOWN is never rendered as a value. RecordedAddress.Known() requires BOTH halves, because
|
||||
// the live deploy path substitutes the catalog's default subdomain — a guess, not the customer's
|
||||
// answer — and offering that back as "what your backup says" would fabricate a fact.
|
||||
//
|
||||
// Every key below is set UNCONDITIONALLY (to its zero value when there is no record), because a
|
||||
// Go template that does `index` or `eq` against an undefined key errors at RENDER time — green
|
||||
// build, green vet, green suite, 500 on the page. That trap is on file in this repo twice.
|
||||
declaresDataPath := meta.HasDeployField("HDD_PATH")
|
||||
data["RestoreFieldValues"] = map[string]string{}
|
||||
data["RestorePrefillHDDPath"] = ""
|
||||
data["RestoreRecordedDrive"] = ""
|
||||
data["RestoreRecordedAddress"] = ""
|
||||
data["RestoreRecordedDeclaresPath"] = declaresDataPath
|
||||
data["RestoreHasRecord"] = false
|
||||
// Part 1's visibility line needs the real path, not a literal in a template.
|
||||
data["SystemDataPath"] = s.cfg.Paths.SystemDataPath
|
||||
if !alreadyDeployed && s.backupMgr != nil {
|
||||
if place, addr, ok := s.backupMgr.RecordedUnitForStack(name); ok {
|
||||
prefill := map[string]string{}
|
||||
if addr.Known() {
|
||||
prefill["SUBDOMAIN"] = addr.Subdomain
|
||||
prefill["DOMAIN"] = addr.Domain
|
||||
}
|
||||
// The folder is offered as a VALUE only when this app actually has a field for it. The
|
||||
// 40-of-53 apps that declare no data path have nothing to change — for them the placement
|
||||
// is stated as a fact, never written into an input that does not exist.
|
||||
if place.Known() && declaresDataPath {
|
||||
prefill["HDD_PATH"] = place.Drive
|
||||
data["RestorePrefillHDDPath"] = place.Drive
|
||||
}
|
||||
data["RestoreFieldValues"] = prefill
|
||||
data["RestoreRecordedDrive"] = place.Drive
|
||||
data["RestoreRecordedAddress"] = addr.FQDN()
|
||||
data["RestoreHasRecord"] = place.Known() || addr.Known()
|
||||
}
|
||||
}
|
||||
// Storage paths with free space info for deploy dropdown
|
||||
var deployPaths []DeployStoragePath
|
||||
for _, sp := range s.settings.GetSchedulableStoragePaths() {
|
||||
|
||||
Reference in New Issue
Block a user