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
+80
View File
@@ -0,0 +1,80 @@
package stacks
import (
"path/filepath"
"strings"
"testing"
"time"
)
var dA = "sha256:" + strings.Repeat("a", 64)
var dB = "sha256:" + strings.Repeat("b", 64)
func TestDigest_RenderAndStrip(t *testing.T) {
in := "# header\nservices:\n web:\n image: redis:7-alpine # pinned\n restart: unless-stopped\n db:\n image: \"mariadb:11.8\"\nvolumes:\n x:\n"
out := string(renderDigests([]byte(in), map[string]string{"web": dA, "db": "not-a-digest"}))
if !strings.Contains(out, " image: redis:7-alpine@"+dA+" # pinned") {
t.Fatalf("web not rendered:\n%s", out)
}
if !strings.Contains(out, " image: \"mariadb:11.8\"") {
t.Fatalf("an invalid digest must leave the line alone:\n%s", out)
}
again := string(renderDigests([]byte(out), map[string]string{"web": dB}))
if strings.Count(again, "@sha256:") != 1 || !strings.Contains(again, "redis:7-alpine@"+dB) {
t.Fatalf("re-render must replace, not stack, the digest:\n%s", again)
}
imgs, err := parseComposeImagesBytes([]byte(again))
if err != nil || imgs["web"] != "redis:7-alpine" {
t.Fatalf("the parsed ref must be digest-free: %v %v", imgs, err)
}
}
// TestDigest_FloatingTagBehindOnlyForANewerTestedDigest — the badge rule, every arm.
//
// COMPANION RED-PROOF (REPORT): make digestBehind return false — the "newer test" case then reads
// Current and this test fails.
func TestDigest_FloatingTagBehindOnlyForANewerTestedDigest(t *testing.T) {
installAt := time.Date(2026, 9, 20, 0, 0, 0, 0, time.UTC)
mk := func(instDigest, instAt string, cat map[string]string, testedAt time.Time) Stack {
return Stack{Deployed: true, CatalogImages: map[string]string{"web": "redis:7-alpine"},
CatalogDigests: cat, CatalogTestedAt: testedAt,
AppConfig: &AppConfig{InstalledImages: map[string]InstalledImage{"web": {Ref: "redis:7-alpine", Digest: instDigest, At: instAt}}}}
}
cases := []struct {
name string
s Stack
want UpdateOrder
}{
{"same digest", mk(dA, installAt.Format(time.RFC3339), map[string]string{"web": dA}, installAt.Add(time.Hour)), UpdateOrderCurrent},
{"newer tested digest", mk(dA, installAt.Format(time.RFC3339), map[string]string{"web": dB}, installAt.Add(time.Hour)), UpdateOrderBehind},
{"test older than the install", mk(dA, installAt.Format(time.RFC3339), map[string]string{"web": dB}, installAt.Add(-time.Hour)), UpdateOrderCurrent},
{"install time unknown", mk(dA, "", map[string]string{"web": dB}, installAt.Add(time.Hour)), UpdateOrderCurrent},
{"installed digest unknown", mk("", installAt.Format(time.RFC3339), map[string]string{"web": dB}, installAt.Add(time.Hour)), UpdateOrderCurrent},
{"no tested digest", mk(dA, installAt.Format(time.RFC3339), nil, time.Time{}), UpdateOrderCurrent},
}
for _, c := range cases {
if got := CatalogOrder(c.s); got != c.want {
t.Errorf("%s: %v, want %v", c.name, got, c.want)
}
}
}
// The update runs EXACTLY the tested image: the pinned step's compose carries the entry's digest, while
// the pin itself stays a plain ref.
func TestDigest_TheUpdateRendersTheStepsTestedDigest(t *testing.T) {
m, dir, _, _, _ := ladderManager(t, true)
catDir := filepath.Dir(m.CatalogTemplatePath("nextcloud", "docker-compose.yml"))
// the ladder fixture's digests are sha256:aaaa…
if err := m.StartGuardedUpdate("nextcloud"); err != nil {
t.Fatal(err)
}
waitUpdateDone(t, m, "nextcloud")
body := fileBody(t, ComposePathIn(dir))
if !strings.Contains(body, "image: "+ladderB+"@"+dA) {
t.Fatalf("the live compose does not pin the tested digest:\n%s", body)
}
if pinOf(t, dir) != ladderB {
t.Fatalf("the pin must stay a plain ref, got %q", pinOf(t, dir))
}
_ = catDir
}