carlos / platform Public

hostclaim: CancelCreate refuses a claim carrying execution authority

Co-Authored-By: Codex (gpt-6-astra) <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
codex agent, operated by paul@keymail.dev pushed by claude agent, operated by paul@keymail.dev 2aadfd9040cd70abc690a9ac1e14366e452d6032 parent ed1c2c9
2 files changed, +58 −0
  • internal/hostclaim/create.go +3 −0
  • internal/hostclaim/create_test.go +55 −0
diff --git a/internal/hostclaim/create.go b/internal/hostclaim/create.go
index 41c3663c5..242e3d09c 100644
--- a/internal/hostclaim/create.go
+++ b/internal/hostclaim/create.go
@@ -102,6 +102,9 @@ func CancelCreate(opID string, now time.Time) func(*Claim) error {
if c.Op.Kind != KindCreating {
return fmt.Errorf("%w: kind %q", ErrWrongState, c.Op.Kind)
}
+ if c.Op.Exec != "" || len(c.Op.Participants) > 0 || c.Holder.Stamp != 0 {
+ return fmt.Errorf("%w: create carries execution authority", ErrWrongState)
+ }
inc, key := c.Holder.Incarnation, c.Holder.Key
retire(c, inc)
c.CancelledCreates = append(c.CancelledCreates, CancelledCreate{Key: key, Incarnation: inc, Until: now.UTC().Add(time.Hour)})
diff --git a/internal/hostclaim/create_test.go b/internal/hostclaim/create_test.go
index e404832ff..a84822520 100644
--- a/internal/hostclaim/create_test.go
+++ b/internal/hostclaim/create_test.go
@@ -1,6 +1,7 @@
package hostclaim
import (
+ "bytes"
"context"
"errors"
"testing"
@@ -113,3 +114,57 @@ func TestCancelRetiresAndRefusesReuse(t *testing.T) {
t.Fatalf("reuse = %v, want ErrRetiredIncarnation", err)
}
}
+
+func TestCancelCreateRefusesExecutionAuthority(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ exec string
+ participants []string
+ stamp uint64
+ }{
+ {name: "executor", exec: "adi-knitting"},
+ {name: "participants", participants: []string{"adi-knitting"}},
+ {name: "stamp", stamp: 1},
+ {name: "all", exec: "adi-knitting", participants: []string{"adi-knitting"}, stamp: 1},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ ctx := context.Background()
+ s := release.NewDirStore(t.TempDir())
+ now := time.Date(2026, 10, 4, 1, 0, 1, 0, time.UTC)
+ reg, err := Read(ctx, s, testHost)
+ if err != nil {
+ t.Fatal(err)
+ }
+ o, err := Update(ctx, s, reg, func(c *Claim) error {
+ if err := BeginCreate(newHolder(), record, "op_c1", "console", now)(c); err != nil {
+ return err
+ }
+ c.Op.Exec = tc.exec
+ c.Op.Participants = tc.participants
+ c.Holder.Stamp = tc.stamp
+ return nil
+ })
+ if err != nil {
+ t.Fatalf("store creating claim with authority: %v", err)
+ }
+ key, err := Key(testHost)
+ if err != nil {
+ t.Fatal(err)
+ }
+ before, beforeETag, err := s.Get(ctx, key)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := Update(ctx, s, o, CancelCreate("op_c1", now)); !errors.Is(err, ErrWrongState) {
+ t.Errorf("cancel with authority = %v, want ErrWrongState", err)
+ }
+ after, afterETag, err := s.Get(ctx, key)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(before, after) || beforeETag != afterETag {
+ t.Errorf("cancel changed stored claim: before ETag %q, after ETag %q", beforeETag, afterETag)
+ }
+ })
+ }
+}