5a3437669f
gates / gates (push) Successful in 27s
Image retention: after a done/undone guarded Update and at remove, an app's images older than its running and previous one are deleted — never an image any container, installed compose or installed/previous record names (box-wide keep set read at delete time); exact id, never forced or pruned; paused while any update runs; a one-time sweep of catalog app images at the first start. Install hold: an after_install app is installed behind the setup gate's door and opens when after_install succeeds or the household says it changed the login. Tests TestImageRetention_* and TestInstallHold_* with red-proofs; parity fixture for the held card. MinAgent: 0.131.0 (unchanged). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
50 lines
2.8 KiB
Go
50 lines
2.8 KiB
Go
package stacks
|
|
|
|
import "log"
|
|
|
|
// carryLifeRecords copies, from the app.yaml a restore is about to replace (prior, as on disk), the records
|
|
// that describe the app's LIFE on this box rather than its definition (R-697, v0.276.0). The restore's
|
|
// write is a fresh AppConfig by design — the env, the locked fields and the pin come from the unit — and it
|
|
// used to drop these with it:
|
|
//
|
|
// - conversion_copy + earlier_conversion_copies: a kept pre-conversion datadir copy whose record is dropped
|
|
// is never released (R-697, measured on 9202). A restore does not remove the volume, so it must not
|
|
// forget it either.
|
|
// - desired_state: the household's intent. Dropped, a dead app after a restore was read as "unknown
|
|
// intent" and never alarmed (R-166's absent case).
|
|
// - failed_update_step, last_update_undone, last_auto_update: the ladder's history on THIS box; the
|
|
// automatic leg must not re-press a step that already failed here because a restore happened.
|
|
//
|
|
// NOT carried: pinned_images (the restore pins to the unit's definition right after — carrying the old pin
|
|
// would freeze a failed SetPin onto the wrong version), installed_images (an observation of what ran
|
|
// before the restore, possibly another version), restored_logins (computed per restore).
|
|
// Pinned by TestR697_ARestoreKeepsTheConversionCopyRecordSoTheCopyIsReleased.
|
|
func carryLifeRecords(logger *log.Logger, name string, prior, cfg *AppConfig) {
|
|
if prior == nil {
|
|
return
|
|
}
|
|
cfg.ConversionCopy = prior.ConversionCopy
|
|
cfg.EarlierConversionCopies = prior.EarlierConversionCopies
|
|
if cfg.DesiredState == "" {
|
|
cfg.DesiredState = prior.DesiredState
|
|
}
|
|
cfg.FailedStep = prior.FailedStep
|
|
cfg.LastUpdateUndone = prior.LastUpdateUndone
|
|
cfg.LastAutoUpdate = prior.LastAutoUpdate
|
|
cfg.AfterInstall = prior.AfterInstall // v0.279.0: what the install's one-time command did stays true after a restore
|
|
// v0.280.0 (decision 46): the setup gate is the app's life here too. A restore never re-gates an app whose gate
|
|
// opened; a gate that was still closed stays closed (its probe opens it if the restored data is set up).
|
|
// No prior record (a removed app, kept data, a rebuilt guest) = no gate: the data comes back with its admin.
|
|
cfg.SetupGate = prior.SetupGate
|
|
cfg.InstallHold = prior.InstallHold // R-741: the loop opens it when the restored record says the login was replaced
|
|
cfg.DefaultLogin = prior.DefaultLogin
|
|
cfg.AfterSetup = prior.AfterSetup
|
|
if n := len(prior.EarlierConversionCopies); prior.ConversionCopy != nil || n > 0 {
|
|
cur := ""
|
|
if prior.ConversionCopy != nil {
|
|
cur = prior.ConversionCopy.Copy
|
|
}
|
|
logger.Printf("[INFO] [stacks] %s: the restore keeps the record of the kept pre-conversion copy %q (+%d earlier) — it is released when a backup written by the converted engine is proven", name, cur, n)
|
|
}
|
|
}
|