v0.235.0: a delivered fix must also refresh the stored definition
gates / gates (push) Successful in 14s

Found by the LIVE validation on demo-hp, not by review. Scenario A passed - a
non-image catalog change reached the pinned app on the real 15-minute cycle - and
that is exactly what exposed the gap: the stored applied-compose.yml is written
when the PIN is written, so the fix landed in the live compose file and not in the
store. The first time the catalog then moved a version, the freeze would have
rendered the pre-fix definition and reverted every fix delivered since - silently
undoing the half of the operator's ruling that says fixes keep flowing.

The equal-images branch now refreshes the store as it delivers. The images cannot
move in that branch by construction, so no version moves and no intent is
rewritten. RenderPlan gains StackDir so the syncer can write it.

TestFixRefreshesTheStoredDefinition asserts both halves: the fix reaches the
store, and it survives the freeze that follows.
This commit is contained in:
2026-09-06 10:04:21 +02:00
parent 8a0e0a59ad
commit 2a56f557d0
4 changed files with 95 additions and 13 deletions
+6
View File
@@ -180,6 +180,11 @@ type RenderPlan struct {
Protected bool
Pinned map[string]string // service -> image ref; nil/empty means UNPINNED
AppliedPath string // the stored definition; "" when none is stored
// StackDir lets the syncer REFRESH the stored definition when it copies the catalog verbatim
// into a pinned app whose images still match. See Syncer.renderSource — without that refresh the
// stored definition goes stale relative to the fixes that flowed after it, and the freeze would
// later undo them. Found by the live validation of v0.235.0, not by review.
StackDir string
}
// RenderPlanFor answers for one app by name. Unknown apps come back as an empty plan, which the
@@ -198,6 +203,7 @@ func (m *Manager) RenderPlanFor(name string) RenderPlan {
plan.Pinned = s.AppConfig.PinnedImages
}
stackDir := filepath.Dir(s.ComposePath)
plan.StackDir = stackDir
if _, err := LoadAppliedDefinition(stackDir); err == nil {
plan.AppliedPath = AppliedComposePath(stackDir)
}