moves: key the home lane's staging lease in the host wiring, not in moves
89dbfe95 mapped the region inside moves.StagingLeasePrefix, which made internal/moves import internal/park; park's tests import moves, so go test ./internal/park failed with an import cycle. moves is back as it was. The box's staging-lease wiring (stagingLeaseKey) maps the lane's placement code through park.PlacementRegion before keying the lease, so a lane in home is control/staging-leases/@home/<host>, named keys are unchanged and a code that is no placement refuses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 files changed,
+44
−31
internal/host/moveshome_test.go+23 −0internal/host/movespass.go+19 −1internal/moves/lease.go+2 −11internal/moves/lease_test.go+0 −19
diff --git a/internal/host/moveshome_test.go b/internal/host/moveshome_test.go| index bc129fd11..67923a1c5 100644 |
| --- a/internal/host/moveshome_test.go |
| +++ b/internal/host/moveshome_test.go |
| @@ -7,6 +7,7 @@ import ( |
| "time" |
| "github.com/carlosframework/platform/internal/hostclaim" |
| + "github.com/carlosframework/platform/internal/moves" |
| "github.com/carlosframework/platform/internal/park" |
| "github.com/carlosframework/platform/internal/provision" |
| ) |
| @@ -202,3 +203,25 @@ func TestMovesClaimPolicyRecoveryFromHome(t *testing.T) { |
| } |
| } |
| } |
| + |
| +// TestHomeLaneStagingLeaseKey: the staging lease of a lane in home (a move |
| +// out of home's return lane, taken with the record's From) is keyed by |
| +// park.HomeRegion, never an empty segment, and apart from every named |
| +// region, one named "home" among them; a code that is no placement refuses. |
| +func TestHomeLaneStagingLeaseKey(t *testing.T) { |
| + const host = "a.go.example.com" |
| + home, err := stagingLeaseKey("", host) |
| + if err != nil || home != "control/staging-leases/"+park.HomeRegion+"/"+host || strings.Contains(home, "//") { |
| + t.Fatalf("the home lane's staging lease is %q, %v", home, err) |
| + } |
| + for _, code := range []string{"uk", "home", "none"} { |
| + if got, err := stagingLeaseKey(code, host); err != nil || got == home || got != moves.StagingLeasePrefix(code, host) { |
| + t.Fatalf("%s's staging lease is %q, %v (home's %q)", code, got, err, home) |
| + } |
| + } |
| + for _, code := range []string{"IE", park.HomeRegion, "a/b"} { |
| + if got, err := stagingLeaseKey(code, host); !errors.Is(err, park.ErrUnsafe) { |
| + t.Fatalf("invalid code %q: %q, %v", code, got, err) |
| + } |
| + } |
| +} |
diff --git a/internal/host/movespass.go b/internal/host/movespass.go| index ad6e3589f..0cb58ef87 100644 |
| --- a/internal/host/movespass.go |
| +++ b/internal/host/movespass.go |
| @@ -283,6 +283,20 @@ func movesDataDir(dd string) string { |
| return abs |
| } |
| +// stagingLeaseKey is the staging lease of a lane in targetRegion, the |
| +// lane's placement code (the record's To, or its From for a return lane): |
| +// moves.StagingLeasePrefix keyed by the lane's engine region, so a lane in |
| +// home is park.HomeRegion and never an empty segment |
| +// (control/staging-leases//<host>; home-region parks, part 1). Named codes |
| +// keep their keys. A code that is no placement refuses. |
| +func stagingLeaseKey(targetRegion, host string) (string, error) { |
| + r := park.PlacementRegion(targetRegion) |
| + if r == "" { |
| + return "", fmt.Errorf("%w: %q is not a placement code", park.ErrUnsafe, targetRegion) |
| + } |
| + return moves.StagingLeasePrefix(r, host), nil |
| +} |
| + |
| // armPhase1 wires the Phase 1 hooks (moves design §7.3). Only with the |
| // switch on: off, none is set and eb.Strict stays nil. writes is the |
| // agent's host-write configuration (the claim wiring's): the landing's |
| @@ -295,7 +309,11 @@ func armPhase1(cfg *moveCfg, w *movesWorker, hib activator.Config, dataDir strin |
| cfg.DeployBucketRegion = edgesFileRegion(getenv, readPlacementRegion()) |
| bucket, region := cfg.DeployBucket, cfg.DeployBucketRegion |
| cfg.StagingLease = func(ctx context.Context, targetRegion, host string) (moves.SafeUntilGuard, func(context.Context) error, error) { |
| - h, err := lease.AcquireTTL(ctx, bucket, moves.StagingLeasePrefix(targetRegion, host), region, moves.StagingLeaseTTL) |
| + key, err := stagingLeaseKey(targetRegion, host) |
| + if err != nil { |
| + return nil, nil, err |
| + } |
| + h, err := lease.AcquireTTL(ctx, bucket, key, region, moves.StagingLeaseTTL) |
| if err != nil { |
| return nil, nil, err |
| } |
diff --git a/internal/moves/lease.go b/internal/moves/lease.go| index e7388cae4..8eb0c08e3 100644 |
| --- a/internal/moves/lease.go |
| +++ b/internal/moves/lease.go |
| @@ -5,8 +5,6 @@ import ( |
| "fmt" |
| "math" |
| "time" |
| - |
| - "github.com/carlosframework/platform/internal/park" |
| ) |
| // SignatureLife is how long S3 accepts a SigV4 signature (15 minutes, |
| @@ -30,16 +28,9 @@ var ErrLeaseUnsafe = errors.New("moves: staging lease too close to its end to si |
| // StagingLeasePrefix is the claim's shared staging-work lease (claim C3, |
| // §7.3 D3): in the deployment bucket, keyed by target region and host, so |
| // a source that cannot read the target's lock.json still excludes other |
| -// writers of the same lane. targetRegion is the lane's placement code; the |
| -// key names its engine region (park.PlacementRegion), so the home's "" (a |
| -// move out of home's return lane) is park.HomeRegion and never an empty |
| -// segment. A code that is no placement is kept as it is. |
| +// writers of the same lane. |
| func StagingLeasePrefix(targetRegion, host string) string { |
| - region := park.PlacementRegion(targetRegion) |
| - if region == "" { |
| - region = targetRegion |
| - } |
| - return "control/staging-leases/" + region + "/" + host |
| + return "control/staging-leases/" + targetRegion + "/" + host |
| } |
| // ErrInvalidSkew is a negative Guarded.Skew: it would shrink the window |
diff --git a/internal/moves/lease_test.go b/internal/moves/lease_test.go| index bc557d6f5..351a664ab 100644 |
| --- a/internal/moves/lease_test.go |
| +++ b/internal/moves/lease_test.go |
| @@ -6,14 +6,12 @@ import ( |
| "math" |
| "net/http" |
| "net/http/httptest" |
| - "strings" |
| "testing" |
| "time" |
| "github.com/aws/aws-sdk-go-v2/aws" |
| "github.com/carlosframework/platform/internal/lease" |
| "github.com/carlosframework/platform/internal/lease/leasetest" |
| - "github.com/carlosframework/platform/internal/park" |
| ) |
| type fixedLease struct{ until time.Time } |
| @@ -187,20 +185,3 @@ func TestGuardedSkewIsValidated(t *testing.T) { |
| } |
| } |
| } |
| - |
| -// Home-region parks, part 1: the staging lease of a lane in the home region |
| -// (a move out of home's return lane) is keyed by park.HomeRegion, never by |
| -// an empty segment, and apart from every named region, one named "home" |
| -// among them. |
| -func TestStagingLeasePrefixNamesHome(t *testing.T) { |
| - const host = "a.go.example.com" |
| - home := StagingLeasePrefix("", host) |
| - if home != "control/staging-leases/"+park.HomeRegion+"/"+host || strings.Contains(home, "//") { |
| - t.Fatalf("the home lane's staging lease is %q", home) |
| - } |
| - for _, code := range []string{"uk", "home", "none"} { |
| - if got := StagingLeasePrefix(code, host); got == home || got != "control/staging-leases/"+code+"/"+host { |
| - t.Fatalf("%s's staging lease is %q (home's %q)", code, got, home) |
| - } |
| - } |
| -} |