controller v0.269.0: whole restore from the second drive; crash loops stopped; exact image digests; steps judged by their own .felhom.yml (decisions 26-28, R-661 R-666 R-667 R-668 R-664 R-665 R-662, 09 6.4 part 6)
gates / gates (push) Successful in 27s

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-09-24 12:18:39 +02:00
parent 7c3b3a9694
commit 3c6b49b31c
141 changed files with 3401 additions and 237 deletions
+14 -3
View File
@@ -92,16 +92,27 @@ func ParseComposeImages(composePath string) (map[string]string, error) {
if err != nil {
return nil, fmt.Errorf("reading compose file: %w", err)
}
out, err := parseComposeImagesBytes(data)
if err != nil {
return nil, fmt.Errorf("parsing compose file %s: %w", composePath, err)
}
return out, nil
}
// parseComposeImagesBytes is ParseComposeImages over bytes. v0.269.0 (`09` §6.4 part 6): a rendered
// `name:tag@sha256:…` is returned WITHOUT its digest — pins, the ladder, the badge and the syncer compare
// plain refs; the digest lives only in the compose file that runs (digest.go).
func parseComposeImagesBytes(data []byte) (map[string]string, error) {
var doc composeImagesDoc
if err := yaml.Unmarshal(data, &doc); err != nil {
return nil, fmt.Errorf("parsing compose file %s: %w", composePath, err)
return nil, err
}
out := make(map[string]string, len(doc.Services))
for svc, def := range doc.Services {
if strings.TrimSpace(def.Image) == "" {
continue
}
out[svc] = strings.TrimSpace(def.Image)
out[svc] = StripDigest(strings.TrimSpace(def.Image))
}
return out, nil
}
@@ -212,7 +223,7 @@ func (m *Manager) observeInstalledImages(stackDir string, env []string) (map[str
continue
}
out[svc] = InstalledImage{
Ref: f.ref,
Ref: StripDigest(f.ref), // v0.269.0: a digest-pinned run records the plain ref; the digest is Digest
Digest: pickDigest(f.ref, digests[f.imageID]),
At: now,
}