package release import ( "errors" "fmt" "os" "testing" "time" ) // Final review finding 1. T7 used to pass whenever live/ named the // pinned version: no authority, no proof policy, no look at the bytes. // VerifyHeldLive is its reading now: the link names the version, D3 admits // the release under the policy in force, and bind accepts the bytes the // link names, read under the lock the publication holds across its swap, // re-hash and rollback. // passLock is a lock that is not one: the bytes check alone. func passLock(read func() error) error { return read() } // chanLock is one lock shared by a publication's guard and a T7 reading, // as the per-host flock is in production. waiting is signalled each time a // reading starts to wait for it. type chanLock struct { mu chan struct{} waiting chan struct{} } func newChanLock() *chanLock { return &chanLock{mu: make(chan struct{}, 1), waiting: make(chan struct{}, 8)} } func (l *chanLock) guard(publish func() error) error { l.mu <- struct{}{} defer func() { <-l.mu }() return publish() } func (l *chanLock) read(fn func() error) error { l.waiting <- struct{}{} l.mu <- struct{}{} defer func() { <-l.mu }() return fn() } // The review's regression: T7 is read between the swap and a failed // post-swap verification. The publication holds the lock from before the // swap until after its rollback, so the reading waits it out and then sees // the link back on v0: not published, never a pass. func TestVerifyHeldLiveWaitsOutAFailedPublication(t *testing.T) { e := newHeldProofEnv(t) e.require(t, "console") e.prove(t, e.pinnedSHA) lock := newChanLock() agent := NewProofGate(e.policy, e.bake) // the agent's own gate prev := heldAfterSwap t.Cleanup(func() { heldAfterSwap = prev }) res := make(chan error, 1) var during error = errors.New("the seam never ran") heldAfterSwap = func() { // The link names v1 now. Its bytes are replaced, so the // publication's second bind will refuse and roll back. if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("replaced after the swap")); err != nil { t.Fatal(err) } if v := e.live(); v != "v1" { t.Fatalf("live = %q inside the swap window, want v1", v) } go func() { res <- VerifyHeldLive(e.ctx, e.store, e.opt, heldRoute(), "v1", agent, lock.read) }() select { case <-lock.waiting: case err := <-res: during = fmt.Errorf("T7 answered %v without waiting for the lock", err) return case <-time.After(5 * time.Second): during = errors.New("T7 never reached the lock") return } select { case err := <-res: during = fmt.Errorf("T7 answered %v while the publication held the lock", err) case <-time.After(200 * time.Millisecond): during = nil } } _, err := MaterializeHeld(e.ctx, e.store, heldPub, nr(t), e.opt, heldRoute(), "v1", e.gate, lock.guard) if !errors.Is(err, ErrHeldNotProven) || e.live() != "v0" { t.Fatalf("the publication: %v, live %q; want ErrHeldNotProven and v0", err, e.live()) } if during != nil { t.Fatal(during) } select { case got := <-res: if !errors.Is(got, ErrHeldNotLive) { t.Fatalf("T7 after the rollback = %v, want ErrHeldNotLive", got) } case <-time.After(5 * time.Second): t.Fatal("T7 never answered") } } // Even with no lock, the reading never passes on the swapped link: it // hashes the bytes the link names, as the publication's second bind does. func TestVerifyHeldLiveRefusesTheSwappedBytes(t *testing.T) { e := newHeldProofEnv(t) e.require(t, "console") e.prove(t, e.pinnedSHA) agent := NewProofGate(e.policy, e.bake) prev := heldAfterSwap t.Cleanup(func() { heldAfterSwap = prev }) var during error = errors.New("the seam never ran") heldAfterSwap = func() { if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("replaced after the swap")); err != nil { t.Fatal(err) } during = VerifyHeldLive(e.ctx, e.store, e.opt, heldRoute(), "v1", agent, passLock) } if _, err := MaterializeHeld(e.ctx, e.store, heldPub, nr(t), e.opt, heldRoute(), "v1", e.gate, openGuard); !errors.Is(err, ErrHeldNotProven) { t.Fatalf("the publication: %v", err) } if !errors.Is(during, ErrHeldNotProven) { t.Fatalf("T7 inside the swap window = %v, want ErrHeldNotProven", during) } } // An existing link is no evidence by itself: T7 re-checks the policy and // the bytes every time. Replaced bytes, a rotated prover key and a policy // that does not load each fail it; a link on another version is not live; // an app the policy does not list passes as adoption would. func TestVerifyHeldLiveRechecksAnExistingLink(t *testing.T) { e := newHeldProofEnv(t) e.require(t, "console") e.prove(t, e.pinnedSHA) if _, err := MaterializeHeld(e.ctx, e.store, heldPub, nr(t), e.opt, heldRoute(), "v1", e.gate, openGuard); err != nil || e.live() != "v1" { t.Fatalf("publish v1: %v, live %q", err, e.live()) } verify := func() error { return VerifyHeldLive(e.ctx, e.store, e.opt, heldRoute(), "v1", NewProofGate(e.policy, e.bake), passLock) } if err := verify(); err != nil { t.Fatalf("published and proven: %v", err) } // The artifact is replaced after publication. if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("replaced later")); err != nil { t.Fatal(err) } if err := verify(); !errors.Is(err, ErrHeldNotProven) { t.Fatalf("replaced bytes: %v, want ErrHeldNotProven", err) } if r, _ := ReadRefusal(HeldRefusalBase(e.bake), heldTestHost); r != nil { t.Fatalf("a reading recorded a refusal: %+v", r) } if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("pinned build")); err != nil { t.Fatal(err) } if err := verify(); err != nil { t.Fatalf("bytes restored: %v", err) } // The proof policy changes: another prover key. _, _, otherPub := proverKey(t) e.writePolicy(t, fmt.Sprintf(`{"schema":1,"require":[{"app":"console","prover":"dev","key":%q}]}`, otherPub)) if err := verify(); !errors.Is(err, ErrHeldNotProven) { t.Fatalf("rotated key: %v, want ErrHeldNotProven", err) } // A policy that does not load. e.writePolicy(t, "{not json") if err := verify(); !errors.Is(err, ErrHeldNotProven) { t.Fatalf("broken policy: %v, want ErrHeldNotProven", err) } // Uncovered: D3 has nothing to say about these bytes, as for adoption. e.require(t, "other") if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("replaced later")); err != nil { t.Fatal(err) } if err := verify(); err != nil { t.Fatalf("uncovered: %v", err) } // Another version live is not published. if err := PointLive(e.opt, heldTestHost, "bab", "v0", "console"); err != nil { t.Fatal(err) } if err := verify(); !errors.Is(err, ErrHeldNotLive) { t.Fatalf("on v0: %v, want ErrHeldNotLive", err) } if err := os.Remove(LiveLink(e.opt, heldTestHost)); err != nil { t.Fatal(err) } if err := verify(); !errors.Is(err, ErrHeldNotLive) { t.Fatalf("no link: %v, want ErrHeldNotLive", err) } // No lock, no gate: refused. if err := VerifyHeldLive(e.ctx, e.store, e.opt, heldRoute(), "v1", e.gate, nil); err == nil { t.Fatal("a reading without the lock passed") } if err := VerifyHeldLive(e.ctx, e.store, e.opt, heldRoute(), "v1", nil, passLock); err == nil { t.Fatal("a reading without a gate passed") } } // Region HA Task 8c: the claim path's reading. VerifyHeldLiveUnder reads // under CARLOS_HOST_CLAIM, and still refuses an unproven link, a link on // another version, and a reading without the lock. VerifyHeldLive writes // nothing, so it reads under the claim too (moves Task 7, Paul's T7 ruling // of 2026-10-12). func TestVerifyHeldLiveUnderReadsUnderTheClaim(t *testing.T) { e := newHeldProofEnv(t) e.require(t, "console") e.prove(t, e.pinnedSHA) if _, err := MaterializeHeld(e.ctx, e.store, heldPub, nr(t), e.opt, heldRoute(), "v1", e.gate, openGuard); err != nil || e.live() != "v1" { t.Fatalf("publish v1: %v, live %q", err, e.live()) } t.Setenv("CARLOS_HOST_CLAIM", "1") verify := func(lock func(func() error) error) error { return VerifyHeldLiveUnder(e.ctx, e.store, e.opt, heldRoute(), "v1", NewProofGate(e.policy, e.bake), lock) } if err := VerifyHeldLive(e.ctx, e.store, e.opt, heldRoute(), "v1", NewProofGate(e.policy, e.bake), passLock); err != nil { t.Fatalf("VerifyHeldLive under the claim: %v", err) } if err := verify(passLock); err != nil { t.Fatalf("published and proven, under the claim: %v", err) } if err := verify(nil); err == nil { t.Fatal("a reading without the lock passed") } if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("replaced later")); err != nil { t.Fatal(err) } if err := verify(passLock); !errors.Is(err, ErrHeldNotProven) { t.Fatalf("replaced bytes: %v, want ErrHeldNotProven", err) } if err := PointLive(e.opt, heldTestHost, "bab", "v0", "console"); err != nil { t.Fatal(err) } if err := verify(passLock); !errors.Is(err, ErrHeldNotLive) { t.Fatalf("on v0: %v, want ErrHeldNotLive", err) } }