carlos / platform Public

zdd-4 task 6: D3 binding tests reach bind through a kept stale copy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev c9d90fe77e8993a4feab8dc1f12ecd2e478fc805 parent 1cb3b09
2 files changed, +64 −33
  • internal/release/materializeheld_proof_test.go +10 −17
  • internal/release/proofgate_test.go +54 −16
diff --git a/internal/release/materializeheld_proof_test.go b/internal/release/materializeheld_proof_test.go
index fb88470bf..e5a71a059 100644
--- a/internal/release/materializeheld_proof_test.go
+++ b/internal/release/materializeheld_proof_test.go
@@ -208,18 +208,17 @@ func TestHeldStageProofBrokenPolicyIsRefused(t *testing.T) {
}
// bind: the manifest's sha256 is proven, but the staged file holds other
-// bytes. Refused before publishing, with adoption's not-proven record.
+// bytes (a kept stale copy: see stageStaleCopyKept). Refused before
+// publishing, with adoption's not-proven record.
func TestHeldStageProofBindingRefusesOtherStagedBytes(t *testing.T) {
e := newHeldProofEnv(t)
e.require(t, "console")
e.prove(t, e.pinnedSHA)
stale := []byte("not the shipped bytes")
- if err := StageArtifact(e.opt, "bab", "v1", "console", stale); err != nil {
- t.Fatal(err)
- }
+ down := stageStaleCopyKept(t, e.ctx, e.store, e.opt, "bab", "console", "v1", stale)
// bind runs inside the publication (fix round 1), so the guard is asked
// and the refusal comes from within it.
- err, _, out := e.materialize(t, e.store)
+ err, _, out := e.materialize(t, down)
if !errors.Is(err, ErrHeldNotProven) || e.live() != "v0" {
t.Fatalf("MaterializeHeld: %v, live %q; want ErrHeldNotProven and v0", err, e.live())
}
@@ -377,10 +376,8 @@ func TestHeldStageProofRefusalCarriesTheMove(t *testing.T) {
e := newHeldProofEnv(t)
e.require(t, "console")
e.prove(t, e.pinnedSHA)
- if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("not the shipped bytes")); err != nil {
- t.Fatal(err)
- }
- e.materialize(t, e.store)
+ down := stageStaleCopyKept(t, e.ctx, e.store, e.opt, "bab", "console", "v1", []byte("not the shipped bytes"))
+ e.materialize(t, down)
r, _ := ReadRefusal(HeldRefusalBase(e.bake), heldTestHost)
if r == nil || r.MoveOp != heldTestOp || r.Kind != RefusalNotProven {
t.Fatalf("refusal = %+v, want a not-proven record naming op %s", r, heldTestOp)
@@ -404,10 +401,8 @@ func TestHeldStageProofLeavesAnAdoptionRefusal(t *testing.T) {
t.Fatal(err)
}
before, _ := os.ReadFile(filepath.Join(RefusalDir(e.bake), heldTestHost+".json"))
- if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("not the shipped bytes")); err != nil {
- t.Fatal(err)
- }
- err, _, _ := e.materialize(t, e.store)
+ down := stageStaleCopyKept(t, e.ctx, e.store, e.opt, "bab", "console", "v1", []byte("not the shipped bytes"))
+ err, _, _ := e.materialize(t, down)
if !errors.Is(err, ErrHeldNotProven) {
t.Fatalf("MaterializeHeld: %v, want ErrHeldNotProven", err)
}
@@ -426,10 +421,8 @@ func TestHeldStageProofSuccessRetiresTheMovesRefusal(t *testing.T) {
e := newHeldProofEnv(t)
e.require(t, "console")
e.prove(t, e.pinnedSHA)
- if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("not the shipped bytes")); err != nil {
- t.Fatal(err)
- }
- e.materialize(t, e.store)
+ down := stageStaleCopyKept(t, e.ctx, e.store, e.opt, "bab", "console", "v1", []byte("not the shipped bytes"))
+ e.materialize(t, down)
if r, _ := ReadRefusal(HeldRefusalBase(e.bake), heldTestHost); r == nil {
t.Fatal("no refusal to retire")
}
diff --git a/internal/release/proofgate_test.go b/internal/release/proofgate_test.go
index 5cf68faaf..08b1f75aa 100644
--- a/internal/release/proofgate_test.go
+++ b/internal/release/proofgate_test.go
@@ -118,6 +118,46 @@ func (e *proofEnv) putAt(t *testing.T, sha string, p *Proof) {
}
}
+// manifestDownStore answers every other key from the wrapped store but fails
+// reads of one manifest with an error that is not a not-found, as a bucket
+// that is unreachable for that object would.
+type manifestDownStore struct {
+ Store
+ key string
+}
+
+func (m manifestDownStore) Get(ctx context.Context, key string) ([]byte, string, error) {
+ if key == m.key {
+ return nil, "", errors.New("bucket unreachable for the manifest")
+ }
+ return m.Store.Get(ctx, key)
+}
+
+// stageStaleCopyKept stages stale as the binary of acct/app/ver, with the
+// canonical manifest (the store's exact bytes) as its sibling, and returns s
+// wrapped so that manifest cannot be read from the bucket.
+//
+// This is how stale bytes reach the binding check since staging hashes a copy
+// it reuses (spec section 4.2): an unreferenced copy that differs from its
+// release is restaged, so the check only still sees different bytes where the
+// canonical manifest cannot be read, a case ensureStagedChecked leaves as
+// staged. The sibling lets the proof gate still learn the manifest's sha256.
+func stageStaleCopyKept(t *testing.T, ctx context.Context, s Store, opt, acct, app, ver string, stale []byte) Store {
+ t.Helper()
+ key := ManifestKey(acct, app, ver)
+ raw, _, err := s.Get(ctx, key)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := StageArtifact(opt, acct, ver, app, stale); err != nil {
+ t.Fatal(err)
+ }
+ if err := StageManifest(opt, acct, ver, app, raw); err != nil {
+ t.Fatal(err)
+ }
+ return manifestDownStore{Store: s, key: key}
+}
+
type proofReadCountingStore struct {
Store
mu sync.Mutex
@@ -232,8 +272,10 @@ func TestProofHealthyHistoryAdmitsWithoutProof(t *testing.T) {
}
}
-// Review focus 2: the manifest's sha256 is proven, but the staged file (a
-// cache hit, which ensureStaged does not re-hash) holds other bytes.
+// Review focus 2: the manifest's sha256 is proven, but the staged file holds
+// other bytes. They reach the binding check through a kept stale copy: the
+// bucket cannot answer for the manifest, so staging leaves the copy as staged
+// (stageStaleCopyKept).
//
// Ruling 3: the record is its own kind, recovered and still on the old
// build, because nothing was restarted. The next pass's gate line says the
@@ -243,11 +285,9 @@ func TestProofBindingRefusesStagedBytesThatDiffer(t *testing.T) {
e := newProofEnv(t)
e.requireDev(t)
stale := []byte("not the shipped bytes")
- if err := StageArtifact(e.opt, "bab", "new", "console", stale); err != nil {
- t.Fatal(err)
- }
+ down := stageStaleCopyKept(t, e.ctx, e.store, e.opt, "bab", "console", "new", stale)
e.putAt(t, e.newSHA, e.signed(t, e.prover, "console", e.newSHA, "dev"))
- out := e.pass(t, e.store)
+ out := e.pass(t, down)
if e.live() != "old" || !strings.Contains(out, "not pointed live") {
t.Fatalf("live = %q, printed %q; want old and the binding refusal", e.live(), out)
}
@@ -272,7 +312,8 @@ func TestProofBindingRefusesStagedBytesThatDiffer(t *testing.T) {
}
// Final review triage (Task 4): the healthy history admits the manifest's
-// sha256, but the staged file holds other bytes. bind must ask the history
+// sha256, but the staged file holds other bytes (a kept stale copy, as in
+// TestProofBindingRefusesStagedBytesThatDiffer). bind must ask the history
// about the staged hash, not the manifest's, so the build is refused.
func TestProofHealthyManifestWithOtherStagedBytesIsRefused(t *testing.T) {
e := newProofEnv(t)
@@ -281,10 +322,8 @@ func TestProofHealthyManifestWithOtherStagedBytesIsRefused(t *testing.T) {
t.Fatal(err)
}
stale := []byte("not the shipped bytes")
- if err := StageArtifact(e.opt, "bab", "new", "console", stale); err != nil {
- t.Fatal(err)
- }
- out := e.pass(t, e.store)
+ down := stageStaleCopyKept(t, e.ctx, e.store, e.opt, "bab", "console", "new", stale)
+ out := e.pass(t, down)
if e.live() != "old" || !strings.Contains(out, "not pointed live") {
t.Fatalf("live = %q, printed %q; want old and the binding refusal", e.live(), out)
}
@@ -517,16 +556,15 @@ func TestProofGateNilIsOff(t *testing.T) {
}
// bindingRefusal is TestProofBindingRefusesStagedBytesThatDiffer's first
-// pass: a proven manifest, other bytes staged, a RefusalNotProven written.
+// pass: a proven manifest, other bytes staged (kept, see stageStaleCopyKept),
+// a RefusalNotProven written.
func bindingRefusal(t *testing.T) *proofEnv {
t.Helper()
e := newProofEnv(t)
e.requireDev(t)
- if err := StageArtifact(e.opt, "bab", "new", "console", []byte("not the shipped bytes")); err != nil {
- t.Fatal(err)
- }
+ down := stageStaleCopyKept(t, e.ctx, e.store, e.opt, "bab", "console", "new", []byte("not the shipped bytes"))
e.putAt(t, e.newSHA, e.signed(t, e.prover, "console", e.newSHA, "dev"))
- e.pass(t, e.store)
+ e.pass(t, down)
if r, _ := ReadRefusal(e.bake, proofHost); r == nil || r.Kind != RefusalNotProven || e.live() != "old" {
t.Fatalf("refusal = %+v, live = %q; want a not-proven refusal and old", r, e.live())
}