carlos / platform Public

release: VerifyHeldLive reads under the claim too (moves Task 7, Q2.1)

The configured step publishes the held release under the held-release
selection and then reads it back with VerifyHeldLive, as T7 does with the
claim off. The reading writes nothing, so it checks the held shape only
and no longer refuses while CARLOS_HOST_CLAIM is on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 5a46611265d20775a9f18e2979c69bb01959ec1b parent b3d4233
2 files changed, +25 −1
  • internal/release/materializeheld.go +6 −1
  • internal/release/materializeheld_claim_test.go +19 −0
diff --git a/internal/release/materializeheld.go b/internal/release/materializeheld.go
index 90b2da387..c3707c4d5 100644
--- a/internal/release/materializeheld.go
+++ b/internal/release/materializeheld.go
@@ -181,8 +181,13 @@ var errHeldNoLock = errors.New("release: a held reading needs the publication's
// never sees a link a failing publication is about to take back. A nil
// lock or gate is refused. A link on another version, or none, is
// ErrHeldNotLive; a refusal is ErrHeldNotProven.
+//
+// It writes nothing, so it does not consult CARLOS_HOST_CLAIM: under the
+// claim it is the configured step's reading of the release it published
+// under the held-release selection (moves Task 7, Paul's T7 ruling of
+// 2026-10-12).
func VerifyHeldLive(ctx context.Context, s Store, optDir string, rt registry.Route, version string, proof *ProofGate, lock func(read func() error) error) error {
- if err := heldStageable(rt, version); err != nil {
+ if err := heldShape(rt, version); err != nil {
return err
}
if proof == nil {
diff --git a/internal/release/materializeheld_claim_test.go b/internal/release/materializeheld_claim_test.go
index b0ec5b9a1..c2b9628a1 100644
--- a/internal/release/materializeheld_claim_test.go
+++ b/internal/release/materializeheld_claim_test.go
@@ -252,3 +252,22 @@ func TestStageHeldVerifiedStagesWithoutPublishing(t *testing.T) {
}
})
}
+
+// TestVerifyHeldLiveReadsUnderTheClaim (moves Task 7): the configured step's
+// reading of a release it published under the held-release selection is
+// VerifyHeldLive's, which writes nothing, so CARLOS_HOST_CLAIM does not
+// refuse it. A link on another version is still ErrHeldNotLive.
+func TestVerifyHeldLiveReadsUnderTheClaim(t *testing.T) {
+ ctx, s, opt := heldFixture(t)
+ t.Setenv("CARLOS_HOST_CLAIM", "1")
+ lock := func(read func() error) error { return read() }
+ if err := VerifyHeldLive(ctx, s, opt, heldRoute(), "v1", offGate(t), lock); !errors.Is(err, ErrHeldNotLive) {
+ t.Fatalf("nothing published: %v, want ErrHeldNotLive", err)
+ }
+ if _, err := MaterializeHeldUnder(ctx, s, heldPub, nr(t), opt, heldRoute(), "v1", offGate(t), &heldClaimWork{}, nil); err != nil {
+ t.Fatal(err)
+ }
+ if err := VerifyHeldLive(ctx, s, opt, heldRoute(), "v1", offGate(t), lock); err != nil {
+ t.Fatalf("published under the selection: %v", err)
+ }
+}