R-773: a removed app restored from its backup gets its sign-up lock back (record opened_by restore + block, before anything starts)
gates / gates (push) Successful in 29s

An installed app the household never closed keeps what it had (decision 49). Red-proof RP-D2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-01 21:01:42 +02:00
parent b53721db34
commit 1e216d3468
4 changed files with 106 additions and 1 deletions
+15 -1
View File
@@ -764,7 +764,21 @@ func (m *Manager) PersistUnitRedeployConfig(name string, env map[string]string)
}
}
cfg.RestoredLogins = restoredLoginFields(name, meta, prior, env)
carryLifeRecords(m.logger, name, LoadAppConfig(stackDir), cfg)
priorRaw := LoadAppConfig(stackDir)
carryLifeRecords(m.logger, name, priorRaw, cfg)
// R-773: a REMOVED app has no app.yaml left, so nothing carries its sign-up lock (decision 47) and the restore
// used to bring it back with sign-up open (measured on 9202: Karakeep's /signup 403 before, 200 after remove +
// restore). The restored data comes back with its admin, so the restore applies what a fresh install gets after
// its setup: the lock record and its block, written HERE — before anything starts. An app that was never removed
// keeps exactly the record it had (carryLifeRecords): a restore never adds a lock to an installed app that the
// household has not closed (decision 49). Pinned by TestR773_*.
if priorRaw == nil && cfg.SetupGate == nil {
rec, err := m.restoreSignupLock(name, stack.ComposePath, env, &meta)
if err != nil {
return fmt.Errorf("the sign-up lock could not be prepared (the app was not started): %w", err)
}
cfg.SetupGate = rec
}
if len(cfg.RestoredLogins) > 0 {
m.logger.Printf("[INFO] [stacks] %s: the restore generated %v — the app's own login came back with its data; the page will not show the new value as the password", name, cfg.RestoredLogins)
}