From db4eff3885121e2075ab8a5241faa52abeee62df Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 6 Nov 2026 01:51:16 +0100 Subject: [PATCH] A composer's cache key covers the mesh package versions its ask names (issue 1921) Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- go/delivery/stepasks.go | 16 ++++++++++++--- go/delivery/stepasks_compose_test.go | 29 +++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/go/delivery/stepasks.go b/go/delivery/stepasks.go index bf8aa49..d0bf7e9 100644 --- a/go/delivery/stepasks.go +++ b/go/delivery/stepasks.go @@ -310,9 +310,19 @@ type ComposeAnswer struct { // ComposerKey is the key a build module keeps a built composer under: the repository, commit, directory and package it // was built from and the toolchain it was built with, hashed (`composer-` and 16 hex digits), so each head is built once -// on a node (ADR 0482 clause 4). -func ComposerKey(repository, commit, dir, pkg string, toolchain ToolchainUsed) string { - sum := sha256.Sum256([]byte(strings.Join([]string{repository, commit, dir, pkg, toolchain.Version, toolchain.Recipe}, "\n"))) +// on a node (ADR 0482 clause 4). The mesh package versions the ask names are an input of the build, so they are in the +// key, sorted, when there are any; an ask that names none gets the key it always had (issue 1921). +func ComposerKey(repository, commit, dir, pkg string, toolchain ToolchainUsed, versions PackageVersions) string { + parts := []string{repository, commit, dir, pkg, toolchain.Version, toolchain.Recipe} + pkgs := make([]string, 0, len(versions)) + for p := range versions { + pkgs = append(pkgs, p) + } + sort.Strings(pkgs) + for _, p := range pkgs { + parts = append(parts, p+"="+versions[p]) + } + sum := sha256.Sum256([]byte(strings.Join(parts, "\n"))) return "composer-" + hex.EncodeToString(sum[:8]) } diff --git a/go/delivery/stepasks_compose_test.go b/go/delivery/stepasks_compose_test.go index a9547f0..765f4c7 100644 --- a/go/delivery/stepasks_compose_test.go +++ b/go/delivery/stepasks_compose_test.go @@ -56,18 +56,37 @@ func TestAComposeAskIsRefusedWhereItDoesNotBelong(t *testing.T) { } } +// 1921: the asked versions are in the key, in any order of naming; none gives the key without them. +func TestAComposerKeyCoversTheAskedVersions(t *testing.T) { + tc := ToolchainUsed{Version: "go1.26.0", Recipe: "3"} + a, b := strings.Repeat("a", 40), strings.Repeat("b", 40) + v1 := PackageVersions{"m/x": a, "m/y": b} + v2 := PackageVersions{"m/x": b, "m/y": a} + k1 := ComposerKey("r", askCommit, "go", "p", tc, v1) + if k1 != ComposerKey("r", askCommit, "go", "p", tc, PackageVersions{"m/y": b, "m/x": a}) { + t.Error("the key depends on the order of the versions") + } + if k1 == ComposerKey("r", askCommit, "go", "p", tc, v2) || k1 == ComposerKey("r", askCommit, "go", "p", tc, nil) { + t.Error("other versions, or none, share the key") + } + if ComposerKey("r", askCommit, "go", "p", tc, nil) != ComposerKey("r", askCommit, "go", "p", tc, PackageVersions{}) { + t.Error("an empty ask changed the key") + } +} + // 1622/AC3: a composer is kept under one key per repository, commit, directory, package and toolchain, so two asks of // one head build it once on a node, and another head or toolchain builds its own. func TestAComposerIsKeptByWhatItWasBuiltFrom(t *testing.T) { tc := ToolchainUsed{Version: "go1.26.0", Recipe: "3"} - k := ComposerKey("novox/mesh-sdk", askCommit, "go", "p", tc) - if k != ComposerKey("novox/mesh-sdk", askCommit, "go", "p", tc) || !strings.HasPrefix(k, "composer-") || len(k) != len("composer-")+16 { + k := ComposerKey("novox/mesh-sdk", askCommit, "go", "p", tc, nil) + if k != ComposerKey("novox/mesh-sdk", askCommit, "go", "p", tc, nil) || !strings.HasPrefix(k, "composer-") || len(k) != len("composer-")+16 { t.Fatalf("key = %q", k) } for _, other := range []string{ - ComposerKey("novox/mesh-sdk", strings.Repeat("1", 40), "go", "p", tc), - ComposerKey("novox/mesh-controller", askCommit, ".", "p", tc), - ComposerKey("novox/mesh-sdk", askCommit, "go", "p", ToolchainUsed{Version: "go1.26.1", Recipe: "3"}), + ComposerKey("novox/mesh-sdk", strings.Repeat("1", 40), "go", "p", tc, nil), + ComposerKey("novox/mesh-controller", askCommit, ".", "p", tc, nil), + ComposerKey("novox/mesh-sdk", askCommit, "go", "p", ToolchainUsed{Version: "go1.26.1", Recipe: "3"}, nil), + ComposerKey("novox/mesh-sdk", askCommit, "go", "p", tc, PackageVersions{"git.novox.be/novox/mesh-sdk/go": strings.Repeat("2", 40)}), } { if other == k { t.Errorf("two composers built from different inputs share the key %s", k)