hostclaim: Commit follows probed only (Plan 3 Task 7, platform-1e m-2)
A commit from probing is refused and leaves the claim untouched, so Group D's adjacent-step rule (probing, probed, committed) holds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 files changed,
+33
−4
internal/hostclaim/move.go+7 −3internal/hostclaim/move_commit_m2_test.go+25 −0internal/hostclaim/move_test.go+1 −1
diff --git a/internal/hostclaim/move.go b/internal/hostclaim/move.go| index 4215bfcec..b05bf963e 100644 |
| --- a/internal/hostclaim/move.go |
| +++ b/internal/hostclaim/move.go |
| @@ -581,8 +581,9 @@ func CompleteLocalPark(opID, box string, earlierStamp uint64) func(*Claim) error |
| // admission records. holder.stamp, session and generation are not touched |
| // (R10-3, H3). |
| // |
| -// Idempotent under the op: a landing already committed returns nil and queues |
| -// nothing. A refused commit leaves the claim as it found it. |
| +// It follows probed only (platform-1e m-2). Idempotent under the op: a |
| +// landing already committed returns nil and queues nothing. A refused commit |
| +// leaves the claim as it found it. |
| func Commit(opID, sourceRegion, sourceBox, targetRegion string, sourceScope, stagingScope ParkScope, newID func() string) func(*Claim) error { |
| return func(c *Claim) error { |
| if err := RequireOp(opID)(c); err != nil { |
| @@ -594,7 +595,10 @@ func Commit(opID, sourceRegion, sourceBox, targetRegion string, sourceScope, sta |
| if c.Op.Phase == PhaseCommitted { |
| return nil |
| } |
| - if c.Op.Phase != "probing" && c.Op.Phase != "probed" { |
| + // The commit follows probed only (platform-1e m-2): with Group D's |
| + // adjacent-step rule (probing, probed, committed) the admitted |
| + // session survives it. |
| + if c.Op.Phase != PhaseLandingProbed { |
| return fmt.Errorf("%w: phase %q", ErrWrongState, c.Op.Phase) |
| } |
| if len(c.Op.Participants) != 2 || c.Op.Exec != c.Op.Participants[1] { |
diff --git a/internal/hostclaim/move_commit_m2_test.go b/internal/hostclaim/move_commit_m2_test.go| new file mode 100644 |
| index 000000000..6bea518dc |
| --- /dev/null |
| +++ b/internal/hostclaim/move_commit_m2_test.go |
| @@ -0,0 +1,25 @@ |
| +package hostclaim |
| + |
| +import ( |
| + "errors" |
| + "testing" |
| +) |
| + |
| +// m-2 (platform-1e; moves Plan 3 Task 7): the commit follows probed only, so |
| +// Group D's adjacent-step rule (probing, probed, committed) holds. A commit |
| +// from probing is refused and leaves the claim as it found it. |
| +func TestCommitAcceptsOnlyProbed(t *testing.T) { |
| + c := probedLanding(t) |
| + c.Op.Phase = "probing" |
| + before := string(mustJSON(t, c)) |
| + src, stg := commitScopes(c) |
| + if err := Commit(moveA, "ie", "ie-box", "uk", src, stg, counter())(c); !errors.Is(err, ErrWrongState) || string(mustJSON(t, c)) != before { |
| + t.Fatalf("a commit from probing: %v", err) |
| + } |
| + c = probedLanding(t) |
| + c.Op.Phase = "probed" |
| + src, stg = commitScopes(c) |
| + if err := KeepWriterSession(moveA, Commit(moveA, "ie", "ie-box", "uk", src, stg, counter()))(c); err != nil || c.Op.Phase != PhaseCommitted { |
| + t.Fatalf("a commit from probed under the session guard: %v %+v", err, c.Op) |
| + } |
| +} |
diff --git a/internal/hostclaim/move_test.go b/internal/hostclaim/move_test.go| index 465dff847..2c6f6777b 100644 |
| --- a/internal/hostclaim/move_test.go |
| +++ b/internal/hostclaim/move_test.go |
| @@ -1193,7 +1193,7 @@ func obligationKinds(c *Claim) map[string]int { |
| // wCommitCoverage, wCommitReplayed, H3, H5(b). |
| func TestCommitQueuesObligationsAndCoversTheSourceRegion(t *testing.T) { |
| - for _, from := range []string{"probing", "probed"} { |
| + for _, from := range []string{"probed"} { |
| c := probedLanding(t) |
| c.Op.Phase = from |
| // The source box holds paths of the stay the move leaves: stamp S = 3. |