hostclaim: one helper builds parking chores for delete and adoption
Co-Authored-By: Codex (gpt-6-astra) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 files changed,
+36
−12
internal/hostclaim/adopt.go+1 −6internal/hostclaim/delete.go+10 −6internal/hostclaim/delete_test.go+25 −0
diff --git a/internal/hostclaim/adopt.go b/internal/hostclaim/adopt.go| index 1498170bb..eb304b5ef 100644 |
| --- a/internal/hostclaim/adopt.go |
| +++ b/internal/hostclaim/adopt.go |
| @@ -20,12 +20,7 @@ func Adopt(nh NewHolder, servingBox string, otherRegions, otherBoxes []string, o |
| Incarnation: inc, CreatedAt: nh.CreatedAt, Location: "instances/" + nh.Host, Stamp: c.Gen + 1} |
| c.Op = Op{Kind: KindServing, Exec: servingBox, Participants: []string{servingBox}} |
| c.Touched = Touched{Complete: true, Regions: []string{nh.Region}, Boxes: []string{servingBox}} |
| - for _, r := range otherRegions { |
| - c.Obligations = append(c.Obligations, Obligation{ID: newID(), Op: opID, Kind: "park-replica", Region: r, Retirement: newID(), Owner: "region:" + r}) |
| - } |
| - for _, b := range otherBoxes { |
| - c.Obligations = append(c.Obligations, Obligation{ID: newID(), Op: opID, Kind: "park-local", Box: b, Retirement: newID(), Owner: "box:" + b}) |
| - } |
| + appendParkObligations(c, opID, otherRegions, otherBoxes, newID) |
| if len(c.Obligations) > 0 { |
| c.Touched.Complete, c.Touched.DiscoveryOp = false, opID |
| } |
diff --git a/internal/hostclaim/delete.go b/internal/hostclaim/delete.go| index ff11f39e4..342b9f80c 100644 |
| --- a/internal/hostclaim/delete.go |
| +++ b/internal/hostclaim/delete.go |
| @@ -44,18 +44,22 @@ func ToParking(opID string, allRegions, allBoxes []string, newID func() string) |
| return fmt.Errorf("%w: no holder to park", ErrWrongState) |
| } |
| regions, boxes := WriteSet(c, allRegions, allBoxes) |
| - for _, r := range regions { |
| - c.Obligations = append(c.Obligations, Obligation{ID: newID(), Op: opID, Kind: "park-replica", Region: r, Retirement: newID(), Owner: "region:" + r}) |
| - } |
| - for _, b := range boxes { |
| - c.Obligations = append(c.Obligations, Obligation{ID: newID(), Op: opID, Kind: "park-local", Box: b, Retirement: newID(), Owner: "box:" + b}) |
| - } |
| + appendParkObligations(c, opID, regions, boxes, newID) |
| retire(c, c.Holder.Incarnation) |
| c.Op.Kind = KindParking |
| return nil |
| } |
| } |
| +func appendParkObligations(c *Claim, opID string, regions, boxes []string, newID func() string) { |
| + for _, r := range regions { |
| + c.Obligations = append(c.Obligations, Obligation{ID: newID(), Op: opID, Kind: "park-replica", Region: r, Retirement: newID(), Owner: "region:" + r}) |
| + } |
| + for _, b := range boxes { |
| + c.Obligations = append(c.Obligations, Obligation{ID: newID(), Op: opID, Kind: "park-local", Box: b, Retirement: newID(), Owner: "box:" + b}) |
| + } |
| +} |
| + |
| // CompleteObligation removes one chore, matched by its OWN op id and id. |
| // Discovery chores outlive the adoption that made them (§9). |
| func CompleteObligation(obligationOp, obligationID string) func(*Claim) error { |
diff --git a/internal/hostclaim/delete_test.go b/internal/hostclaim/delete_test.go| index 62ea15ecc..3813ebd48 100644 |
| --- a/internal/hostclaim/delete_test.go |
| +++ b/internal/hostclaim/delete_test.go |
| @@ -4,6 +4,7 @@ import ( |
| "context" |
| "errors" |
| "fmt" |
| + "reflect" |
| "testing" |
| "time" |
| @@ -93,3 +94,27 @@ func TestRetiredCapKeepsNewest32(t *testing.T) { |
| t.Fatalf("retired = %v", c.RetiredIncarnations) |
| } |
| } |
| + |
| +func TestAppendParkObligations(t *testing.T) { |
| + existing := Obligation{ID: "existing", Op: "op_old", Kind: "park-local", Box: "b0", Retirement: "retired", Owner: "box:b0"} |
| + c := &Claim{Obligations: []Obligation{existing}} |
| + newID := counter() |
| + appendParkObligations(c, "op_park", []string{"uk", "us"}, []string{"b1", "b2"}, newID) |
| + want := []Obligation{ |
| + existing, |
| + {ID: "id_1", Op: "op_park", Kind: "park-replica", Region: "uk", Retirement: "id_2", Owner: "region:uk"}, |
| + {ID: "id_3", Op: "op_park", Kind: "park-replica", Region: "us", Retirement: "id_4", Owner: "region:us"}, |
| + {ID: "id_5", Op: "op_park", Kind: "park-local", Box: "b1", Retirement: "id_6", Owner: "box:b1"}, |
| + {ID: "id_7", Op: "op_park", Kind: "park-local", Box: "b2", Retirement: "id_8", Owner: "box:b2"}, |
| + } |
| + if !reflect.DeepEqual(c.Obligations, want) { |
| + t.Fatalf("obligations = %+v, want %+v", c.Obligations, want) |
| + } |
| + appendParkObligations(c, "op_empty", nil, nil, newID) |
| + if !reflect.DeepEqual(c.Obligations, want) { |
| + t.Fatalf("empty append changed obligations: %+v", c.Obligations) |
| + } |
| + if got := newID(); got != "id_9" { |
| + t.Fatalf("next id = %q, want id_9", got) |
| + } |
| +} |