moves: TargetLocationScope and ResidueScope; the rollback's target release and the landing's residue parks use them (Task 2 fix round 1)
Both build holder.location, the holder's incarnation and the 120 s request limit from their genuine bound (the target's stop report's CheckedAt; the flip), as the two call sites did by hand. Behaviour is unchanged; TestScopeHelpersUseTheGenuineBound covers both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 files changed,
+26
−4
internal/host/movesinstall.go+1 −1internal/host/movesrollback.go+1 −2internal/moves/parkscope.go+20 −1internal/moves/parkscope_test.go+4 −0
diff --git a/internal/host/movesinstall.go b/internal/host/movesinstall.go| index 5d1f4e8f8..f90509e38 100644 |
| --- a/internal/host/movesinstall.go |
| +++ b/internal/host/movesinstall.go |
| @@ -424,7 +424,7 @@ func (ir *installRun) parking(ctx context.Context) (bool, error) { |
| if ir.rec.Move.FlippedAt == nil { |
| return false, fmt.Errorf("move %s: the record has no flip time", id) |
| } |
| - scope := hostclaim.ParkScope{Location: location, Incarnation: holder.Incarnation, EvidenceAt: ir.rec.Move.FlippedAt.UTC(), RequestLimit: landingRequestLimit} |
| + scope := moves.ResidueScope(holder, *ir.rec.Move.FlippedAt) |
| if obs, err = hostclaim.Update(ctx, ir.s, obs, hostclaim.QueueResidueParks(id, cfg.Region, scope, gens, obligationID)); err != nil { |
| return false, fmt.Errorf("queue the residue parks: %w", err) |
| } |
diff --git a/internal/host/movesrollback.go b/internal/host/movesrollback.go| index 7a0b1734a..d52afdedc 100644 |
| --- a/internal/host/movesrollback.go |
| +++ b/internal/host/movesrollback.go |
| @@ -582,8 +582,7 @@ func (r *rollbackRun) claimReleased(ctx context.Context, evidence string) error |
| return err |
| } |
| holder := obs.Claim.Holder |
| - scope := hostclaim.ParkScope{Location: strings.TrimSuffix(holder.Location, "/"), Incarnation: holder.Incarnation, |
| - EvidenceAt: report.CheckedAt.UTC(), RequestLimit: cutRequestLimit} |
| + scope := moves.TargetLocationScope(*holder, report.CheckedAt) |
| if _, err := hostclaim.Update(ctx, r.s, obs, hostclaim.TargetReleased(r.g.ID, cfg.Box, cfg.Region, evidence, scope, obligationID)); err != nil { |
| return fmt.Errorf("the claim half of the release: %w", err) |
| } |
diff --git a/internal/moves/parkscope.go b/internal/moves/parkscope.go| index daedeac6d..f1611d6f0 100644 |
| --- a/internal/moves/parkscope.go |
| +++ b/internal/moves/parkscope.go |
| @@ -26,7 +26,26 @@ const ParkRequestLimit = 120 * time.Second |
| // comes after ExecToNone, after which the source never writes its location |
| // again. |
| func SourceLocationScope(holder hostclaim.Holder, flippedAt time.Time) hostclaim.ParkScope { |
| - return hostclaim.ParkScope{Location: strings.TrimSuffix(holder.Location, "/"), Incarnation: holder.Incarnation, EvidenceAt: flippedAt.UTC(), RequestLimit: ParkRequestLimit} |
| + return locationScope(holder, flippedAt) |
| +} |
| + |
| +// TargetLocationScope is a rolled-back target region's location park, queued |
| +// when the target releases (hostclaim.TargetReleased): holder.location and |
| +// the holder's incarnation. Its horizon is the target's stop report's |
| +// CheckedAt, after which the target writes its location no more. |
| +func TargetLocationScope(holder hostclaim.Holder, stopCheckedAt time.Time) hostclaim.ParkScope { |
| + return locationScope(holder, stopCheckedAt) |
| +} |
| + |
| +// ResidueScope is the location a landing's earlier-stay residue parks |
| +// (hostclaim.QueueResidueParks) are scoped to: holder.location and the |
| +// holder's incarnation. Its horizon starts at the flip, like the source's. |
| +func ResidueScope(holder hostclaim.Holder, flippedAt time.Time) hostclaim.ParkScope { |
| + return locationScope(holder, flippedAt) |
| +} |
| + |
| +func locationScope(holder hostclaim.Holder, bound time.Time) hostclaim.ParkScope { |
| + return hostclaim.ParkScope{Location: strings.TrimSuffix(holder.Location, "/"), Incarnation: holder.Incarnation, EvidenceAt: bound.UTC(), RequestLimit: ParkRequestLimit} |
| } |
| // StagingScope is the park of a move's own staging lane, moves/<host>/<op>, |
diff --git a/internal/moves/parkscope_test.go b/internal/moves/parkscope_test.go| index c20bef500..5c3ee56e1 100644 |
| --- a/internal/moves/parkscope_test.go |
| +++ b/internal/moves/parkscope_test.go |
| @@ -28,6 +28,10 @@ func TestScopeHelpersUseTheGenuineBound(t *testing.T) { |
| }{ |
| {"source location, holder with a slash", SourceLocationScope(hostclaim.Holder{Location: "instances/" + host + "/" + inc + "/", Incarnation: inc}, bound), "instances/" + host + "/" + inc}, |
| {"source location, holder without one", SourceLocationScope(hostclaim.Holder{Location: "instances/" + host, Incarnation: inc}, bound), "instances/" + host}, |
| + {"target location, holder with a slash", TargetLocationScope(hostclaim.Holder{Location: "instances/" + host + "/" + inc + "/", Incarnation: inc}, bound), "instances/" + host + "/" + inc}, |
| + {"target location, holder without one", TargetLocationScope(hostclaim.Holder{Location: "instances/" + host, Incarnation: inc}, bound), "instances/" + host}, |
| + {"residue, holder with a slash", ResidueScope(hostclaim.Holder{Location: "instances/" + host + "/" + inc + "/", Incarnation: inc}, bound), "instances/" + host + "/" + inc}, |
| + {"residue, holder without one", ResidueScope(hostclaim.Holder{Location: "instances/" + host, Incarnation: inc}, bound), "instances/" + host}, |
| {"staging", StagingScope(host, "move-1", inc, bound), "moves/" + host + "/move-1"}, |
| {"abort", AbortScope(host, "move-1", inc, bound), "moves/" + host + "/move-1"}, |
| {"return lane", ReturnLaneScope(host, "rb-1", inc, bound), "moves/" + host + "/rb-1"}, |