v0.269.1: an installed app keeps the image digest it runs until a guarded Update moves it
gates / gates (push) Successful in 26s
gates / gates (push) Successful in 26s
Found live on 9202 (night 2026-09-24 Part B): the sync rendered the ladder's newest tested digest into a RUNNING app's compose, so the next restart would pull a new image with no backup and no undo. stacks.CarryDigests keeps the running digest for an installed app; a fresh install still takes the tested digest. Red-proofed. 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:
@@ -103,6 +103,57 @@ func RenderWithLadderDigests(templateDir string, compose []byte) []byte {
|
||||
return renderDigests(compose, e.Digest)
|
||||
}
|
||||
|
||||
// composeImageLines returns each service's OWN image reference as written, digest included — the same
|
||||
// line walk as renderDigests.
|
||||
func composeImageLines(compose []byte) map[string]string {
|
||||
out := map[string]string{}
|
||||
svc, inServices := "", false
|
||||
for _, l := range strings.Split(string(compose), "\n") {
|
||||
if strings.HasPrefix(l, "services:") {
|
||||
inServices = true
|
||||
continue
|
||||
}
|
||||
if l != "" && !strings.HasPrefix(l, " ") && !strings.HasPrefix(l, "#") {
|
||||
inServices = false
|
||||
}
|
||||
if !inServices {
|
||||
continue
|
||||
}
|
||||
if m := serviceLineRe.FindStringSubmatch(l); m != nil {
|
||||
svc = m[1]
|
||||
continue
|
||||
}
|
||||
if m := imageLineRe.FindStringSubmatch(l); m != nil && svc != "" {
|
||||
if _, seen := out[svc]; !seen {
|
||||
out[svc] = m[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// CarryDigests is the syncer's rule for a DEPLOYED app: the catalog compose, with the digest the app's
|
||||
// CURRENT file already names for each service whose reference did not change — and no other. The digest
|
||||
// is part of what the app runs, so only a guarded update (advancePinTo) may move it. Rendering the
|
||||
// ladder's newest digest here instead let a sync change the image under a running app: the next restart
|
||||
// pulled it with no backup and no undo (MEASURED on 9202, night 2026-09-24 Part B,
|
||||
// `audits/night-2026-09-24/B/10-floating-tag.*`). Pinned by TestDigest_SyncerKeepsTheRunningDigest.
|
||||
func CarryDigests(compose, current []byte) []byte {
|
||||
cur := composeImageLines(current)
|
||||
next := composeImageLines(compose)
|
||||
keep := map[string]string{}
|
||||
for svc, ref := range cur {
|
||||
at := strings.LastIndex(ref, "@")
|
||||
if at < 0 || !digestRe.MatchString(ref[at+1:]) {
|
||||
continue
|
||||
}
|
||||
if n, ok := next[svc]; ok && StripDigest(n) == ref[:at] {
|
||||
keep[svc] = ref[at+1:]
|
||||
}
|
||||
}
|
||||
return renderDigests(compose, keep)
|
||||
}
|
||||
|
||||
// catalogTestedDigests is the badge's input: the tested digest per service of the catalog's current
|
||||
// refs, and when that test ran. Empty when the ladder has no entry for them.
|
||||
func catalogTestedDigests(templateDir string, catalogRefs map[string]string) (map[string]string, time.Time) {
|
||||
|
||||
Reference in New Issue
Block a user