carlos / platform Public

release: canary/x pointer-protection test + DeleteVersion race note

TestPruneEligibleProtectsCanaryBranchPointerAndHistory pins the
TrimPrefix+CutSuffix nested-channel-name parsing in PruneEligible against
a channel that itself contains a slash — production fleets run on
canary/* today and nothing exercised the nested case. Verified non-vacuous
by temporarily breaking the parse (skip names containing "/") and
confirming the new test goes red before reverting.

DeleteVersion's comment gets one more sentence: a concurrent re-ship of
the same version name can interleave with a prune (Ship writes artifacts
first, manifest last; DeleteVersion deletes manifest first from a
pre-race listing), leaving a manifest-present/artifacts-missing state
until the next tick re-deletes it — same accepted-TOCTOU family as the
spec's promote-vs-prune ruling.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 3dff97fb14e0d78b993d50720ef53dcedea6208d parent e19fdf1
2 files changed, +38 −0
  • internal/release/prune.go +7 −0
  • internal/release/prune_test.go +31 −0
diff --git a/internal/release/prune.go b/internal/release/prune.go
index 5091440cb..28f2e543a 100644
--- a/internal/release/prune.go
+++ b/internal/release/prune.go
@@ -103,6 +103,13 @@ func PruneEligible(ctx context.Context, s Store, pub ed25519.PublicKey, acct, ap
// error row), never as a complete version with missing artifacts. Built
// from idempotent Deletes, so re-running after a partial failure — or
// racing another box — finishes the job instead of erroring.
+//
+// A concurrent re-ship of the same version name can still interleave here:
+// Ship writes artifacts first and the manifest last, while DeleteVersion
+// deletes the manifest first from a pre-race listing — the window can leave
+// a manifest-present/artifacts-missing state until the next prune tick
+// re-deletes it. Same accepted-TOCTOU family as the spec's promote-vs-prune
+// ruling.
func DeleteVersion(ctx context.Context, s Store, acct, app, version string) error {
if !ValidAccount(acct) {
return fmt.Errorf("delete version: invalid account %q", acct)
diff --git a/internal/release/prune_test.go b/internal/release/prune_test.go
index 2fa121555..25a2f2f0f 100644
--- a/internal/release/prune_test.go
+++ b/internal/release/prune_test.go
@@ -129,6 +129,37 @@ func TestPruneEligibleProtectsPointerVersionAndHistory(t *testing.T) {
}
}
+func TestPruneEligibleProtectsCanaryBranchPointerAndHistory(t *testing.T) {
+ _, s, now := pruneFixture(t, 25)
+ seedB64, pubB64, _ := GenerateKey()
+ priv, _ := ParseSigningKey(seedB64, "test")
+ pub, _ := ParsePublicKey(pubB64, "test")
+ ctx := context.Background()
+ // Promote v01 then v02 onto the nested channel name canary/x: pointer
+ // names v02, History carries v01. Pins the TrimPrefix+CutSuffix parsing
+ // in PruneEligible against a channel name that itself contains a slash —
+ // production fleets run on canary/* channels today.
+ if err := Promote(ctx, s, priv, "bab", "kass", "canary/x", "v01"); err != nil {
+ t.Fatal(err)
+ }
+ if err := Promote(ctx, s, priv, "bab", "kass", "canary/x", "v02"); err != nil {
+ t.Fatal(err)
+ }
+ got, err := PruneEligible(ctx, s, pub, "bab", "kass", 20, now, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, v := range got {
+ if v == "v01" || v == "v02" {
+ t.Fatalf("eligible = %v, want v01 and v02 protected (canary/x pointer + history)", got)
+ }
+ }
+ want := []string{"v03", "v04", "v05"}
+ if strings.Join(got, ",") != strings.Join(want, ",") {
+ t.Fatalf("eligible = %v, want %v (canary/x pointer + history protected)", got, want)
+ }
+}
+
func TestPruneEligibleProtectsTagReleases(t *testing.T) {
_, s, now := pruneFixture(t, 25)
_, pubB64, _ := GenerateKey()