v0.284.0 — a box deletes old app images (decision 53, R-736); an after_install app is held until its known login is replaced (R-741)
gates / gates (push) Successful in 27s
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
This commit is contained in:
@@ -757,6 +757,13 @@ func (m *Manager) runGuardedUpdate(ctx context.Context, name string) {
|
||||
dir := filepath.Dir(st.ComposePath)
|
||||
g := m.guards()
|
||||
entry := updateJournalEntry{StartedAt: start}
|
||||
// decision 53: what the app ran before this press — kept as the previous image if the update ends done.
|
||||
if st.AppConfig != nil && len(st.AppConfig.InstalledImages) > 0 {
|
||||
entry.BeforeImages = make(map[string]InstalledImage, len(st.AppConfig.InstalledImages))
|
||||
for k, v := range st.AppConfig.InstalledImages {
|
||||
entry.BeforeImages[k] = v
|
||||
}
|
||||
}
|
||||
fail := func(key, detail string, args ...interface{}) {
|
||||
m.logger.Printf("[ERROR] [stacks] update %s FAILED in phase %s after %s — nothing was moved: %s", name, entry.Phase, m.now().Sub(start).Round(time.Millisecond), detail)
|
||||
m.clearJournal(name)
|
||||
@@ -1031,6 +1038,7 @@ func (m *Manager) verifyAndConclude(ctx context.Context, name, dir string, env [
|
||||
}
|
||||
m.finishUpdate(name, UpdatePhaseDone, "")
|
||||
m.logger.Printf("[INFO] [stacks] update %s: DONE in %s", name, m.now().Sub(start).Round(time.Second))
|
||||
m.retainAfterUpdate(name, entry.BeforeImages) // decision 53 (R-736)
|
||||
}
|
||||
|
||||
// holdLogTailLines is how much of each service's log the hold keeps. 400 lines is enough to hold a
|
||||
@@ -1259,12 +1267,14 @@ func (m *Manager) waitUpdateHealthyMeta(ctx context.Context, name string, timeou
|
||||
// ── the journal ──────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
type updateJournalEntry struct {
|
||||
Phase string `json:"phase"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
PrevPin map[string]string `json:"prev_pin,omitempty"`
|
||||
PrevCompose string `json:"prev_compose,omitempty"`
|
||||
PrevApplied string `json:"prev_applied,omitempty"`
|
||||
ProvenCopyAt string `json:"proven_copy_at,omitempty"`
|
||||
Phase string `json:"phase"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
PrevPin map[string]string `json:"prev_pin,omitempty"`
|
||||
// BeforeImages (decision 53): installed_images at the press — the previous image kept if the update ends done.
|
||||
BeforeImages map[string]InstalledImage `json:"before_images,omitempty"`
|
||||
PrevCompose string `json:"prev_compose,omitempty"`
|
||||
PrevApplied string `json:"prev_applied,omitempty"`
|
||||
ProvenCopyAt string `json:"proven_copy_at,omitempty"`
|
||||
// ProvenTier (R-475) — which tier ProvenCopyAt belongs to, so a resumed update that fails names
|
||||
// the right copy. 0 in a journal written by v0.238.1 or older.
|
||||
ProvenTier int `json:"proven_tier,omitempty"`
|
||||
|
||||
Reference in New Issue
Block a user