carlos / platform Public

plan: claim Plan 13 rev 1, home-region naming (the engine keeps a home box's verified roster name, bound to moves' @home on the verified local home placement; shared HomePlacementCode for the console door and BeginClaimMove; loss SourceRegion through PlacementRegion; spec rev 6.33)

Co-Authored-By: Codex <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 c82a060d8423aa7124ebc109547af7787aecb036 parent 0fd8386
1 file changed, +2273 −0
  • docs/superpowers/plans/2026-10-12-host-lifecycle-claim-13-home-region.md +2273 −0

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.011s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok  	github.com/carlosframework/platform	150.143s
ok  	github.com/carlosframework/platform/cmd/console	2.158s
ok  	github.com/carlosframework/platform/internal/activator	55.688s
ok  	github.com/carlosframework/platform/internal/agentsock	0.014s
ok  	github.com/carlosframework/platform/internal/analytics	0.020s
ok  	github.com/carlosframework/platform/internal/appconfig	0.946s
ok  	github.com/carlosframework/platform/internal/apperr	0.070s
ok  	github.com/carlosframework/platform/internal/applog	0.111s
ok  	github.com/carlosframework/platform/internal/awsinv	0.004s
ok  	github.com/carlosframework/platform/internal/caps	0.011s
ok  	github.com/carlosframework/platform/internal/certwarm	0.002s
ok  	github.com/carlosframework/platform/internal/cgroup	0.038s
ok  	github.com/carlosframework/platform/internal/clamd	0.054s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	50.410s
?   	github.com/carlosframework/platform/internal/controlname	[no test files]
?   	github.com/carlosframework/platform/internal/controlsock	[no test files]
ok  	github.com/carlosframework/platform/internal/dbdoor	1.381s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.012s
ok  	github.com/carlosframework/platform/internal/dbverify	0.012s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.012s
ok  	github.com/carlosframework/platform/internal/docsite	0.012s
ok  	github.com/carlosframework/platform/internal/domains	1.047s
ok  	github.com/carlosframework/platform/internal/doorkey	0.011s
ok  	github.com/carlosframework/platform/internal/economics	0.012s
ok  	github.com/carlosframework/platform/internal/edge	23.001s
ok  	github.com/carlosframework/platform/internal/edgesfile	0.012s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.098s
ok  	github.com/carlosframework/platform/internal/gate	1.655s
ok  	github.com/carlosframework/platform/internal/host	6.641s
ok  	github.com/carlosframework/platform/internal/infraview	0.011s
ok  	github.com/carlosframework/platform/internal/instancectl	0.414s
ok  	github.com/carlosframework/platform/internal/instanceexec	0.011s
ok  	github.com/carlosframework/platform/internal/instancefs	0.015s
ok  	github.com/carlosframework/platform/internal/lease	5.074s
ok  	github.com/carlosframework/platform/internal/ledger	0.022s
ok  	github.com/carlosframework/platform/internal/mail	0.061s
ok  	github.com/carlosframework/platform/internal/maintenance	0.075s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.497s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.046s
ok  	github.com/carlosframework/platform/internal/metrics	0.016s
ok  	github.com/carlosframework/platform/internal/objstore	0.085s
ok  	github.com/carlosframework/platform/internal/ops	0.102s
ok  	github.com/carlosframework/platform/internal/privatefile	0.004s
ok  	github.com/carlosframework/platform/internal/provision	0.822s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.007s
ok  	github.com/carlosframework/platform/internal/registry	1.216s
ok  	github.com/carlosframework/platform/internal/relay	7.213s
ok  	github.com/carlosframework/platform/internal/release	3.542s
ok  	github.com/carlosframework/platform/internal/s3compat	0.005s
ok  	github.com/carlosframework/platform/internal/schedule	1.382s
ok  	github.com/carlosframework/platform/internal/status	0.024s
ok  	github.com/carlosframework/platform/internal/steering	0.005s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.005s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.004s
ok  	github.com/carlosframework/platform/internal/unixdial	0.006s
ok  	github.com/carlosframework/platform/internal/usage	0.004s
ok  	github.com/carlosframework/platform/internal/version	0.003s
ok  	github.com/carlosframework/platform/internal/vet	1.531s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
  minio: cached, digest verified
  mc: cached, digest verified
  minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
  buckets and scoped user ready
=== RUN   TestDeliveredContractMinio
=== RUN   TestDeliveredContractMinio/put
=== RUN   TestDeliveredContractMinio/get_round-trips_the_body
=== RUN   TestDeliveredContractMinio/list_shows_the_key
=== RUN   TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN   TestDeliveredContractMinio/delete_removes_it
=== RUN   TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
    --- PASS: TestDeliveredContractMinio/put (0.00s)
    --- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
    --- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
    --- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
    --- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
    --- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok  	github.com/carlosframework/platform/internal/objstore	0.022s
minio contract: PASS
diff --git a/docs/superpowers/plans/2026-10-12-host-lifecycle-claim-13-home-region.md b/docs/superpowers/plans/2026-10-12-host-lifecycle-claim-13-home-region.md
new file mode 100644
index 000000000..e7a185b9b
--- /dev/null
+++ b/docs/superpowers/plans/2026-10-12-host-lifecycle-claim-13-home-region.md
@@ -0,0 +1,2273 @@
+# Host lifecycle claim, Plan 13: home-region naming
+
+Implementation plan, 2026-10-12, rev 1. Implements the controller's three binding home-region answers and both addendum answers in `plan13-brief.md`. Apply Tasks A–I in order, including G2 before H. Task I amends the claim spec to rev 6.33. This plan authorizes no production changes or commits.
+
+Requirements: `/home/paulca/.cache/tmp/claude-1001/own/after236/plan13-brief.md`, which carries the controller's answers to moves on amadan discussion #236. This document and the write report are implementation instructions for the controller.
+
+## Global Constraints
+
+- Every code hunk is based on `1b5802fb75864e7659bd44ca1f90ad20a1979cc9`, with `internal/park/region.go` from `66b4e84d17649d2c0aad83127694aae86167f08f`, followed by earlier tasks here. Never substitute a moving main or export the older moves branch over the merged Plans 1–12.
+- Keep `CARLOS_HOST_CLAIM` inert when off. The existing moves switch also remains required. The console's legacy dbClaimRegion fallback is byte-for-byte unchanged in the off arm; shared homeRegionIn retains the same rule. No service, initializer or production activation is added.
+- Preserve #267: no by-name privileged filesystem effects, no root SQLite open, and no new AWS configuration loader. This plan adds no AWS client. The guard, root descriptor handling, stop-evidence signatures and leases retain their existing checks.
+- Empty remains unset at every engine or claim boundary. Do not change Validate, parkQueueGuard, AdmitWriter, PublishGeneration or Cover*Predecessors to accept empty. PlacementRegion is applied to placement inputs only; applying it a second time to `@home` must still refuse.
+- The claim engine keeps a home box's verified roster name. The alias is limited to that name and `@home`, under a verified empty local placement or the console-bound operation home name. No region-only fallback, bucket shortcut, authority widening to other regions or rewrite of stored claims is allowed.
+- Census changes need the classifications in Task H. Do not regenerate inventories wholesale. No authority call site is added; Task H updates one existing call expression to name its verified-region adapter.
+- No subagents, Claude/Anthropic tools, git writes, commits or production actions. Use the controller-designated shared GOCACHE, never delete it, and remove only the exact scratch directory created for this replay.
+- No new product copy ships here. Existing refusal messages remain unchanged. The copy inventory at the end distinguishes internal protocol names from product language.
+
+## Home-name decision and compatibility
+
+Keep the roster name as the claim engine's name. If a home box is enrolled in `ie`, its engine, ordinary admissions, writer identity and root stop identity continue to use `ie`. The engine name passes through `park.PlacementRegion`; it is a valid named code. The box's empty `CARLOS_REGION` separately establishes that `ie` and `@home` denote its same storage region. On a named box the placement must equal its roster region and no alias is installed.
+
+This is the brief's permitted “stays the name” choice. Changing the existing engine identity to `@home` would also change admission chains, signed receipts, manifests and recovery comparisons. Preserving the name avoids a migration that would have to prove every retained evidence reference still means the same thing. There is no bulk rewrite or opportunistic CAS conversion. Old named claims continue through the existing writer path. The worker also recognizes moves' `region:@home` chores and reads the maximum of the two home quiet bounds.
+
+The alias has explicit context. `SameRegion("", "ie", "@home")` is false. A named region called `home` is distinct from `@home` unless the verified box is actually the home placement and its roster name is `home`. Exact obligation owners, retirement IDs, scopes, manifest regions and evidence remain untouched. Storage-side live-set comparisons understand both home spellings, so old named admissions remain protected under a new `@home` view. Read-only holder projections are solely inputs to generation selection; never write them back or use them to validate signed evidence.
+
+For a move, the request door and BeginClaimMove use the same exported roster-placement predicate. Its HomePlacementCode rule is the moves branch's existing homeRegionIn rule. BeginClaimMove receives the console's deployment home name and retains it in `op.intent.home_region` in the same start CAS. The record's From/To stay placement codes. The claim's target and the loss intent's SourceRegion/TargetRegion are engine values. SourceRegion is never fed into a record or placement resolver; current readers use it as an admission/write-source/park region.
+
+## Review Focus
+
+1. A named-box roster mismatch refuses; an empty roster or an engine token used as a placement refuses. VerifyRegional still checks the entire fresh actor, bucket and request limit, including on alias chores.
+2. The regional census finds `@home` chores even without residue. Both home names can complete through the real engine. A foreign chore stays queued. The manifest keeps the chore's original name across retries.
+3. Existing named admission chains remain live in an `@home` view. Quiet windows under either name block reconcile, WorkSource, root launch, token preparation and moves readiness. Baseline projections never mutate the original holder.
+4. The console's home touched region no longer depends on HibernateRegion's AWS name. Named records use their own placement. The console's flag-off fallback stays intact.
+5. BeginClaimMove compares mapped source and target roster regions with the record and request, rereads the signed directory, checks both enrollments/fences and retains the existing admission/CAS checks. StartClaimMoveRecord keeps the record target as a placement code.
+6. A home-source loss persists `@home`, survives the real declare-lost flow and writes rollback Region/From as empty. Recovery compares aliases only under the operation's home binding. No loss coverage or stop proof is manufactured.
+7. Keep the deployment home name stable during rollout. Upgrade home workers/readers before allowing console home moves. The implementation is not an activation authorization and does not close existing moves/C8/region-HA gates.
+
+## Integration with moves-home-parks
+
+Land the moves home work first: `6dd7297f` supplies HomeRegion, PlacementRegion and reserved-token tests; `7fb8079a` maps move scopes, views, cleanup, predecessor regions and recovery commits; `89dbfe95` followed by `66b4e84d` puts the home staging lease mapping in host wiring without an import cycle. Read the individual commits, not a whole-tree diff against the moves branch's older base.
+
+The requested isolated replay overlays only region.go onto the pinned main. It proves these claim-side patches compile and their tests run against merged Plans 1–12. It is not a replay of the entire not-yet-merged moves branch. On integration, retain those moves changes and resolve overlapping functions to the complete versions in this plan: the shared console/hostops roster predicate, the alias-aware armMovesParks condition, productionRosterRegion's checked placement result, and the alias-aware moves policy/probe checks. Do not restore old placement comparisons while resolving a patch conflict.
+
+In particular, movesBoxRegion continues to require `PlacementRegion(CARLOS_REGION)`; productionRosterRegion now returns that token after checking the signed roster binding. The claim engine may retain the roster name because armMovesParks uses Engine.OwnsRegion. Moves' homePlacementCode wrapper, if retained when merging, must delegate to park.HomePlacementCode; there must be one rule, not two independently edited copies. Preserve the moves branch's predecessorRegion mapping and stagingLeaseKey changes, neither of which this replay replaces. Re-run both branches' home witnesses after combining them.
+
+## Replay without git writes
+
+The spec is a separate design-branch input: rev 6.32 at SHA-256 `6b7b1db1b51571e8877d8d9e4ecc90c3a14476f775d2bdc1534f25120524f4ba`. It is not present in the main export. The original design spec stays unchanged during authoring; Task I's patch is tested on its scratch copy.
+
+```bash
+CLAIM13_REPO=/home/paulca/amadan.net/carlos/platform/.claude/worktrees/replica-ownership
+CLAIM13_PLAN="$CLAIM13_REPO/docs/superpowers/plans/2026-10-12-host-lifecycle-claim-13-home-region.md"
+CLAIM13_BASE=1b5802fb75864e7659bd44ca1f90ad20a1979cc9
+CLAIM13_SCRATCH=$(mktemp -d "$TMPDIR/claim13.XXXXXX")
+export CLAIM13_REPO CLAIM13_PLAN CLAIM13_BASE CLAIM13_SCRATCH
+mkdir "$CLAIM13_SCRATCH/tree"
+git -C "$CLAIM13_REPO" archive "$CLAIM13_BASE" | tar -x -C "$CLAIM13_SCRATCH/tree"
+git -C "$CLAIM13_REPO" show 66b4e84d:internal/park/region.go > "$CLAIM13_SCRATCH/tree/internal/park/region.go"
+mkdir -p "$CLAIM13_SCRATCH/tree/docs/superpowers/specs"
+cp "$CLAIM13_REPO/docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md" "$CLAIM13_SCRATCH/tree/docs/superpowers/specs/"
+export GOCACHE=/home/paulca/.cache/tmp/claude-1001/gocache
+export GOFLAGS="-mod=mod -buildvcs=false" GOPROXY=off
+cd "$CLAIM13_SCRATCH/tree"
+```
+
+Save this complete helper as `$CLAIM13_SCRATCH/apply.py`. It rejects offsets, fuzz and missing phases.
+
+```python
+import pathlib, re, subprocess, sys
+plan, task, phase = sys.argv[1:]
+blocks = [body for name, stage, body in re.findall(
+ r'<!-- apply: (\w+) (tests|implementation) -->\n```diff\n(.*?)\n```',
+ pathlib.Path(plan).read_text(), re.S) if name == task and stage == phase]
+if not blocks:
+ raise SystemExit('missing task/phase')
+for body in blocks:
+ result = subprocess.run(['patch', '--batch', '--forward', '--fuzz=0', '-p1'],
+ input=body + '\n', text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ print(result.stdout, end='')
+ if result.returncode or re.search(r'offset|fuzz|previously applied|Reversed', result.stdout):
+ raise SystemExit('patch did not apply exactly')
+```
+
+For A–G and G2, apply the tests, run the task command and observe the stated regression, then apply implementation and rerun. H uses the existing censuses; I uses the spec assertions below. A missing cache or denied socket is not a regression witness.
+
+```bash
+python3 "$CLAIM13_SCRATCH/apply.py" "$CLAIM13_PLAN" A tests
+go test ./internal/park -run TestPlan13 -count=1
+python3 "$CLAIM13_SCRATCH/apply.py" "$CLAIM13_PLAN" A implementation
+go test ./internal/park -run TestPlan13 -count=1
+```
+
+## Task A: Bind home aliases in the park engine
+
+Add the shared home-name rule, nonempty alias equality and maximum quiet bound. Give Engine and View the verified HomeRegion binding. Use it for ownership and live-generation comparisons while keeping original obligation and manifest fields. The test retains an old named admission chain and proves a foreign view does not acquire it.
+
+Focused command: `go test ./internal/park -run TestPlan13 -count=1`.
+
+Expected first failure: The helpers and View.HomeRegion do not exist.
+
+### Failing tests
+
+<!-- apply: A tests -->
+```diff
+--- /dev/null
++++ b/internal/park/plan13_region_test.go
+@@ -0,0 +1,51 @@
++package park
++
++import (
++ "github.com/carlosframework/platform/internal/hostclaim"
++ "testing"
++ "time"
++)
++
++func TestPlan13RegionNames(t *testing.T) {
++ for _, tc := range []struct{ home, region, want string }{
++ {"ie", "ie", ""}, {"ie", "", ""}, {"ie", "uk", "uk"}, {"", "ie", "ie"},
++ } {
++ if got := HomePlacementCode(tc.home, tc.region); got != tc.want {
++ t.Fatalf("%+v: %q", tc, got)
++ }
++ }
++ for _, tc := range []struct {
++ home, a, b string
++ want bool
++ }{
++ {"ie", "ie", HomeRegion, true}, {"ie", HomeRegion, "ie", true}, {"ie", "uk", HomeRegion, false},
++ {"", "ie", HomeRegion, false}, {"ie", "", HomeRegion, false}, {"ie", "", "", false},
++ {"ie", "uk", "uk", true}, {"home", "home", HomeRegion, true},
++ } {
++ if got := SameRegion(tc.home, tc.a, tc.b); got != tc.want {
++ t.Fatalf("%+v: %v", tc, got)
++ }
++ }
++ q := map[string]time.Time{"ie": time.Unix(5, 0), HomeRegion: time.Unix(9, 0), "uk": time.Unix(12, 0)}
++ if !QuietUntil(q, "ie", "ie").Equal(q[HomeRegion]) || !QuietUntil(q, "ie", "uk").Equal(q["uk"]) {
++ t.Fatal("quiet aliases")
++ }
++}
++func TestPlan13OldAdmissionLiveSet(t *testing.T) {
++ c := &hostclaim.Claim{Holder: &hostclaim.Holder{Location: "instances/h/i", Generation: "new", Admissions: []hostclaim.AdmissionRecord{
++ {Generation: "new", Predecessor: "old", Region: "ie", PredecessorRegion: "ie", Mode: "cold"},
++ }}, Op: hostclaim.Op{Kind: hostclaim.KindServing}}
++ v := View{HomeRegion: "ie", Region: HomeRegion, AuthoritativeRegion: HomeRegion, MarkerKnown: true, MarkerGeneration: "old", GenerationOf: func(Ref) (string, bool) { return "old", true }}
++ got, err := LiveGenerations(c, v, c.Holder.Location)
++ if err != nil || !got["old"] || !got["new"] {
++ t.Fatalf("%v %v", got, err)
++ }
++ if c.Holder.Admissions[0].Region != "ie" {
++ t.Fatal("rewrote history")
++ }
++ v.Region = "uk"
++ got, err = LiveGenerations(c, v, c.Holder.Location)
++ if err != nil || len(got) != 0 {
++ t.Fatalf("foreign: %v %v", got, err)
++ }
++}
+```
+
+### Implementation
+
+<!-- apply: A implementation -->
+```diff
+--- /dev/null
++++ b/internal/park/region_names.go
+@@ -0,0 +1,41 @@
++package park
++
++import "time"
++
++// HomePlacementCode is the console door's home-name rule. It consumes a
++// roster/API name, not an engine region; @home is never a placement code.
++func HomePlacementCode(home, region string) string {
++ if home != "" && region == home {
++ return ""
++ }
++ return region
++}
++
++// SameRegion preserves exact stored identities. The only alias is a verified
++// home roster name and @home. Empty always means unset at this boundary.
++func SameRegion(home, a, b string) bool {
++ if a == "" || b == "" {
++ return false
++ }
++ if a == b {
++ return true
++ }
++ return home != "" && PlacementRegion(home) == home &&
++ ((a == home && b == HomeRegion) || (a == HomeRegion && b == home))
++}
++
++func QuietUntil(bounds map[string]time.Time, home, region string) time.Time {
++ var latest time.Time
++ for r, until := range bounds {
++ if SameRegion(home, r, region) && latest.Before(until) {
++ latest = until
++ }
++ }
++ return latest
++}
++
++func (e Engine) OwnsRegion(region string) bool {
++ return SameRegion(e.HomeRegion, e.Region, region)
++}
++
++func (v View) sameRegion(a, b string) bool { return SameRegion(v.HomeRegion, a, b) }
+--- a/internal/park/remote.go
++++ b/internal/park/remote.go
+@@ -54,6 +54,7 @@
+ }
+
+ type Engine struct {
++ HomeRegion string
+ Store lease.VersionedStore
+ Claims release.Store
+ Bucket, Region string
+@@ -325,9 +326,10 @@
+ if err != nil {
+ return obs, v, err
+ }
+- if v.ClaimETag != obs.ETag || v.Region != e.Region {
++ if v.ClaimETag != obs.ETag || !e.OwnsRegion(v.Region) {
+ return obs, v, ErrChanged
+ }
++ v.HomeRegion = e.HomeRegion
+ return obs, v, nil
+ }
+ func (e Engine) selected(ctx context.Context, w Work, o hostclaim.Obligation, held HeldLease, vs []lease.ObjectVersion) (hostclaim.Observed, View, []lease.ObjectVersion, error) {
+@@ -348,7 +350,7 @@
+ if isLock(w.Host, o.Park.Location, x.Key) {
+ return obs, v, nil, ErrUnsafe
+ }
+- if obs.Claim.Holder != nil && obs.Claim.Op.Kind != hostclaim.KindParking && o.Park.Location == obs.Claim.Holder.Location && v.Region == v.AuthoritativeRegion {
++ if obs.Claim.Holder != nil && obs.Claim.Op.Kind != hostclaim.KindParking && o.Park.Location == obs.Claim.Holder.Location && v.sameRegion(v.Region, v.AuthoritativeRegion) {
+ if x.Key == v.Marker.Key {
+ if o.Kind == "park-generation" {
+ continue
+@@ -414,7 +416,7 @@
+ if err := validateScope(w.Host, o); err != nil {
+ return err
+ }
+- if o.Region != e.Region {
++ if !e.OwnsRegion(o.Region) {
+ return ErrUnsafe
+ }
+ var held HeldLease
+--- a/internal/park/sweep.go
++++ b/internal/park/sweep.go
+@@ -18,6 +18,7 @@
+ // Pinned includes the exact noncurrent versions needed by the installed root.
+ // MarkerKnown means its read succeeded; an absent marker may denote @root.
+ type View struct {
++ HomeRegion string
+ // ArchivedIncarnations labels proven retired locations independently of
+ // the moves-sweep proof's incarnation. Missing entries never invent labels.
+ ArchivedIncarnations map[string]string
+@@ -51,7 +52,7 @@
+ if v.AuthoritativeRegion == "" {
+ return nil, ErrUnsafe
+ }
+- if v.Region != v.AuthoritativeRegion {
++ if !v.sameRegion(v.Region, v.AuthoritativeRegion) {
+ return live, nil
+ }
+ if !v.MarkerKnown || v.GenerationOf == nil {
+@@ -82,7 +83,7 @@
+ want, region := current, v.AuthoritativeRegion
+ for i := len(h.Admissions) - 1; i >= 0; i-- {
+ a := h.Admissions[i]
+- if generation(a.Generation) != want || a.Region != region {
++ if generation(a.Generation) != want || !v.sameRegion(a.Region, region) {
+ continue
+ }
+ if a.Mode != "cold" {
+@@ -94,14 +95,14 @@
+ }
+ want = pred
+ region = a.PredecessorRegion
+- if region == "" || region == v.AuthoritativeRegion {
++ if region == "" || v.sameRegion(region, v.AuthoritativeRegion) {
+ if a.Covered {
+ return nil, ErrUnsafe
+ }
+ live[want] = true
+ }
+ if want == marker {
+- if region != "" && region != v.AuthoritativeRegion {
++ if region != "" && !v.sameRegion(region, v.AuthoritativeRegion) {
+ return nil, ErrUnsafe
+ }
+ return live, nil
+@@ -141,7 +142,7 @@
+ return d, nil
+ }
+ for _, o := range c.Obligations {
+- if o.Region == v.Region {
++ if v.sameRegion(o.Region, v.Region) {
+ return d, nil
+ }
+ }
+@@ -161,7 +162,7 @@
+ if isLock(c.Host, loc, x.Key) {
+ continue
+ }
+- if refusal := v.RefusedLocations[loc]; refusal != nil && !(c.Holder != nil && loc == c.Holder.Location && v.Region == v.AuthoritativeRegion) {
++ if refusal := v.RefusedLocations[loc]; refusal != nil && !(c.Holder != nil && loc == c.Holder.Location && v.sameRegion(v.Region, v.AuthoritativeRegion)) {
+ if !refused[loc] {
+ d.Alerts = append(d.Alerts, Alert{loc, refusal.Error()})
+ refused[loc] = true
+@@ -177,7 +178,7 @@
+ candidate.Incarnation = v.ArchivedIncarnations[loc]
+ }
+ }
+- if c.Holder != nil && loc == c.Holder.Location && v.Region == v.AuthoritativeRegion {
++ if c.Holder != nil && loc == c.Holder.Location && v.sameRegion(v.Region, v.AuthoritativeRegion) {
+ if liveErr != nil {
+ continue
+ }
+@@ -329,6 +330,7 @@
+ return empty, ErrChanged
+ }
+ v, err := e.View(ctx, obs)
++ v.HomeRegion = e.HomeRegion
+ if err != nil {
+ return empty, err
+ }
+```
+
+## Task B: Acknowledge home chores and honor both quiet keys
+
+Carry the binding into WorkSource and Reconciler. Census and dispatch must compare the region through Engine.OwnsRegion but still require the exact persisted region owner. Run the real engine against home-token, old named-home and foreign queued chores.
+
+Focused command: `go test ./internal/hostbox -run TestPlan13 -count=1`.
+
+Expected first failure: WorkSource.HomeRegion and Reconciler.HomeRegion do not exist.
+
+### Failing tests
+
+<!-- apply: B tests -->
+```diff
+--- /dev/null
++++ b/internal/hostbox/plan13_region_test.go
+@@ -0,0 +1,103 @@
++//go:build unix
++
++package hostbox
++
++import (
++ "context"
++ "errors"
++ "github.com/carlosframework/platform/internal/hostclaim"
++ "github.com/carlosframework/platform/internal/hostwrite"
++ "github.com/carlosframework/platform/internal/lease"
++ "github.com/carlosframework/platform/internal/lease/leasetest"
++ "github.com/carlosframework/platform/internal/park"
++ "github.com/carlosframework/platform/internal/release"
++ "testing"
++ "time"
++)
++
++func TestPlan13HomeQuietWindow(t *testing.T) {
++ for _, home := range []string{"ie", ""} {
++ t.Run("home="+home, func(t *testing.T) {
++ source, store, obs := workFixture(t)
++ source.HomeRegion = home
++ _, err := hostclaim.Update(t.Context(), store, obs, func(c *hostclaim.Claim) error {
++ c.QuietAfter = map[string]time.Time{park.HomeRegion: time.Unix(200, 0)}
++ return nil
++ })
++ if err != nil {
++ t.Fatal(err)
++ }
++ work, err := source.Select(t.Context(), obs.Host, "probe")
++ if err != nil {
++ t.Fatal(err)
++ }
++ ran := false
++ err = work.With(t.Context(), hostwrite.Identity{}, func() error { ran = true; return nil })
++ if home != "" && (!errors.Is(err, ErrChanged) || ran) {
++ t.Fatalf("home: %v %v", ran, err)
++ }
++ if home == "" && err != nil {
++ t.Fatalf("named: %v", err)
++ }
++ h := obs.Claim.Holder
++ selected := RecordSelection{Host: obs.Host, Key: h.Key, Incarnation: h.Incarnation, Family: h.Family, ETag: "record"}
++ r := Reconciler{HomeRegion: home, Claims: store, Box: "box", Region: "ie", Now: source.Now, CheckRecord: func(context.Context, RecordSelection) error { return nil }, CheckLocal: func(context.Context, hostclaim.Observed, RecordSelection) error { return nil }}
++ current, err := hostclaim.Read(t.Context(), store, obs.Host)
++ if err != nil {
++ t.Fatal(err)
++ }
++ err = r.check(t.Context(), current, selected)
++ if (err != nil) != (home != "") {
++ t.Fatalf("reconcile home=%q: %v", home, err)
++ }
++ })
++ }
++}
++func TestPlan13RegionalHomeAcknowledgesBothNames(t *testing.T) {
++ for _, region := range []string{park.HomeRegion, "ie", "uk"} {
++ t.Run(region, func(t *testing.T) {
++ claims := release.NewDirStore(t.TempDir())
++ bucket := leasetest.NewVersionedBucket()
++ bucket.AddBucket("data")
++ now := time.Unix(2000, 0).UTC()
++ host := "home.example"
++ inc, err := hostclaim.Incarnation("bab", "app", host, now)
++ if err != nil {
++ t.Fatal(err)
++ }
++ _, err = hostclaim.Update(t.Context(), claims, hostclaim.Observed{Host: host, Absent: true}, func(c *hostclaim.Claim) error {
++ c.Holder = &hostclaim.Holder{Family: "instance", Key: "apps/bab/app/instances/" + host + ".json", Account: "bab", App: "app", Incarnation: inc, CreatedAt: now, Location: "instances/" + host + "/" + inc, Stamp: 1}
++ c.Op = hostclaim.Op{Kind: hostclaim.KindParking, ID: "op"}
++ c.Obligations = []hostclaim.Obligation{{ID: "id", Op: "op", Kind: "park-replica", Region: region, Owner: "region:" + region, Retirement: "ret", Park: &hostclaim.ParkScope{Location: c.Holder.Location, Incarnation: inc, EvidenceAt: time.Unix(1, 0).UTC(), RequestLimit: RequestLimit}}}
++ c.Touched = hostclaim.Touched{Complete: true, Regions: []string{region}}
++ return nil
++ })
++ if err != nil {
++ t.Fatal(err)
++ }
++ e := park.Engine{HomeRegion: "ie", Claims: claims, Store: bucket, Bucket: "data", Region: "ie", Leases: workerLeases{}, Now: func() time.Time { return now }, NewID: func() string { return "id2" }, Authorize: func(context.Context, hostclaim.Observed, hostclaim.Obligation) error { return nil }, View: func(_ context.Context, o hostclaim.Observed) (park.View, error) {
++ return park.View{ClaimETag: o.ETag, Region: park.HomeRegion}, nil
++ }}
++ w := RegionalWorker{Regional: Regional{Engine: e, Owner: func(context.Context, hostclaim.Observed) error { return nil }}, Enrollment: enrollmentFunc(func(context.Context, string, string, time.Duration) error { return nil }), Evidence: evidenceFunc(func(context.Context, hostclaim.Observed, []lease.ObjectVersion) (string, time.Time, error) {
++ return "", now, nil
++ })}
++ hosts, err := w.Census(t.Context())
++ if err != nil {
++ t.Fatal(err)
++ }
++ if (len(hosts) == 1) != (region != "uk") {
++ t.Fatalf("census %v", hosts)
++ }
++ if err = w.Run(t.Context()); err != nil {
++ t.Fatal(err)
++ }
++ o, err := hostclaim.Read(t.Context(), claims, host)
++ if err != nil {
++ t.Fatal(err)
++ }
++ if (len(o.Claim.Obligations) == 0) != (region != "uk") {
++ t.Fatalf("obligations %+v", o.Claim.Obligations)
++ }
++ })
++ }
++}
+```
+
+### Implementation
+
+<!-- apply: B implementation -->
+```diff
+--- a/internal/hostbox/work.go
++++ b/internal/hostbox/work.go
+@@ -3,6 +3,7 @@
+ import (
+ "context"
+ "errors"
++ "github.com/carlosframework/platform/internal/park"
+ "sync"
+ "time"
+
+@@ -22,6 +23,7 @@
+ }
+
+ type WorkSource struct {
++ HomeRegion string
+ Prepare func(context.Context, hostclaim.Observed) error
+ Guard hostguard.Guard
+ Box, Region string
+@@ -86,7 +88,7 @@
+ }
+ switch selected.Action {
+ case "install", "restore", "spawn", "seed", "writer", "probe":
+- if until, ok := c.QuietAfter[s.Region]; ok && !s.Now().After(until) {
++ if until := park.QuietUntil(c.QuietAfter, s.HomeRegion, s.Region); !until.IsZero() && !s.Now().After(until) {
+ return ErrChanged
+ }
+ }
+--- a/internal/hostbox/reconcile.go
++++ b/internal/hostbox/reconcile.go
+@@ -18,6 +18,7 @@
+ DryRun bool
+ }
+ type Reconciler struct {
++ HomeRegion string
+ Claims release.Store
+ Guard hostguard.Guard
+ Local park.Local
+@@ -60,7 +61,7 @@
+ if err := Serving(obs.Claim, Selection{Box: r.Box}); err != nil {
+ return err
+ }
+- if until, ok := obs.Claim.QuietAfter[r.Region]; ok && !r.Now().After(until) {
++ if until := park.QuietUntil(obs.Claim.QuietAfter, r.HomeRegion, r.Region); !until.IsZero() && !r.Now().After(until) {
+ return ErrChanged
+ }
+ if err := r.CheckRecord(ctx, selected); err != nil {
+--- a/internal/hostbox/regionalworker.go
++++ b/internal/hostbox/regionalworker.go
+@@ -102,7 +102,7 @@
+ return nil, readErr
+ }
+ for _, o := range obs.Claim.Obligations {
+- if o.Region == w.Regional.Engine.Region && o.Owner == "region:"+w.Regional.Engine.Region {
++ if w.Regional.Engine.OwnsRegion(o.Region) && o.Owner == "region:"+o.Region {
+ hosts[host] = true
+ break
+ }
+@@ -197,7 +197,7 @@
+ if !found {
+ continue
+ }
+- if o.Owner != "region:"+w.Regional.Engine.Region || o.Region != w.Regional.Engine.Region {
++ if o.Owner != "region:"+o.Region || !w.Regional.Engine.OwnsRegion(o.Region) {
+ continue
+ }
+ if o.Kind != "park-replica" && o.Kind != "park-generation" {
+--- a/internal/hostbox/park.go
++++ b/internal/hostbox/park.go
+@@ -41,7 +41,7 @@
+ if ob.ID != work.ID || ob.Op != work.Op {
+ continue
+ }
+- if ob.Region != r.Engine.Region || ob.Owner != "region:"+r.Engine.Region || (ob.Kind != "park-replica" && ob.Kind != "park-generation") {
++ if !r.Engine.OwnsRegion(ob.Region) || ob.Owner != "region:"+ob.Region || (ob.Kind != "park-replica" && ob.Kind != "park-generation") {
+ return ErrUnsupported
+ }
+ return r.Engine.Run(ctx, work)
+```
+
+## Task C: Wire the verified box identity into claim engines
+
+Keep Actor.Region as the roster identity. IdentityProvider verifies the original row/cloud evidence before attaching HomeRegion. Production derives the binding from the signed roster and local placement, supplies it to reconcile and parks, and verifies aliases on every regional effect. Root cleanup retains the same metadata without gaining execution eligibility. Map the production view resolver to the placement token only after checking its roster binding.
+
+Focused command: `go test ./internal/host -run TestPlan13 -count=1`.
+
+Expected first failure: claimRegionBinding and IdentityProvider.HomeRegion do not exist.
+
+### Failing tests
+
+<!-- apply: C tests -->
+```diff
+--- /dev/null
++++ b/internal/host/plan13_region_test.go
+@@ -0,0 +1,44 @@
++package host
++
++import (
++ "errors"
++ "github.com/carlosframework/platform/internal/hostbox"
++ "github.com/carlosframework/platform/internal/park"
++ "testing"
++)
++
++func TestPlan13ClaimRegionBinding(t *testing.T) {
++ for _, tc := range []struct {
++ roster, code, engine, home string
++ bad bool
++ }{
++ {"ie", "", "ie", "ie", false}, {"uk", "uk", "uk", "", false},
++ {"uk", "ie", "", "", true}, {"", "", "", "", true}, {"@home", "", "", "", true}, {"ie", "@home", "", "", true},
++ } {
++ e, h, err := claimRegionBinding(tc.roster, tc.code)
++ if e != tc.engine || h != tc.home || (err != nil) != tc.bad {
++ t.Fatalf("%+v: %q %q %v", tc, e, h, err)
++ }
++ }
++}
++func TestPlan13VerifyRegionalHomeNames(t *testing.T) {
++ for _, home := range []string{"region", ""} {
++ identity := handoverRecoveryIdentity(t, hostbox.Actor{Box: "box", Enrollment: "enrolled", Region: "region", ControlBucket: "control", Revision: 1})
++ identity.HomeRegion = home
++ actor, err := identity.Verify(t.Context())
++ if err != nil {
++ t.Fatal(err)
++ }
++ e := claimRegionalEnrollment{identity: identity, actor: actor}
++ for _, region := range []string{"region", park.HomeRegion, "other", ""} {
++ err = e.VerifyRegional(t.Context(), region, actor.Bucket, hostbox.RequestLimit)
++ want := region == "region" || (home != "" && region == park.HomeRegion)
++ if (err == nil) != want {
++ t.Fatalf("home %q region %q: %v", home, region, err)
++ }
++ }
++ if err = e.VerifyRegional(t.Context(), actor.Region, "other", hostbox.RequestLimit); !errors.Is(err, hostbox.ErrChanged) {
++ t.Fatal(err)
++ }
++ }
++}
+```
+
+### Implementation
+
+<!-- apply: C implementation -->
+```diff
+--- /dev/null
++++ b/internal/host/claimregion.go
+@@ -0,0 +1,20 @@
++package host
++
++import (
++ "github.com/carlosframework/platform/internal/hostbox"
++ "github.com/carlosframework/platform/internal/park"
++)
++
++// The signed roster retains the claim's engine name. CARLOS_REGION supplies
++// this box's placement code; only its empty home placement binds an alias.
++func claimRegionBinding(roster, code string) (engine, home string, err error) {
++ engine = park.PlacementRegion(roster)
++ placement := park.PlacementRegion(code)
++ if roster == "" || engine == "" || placement == "" || (code != "" && engine != placement) {
++ return "", "", hostbox.ErrChanged
++ }
++ if code == "" {
++ home = engine
++ }
++ return engine, home, nil
++}
+--- a/internal/hostbox/identity.go
++++ b/internal/hostbox/identity.go
+@@ -3,6 +3,7 @@
+ import (
+ "context"
+ "crypto/x509"
++ "github.com/carlosframework/platform/internal/park"
+
+ "github.com/carlosframework/platform/internal/boxid"
+ "github.com/carlosframework/platform/internal/hostclaim"
+@@ -12,6 +13,7 @@
+ // Actor is the verified projection of the sole control/host-actors.json roster.
+ // Roster authenticates that document and its revision before returning a row.
+ type Actor struct {
++ HomeRegion string
+ ControlBucket string
+ Box, Enrollment string
+ Revision uint64
+@@ -31,6 +33,7 @@
+ }
+
+ type IdentityProvider struct {
++ HomeRegion string
+ AWSRegion string
+ Roster Roster
+ Evidence BoxEvidence
+@@ -67,6 +70,10 @@
+ if ev.Box != p.Box || ev.Cloud != cloud {
+ return Actor{}, ErrChanged
+ }
++ if p.HomeRegion != "" && (p.HomeRegion != a.Region || park.PlacementRegion(p.HomeRegion) != p.HomeRegion) {
++ return Actor{}, ErrChanged
++ }
++ a.HomeRegion = p.HomeRegion
+ return a, nil
+ }
+
+--- a/internal/host/claimproduction.go
++++ b/internal/host/claimproduction.go
+@@ -118,7 +118,11 @@
+ return out, hostwrite.ErrUnavailable
+ }
+ authority := productionClaimRoster{Directory: directory, DataBucket: dataBucket, FloorDir: filepath.Join(r.OptDir, "system", "guard")}
+- out.Identity = hostbox.IdentityProvider{AWSRegion: row.Cloud.Region, Account: row.Cloud.Account, Box: row.BoxID, Region: row.Region, Bucket: dataBucket, Namespace: row.Namespace, Certs: certs, Roster: authority, Evidence: productionBoxEvidence{store}}
++ _, homeRegion, err := claimRegionBinding(row.Region, os.Getenv("CARLOS_REGION"))
++ if err != nil {
++ return out, err
++ }
++ out.Identity = hostbox.IdentityProvider{HomeRegion: homeRegion, AWSRegion: row.Cloud.Region, Account: row.Cloud.Account, Box: row.BoxID, Region: row.Region, Bucket: dataBucket, Namespace: row.Namespace, Certs: certs, Roster: authority, Evidence: productionBoxEvidence{store}}
+ var actor hostbox.Actor
+ if r.Mode == "instance-control" {
+ out.cleanupEnrollment, err = verifyClaimCleanupEnrollment(ctx, out.Identity, directory)
+@@ -355,13 +359,15 @@
+ if p.Parks == nil || p.Parks.View == nil {
+ return park.View{}, &ClaimDependencyError{Contracts: []string{"moves Plan 5 exact-version generation and files-manifest view"}}
+ }
+- if _, err := p.Identity.Verify(ctx); err != nil {
++ actor, err := p.Identity.Verify(ctx)
++ if err != nil {
+ return park.View{}, err
+ }
+- v, err := p.Parks.View(ctx, o)
++ v, err := p.Parks.View(ctx, park.HomeView(o, actor.HomeRegion))
+ if err != nil {
+ return park.View{}, err
+ }
++ v.HomeRegion = actor.HomeRegion
+ v.ArchivedIncarnations = claimArchivedLocations(o.Claim)
+ v.RefusedLocations = map[string]error{}
+ if _, err := claimLegacyIncarnation(o.Claim); err != nil {
+@@ -393,7 +399,7 @@
+ if err != nil {
+ return err
+ }
+- if o.Claim == nil || ob.Op != o.Claim.Op.ID || (ob.Owner != "box:"+a.Box && ob.Owner != "region:"+a.Region) || (ob.Box != "" && ob.Box != a.Box) || (ob.Region != "" && ob.Region != a.Region) {
++ if o.Claim == nil || ob.Op != o.Claim.Op.ID || (ob.Owner != "box:"+a.Box && (ob.Owner != "region:"+ob.Region || !park.SameRegion(a.HomeRegion, ob.Region, a.Region))) || (ob.Box != "" && ob.Box != a.Box) || (ob.Region != "" && !park.SameRegion(a.HomeRegion, ob.Region, a.Region)) {
+ return hostbox.ErrChanged
+ }
+ found := false
+@@ -450,7 +456,7 @@
+ if c.Op.Kind != sel.Kind || c.Op.ID != sel.Op || c.Op.Phase != sel.Phase || c.Holder.Incarnation != sel.Captured.Incarnation || c.Holder.Stamp != sel.Captured.Stamp || c.Holder.Session != sel.Session || c.Holder.Generation != sel.Generation {
+ return hostbox.ErrChanged
+ }
+- if until, ok := c.QuietAfter[sel.Region]; ok && !t.Now().After(until) {
++ if until := park.QuietUntil(c.QuietAfter, actor.HomeRegion, sel.Region); !until.IsZero() && !t.Now().After(until) {
+ return hostbox.ErrChanged
+ }
+ if err := t.Local(ctx, c, sel); err != nil {
+--- a/internal/host/claimcleanup_identity.go
++++ b/internal/host/claimcleanup_identity.go
+@@ -40,6 +40,7 @@
+ if _, err := identity.Verify(ctx); err != nil {
+ return nil, err
+ }
++ actor.HomeRegion = identity.HomeRegion
+ actor.Eligible, actor.Quarantined = false, true
+ return &claimCleanupEnrollment{actor: actor, directory: directory}, nil
+ }
+--- a/internal/host/claimassembly.go
++++ b/internal/host/claimassembly.go
+@@ -156,12 +156,13 @@
+ }
+ a.Guard.Claims = hostguard.StoreReader{Store: a.Claims}
+ // Continue is the moves C1 session table, nil unless both flags are on (D1).
+- source := hostbox.WorkSource{Guard: a.Guard, Box: actor.Box, Region: actor.Region, Now: time.Now, Policy: a.Policy.Check, Local: a.Identity.Local(a.Local), Continue: a.Wake.Continue}
++ source := hostbox.WorkSource{HomeRegion: actor.HomeRegion, Guard: a.Guard, Box: actor.Box, Region: park.PlacementRegion(actor.Region), Now: time.Now, Policy: a.Policy.Check, Local: a.Identity.Local(a.Local), Continue: a.Wake.Continue}
+ a.Reconcile.Reconciler.Claims = a.Claims
+ a.Reconcile.Reconciler.Guard = a.Guard
+ a.Reconcile.Reconciler.Writes = hostwrite.Config{Enabled: true, Selector: source}
+ a.Reconcile.Reconciler.Box = actor.Box
+- a.Reconcile.Reconciler.Region = actor.Region
++ a.Reconcile.Reconciler.Region = park.PlacementRegion(actor.Region)
++ a.Reconcile.Reconciler.HomeRegion = actor.HomeRegion
+ a.Reconcile.Reconciler.Now = time.Now
+ a.Reconcile.Reconciler.Local = park.Local{Guard: a.Guard, Claims: a.Claims, DataDir: a.Guard.DataDir, Box: actor.Box}
+ a.Reconcile.Box = actor.Box
+@@ -177,7 +178,7 @@
+ a.Provision = a.Reconcile.Configure
+ check := claimBoundIdentity(a.Identity.Verify, actor)
+ data := hostbox.RequestStore{Store: a.Data, Check: check}
+- writer := hostbox.Writer{Loss: a.LossAdmission, Claims: a.Claims, Data: data, Bucket: actor.Bucket, Region: actor.Region, Box: actor.Box, AdmissionEvidence: a.AdmissionEvidence, PublicationEvidence: a.PublicationEvidence, NewID: a.NewID}
++ writer := hostbox.Writer{Loss: a.LossAdmission, Claims: a.Claims, Data: data, Bucket: actor.Bucket, Region: park.PlacementRegion(actor.Region), Box: actor.Box, AdmissionEvidence: a.AdmissionEvidence, PublicationEvidence: a.PublicationEvidence, NewID: a.NewID}
+ a.bindClaimWakeStop()
+ if local, ok := a.Local.(*productionClaimLocal); ok {
+ if err := local.bind(ctx, &a); err != nil {
+@@ -227,7 +228,7 @@
+ }
+ return a.View(ctx, o)
+ }
+- engine := park.Engine{Store: data, Claims: a.Claims, Bucket: actor.Bucket, Region: actor.Region, Leases: hostbox.AggregateLeases{Leases: hostbox.RenewingLeases{Region: actor.Cloud.Region}, Resolve: leases}, Now: time.Now, NewID: a.NewID, View: view, Authorize: authorize}
++ engine := park.Engine{HomeRegion: actor.HomeRegion, Store: data, Claims: a.Claims, Bucket: actor.Bucket, Region: park.PlacementRegion(actor.Region), Leases: hostbox.AggregateLeases{Leases: hostbox.RenewingLeases{Region: actor.Cloud.Region}, Resolve: leases}, Now: time.Now, NewID: a.NewID, View: view, Authorize: authorize}
+ engine.VerifyMovesSweep = func(ctx context.Context, o hostclaim.Observed, versions []lease.ObjectVersion) (park.MoveSweepProof, error) {
+ if err := check(ctx); err != nil {
+ return park.MoveSweepProof{}, err
+@@ -267,7 +268,7 @@
+ if err != nil {
+ return err
+ }
+- if a != e.actor || a.Region != region || a.Bucket != bucket {
++ if a != e.actor || !park.SameRegion(a.HomeRegion, park.PlacementRegion(a.Region), region) || a.Bucket != bucket {
+ return hostbox.ErrChanged
+ }
+ return nil
+--- a/internal/host/claimlaunch_authority.go
++++ b/internal/host/claimlaunch_authority.go
+@@ -3,6 +3,7 @@
+ import (
+ "context"
+ "errors"
++ "github.com/carlosframework/platform/internal/park"
+ "os"
+ "path/filepath"
+ "strconv"
+@@ -135,7 +136,7 @@
+ if c.Op.Kind != hostclaim.KindServing && boot >= state.SafeUntil {
+ return execlease.ErrLost
+ }
+- if until, ok := c.QuietAfter[t.Region]; ok && !now.After(until) {
++ if until := park.QuietUntil(c.QuietAfter, actor.HomeRegion, t.Region); !until.IsZero() && !now.After(until) {
+ return hostbox.ErrChanged
+ }
+ return nil
+--- a/internal/host/claimconstruction.go
++++ b/internal/host/claimconstruction.go
+@@ -258,7 +258,7 @@
+ var publisher ClaimFencedPublication
+ var groupD claimGroupD
+ if movesC1 {
+- serving = &claimServingOwners{Data: hostbox.RequestStore{Store: owners.Data, Check: func(ctx context.Context) error { _, err := owners.Identity.Verify(ctx); return err }}, Bucket: actor.Bucket, Region: actor.Region, Box: actor.Box, NewID: newID, Now: time.Now, Sources: owners.Serving}
++ serving = &claimServingOwners{Data: hostbox.RequestStore{Store: owners.Data, Check: func(ctx context.Context) error { _, err := owners.Identity.Verify(ctx); return err }}, Bucket: actor.Bucket, Region: park.PlacementRegion(actor.Region), Box: actor.Box, NewID: newID, Now: time.Now, Sources: owners.Serving}
+ groupD, err = claimGroupDWiring(claimEntryEnv(ctx), serving.owners(), owners.Moves, owners.Relocation)
+ if err != nil {
+ return nil, err
+--- a/internal/host/movessources.go
++++ b/internal/host/movessources.go
+@@ -586,7 +586,7 @@
+ if err != nil {
+ return fmt.Errorf("the claim's MoveParks: %w", err)
+ }
+- if engine.Bucket != cfg.OwnBucket || engine.Region != cfg.Region {
++ if engine.Bucket != cfg.OwnBucket || !engine.OwnsRegion(park.PlacementRegion(cfg.Region)) {
+ return fmt.Errorf("%w: the claim's park engine is %s in %q, this box's moves use %s in %q", hostbox.ErrChanged, engine.Bucket, engine.Region, cfg.OwnBucket, cfg.Region)
+ }
+ if engine.Leases == nil || engine.View == nil || engine.Authorize == nil {
+@@ -646,6 +646,10 @@
+ if err != nil {
+ return "", err
+ }
+- return rr.row.Region, nil
+- }
+-}
++ _, _, err = claimRegionBinding(rr.row.Region, getenv("CARLOS_REGION"))
++ if err != nil {
++ return "", err
++ }
++ return park.PlacementRegion(getenv("CARLOS_REGION")), nil
++ }
++}
+--- /dev/null
++++ b/internal/park/home_view.go
+@@ -0,0 +1,23 @@
++package park
++
++import "github.com/carlosframework/platform/internal/hostclaim"
++
++func HomeView(o hostclaim.Observed, home string) hostclaim.Observed {
++ if home == "" || o.Claim == nil || o.Claim.Holder == nil {
++ return o
++ }
++ c, h := *o.Claim, *o.Claim.Holder
++ h.Admissions = append([]hostclaim.AdmissionRecord(nil), h.Admissions...)
++ for i := range h.Admissions {
++ a := &h.Admissions[i]
++ if a.Region == home {
++ a.Region = HomeRegion
++ }
++ if a.PredecessorRegion == home {
++ a.PredecessorRegion = HomeRegion
++ }
++ }
++ c.Holder = &h
++ o.Claim = &c
++ return o
++}
+```
+
+## Task D: Use the same home rule at create and move start
+
+Select touched regions from each record placement. A configured home name is the claim spelling; absent a configured name, the home token is still nonempty. Preserve the old dbClaimRegion fallback in the flag-off arm. Share the exact roster predicate between console preflight and BeginClaimMove, including source and target checks. BeginClaimMove accepts an optional deployment home name for source compatibility with existing named callers and rejects multiple or invalid names. Keep existing enrollment/fence and admission checks.
+
+Focused command: `go test ./internal/hostops ./internal/console -run TestPlan13 -count=1`.
+
+Expected first failure: The console helper and BeginClaimMove home argument do not exist.
+
+### Failing tests
+
+<!-- apply: D tests -->
+```diff
+--- /dev/null
++++ b/internal/console/plan13_region_test.go
+@@ -0,0 +1,36 @@
++package console
++
++import (
++ "github.com/carlosframework/platform/internal/park"
++ "testing"
++)
++
++func TestPlan13ConsoleClaimRegion(t *testing.T) {
++ for _, tc := range []struct {
++ cfg Config
++ code, want string
++ }{
++ {Config{HostClaim: true, HomeRegion: "ie"}, "", "ie"},
++ {Config{HostClaim: true, HomeRegion: "ie"}, "uk", "uk"},
++ {Config{HostClaim: true}, "", park.HomeRegion},
++ {Config{HostClaim: true, HomeRegion: "ie"}, "@home", ""},
++ } {
++ if got := tc.cfg.claimPlacementRegion(tc.code); got != tc.want {
++ t.Fatalf("%+v: %q", tc, got)
++ }
++ }
++ for _, tc := range []struct {
++ cfg Config
++ want string
++ }{
++ {Config{Region: "", HomeRegion: "ie", HibernateRegion: "eu-west-1"}, "ie"},
++ {Config{HibernateRegion: "eu-west-1"}, "eu-west-1"},
++ {Config{HostClaim: true, HomeRegion: "ie", HibernateRegion: "eu-west-1"}, "ie"},
++ {Config{HostClaim: true, Region: "uk", HomeRegion: "ie"}, "uk"},
++ {Config{HostClaim: true, HibernateRegion: "eu-west-1"}, park.HomeRegion},
++ } {
++ if got := tc.cfg.dbClaimRegion(); got != tc.want {
++ t.Fatalf("%+v: %q", tc, got)
++ }
++ }
++}
+--- /dev/null
++++ b/internal/hostops/plan13_enrollment_test.go
+@@ -0,0 +1,50 @@
++package hostops
++
++import (
++ "encoding/json"
++ "github.com/carlosframework/platform/internal/hostclaim"
++ "github.com/carlosframework/platform/internal/park"
++ "github.com/carlosframework/platform/internal/provision"
++ "testing"
++)
++
++func TestPlan13BeginClaimMoveHomeAndNamed(t *testing.T) {
++ for _, tc := range []struct{ name, home, from, to, target, want string }{
++ {"out-home", "ie", "", "uk", "uk-box", "uk"},
++ {"into-home", "uk", "ie", "", "uk-box", park.HomeRegion},
++ {"named", "", "ie", "uk", "uk-box", "uk"},
++ } {
++ t.Run(tc.name, func(t *testing.T) {
++ f := newMoveStartFixture(t, nil)
++ rec, etag, err := provision.GetRecordETag(t.Context(), f.raw, "bab", "app", f.host)
++ if err != nil {
++ t.Fatal(err)
++ }
++ rec.Region = tc.from
++ raw, err := json.Marshal(rec)
++ if err != nil {
++ t.Fatal(err)
++ }
++ if _, err = f.raw.Store.Put(t.Context(), f.raw.recordKey, raw, etag); err != nil {
++ t.Fatal(err)
++ }
++ o := f.read(t)
++ // This is an existing claim, including its named touched region.
++ h, err := f.authority.RegisterMove(t.Context(), o, "canonical", moveStartID)
++ if err != nil {
++ t.Fatal(err)
++ }
++ got, err := BeginClaimMove(t.Context(), f.raw, f.authority.Directory, h, o, moveStartID, "operator", tc.target, tc.to, f.now, tc.home)
++ if err != nil {
++ t.Fatal(err)
++ }
++ if got.Claim.Op.Intent["to"] != tc.want || got.Claim.Touched.Regions[0] != "ie" {
++ t.Fatalf("%+v", got.Claim)
++ }
++ if got.Claim.Op.Intent["home_region"] != tc.home {
++ t.Fatal("lost home binding")
++ }
++ _ = hostclaim.KindMoving
++ })
++ }
++}
+```
+
+### Implementation
+
+<!-- apply: D implementation -->
+```diff
+--- a/internal/console/apidbclaim.go
++++ b/internal/console/apidbclaim.go
+@@ -10,6 +10,7 @@
+ "encoding/json"
+ "errors"
+ "fmt"
++ "github.com/carlosframework/platform/internal/park"
+ "io"
+ "net/http"
+ "os"
+@@ -176,7 +177,18 @@
+ }
+ }
+
++func (cfg Config) claimPlacementRegion(code string) string {
++ region := park.PlacementRegion(code)
++ if region == park.HomeRegion && park.PlacementRegion(cfg.HomeRegion) == cfg.HomeRegion && cfg.HomeRegion != "" {
++ return cfg.HomeRegion
++ }
++ return region
++}
++
+ func (cfg Config) dbClaimRegion() string {
++ if cfg.HostClaim {
++ return cfg.claimPlacementRegion(cfg.Region)
++ }
+ for _, region := range []string{cfg.Region, cfg.HomeRegion, cfg.HibernateRegion} {
+ if region = strings.TrimSpace(region); region != "" {
+ return region
+--- a/internal/console/apiinstances.go
++++ b/internal/console/apiinstances.go
+@@ -1395,10 +1395,7 @@
+ claimRequest, createErr = s.beginClaimCreate(r.Context(), acct, app, host, principal, func(now time.Time, acquisition string) (hostops.Record, error) {
+ rec.CreatedAt = time.Time{}
+ rec.Acquisition = acquisition
+- region := s.cfg.dbClaimRegion()
+- if rec.Region != "" {
+- region = rec.Region
+- }
++ region := s.cfg.claimPlacementRegion(rec.Region)
+ prepared, err := hostops.PrepareInstance(rec, now, region)
+ if err == nil {
+ rec.CreatedAt = now
+--- a/internal/console/apisidecars.go
++++ b/internal/console/apisidecars.go
+@@ -343,10 +343,7 @@
+ request, err := s.beginClaimCreate(r.Context(), acct, app, rec.Host, principal, func(now time.Time, acquisition string) (hostops.Record, error) {
+ rec.CreatedAt = time.Time{}
+ rec.Acquisition = acquisition
+- region := s.cfg.dbClaimRegion()
+- if rec.Region != "" {
+- region = rec.Region
+- }
++ region := s.cfg.claimPlacementRegion(rec.Region)
+ prepared, err := hostops.PrepareSidecar(rec, now, region)
+ if err == nil {
+ rec.CreatedAt = now
+--- a/internal/console/claim_first_route.go
++++ b/internal/console/claim_first_route.go
+@@ -17,7 +17,7 @@
+ }
+ request, err := s.beginClaimCreate(ctx, acct, app, host, principal, func(now time.Time, acquisition string) (hostops.Record, error) {
+ rec := provision.Record{Schema: 1, Host: host, App: app, Account: acct.Sqid, Channel: channel, Kind: "static", CreatedBy: principal, Acquisition: acquisition}
+- record, err := hostops.PrepareInstance(rec, now, s.cfg.dbClaimRegion())
++ record, err := hostops.PrepareInstance(rec, now, s.cfg.claimPlacementRegion(rec.Region))
+ record.VerifiedOwnership = true
+ return record, err
+ })
+--- a/internal/console/apiinstancemove.go
++++ b/internal/console/apiinstancemove.go
+@@ -7,6 +7,7 @@
+ "encoding/json"
+ "errors"
+ "fmt"
++ "github.com/carlosframework/platform/internal/park"
+ "log"
+ "net/http"
+ "os"
+@@ -250,10 +251,7 @@
+ // record's "" (the unconstrained value the fleet's reconcile has always
+ // scoped by). The record NEVER stores the code; the API never shows "".
+ func (s *Server) homeRegionIn(region string) string {
+- if s.cfg.HomeRegion != "" && region == s.cfg.HomeRegion {
+- return ""
+- }
+- return region
++ return park.HomePlacementCode(s.cfg.HomeRegion, region)
+ }
+
+ func (s *Server) homeRegionOut(region string) string {
+@@ -761,7 +759,7 @@
+ ctx := r.Context()
+ a := s.cfg.ClaimAuthority
+ to := ms.To
+- if !claimMoveRosterPlaces(roster.Roster, obs.Claim.Op.Exec, target, recRegion, to) {
++ if !claimMoveRosterPlaces(roster.Roster, obs.Claim.Op.Exec, target, recRegion, to, s.cfg.HomeRegion) {
+ jsonError(w, http.StatusConflict, "the host roster does not allow this move. the source and target must be active boxes in "+from+" and "+to)
+ return
+ }
+@@ -798,7 +796,7 @@
+ }
+ return
+ }
+- moving, err := hostops.BeginClaimMove(ctx, s.cfg.Releases, a.Directory, h, obs, ms.ID, ms.By, target, to, ms.Now)
++ moving, err := hostops.BeginClaimMove(ctx, s.cfg.Releases, a.Directory, h, obs, ms.ID, ms.By, target, to, ms.Now, s.cfg.HomeRegion)
+ if err != nil {
+ switch {
+ case errors.Is(err, hostops.ErrIdentity), errors.Is(err, hostactors.ErrRoster):
+@@ -1252,11 +1250,8 @@
+ // the door's own observation: both boxes have active, enrolled entries
+ // under their box references, they differ, the source is in the record's
+ // region and the target in to, which is not the source's region.
+-func claimMoveRosterPlaces(r hostactors.Roster, source, target, recRegion, to string) bool {
+- src, sourceOK := r.Boxes[source]
+- dst, targetOK := r.Boxes[target]
+- return sourceOK && targetOK && source != target && src.State == "active" && dst.State == "active" && src.Enrollment != "" && dst.Enrollment != "" &&
+- src.Region != "" && src.Region == recRegion && dst.Region == to && src.Region != to
++func claimMoveRosterPlaces(r hostactors.Roster, source, target, recRegion, to string, home ...string) bool {
++ return hostops.ClaimMoveRosterPlaces(r, source, target, recRegion, to, home...)
+ }
+
+ // sourceEnrollment is the enrollment of the box that executes the claim, and
+--- a/internal/hostops/move_enrollment.go
++++ b/internal/hostops/move_enrollment.go
+@@ -3,6 +3,7 @@
+ import (
+ "context"
+ "errors"
++ "github.com/carlosframework/platform/internal/park"
+ "sync/atomic"
+ "time"
+
+@@ -59,8 +60,18 @@
+ // the roster. The admission is reread inside the transition, so the last
+ // admission read precedes the one claim CAS, which itself runs under h's
+ // lifecycle grant (the data boundary rereads admission again at the Put).
+-func BeginClaimMove(ctx context.Context, s release.Store, d hostactors.Directory, h *StartHandle, o hostclaim.Observed, id, by, target, to string, now time.Time) (hostclaim.Observed, error) {
++func BeginClaimMove(ctx context.Context, s release.Store, d hostactors.Directory, h *StartHandle, o hostclaim.Observed, id, by, target, to string, now time.Time, homeNames ...string) (hostclaim.Observed, error) {
+ if h == nil || h.w == nil || s == nil || o.Absent || o.Claim == nil || o.Claim.Holder == nil || o.ETag == "" || id == "" || id != h.id || now.IsZero() {
++ return hostclaim.Observed{}, ErrIdentity
++ }
++ home := ""
++ if len(homeNames) > 1 {
++ return hostclaim.Observed{}, ErrIdentity
++ }
++ if len(homeNames) == 1 {
++ home = homeNames[0]
++ }
++ if home != "" && park.PlacementRegion(home) != home {
+ return hostclaim.Observed{}, ErrIdentity
+ }
+ holder := o.Claim.Holder
+@@ -74,7 +85,7 @@
+ }
+ src, sourceOK := roster.Roster.Boxes[source]
+ dst, targetOK := roster.Roster.Boxes[target]
+- if !sourceOK || !targetOK || source == target || src.State != "active" || dst.State != "active" || src.Region == "" || dst.Region != to || src.Region == to {
++ if !sourceOK || !targetOK || source == target || src.State != "active" || dst.State != "active" || src.Region == "" || dst.Region == "" || park.PlacementRegion(park.HomePlacementCode(home, dst.Region)) != park.PlacementRegion(to) || park.PlacementRegion(park.HomePlacementCode(home, src.Region)) == park.PlacementRegion(to) {
+ return hostclaim.Observed{}, ErrIdentity
+ }
+ // Lookup also refuses a box with a termination fence.
+@@ -95,7 +106,7 @@
+ return hostclaim.Observed{}, err
+ }
+ inc, err := hostclaim.Incarnation(rec.Account, rec.App, rec.Host, rec.CreatedAt)
+- if err != nil || inc != holder.Incarnation || rec.Region != src.Region || rec.Move != nil {
++ if err != nil || inc != holder.Incarnation || !ClaimMoveRosterPlaces(roster.Roster, source, target, rec.Region, to, home) || rec.Move != nil {
+ return hostclaim.Observed{}, ErrIdentity
+ }
+ admitted := hostclaim.MoveAdmission{AppID: h.appID, AdmissionID: h.id, SourceEnrollment: src.Enrollment, TargetEnrollment: dst.Enrollment}
+@@ -103,7 +114,13 @@
+ if err := liveMoveRegistration(ctx, s, h, o); err != nil {
+ return err
+ }
+- return hostclaim.BeginAdmittedMove(id, by, source, target, to, now, admitted)(c)
++ if err := hostclaim.BeginAdmittedMove(id, by, source, target, park.PlacementRegion(to), now, admitted)(c); err != nil {
++ return err
++ }
++ if home != "" {
++ c.Op.Intent["home_region"] = home
++ }
++ return nil
+ })
+ }
+
+@@ -134,7 +151,7 @@
+ return nil, ErrIdentity
+ }
+ c := moving.Claim
+- if c.Op.Kind != hostclaim.KindMoving || c.Op.Phase != hostclaim.PhaseMoving || p.ID != c.Op.ID || p.Incarnation != c.Holder.Incarnation || p.To != c.Op.Intent["to"] ||
++ if c.Op.Kind != hostclaim.KindMoving || c.Op.Phase != hostclaim.PhaseMoving || p.ID != c.Op.ID || p.Incarnation != c.Holder.Incarnation || park.PlacementRegion(p.To) != c.Op.Intent["to"] ||
+ c.Op.Intent["admission_id"] != h.id || c.Op.Intent["app_id"] != h.appID {
+ return nil, ErrIdentity
+ }
+--- /dev/null
++++ b/internal/hostops/move_region.go
+@@ -0,0 +1,27 @@
++package hostops
++
++import (
++ "github.com/carlosframework/platform/internal/hostactors"
++ "github.com/carlosframework/platform/internal/park"
++)
++
++// ClaimMoveRosterPlaces is shared by the console preflight and the start CAS.
++func ClaimMoveRosterPlaces(r hostactors.Roster, source, target, recRegion, to string, homeNames ...string) bool {
++ if len(homeNames) > 1 {
++ return false
++ }
++ home := ""
++ if len(homeNames) == 1 {
++ home = homeNames[0]
++ }
++ if home != "" && park.PlacementRegion(home) != home {
++ return false
++ }
++ src, sourceOK := r.Boxes[source]
++ dst, targetOK := r.Boxes[target]
++ if !sourceOK || !targetOK || source == target || src.State != "active" || dst.State != "active" || src.Enrollment == "" || dst.Enrollment == "" || src.Region == "" || dst.Region == "" {
++ return false
++ }
++ from, into := park.PlacementRegion(park.HomePlacementCode(home, src.Region)), park.PlacementRegion(park.HomePlacementCode(home, dst.Region))
++ return from != "" && into != "" && from != into && from == park.PlacementRegion(recRegion) && into == park.PlacementRegion(to)
++}
+```
+
+## Task E: Persist loss source regions as engine values
+
+Map both From and To at record-to-loss/park boundaries. Remove the home-placement refusal, preserving invalid-code and same-region refusals. The full home-source declaration test checks the signed loss intent and then reads the actual rollback record to prove the home code remains empty. Recovery policy accepts the retained named engine only under the current operation home binding.
+
+Focused command: `go test ./internal/hostops -run "TestPlan13Loss|TestPlan13HomeLoss|TestPlan12LossOwner" -count=1`.
+
+Expected first failure: The home-source and home-target loss intent cases refuse; the real home-source loss inspection refuses.
+
+### Failing tests
+
+<!-- apply: E tests -->
+```diff
+--- /dev/null
++++ b/internal/hostops/plan13_loss_test.go
+@@ -0,0 +1,64 @@
++package hostops
++
++import (
++ "encoding/json"
++ "github.com/carlosframework/platform/internal/hostclaim"
++ "github.com/carlosframework/platform/internal/park"
++ "github.com/carlosframework/platform/internal/provision"
++ "testing"
++)
++
++func TestPlan13LossSourceRegionIsEngineName(t *testing.T) {
++ for _, tc := range []struct{ from, to, want string }{
++ {"", "uk", park.HomeRegion}, {"ie", "uk", "ie"}, {"uk", "", "uk"},
++ } {
++ t.Run(tc.from+"-"+tc.to, func(t *testing.T) {
++ in := moveLossIntent{Move: lossOp, Incarnation: "inc", Target: "target", TargetRegion: park.PlacementRegion(tc.to)}
++ rec := provision.Record{Region: tc.to, Move: &provision.MoveState{ID: lossOp, Incarnation: "inc", From: tc.from, To: tc.to, State: provision.MoveLanding}}
++ op := hostclaim.Op{ID: lossOp, Kind: hostclaim.KindLanding}
++ if err := recordLossIntent(&in, rec, "etag", 1, op, "claim-etag", nil); err != nil {
++ t.Fatal(err)
++ }
++ if in.SourceRegion != tc.want || rec.Move.From != tc.from || rec.Region != tc.to {
++ t.Fatalf("%+v %+v", in, rec)
++ }
++ raw, err := json.Marshal(in)
++ if err != nil {
++ t.Fatal(err)
++ }
++ var persisted map[string]any
++ if err = json.Unmarshal(raw, &persisted); err != nil {
++ t.Fatal(err)
++ }
++ if persisted["source_region"] != tc.want {
++ t.Fatal(persisted)
++ }
++ })
++ }
++}
++func TestPlan13HomeLossDeclarationKeepsPlacement(t *testing.T) {
++ w := newLossWorld(t, "landing", func(c *hostclaim.Claim, r *provision.Record, _ *AppAdmission) {
++ r.Move.From = ""
++ c.Op.Intent["home_region"] = "ie"
++ })
++ inspect := w.inspect(t, "uk-label")
++ after, err := w.declare(w.submission(inspect))
++ if err != nil {
++ t.Fatal(err)
++ }
++ rec, _ := w.record(t)
++ if rec.Region != "" || rec.Move.From != "" {
++ t.Fatalf("record leaked engine token: %+v", rec)
++ }
++ loss, err := hostclaim.ReadLossRecord(t.Context(), w.raw, w.pub, inspect.Details.ID)
++ if err != nil {
++ t.Fatal(err)
++ }
++ in, _, err := lossIntentOf(loss.Record)
++ if err != nil {
++ t.Fatal(err)
++ }
++ if in.SourceRegion != park.HomeRegion || after.Claim.Op.Kind != hostclaim.KindRestoring {
++ t.Fatalf("%+v", in)
++ }
++}
+```
+
+### Implementation
+
+<!-- apply: E implementation -->
+```diff
+--- a/internal/hostops/move_loss_owner.go
++++ b/internal/hostops/move_loss_owner.go
+@@ -10,6 +10,7 @@
+ "encoding/json"
+ "errors"
+ "fmt"
++ "github.com/carlosframework/platform/internal/park"
+ "io"
+ "reflect"
+ "strconv"
+@@ -313,13 +314,13 @@
+ // rollback, phase, Dirty and return lane.
+ func recordLossIntent(in *moveLossIntent, rec provision.Record, etag string, seq uint64, op hostclaim.Op, claimETag string, prior *supersededLoss) error {
+ m := rec.Move
+- if m == nil || m.ID != in.Move || m.Incarnation != in.Incarnation || m.To != in.TargetRegion || m.From == "" || m.From == m.To {
++ if m == nil || m.ID != in.Move || m.Incarnation != in.Incarnation || park.PlacementRegion(m.To) != in.TargetRegion || park.PlacementRegion(m.From) == "" || m.From == m.To {
+ return hostresolve.ErrResolve
+ }
+ if m.State == provision.MoveCommitted || m.CommittedAt != nil {
+ return moveLossRefusal(provision.RefusedCommitted)
+ }
+- in.SourceRegion, in.RecordETag, in.RecordSeq = m.From, etag, seq
++ in.SourceRegion, in.RecordETag, in.RecordSeq = park.PlacementRegion(m.From), etag, seq
+ switch m.State {
+ case provision.MoveLanding:
+ if op.Kind != hostclaim.KindLanding || m.Rollback != nil {
+--- a/internal/hostops/move_loss_admission.go
++++ b/internal/hostops/move_loss_admission.go
+@@ -2,6 +2,7 @@
+
+ import (
+ "github.com/carlosframework/platform/internal/hostclaim"
++ "github.com/carlosframework/platform/internal/park"
+ "strconv"
+ "strings"
+ )
+@@ -41,7 +42,7 @@
+ c.Op.ID != in.Move || c.Op.Kind != hostclaim.KindRestoring || c.Op.Phase != hostclaim.PhaseProbing ||
+ c.Op.Intent["rollback"] != in.Rollback || c.Op.Exec != in.Source || c.Op.Intent["source"] != in.Source ||
+ c.Op.Intent["target"] != in.Target || c.Op.Intent["to"] != in.TargetRegion ||
+- !hostclaim.LostTargetReleased(&c, loss.ID) || a.Box != in.Source || a.Region != in.SourceRegion ||
++ !hostclaim.LostTargetReleased(&c, loss.ID) || a.Box != in.Source || !park.SameRegion(c.Op.Intent["home_region"], a.Region, in.SourceRegion) ||
+ a.Mode != "cold" || a.Coverage != "" || a.KeepStamp || a.PredecessorCertified {
+ return hostclaim.ErrLossRecord
+ }
+--- a/internal/hostops/move_fence_release.go
++++ b/internal/hostops/move_fence_release.go
+@@ -7,6 +7,7 @@
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostops/internal/fencerelease"
+ "github.com/carlosframework/platform/internal/moves"
++ "github.com/carlosframework/platform/internal/park"
+ "github.com/carlosframework/platform/internal/provision"
+ "github.com/carlosframework/platform/internal/release"
+ )
+@@ -94,7 +95,7 @@
+ return writeRecord()
+ }
+ h := o.Claim.Holder
+- r := hostclaim.FencedTargetRelease{OpID: m.ID, RollbackID: rb.ID, Target: o.Claim.Op.Intent["target"], TargetRegion: m.To, SourceRegion: m.From,
++ r := hostclaim.FencedTargetRelease{OpID: m.ID, RollbackID: rb.ID, Target: o.Claim.Op.Intent["target"], TargetRegion: park.PlacementRegion(m.To), SourceRegion: park.PlacementRegion(m.From),
+ Location: moves.TargetLocationScope(*h, fence.ObservedAt()), Lane: moves.StagingScope(o.Host, m.ID, h.Incarnation, fence.ObservedAt()), NewID: moveLossNewID(ref.SHA256), Fence: fence}
+ if err := CheckCreateFreeze(ctx, g.w.raw, g.w.pub); err != nil {
+ return err
+```
+
+## Task F: Preserve mixed home history in generation selection
+
+Add a read-only holder projection for baseline selection and pass the home binding into the serving and writer adapters. The production park view gets a projected copy while authorization keeps the original observation. Teach moves policy and probe checks the same bound alias. Keep original signed tuples. Exact-version park completion can cover predecessors under both names only when the engine has the verified home binding and the same parked-generation census.
+
+Focused command: `go test ./internal/park ./internal/host ./internal/hostbox -run TestPlan13 -count=1`.
+
+Expected first failure: HolderRegionView is missing and the home target policy refuses the named engine.
+
+### Failing tests
+
+<!-- apply: F tests -->
+```diff
+--- /dev/null
++++ b/internal/host/plan13_policy_test.go
+@@ -0,0 +1,22 @@
++package host
++
++import (
++ "github.com/carlosframework/platform/internal/hostclaim"
++ "github.com/carlosframework/platform/internal/park"
++ "github.com/carlosframework/platform/internal/provision"
++ "testing"
++)
++
++func TestPlan13HomeMovePolicy(t *testing.T) {
++ for _, home := range []string{"uk", ""} {
++ c := mpClaim(hostclaim.KindLanding, provision.LandConfigured)
++ c.Op.Intent["to"] = park.HomeRegion
++ c.Op.Intent["home_region"] = home
++ s := mpSelection(c, "restore")
++ policy, _ := mpPolicy()
++ err := policy(t.Context(), c, s, mpIdentity("restore"))
++ if (err == nil) != (home != "") {
++ t.Fatalf("home=%q: %v", home, err)
++ }
++ }
++}
+--- /dev/null
++++ b/internal/park/plan13_holder_test.go
+@@ -0,0 +1,25 @@
++package park
++
++import (
++ "github.com/carlosframework/platform/internal/hostclaim"
++ "testing"
++)
++
++func TestPlan13HolderProjectionPreservesEvidence(t *testing.T) {
++ h := hostclaim.Holder{Admissions: []hostclaim.AdmissionRecord{{Region: "ie", PredecessorRegion: HomeRegion}, {Region: "uk", PredecessorRegion: ""}}}
++ got := HolderRegionView(h, "ie", "ie")
++ if got.Admissions[0].PredecessorRegion != "ie" || got.Admissions[1].Region != "uk" || got.Admissions[1].PredecessorRegion != "" {
++ t.Fatal(got)
++ }
++ if h.Admissions[0].PredecessorRegion != HomeRegion {
++ t.Fatal("rewrote input")
++ }
++ got = HolderRegionView(h, "ie", HomeRegion)
++ if got.Admissions[0].Region != HomeRegion {
++ t.Fatal(got)
++ }
++ got = HolderRegionView(h, "", "uk")
++ if got.Admissions[0].Region != "ie" || got.Admissions[0].PredecessorRegion != HomeRegion {
++ t.Fatal("unbound alias")
++ }
++}
+```
+
+### Implementation
+
+<!-- apply: F implementation -->
+```diff
+--- a/internal/park/home_view.go
++++ b/internal/park/home_view.go
+@@ -1,22 +1,32 @@
+ package park
+
+ import "github.com/carlosframework/platform/internal/hostclaim"
++
++// HolderRegionView is for read-only generation selection. Never persist it
++// or use it to validate signed stop/loss evidence; those keep exact tuples.
++func HolderRegionView(h hostclaim.Holder, home, region string) hostclaim.Holder {
++ if home == "" || !SameRegion(home, region, HomeRegion) {
++ return h
++ }
++ h.Admissions = append([]hostclaim.AdmissionRecord(nil), h.Admissions...)
++ for i := range h.Admissions {
++ a := &h.Admissions[i]
++ if SameRegion(home, a.Region, region) {
++ a.Region = region
++ }
++ if SameRegion(home, a.PredecessorRegion, region) {
++ a.PredecessorRegion = region
++ }
++ }
++ return h
++}
+
+ func HomeView(o hostclaim.Observed, home string) hostclaim.Observed {
+ if home == "" || o.Claim == nil || o.Claim.Holder == nil {
+ return o
+ }
+- c, h := *o.Claim, *o.Claim.Holder
+- h.Admissions = append([]hostclaim.AdmissionRecord(nil), h.Admissions...)
+- for i := range h.Admissions {
+- a := &h.Admissions[i]
+- if a.Region == home {
+- a.Region = HomeRegion
+- }
+- if a.PredecessorRegion == home {
+- a.PredecessorRegion = HomeRegion
+- }
+- }
++ c := *o.Claim
++ h := HolderRegionView(*c.Holder, home, HomeRegion)
+ c.Holder = &h
+ o.Claim = &c
+ return o
+--- a/internal/host/movespolicy.go
++++ b/internal/host/movespolicy.go
+@@ -5,6 +5,7 @@
+ "encoding/json"
+ "errors"
+ "fmt"
++ "github.com/carlosframework/platform/internal/park"
+ "strconv"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+@@ -296,7 +297,7 @@
+ if len(c.Op.Participants) != 2 || c.Op.Participants[1] != s.Box || c.Op.Intent["target"] != s.Box || c.Op.Intent["source"] == s.Box {
+ return refuse("this box is not the move's target", nil)
+ }
+- if c.Op.Intent["to"] != s.Region {
++ if !park.SameRegion(c.Op.Intent["home_region"], c.Op.Intent["to"], s.Region) {
+ return refuse("this region is not the move's target region", nil)
+ }
+ for _, lost := range c.Lost {
+@@ -415,7 +416,7 @@
+ if len(c.Op.Participants) == 0 || c.Op.Participants[0] != s.Box || c.Op.Intent["source"] != s.Box || c.Op.Intent["target"] == s.Box {
+ return refuse("this box is not the move's source", nil)
+ }
+- if c.Op.Intent["to"] == s.Region {
++ if park.SameRegion(c.Op.Intent["home_region"], c.Op.Intent["to"], s.Region) {
+ return refuse("this region is the move's target region", nil)
+ }
+ for _, lost := range c.Lost {
+@@ -436,7 +437,7 @@
+ if rec == nil || rec.Host != c.Host || rec.Move == nil || rec.Move.ID != s.Op || rec.Move.Incarnation != c.Holder.Incarnation {
+ return refuse("the record is not this move", nil)
+ }
+- if rec.Move.From != s.Region {
++ if !park.SameRegion(c.Op.Intent["home_region"], park.PlacementRegion(rec.Move.From), s.Region) {
+ return refuse("this region is not the move's source region", nil)
+ }
+ var (
+--- a/internal/host/movesprobeowners.go
++++ b/internal/host/movesprobeowners.go
+@@ -8,6 +8,7 @@
+ "encoding/json"
+ "errors"
+ "fmt"
++ "github.com/carlosframework/platform/internal/park"
+ "io/fs"
+ "log"
+ "os"
+@@ -91,6 +92,7 @@
+ // Production: nothing registers this owner set yet. The probe is unarmed in
+ // production until Plan 5 Task 9 arms the parks; the tests use fakes.
+ type movesProbeOwners struct {
++ HomeRegion string
+ // Claims is the claim store (the deployment store in production).
+ Claims release.Store
+ Records MovesRecordReader
+@@ -209,7 +211,7 @@
+ return v, hostbox.ErrChanged
+ }
+ if c.Host != o.Host || c.Op.ID == "" || c.Op.Exec != m.Box || c.Op.Intent["target"] != m.Box ||
+- c.Op.Intent["source"] == m.Box || c.Op.Intent["to"] != m.Region || len(c.Op.Participants) != 2 || c.Op.Participants[1] != m.Box {
++ c.Op.Intent["source"] == m.Box || !park.SameRegion(c.Op.Intent["home_region"], c.Op.Intent["to"], m.Region) || len(c.Op.Participants) != 2 || c.Op.Participants[1] != m.Box {
+ return v, hostbox.ErrChanged
+ }
+ for _, lost := range c.Lost {
+@@ -226,7 +228,7 @@
+ return v, hostbox.ErrChanged
+ }
+ mv := rec.Move
+- if mv.ID != v.op || mv.Incarnation != v.holder.Incarnation || mv.To != m.Region || mv.Inputs == nil || mv.Inputs.Release == "" {
++ if mv.ID != v.op || mv.Incarnation != v.holder.Incarnation || !park.SameRegion(c.Op.Intent["home_region"], park.PlacementRegion(mv.To), m.Region) || mv.Inputs == nil || mv.Inputs.Release == "" {
+ return v, hostbox.ErrChanged
+ }
+ if v.committed {
+@@ -394,7 +396,7 @@
+ }
+
+ func (m *movesProbeOwners) request(host string, h *hostclaim.Holder) genselect.Request {
+- return genselect.Request{Bucket: m.Bucket, Region: m.Region, Host: host, Holder: *h}
++ return genselect.Request{Bucket: m.Bucket, Region: m.Region, Host: host, Holder: park.HolderRegionView(*h, m.HomeRegion, m.Region)}
+ }
+
+ func preparedPin(v probeLanding) genselect.PreparedPin {
+--- a/internal/hostbox/writer.go
++++ b/internal/hostbox/writer.go
+@@ -3,6 +3,7 @@
+ import (
+ "context"
+ "fmt"
++ "github.com/carlosframework/platform/internal/park"
+ "strings"
+
+ "github.com/carlosframework/platform/internal/genselect"
+@@ -12,6 +13,7 @@
+
+ // Writer supplies transition adapters, not stop, certificate or marker evidence.
+ type Writer struct {
++ HomeRegion string
+ Loss *LossAdmission
+ Claims release.Store
+ Data genselect.Reader
+@@ -25,7 +27,7 @@
+ if w.Data == nil || w.Bucket == "" || w.Region == "" || observed.Absent || observed.Claim == nil || observed.Claim.Holder == nil {
+ return nil, ErrUnsupported
+ }
+- return genselect.Select(ctx, w.Data, genselect.Request{Bucket: w.Bucket, Region: w.Region, Host: observed.Host, Holder: *observed.Claim.Holder})
++ return genselect.Select(ctx, w.Data, genselect.Request{Bucket: w.Bucket, Region: w.Region, Host: observed.Host, Holder: park.HolderRegionView(*observed.Claim.Holder, w.HomeRegion, w.Region)})
+ }
+
+ func (w Writer) Admit(ctx context.Context, observed hostclaim.Observed, in hostclaim.Admission, baseline *genselect.Plan) (hostclaim.Observed, error) {
+--- a/internal/host/claimassembly.go
++++ b/internal/host/claimassembly.go
+@@ -178,7 +178,7 @@
+ a.Provision = a.Reconcile.Configure
+ check := claimBoundIdentity(a.Identity.Verify, actor)
+ data := hostbox.RequestStore{Store: a.Data, Check: check}
+- writer := hostbox.Writer{Loss: a.LossAdmission, Claims: a.Claims, Data: data, Bucket: actor.Bucket, Region: park.PlacementRegion(actor.Region), Box: actor.Box, AdmissionEvidence: a.AdmissionEvidence, PublicationEvidence: a.PublicationEvidence, NewID: a.NewID}
++ writer := hostbox.Writer{HomeRegion: actor.HomeRegion, Loss: a.LossAdmission, Claims: a.Claims, Data: data, Bucket: actor.Bucket, Region: park.PlacementRegion(actor.Region), Box: actor.Box, AdmissionEvidence: a.AdmissionEvidence, PublicationEvidence: a.PublicationEvidence, NewID: a.NewID}
+ a.bindClaimWakeStop()
+ if local, ok := a.Local.(*productionClaimLocal); ok {
+ if err := local.bind(ctx, &a); err != nil {
+--- a/internal/host/claimowners.go
++++ b/internal/host/claimowners.go
+@@ -4,6 +4,7 @@
+ "context"
+ "encoding/json"
+ "errors"
++ "github.com/carlosframework/platform/internal/park"
+ "path/filepath"
+ "reflect"
+ "strconv"
+@@ -306,6 +307,7 @@
+ // acknowledged (INV-11), and publication evidence binds the exact marker
+ // version this process wrote and the exact root version it named.
+ type claimServingOwners struct {
++ HomeRegion string
+ Data genselect.Reader
+ Bucket, Region, Box string
+ NewID func() string
+@@ -330,7 +332,7 @@
+ }
+
+ func (s *claimServingOwners) baseline(ctx context.Context, o hostclaim.Observed) (*genselect.Plan, error) {
+- return genselect.Select(ctx, s.Data, genselect.Request{Bucket: s.Bucket, Region: s.Region, Host: o.Host, Holder: *o.Claim.Holder})
++ return genselect.Select(ctx, s.Data, genselect.Request{Bucket: s.Bucket, Region: s.Region, Host: o.Host, Holder: park.HolderRegionView(*o.Claim.Holder, s.HomeRegion, s.Region)})
+ }
+
+ // claimEmptyBootstrap: a created incarnation before its first writer.
+--- a/internal/host/claimconstruction.go
++++ b/internal/host/claimconstruction.go
+@@ -258,7 +258,7 @@
+ var publisher ClaimFencedPublication
+ var groupD claimGroupD
+ if movesC1 {
+- serving = &claimServingOwners{Data: hostbox.RequestStore{Store: owners.Data, Check: func(ctx context.Context) error { _, err := owners.Identity.Verify(ctx); return err }}, Bucket: actor.Bucket, Region: park.PlacementRegion(actor.Region), Box: actor.Box, NewID: newID, Now: time.Now, Sources: owners.Serving}
++ serving = &claimServingOwners{HomeRegion: actor.HomeRegion, Data: hostbox.RequestStore{Store: owners.Data, Check: func(ctx context.Context) error { _, err := owners.Identity.Verify(ctx); return err }}, Bucket: actor.Bucket, Region: park.PlacementRegion(actor.Region), Box: actor.Box, NewID: newID, Now: time.Now, Sources: owners.Serving}
+ groupD, err = claimGroupDWiring(claimEntryEnv(ctx), serving.owners(), owners.Moves, owners.Relocation)
+ if err != nil {
+ return nil, err
+--- a/internal/console/apidbclaim.go
++++ b/internal/console/apidbclaim.go
+@@ -202,7 +202,7 @@
+ out := dbdoor.ListResponse{Protocol: 2, Plan: true, Files: []dbdoor.LTXFile{}}
+ h := g.claim.Claim.Holder
+ plan, err := genselect.Select(ctx, s.cfg.DBVersions, genselect.Request{
+- Bucket: s.cfg.HibernateBucket, Region: s.cfg.dbClaimRegion(), Host: g.host, Holder: *h,
++ Bucket: s.cfg.HibernateBucket, Region: s.cfg.dbClaimRegion(), Host: g.host, Holder: park.HolderRegionView(*h, s.cfg.HomeRegion, s.cfg.dbClaimRegion()),
+ })
+ if err != nil {
+ return dbdoor.ListResponse{}, errDBClaimChanged
+--- a/internal/park/remote.go
++++ b/internal/park/remote.go
+@@ -552,7 +552,17 @@
+ if c.Holder == nil {
+ return nil
+ }
+- return hostclaim.CoverParkedPredecessors(obs.Claim.Op.ID, obs.Claim.Op.Phase, o.Region, generations)(c)
++ if err := hostclaim.CoverParkedPredecessors(obs.Claim.Op.ID, obs.Claim.Op.Phase, o.Region, generations)(c); err != nil {
++ return err
++ }
++ if e.HomeRegion != "" && e.OwnsRegion(HomeRegion) {
++ other := HomeRegion
++ if o.Region == HomeRegion {
++ other = e.HomeRegion
++ }
++ return hostclaim.CoverParkedPredecessors(obs.Claim.Op.ID, obs.Claim.Op.Phase, other, generations)(c)
++ }
++ return nil
+ }
+ if len(vs) > 0 {
+ next := e.NewID()
+```
+
+## Task G: Complete the moves quiet and baseline readers
+
+Map the report target placement before reading quiet_after. Read the maximum of both home keys in readiness and abort-overdue checks. Derive a probe baseline projection from the observed operation home binding, including recovery, rather than relying on a future external provider to populate an optional field. Also reload an existing named claim and admit its next writer on both home and named boxes. The earlier temporary movesProbeOwners.HomeRegion field from F is removed here.
+
+Focused command: `go test ./internal/host ./internal/hostbox -run TestPlan13 -count=1`.
+
+Expected first failure: A legacy named-home quiet bound fails to block the @home readiness check.
+
+### Failing tests
+
+<!-- apply: G tests -->
+```diff
+--- /dev/null
++++ b/internal/host/plan13_quiet_test.go
+@@ -0,0 +1,23 @@
++package host
++
++import (
++ "github.com/carlosframework/platform/internal/hostclaim"
++ "github.com/carlosframework/platform/internal/park"
++ "testing"
++ "time"
++)
++
++func TestPlan13MoveQuietReadsLegacyAndHomeToken(t *testing.T) {
++ now := time.Unix(100, 0)
++ for _, stored := range []string{"ie", park.HomeRegion, "uk"} {
++ o := hostclaim.Observed{Claim: &hostclaim.Claim{Op: hostclaim.Op{Intent: map[string]string{"home_region": "ie"}}, QuietAfter: map[string]time.Time{stored: now.Add(time.Hour)}}}
++ got := checkQuiet(o, park.HomeRegion, now)
++ if got.OK != (stored == "uk") {
++ t.Fatalf("%q: %+v", stored, got)
++ }
++ got = checkQuiet(o, "uk", now)
++ if got.OK != (stored != "uk") {
++ t.Fatalf("named %q: %+v", stored, got)
++ }
++ }
++}
+--- /dev/null
++++ b/internal/hostbox/plan13_admission_test.go
+@@ -0,0 +1,46 @@
++//go:build unix
++
++package hostbox
++
++import (
++ "context"
++ "github.com/carlosframework/platform/internal/genselect"
++ "github.com/carlosframework/platform/internal/hostclaim"
++ "github.com/carlosframework/platform/internal/park"
++ "testing"
++)
++
++func TestPlan13AdmissionKeepsExistingEngineName(t *testing.T) {
++ for _, home := range []string{"ie", ""} {
++ t.Run("home="+home, func(t *testing.T) {
++ _, store, o := workFixture(t)
++ writer := Writer{HomeRegion: home, Claims: store, Box: "box", Region: park.PlacementRegion("ie"), AdmissionEvidence: func(context.Context, hostclaim.Observed, hostclaim.Admission, *genselect.Plan) error { return nil }}
++ in := hostclaim.Admission{Box: "box", Mode: "cold", NewGeneration: "old", Region: "ie", PredecessorRegion: "ie"}
++ old, err := writer.Admit(t.Context(), o, in, nil)
++ if err != nil {
++ t.Fatal(err)
++ }
++ // Restart against the stored named admission, rather than a synthesized holder.
++ old, err = hostclaim.Read(t.Context(), store, o.Host)
++ if err != nil {
++ t.Fatal(err)
++ }
++ in.NewGeneration = "next"
++ in.ObservedSession = old.Claim.Holder.Session
++ in.ObservedGeneration = "old"
++ next, err := writer.Admit(t.Context(), old, in, nil)
++ if err != nil {
++ t.Fatal(err)
++ }
++ a := next.Claim.Holder.Admissions
++ if len(a) != 2 || a[0].Region != "ie" || a[1].Region != "ie" || a[1].PredecessorRegion != "ie" || a[1].Predecessor != "old" {
++ t.Fatalf("%+v", a)
++ }
++ writer.Region = ""
++ in.Region = ""
++ if _, err = writer.Admit(t.Context(), next, in, nil); err == nil {
++ t.Fatal("unset admitted")
++ }
++ })
++ }
++}
+```
+
+### Implementation
+
+<!-- apply: G implementation -->
+```diff
+--- a/internal/host/movesreport.go
++++ b/internal/host/movesreport.go
+@@ -6,6 +6,7 @@
+ "encoding/json"
+ "errors"
+ "fmt"
++ "github.com/carlosframework/platform/internal/park"
+ "io"
+ "io/fs"
+ "log"
+@@ -245,7 +246,7 @@
+ rep.Checks[moves.CheckOneOwner] = checkOneOwner(fleet, rec.Move.To)
+ rep.Checks[moves.CheckClaim] = w.checkClaim(ctx, obs, rec)
+ rep.Checks[moves.CheckLane] = w.checkLane(ctx, rec, fleet)
+- rep.Checks[moves.CheckQuiet] = checkQuiet(obs, rec.Move.To, now)
++ rep.Checks[moves.CheckQuiet] = checkQuiet(obs, park.PlacementRegion(rec.Move.To), now)
+ rep.Checks[moves.CheckArmed] = checkArmed(cfg)
+
+ // T11 from measured rates: this box's own, and the source's cross rate.
+@@ -771,7 +772,7 @@
+
+ // checkQuiet is T12: the claim's quiet_after for the target region passed.
+ func checkQuiet(obs hostclaim.Observed, region string, now time.Time) moves.Check {
+- if until, ok := obs.Claim.QuietAfter[region]; ok && now.Before(until) {
++ if until := park.QuietUntil(obs.Claim.QuietAfter, obs.Claim.Op.Intent["home_region"], region); !until.IsZero() && now.Before(until) {
+ return moves.Check{Reason: t12NotQuiet(region, until)}
+ }
+ return moves.Check{OK: true}
+--- a/internal/console/movesrecovery.go
++++ b/internal/console/movesrecovery.go
+@@ -8,6 +8,7 @@
+ "encoding/json"
+ "errors"
+ "fmt"
++ "github.com/carlosframework/platform/internal/park"
+ "log"
+ "os"
+ "time"
+@@ -365,7 +366,7 @@
+ s.pageMove(ctx, movePageCloseOverdue, o, c, rec, now)
+ return nil
+ case moveActAbortOverdue:
+- if s.moveAbortOverdue(o, c.QuietAfter[rec.Move.To], now) {
++ if s.moveAbortOverdue(o, park.QuietUntil(c.QuietAfter, s.cfg.HomeRegion, park.PlacementRegion(rec.Move.To)), now) {
+ s.pageMove(ctx, movePageAbortOverdue, o, c, rec, now)
+ }
+ return nil
+--- a/internal/host/movesprobeowners.go
++++ b/internal/host/movesprobeowners.go
+@@ -92,7 +92,6 @@
+ // Production: nothing registers this owner set yet. The probe is unarmed in
+ // production until Plan 5 Task 9 arms the parks; the tests use fakes.
+ type movesProbeOwners struct {
+- HomeRegion string
+ // Claims is the claim store (the deployment store in production).
+ Claims release.Store
+ Records MovesRecordReader
+@@ -182,6 +181,7 @@
+
+ // probeLanding is what one landing probe's checks read.
+ type probeLanding struct {
++ homeRegion string
+ // committed is a landing wake after the commit (Plan 3 Task 7): the
+ // claim at landing/committed, the record committed.
+ committed bool
+@@ -203,6 +203,7 @@
+ return v, hostbox.ErrUnsupported
+ }
+ c := o.Claim
++ v.homeRegion = c.Op.Intent["home_region"]
+ switch c.Op.Phase {
+ case hostclaim.PhaseLandingProbing:
+ case hostclaim.PhaseLandingCommitted:
+@@ -395,8 +396,8 @@
+ return from
+ }
+
+-func (m *movesProbeOwners) request(host string, h *hostclaim.Holder) genselect.Request {
+- return genselect.Request{Bucket: m.Bucket, Region: m.Region, Host: host, Holder: park.HolderRegionView(*h, m.HomeRegion, m.Region)}
++func (m *movesProbeOwners) request(host string, h *hostclaim.Holder, home string) genselect.Request {
++ return genselect.Request{Bucket: m.Bucket, Region: m.Region, Host: host, Holder: park.HolderRegionView(*h, home, m.Region)}
+ }
+
+ func preparedPin(v probeLanding) genselect.PreparedPin {
+@@ -434,7 +435,7 @@
+ // certified remote once it is.
+ func (m *movesProbeOwners) baseline(ctx context.Context, host string, v probeLanding, prepared bool) (*genselect.Plan, error) {
+ if prepared {
+- return genselect.SelectPrepared(ctx, m.Data, m.request(host, v.holder), preparedPin(v))
++ return genselect.SelectPrepared(ctx, m.Data, m.request(host, v.holder, v.homeRegion), preparedPin(v))
+ }
+ digest, err := manifestDigest(v.manifest)
+ if err != nil {
+@@ -445,9 +446,9 @@
+ return nil, err
+ }
+ if !m.landingMarker(marker, host, v, digest) {
+- return genselect.SelectPrepared(ctx, m.Data, m.request(host, v.holder), preparedPin(v))
+- }
+- return genselect.Select(ctx, m.Data, m.request(host, v.holder))
++ return genselect.SelectPrepared(ctx, m.Data, m.request(host, v.holder, v.homeRegion), preparedPin(v))
++ }
++ return genselect.Select(ctx, m.Data, m.request(host, v.holder, v.homeRegion))
+ }
+
+ // localMarkers reads .files-gen (nil when absent) and <dbPath>.gen ("" when
+@@ -978,7 +979,7 @@
+ }
+ bounded, cancel := context.WithTimeout(ctx, hostbox.RequestLimit)
+ defer cancel()
+- plan, err := genselect.Select(bounded, m.Data, m.request(o.Host, h))
++ plan, err := genselect.Select(bounded, m.Data, m.request(o.Host, h, o.Claim.Op.Intent["home_region"]))
+ if err != nil {
+ return ClaimPublicationReceipt{}, err
+ }
+@@ -1205,7 +1206,7 @@
+ if !ok || err != nil || receipt.MarkerVersion == "" || receipt.Root.VersionID == "" || !strings.HasPrefix(receipt.Root.Key, prefix) {
+ return hostbox.ErrChanged
+ }
+- plan, err := genselect.Select(ctx, m.Data, m.request(o.Host, h))
++ plan, err := genselect.Select(ctx, m.Data, m.request(o.Host, h, o.Claim.Op.Intent["home_region"]))
+ if err != nil {
+ return err
+ }
+@@ -1294,7 +1295,7 @@
+ if gen == v.op || !claimFreshGeneration(h, gen) {
+ return ClaimLaunchInputs{}, hostwrite.ErrUnavailable
+ }
+- plan, err := genselect.Select(ctx, m.Data, m.request(o.Host, h))
++ plan, err := genselect.Select(ctx, m.Data, m.request(o.Host, h, o.Claim.Op.Intent["home_region"]))
+ if err != nil {
+ return ClaimLaunchInputs{}, err
+ }
+@@ -1325,7 +1326,7 @@
+ if !reflect.DeepEqual(in, m.committedAdmission(o, v, in.NewGeneration, baseline)) {
+ return hostbox.ErrChanged
+ }
+- fresh, err := genselect.Select(ctx, m.Data, m.request(o.Host, h))
++ fresh, err := genselect.Select(ctx, m.Data, m.request(o.Host, h, o.Claim.Op.Intent["home_region"]))
+ if err != nil {
+ return err
+ }
+```
+
+## Task G2: Map verified target fences through the operation home binding
+
+Keep signed fence bytes and cloud identity unchanged. MoveFenceRegions calls the existing authority first, then maps only its verified roster-region binding for the move. Wire it into both the recovery grant and box target-fence verifier; recovery handover compares the same bound alias. The regression executes the actual fenced-target recovery grant with a home source, a home target and two named placements, then completes the record without leaking an engine token.
+
+Focused command: `go test ./internal/hostops -run "TestPlan13Fence|TestPlan12RecoveryFence" -count=1`.
+
+Expected first failure: Target-home recovery rejects the verified named roster fence.
+
+### Failing tests
+
+<!-- apply: G2 tests -->
+```diff
+--- /dev/null
++++ b/internal/hostops/plan13_fence_test.go
+@@ -0,0 +1,73 @@
++package hostops
++
++import (
++ "context"
++ "github.com/carlosframework/platform/internal/hostclaim"
++ "github.com/carlosframework/platform/internal/park"
++ "github.com/carlosframework/platform/internal/provision"
++ "strings"
++ "testing"
++)
++
++func TestPlan13FenceRecoveryHomeAndNamed(t *testing.T) {
++ for _, which := range []string{"source-home", "target-home", "named"} {
++ t.Run(which, func(t *testing.T) {
++ w := newC7World(t, func(c *hostclaim.Claim, r *provision.Record, _ *AppAdmission) {
++ c.Op.Kind, c.Op.Phase, c.Op.Exec = hostclaim.KindLanding, hostclaim.PhaseProbing, "source"
++ c.Op.Intent["source"], c.Op.Intent["target"], c.Op.Intent["to"], c.Op.Intent["target_enrollment"] = "target", "source", "ie", strings.Repeat("a", 32)
++ c.Op.Participants = []string{"target", "source"}
++ c.Obligations = nil
++ c.Op.Journal = map[string]string{hostclaim.JournalSourceStamp: "1", hostclaim.JournalSrcGen: "src"}
++ r.Region = "uk"
++ r.Move.From, r.Move.To, r.Move.State = "uk", "ie", provision.MoveRollingBack
++ if which == "source-home" {
++ r.Region = ""
++ r.Move.From = ""
++ c.Op.Intent["home_region"] = "uk"
++ }
++ if which == "target-home" {
++ r.Move.To = ""
++ c.Op.Intent["to"] = park.HomeRegion
++ c.Op.Intent["home_region"] = "ie"
++ }
++ at := r.CreatedAt
++ key := "control/box-fences/" + strings.Repeat("a", 32) + ".json"
++ r.Move.Rollback = &provision.MoveRollback{ID: "rb_abcdefghijklmnopqrstuvwxyz", Reason: provision.RollbackTargetFenced, Evidence: key, RequestedAt: &at, TargetPhase: provision.RbTargetLost, LossRef: key, LostAt: &at, SourcePhase: provision.RbSourceWaiting}
++ })
++ run := func() error {
++ return w.control.RunRecovery(t.Context(), func(ctx context.Context) error {
++ return runMoveRecovery(ctx, w.claim(t), func(ctx context.Context, g *MoveRecoveryGrant) error { return g.ReleaseFencedTarget(ctx, w.ref) }, w.authority)
++ })
++ }
++ if err := run(); err != nil {
++ t.Fatal(err)
++ }
++ c := w.claim(t).Claim
++ if c.Op.Phase != hostclaim.PhaseReleased {
++ t.Fatal(c.Op)
++ }
++ want := "ie"
++ if which == "target-home" {
++ want = park.HomeRegion
++ }
++ for _, ob := range c.Obligations {
++ if ob.Region != want {
++ t.Fatalf("%+v", ob)
++ }
++ }
++ if err := run(); err != nil {
++ t.Fatal("record completion", err)
++ }
++ rec, err := provision.GetRecord(t.Context(), w.raw, "bab", "app", w.host)
++ if err != nil {
++ t.Fatal(err)
++ }
++ if which == "source-home" && (rec.Region != "" || rec.Move.From != "") {
++ t.Fatal("source placement rewritten")
++ }
++ if which == "target-home" && rec.Move.To != "" {
++ t.Fatal("target placement rewritten")
++ }
++ })
++ }
++}
+```
+
+### Implementation
+
+<!-- apply: G2 implementation -->
+```diff
+--- /dev/null
++++ b/internal/hostops/move_fence_regions.go
+@@ -0,0 +1,34 @@
++package hostops
++
++import (
++ "context"
++ "github.com/carlosframework/platform/internal/hostclaim"
++ "github.com/carlosframework/platform/internal/park"
++ "github.com/carlosframework/platform/internal/termfence"
++)
++
++// MoveFenceRegions preserves the verifier's roster/enrollment checks, then
++// projects only its region binding into the move's engine vocabulary.
++// Signed fence bytes and the cloud region are never edited.
++type MoveFenceRegions struct {
++ Authority hostclaim.FenceAuthority
++ HomeRegion string
++}
++
++func (a MoveFenceRegions) ValidateFence(ctx context.Context, f termfence.Record) (hostclaim.FenceBinding, error) {
++ if a.Authority == nil {
++ return hostclaim.FenceBinding{}, hostclaim.ErrFence
++ }
++ binding, err := a.Authority.ValidateFence(ctx, f)
++ if err != nil {
++ return hostclaim.FenceBinding{}, err
++ }
++ if binding.Region == "" || (a.HomeRegion != "" && park.PlacementRegion(a.HomeRegion) != a.HomeRegion) {
++ return hostclaim.FenceBinding{}, hostclaim.ErrFence
++ }
++ binding.Region = park.PlacementRegion(park.HomePlacementCode(a.HomeRegion, binding.Region))
++ if binding.Region == "" {
++ return hostclaim.FenceBinding{}, hostclaim.ErrFence
++ }
++ return binding, nil
++}
+--- a/internal/hostops/move_fence_release.go
++++ b/internal/hostops/move_fence_release.go
+@@ -58,7 +58,7 @@
+ }
+ return writeRecord()
+ }
+- fence, err := hostclaim.VerifyMoveTargetFence(ctx, g.w.raw, o, ref, g.authority)
++ fence, err := hostclaim.VerifyMoveTargetFence(ctx, g.w.raw, o, ref, MoveFenceRegions{Authority: g.authority, HomeRegion: o.Claim.Op.Intent["home_region"]})
+ if err != nil {
+ return err
+ }
+--- a/internal/host/claimmovefence.go
++++ b/internal/host/claimmovefence.go
+@@ -36,6 +36,6 @@
+ if json.Unmarshal([]byte(o.Claim.Op.Journal["fence."+o.Claim.Op.Intent["target"]]), &ref) != nil {
+ return hostclaim.ReturnFence{}, hostclaim.ErrFence
+ }
+- return hostclaim.VerifyMoveTargetFence(ctx, store, o, ref, hostfence.ClaimFenceAuthority{Directory: authority.Directory, Now: time.Now})
++ return hostclaim.VerifyMoveTargetFence(ctx, store, o, ref, hostops.MoveFenceRegions{Authority: hostfence.ClaimFenceAuthority{Directory: authority.Directory, Now: time.Now}, HomeRegion: o.Claim.Op.Intent["home_region"]})
+ }
+ }
+--- a/internal/host/claimhandover_recovery.go
++++ b/internal/host/claimhandover_recovery.go
+@@ -12,6 +12,7 @@
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostguard"
+ "github.com/carlosframework/platform/internal/moves"
++ "github.com/carlosframework/platform/internal/park"
+ "github.com/carlosframework/platform/internal/registry"
+ "strconv"
+ )
+@@ -72,7 +73,7 @@
+ if err != nil {
+ return err
+ }
+- if verified.Box() != p.Box || verified.Region() != op.Intent["to"] {
++ if verified.Box() != p.Box || !park.SameRegion(op.Intent["home_region"], verified.Region(), op.Intent["to"]) {
+ return hostclaim.ErrFence
+ }
+ return nil
+```
+
+## Task H: Classify the census changes
+
+Run `go test ./internal/provision ./internal/console -run 'TestPlan9B8|TestClaimAuthoritySourceCensus' -count=1` before applying this patch. The record census must report these nine changed units; the console authority census must report only the changed VerifyMoveTargetFence argument. Then apply the patch and rerun the same command.
+
+| Unit | Classification retained | Reason for changed source |
+| --- | --- | --- |
+| Server.handleAPIInstanceCreate | record-writer | Map the record placement for claim creation; the existing typed writer stays. |
+| Server.handleAPISidecarCreate | record-writer | Same change for sidecars. |
+| productionClaimRegional.View | key-site | Project admission names for the view; preserve retirement-label and refusal logic. |
+| RegionalWorker.Census | key-site | Include bound home-alias obligations in the existing host census. |
+| MoveRecoveryGrant.ReleaseFencedTarget | record-writer | Map From/To and wrap the existing verified fence binding; the guarded record write is unchanged. |
+| moveLossOwner.ValidateLossRecovery | key-site | Compare SourceRegion with the admission using the operation's bound alias. |
+| Engine.run | versioned-transport | Match the bound alias and cover exact parked predecessors under either name. |
+| Decide | key-site | Protect holder locations and queued work under either bound home name. |
+| movesWorker.targetReport | nonrecord | Map the target placement for the existing report quiet check. |
+
+No class, site-kind set, raw sink or authority call is added or removed. The digest edits correspond only to those inspected units. The existing ReleaseFencedTarget → VerifyMoveTargetFence call now receives MoveFenceRegions around the same authority; classify it as verified region projection after the original verifier, with no new grant or signature authority. Update only that expression in the console inventory.
+
+<!-- apply: H implementation -->
+```diff
+--- a/internal/provision/plan9_b8_census_test.go
++++ b/internal/provision/plan9_b8_census_test.go
+@@ -474,11 +474,11 @@
+ var plan9B8CensusInventory = []censusEntry{
+ {"internal/hostbox/parkleases.go", "ReplicaParkLeaseSet", "key-site", "lit:instances", "0ca8d9b743402fda9d15cc215464423b35e21c1c2b47f91294d76188b7d43fb6"},
+ {"internal/host/claimretirementcensus.go", "ClaimRetirementCensus", "key-site", "lit:instances", "abc0bd8c5f5d328abfac8821f42f8f23954723ed6506747f33b0197824e2611f"},
+- {"internal/host/claimproduction.go", "productionClaimRegional.View", "key-site", "lit:instances", "61ff9ff47054b6a96cb24467c7aaf41c112b7c5129987af5ce27e0b7f862348e"},
+- {"internal/hostops/move_loss_admission.go", "moveLossOwner.ValidateLossRecovery", "key-site", "key:RecordKey", "ea2aa35a069ffdc846ecb208c33940245fc865795188818e86ea7a93135dd6a9"},
++ {"internal/host/claimproduction.go", "productionClaimRegional.View", "key-site", "lit:instances", "b0c6bac0beb2cbff790b646a7d978a3f22e6ecc7bac78b63402e73fe5c6e9776"},
++ {"internal/hostops/move_loss_admission.go", "moveLossOwner.ValidateLossRecovery", "key-site", "key:RecordKey", "1ad4af4595c764fc5051e50c2b187472cb20ce73c3b3e8245b8527228f326f5f"},
+ {"internal/host/claimretirementcensus.go", "claimArchivedLocations", "key-site", "lit:instances", "9ba8ea4ecb5ad63621c1322467b9a80f8f5703896ba1fc3f7cb1addd56418950"},
+ {"internal/host/claimretirementcensus.go", "claimLegacyIncarnation", "key-site", "lit:instances", "31a14ea105c67e00a24f06b9500a221ee5bfc47402b9c33cecfde807e51db530"},
+- {"internal/hostops/move_fence_release.go", "MoveRecoveryGrant.ReleaseFencedTarget", "record-writer", "key:WriteRecord,key:writeRecord", "031a3a8169a92775bff8a1a1b08cfe82a5a613156a1bce45dc9cfcb34c56befe"},
++ {"internal/hostops/move_fence_release.go", "MoveRecoveryGrant.ReleaseFencedTarget", "record-writer", "key:WriteRecord,key:writeRecord", "fc0dda0802a3cc0cf8cc33d2d68aa9ae2eb49a90e20720607fec613b822f4f7f"},
+ {"internal/hostops/move_fence_release.go", "MoveRecoveryGrant.StampLostMoveTargetParked", "record-writer", "key:WriteRecord", "b082db5634a2f4d9a5b45cc4a9cb725964e9bb24b7aeb53a6346167d7737227c"},
+ {"internal/hostops/move_recovery_grant.go", "MoveRecoveryGrant.Convert", "record-writer", "key:WriteRecord", "794963a24a0a50d980505f83ac912973a10267b21f74bd41c56584ab9b0b25a1"},
+ // The recovery pass's move log writer: one create-only entry of a move's
+@@ -612,11 +612,11 @@
+ {"internal/console/apiinstancemove.go", "Server.registerMovesPhase1Routes", "key-site", "lit:instances", "5af589d288ba22856f631481c40cc001aac342ea74cee6ee0464ec77d0320bbc"},
+ {"internal/console/apiinstances.go", "Server.instanceManifest", "key-site", "lit:instances", "e6160ffe84abda9163527ccb33e2ffff2109b35908adb2f3a754534325658fdf"},
+ {"internal/console/apiinstances.go", "Server.handleAPIInstanceList", "key-site", "lit:instances", "73a40e68abf796870589df197fc72697bfe201fe0b3ca6c14829f1fd5761f758"},
+- {"internal/console/apiinstances.go", "Server.handleAPIInstanceCreate", "record-writer", "key:CreateRecord", "20f862446f119f25d7d72eab6edd1b7cfb1528afd5b4c352e0cb8442f5080b13"},
++ {"internal/console/apiinstances.go", "Server.handleAPIInstanceCreate", "record-writer", "key:CreateRecord", "9b185998d430710c3ba14c4ace8c19a487d15b7f391b222c6e6479168a0c7603"},
+ {"internal/console/apiinstances.go", "Server.handleAPIInstanceUpdate", "record-writer", "key:UpdateRecord", "4b792c410399631eb3b38c78829df85dda045651bbc32df769767cf52f015b6d"},
+ {"internal/console/apiscan.go", "Server.writeScanRecord", "nonrecord", "sel:Put", "c3c092f4b7be9bf6bf09d0fe1e2c9fe52892ee97e57f6c7ab964b85fe36fb5c6"},
+ {"internal/console/apisidecars.go", "Server.handleAPISidecarList", "key-site", "lit:sidecars", "6a5e461256c985ea4924bf7ba7c0793ae601a59efe8ef570913138ef8b47441a"},
+- {"internal/console/apisidecars.go", "Server.handleAPISidecarCreate", "record-writer", "key:CreateSidecarRecord", "a7d5eb534b540499b9355b455966e733d61c904be5cc5a76a168833df59de1c3"},
++ {"internal/console/apisidecars.go", "Server.handleAPISidecarCreate", "record-writer", "key:CreateSidecarRecord", "f3b049692e3289d9bb3c77e9e6a55dbdfac78bd9ff90af893b6cc753e76a0164"},
+ {"internal/console/apistatusinstances.go", "Server.handleAPIStatusInstances", "key-site", "lit:instances", "2b4b9a2bea5f2fc5727869a5dcb8ae96c020e72571621f19898b195ca3b61aaa"},
+ {"internal/console/apisystemupdate.go", "Server.mirrorSystemArtifact", "nonrecord", "sel:Put", "76a8dcb558e3723ad5774788da37c22234c229dc3257ded5a6c7b04746c69c2f"},
+ {"internal/console/apiupdates.go", "Server.handleAPIUpdatesPut", "record-writer", "key:UpdateRecord", "53044a537795302e7ffd8fa72799b7a77e7bed277f00ad17ed1fd465b7f7cfaa"},
+@@ -709,14 +709,14 @@
+ {"internal/host/movesphase1.go", "movesWorker.refreshCrossRate", "nonrecord", "sel:CopyVersioned", "7e03e680bd61be26dddcf168cacd2427bb535571dfd326c4a3130ebe65ea1968"},
+ {"internal/host/movesphase1.go", "movesWorker.writeSourceReport", "nonrecord", "sel:PutReplace", "370407cca2c1b91f6e5e7f135238f04c0dce9d7986e6e1ee9bebbf88cad0b9e0"},
+ {"internal/host/movesphase1.go", "movesWorker.decideReady", "record-writer", "key:SetRecordMove", "d29c22eafacfb894b96cd0c40ec447b6ab02f46ea087201685627861f992085d"},
+- {"internal/host/movesreport.go", "movesWorker.targetReport", "nonrecord", "sel:PutReplace", "fce9296957f2a8f91840ddeb5c3553a0a248bc11f7f98a08d0ae7d00ae1c2eb4"},
++ {"internal/host/movesreport.go", "movesWorker.targetReport", "nonrecord", "sel:PutReplace", "7a8eaabb9c56b2352969ea49124481a1d3687376b1f9ab6b9ce9ae49d0573843"},
+ {"internal/host/movesstore.go", "laneProbe", "nonrecord", "sel:PutIfAbsent", "f4a17a1143e7370b9039b2e66e736606015c46619bab92c553ce7b1ad6b7918b"},
+ {"internal/host/snapshot_loop.go", "registrySnapshotPass", "nonrecord", "sel:Put", "6b01b46b361c3a2d1d432659a0d234c01975849b9a8ac5571858fdd78d376849"},
+ {"internal/host/snapshot_loop.go", "putReplacing", "nonrecord", "sel:Put", "11e8639effce142eab707b67cbdd425d0d7cea510ddf95d292bdba8f79c9b510"},
+ {"internal/hostactors/censuskey.go", "Directory.BindCensusKey", "nonrecord", "sel:Put", "7d9d673435e8c8a6aac3105e3a754bde2adcbc5d603945dd599cef703f334a5b"},
+ {"internal/hostactors/invitation.go", "Directory.Invite", "nonrecord", "sel:Put", "356618a75a23d0157d8e0402e20187a42956f67a4332d698bb22bf05133ac489"},
+ {"internal/hostactors/roster.go", "Directory.Replace", "nonrecord", "sel:Put", "99d6830102942188370207b21a87245056b1a56af1f95344b0337091e720823f"},
+- {"internal/hostbox/regionalworker.go", "RegionalWorker.Census", "key-site", "lit:instances", "5d554df4562248c6e6467f5009910dc4e5502bc09679baa55f0d2cd1bdc06037"},
++ {"internal/hostbox/regionalworker.go", "RegionalWorker.Census", "key-site", "lit:instances", "477bcb896b5ae8ec80af592d68eceb7992ad8a23cd6cff6043ad03b3ef2a8085"},
+ {"internal/hostbox/requeststore.go", "RequestStore.CopyVersion", "versioned-transport", "def:CopyVersion,sel:CopyVersion", "3a50049ce7ca7f2cbc770bef46218944cdc7000205a86c20e4932f0b510da28f"},
+ {"internal/hostbox/requeststore.go", "RequestStore.PutIfAbsent", "versioned-transport", "def:PutIfAbsent,sel:PutIfAbsent", "3666cbe5d2b91c1c652ffb4459c10a374fbd92e325e4e5c574fa3525d0ce2ffe"},
+ {"internal/hostclaim/adopt.go", "Adopt", "key-site", "lit:instances", "24c06b99cce6c638acb19826985cfadd3c736fd0ace7289bcf7ac3dfe784f05a"},
+@@ -847,8 +847,8 @@
+ {"internal/objstore/sidecar.go", "claimRawPutSidecar", "nonrecord", "sel:Put", "233e3d808dd462ff83aecdac5176b180556b948c552aa26f6e0b778df91a24c7"},
+ {"internal/ops/litestream.go", "FleetDBsWithSkipped", "key-site", "lit:instances", "f62291df0685db15529c4946dca358a29aba66fc31e81aab299de579e37f60f1"},
+ {"internal/park/remote.go", "locationOf", "key-site", "lit:instances", "83b3efca31b3faadcd5997dc181d81b7bb8fc866cc4a448683bf8423b1bea866"},
+- {"internal/park/remote.go", "Engine.run", "versioned-transport", "sel:CopyVersion,sel:PutIfAbsent", "3de5b215e4b7aba67768243891b3f6d8ff8c77a465aefe5168c789cb012b1ea9"},
+- {"internal/park/sweep.go", "Decide", "key-site", "lit:instances", "d03c3af1430816d3f9517a3944e553291805f97a317e54b96a02b9fbfbb5efb5"},
++ {"internal/park/remote.go", "Engine.run", "versioned-transport", "sel:CopyVersion,sel:PutIfAbsent", "538230265e42b2d828bc8f842034d200b4fe3b0c908bd369a3d309c1c79af78d"},
++ {"internal/park/sweep.go", "Decide", "key-site", "lit:instances", "5f42dc7646841e2cf6cb8835f9b74710bb02ab5b3e9a31a9639bdea80e5f4573"},
+ {"internal/park/sweep.go", "Engine.Discover", "key-site", "lit:instances", "69b83e6e859c1c9809ba76d46cb0192888df1ecdcd161b4f2b6435733eecdfa3"},
+ {"internal/provision/claim_app_writes.go", "UpdateRecord", "record-writer", "key:UpdateRecord,key:claimRawUpdateRecord", "caf714cb78e1280d489c7ad5b3cefb1420e812d541108b1fe13d733abed97229"},
+ {"internal/provision/provision.go", "ManifestKey", "key-site", "lit:instances", "b8ee0e77bdfade19af79a4012c411c1eb0b6ddca16037e5536fa8597f7cde270"},
+--- a/internal/console/testdata/claim-authority-sites.txt
++++ b/internal/console/testdata/claim-authority-sites.txt
+@@ -13,7 +13,7 @@
+ ../hostops/move_enrollment.go|RegisterMove|a.Control.RegisterStart(ctx, h.Account, h.App, appID, id, reg, a.Now())
+ ../hostops/move_enrollment.go|StartClaimMoveRecord|h.captureMoveStartBase(ctx, seq)
+ ../hostops/move_fence_release.go|ReleaseFencedTarget|hostclaim.ReleaseFencedTarget(r)
+-../hostops/move_fence_release.go|ReleaseFencedTarget|hostclaim.VerifyMoveTargetFence(ctx, g.w.raw, o, ref, g.authority)
++../hostops/move_fence_release.go|ReleaseFencedTarget|hostclaim.VerifyMoveTargetFence(ctx, g.w.raw, o, ref, MoveFenceRegions{Authority: g.authority, HomeRegion: o.Claim.Op.Intent["home_region"]})
+ ../hostops/move_loss_authority.go|DeclareMoveLoss|a.Control.DeclareMoveLoss(ctx, a.Signer, a.Directory.Bucket, actor, account, app, host, box, in)
+ ../hostops/move_loss_authority.go|InspectMoveLoss|a.Control.InspectMoveLoss(ctx, a.Signer, a.Directory.Bucket, actor, account, app, host, box)
+ ../hostops/move_recovery_bridge.go|RecoverTerminatedMovingHost|hostclaim.ConvertMovingToRelocating(mh.Lifecycle(ctx), s, fresh, c.Op.ID, ref, authority, guard)
+```
+
+## Task I: Amend the claim spec to rev 6.33
+
+Apply this to the separately identified rev 6.32 design spec, after checking its SHA-256. The amendment records the name decision, existing-claim compatibility, alias scope, empty refusals, record/engine boundary and rollout ordering.
+
+<!-- apply: I implementation -->
+```diff
+--- a/docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md
++++ b/docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md
+@@ -1,10 +1,12 @@
+ # The host lifecycle claim
+
++**Rev 6.33 (2026-10-12):** binds home placements to claim engine regions, preserves existing roster-name evidence, and completes the home move enrollment and loss boundaries. Claim Plan 13 supplies the implementation.
++
+ **Rev 6.32 (2026-10-10):** completes the claim interfaces for moves M2, M3, M5, M6, M7, M9, M12 and M21. Claim Plan 12 supplies loss-bound Group D admission, recovery predecessor evidence, atomic fenced-target release and restore retirement, held-lease parks, local park authority and retained retirement-location evidence.
+
+ **Rev 6.31 (2026-10-09):** records the accepted region HA contracts in amadan #236: `AutomaticLoss` in claim-level `evidence_refs`, the stop-only `relocation-release` obligation, and immutable signed `enrolled_at`. Section 6.2a binds moves' push, landing-install and rollback-return parks by name and region; R6 refuses automatic empty failover after any writer session. Claim Plan 11 supplies the integration.
+
+-Design, 2026-10-10, **rev 6.32**. Joint rounds 6 and 7 judged the claim ready for primitive planning. Rev
++Design, 2026-10-12, **rev 6.33**. Joint rounds 6 and 7 judged the claim ready for primitive planning. Rev
+ 6.5 added the R6-4 rule. Rev 6.6 added writer epochs (R7-1, amadan #225).
+ Rev 6.7 answered the moves session's notes on §6.4a. Rev 6.8 rewrote §6.4a
+ around writer sessions, for joint round 8. Rev 6.10 answers joint round 10
+@@ -1123,6 +1125,57 @@
+ 5. Execute Plan 4’s exact-version park.
+ 6. Acknowledge with the chore’s own operation ID, updating `quiet_after` in the same CAS.
+
++### Home placement and retained claim names (rev 6.33)
++
++A home placement is still `""` in an instance record and in moves From/To.
++At the placement-to-engine boundary, `park.PlacementRegion` maps it to
++`park.HomeRegion` (`"@home"`). Empty engine regions still mean unset and
++remain refused by Validate, parkQueueGuard, AdmitWriter, PublishGeneration
++and every Cover*Predecessors entry. Invalid placement codes, including
++`"@home"` supplied as a code, refuse. A named placement called `home` is
++independent of the home token.
++
++The claim engine of an enrolled home box retains its signed roster name
++(for example `ie`), passed through PlacementRegion. This avoids changing
++existing admission tuples, stop receipts, retirement manifests and signed
++loss evidence. On a box whose configured CARLOS_REGION is the empty home
++placement, its verified roster name binds the single alias between that
++name and `@home`. A named placement must agree with the roster and gains
++no alias. Membership, enrollment, cloud identity, bucket and request limit
++checks precede use of the alias and remain fresh at every effect.
++
++The regional worker acknowledges obligations under either home spelling,
++including queued work without bucket residue. It keeps the obligation's
++original owner, region, evidence and retirement identity. Live-generation
++selection treats the two names as the same region, so an old named
++admission can never become disposable merely because a new view names
++`@home`. Read-only holder projections may normalize those names for
++selection; they must never be persisted or used to rewrite signed evidence.
++Quiet checks use the later bound under the two names. Other regions retain
++exact matching. Existing claims need no conversion and no stored rewrite.
++
++The console and BeginClaimMove share HomePlacementCode for roster/record
++comparisons. The console passes its deployment home name to BeginClaimMove;
++the start CAS retains that name as `op.intent.home_region`, beside the
++existing enrolled source and target bindings. It persists the target as
++an engine region, while the moves record and door keep placement codes.
++The record write compares its target through PlacementRegion.
++
++A move loss persists SourceRegion through PlacementRegion, so a home source
++is `@home`, never unset. SourceRegion and TargetRegion are engine values in
++the loss intent, admission policy, write-source tuples and park requests.
++They are not placement codes: do not pass them to a moves door, record
++writer or placement resolver. The loss/rollback record is written from the
++original record's From/To codes. Admission policy recognizes the operation's
++bound home alias without altering the signed loss or granting coverage.
++
++The changes remain behind CARLOS_HOST_CLAIM and preserve flag-off behavior.
++They add no root file effects, SQLite opens, AWS client construction or new
++production activation. Existing launch, moves and files-provider gates
++remain required. Roll out the alias-aware readers and quiet checks on all
++home workers before allowing home moves; earlier readers cannot service
++`@home` chores safely. The home name must remain stable across that rollout.
++
+ Regional ownership remains `region:<r>`. Any eligible enrolled worker with access to that region may perform the chore under the aggregate leases. Local chores remain bound to the original machine enrollment.
+
+ Receipt publication uses the existing trusted box-writer boundary. A console-generated receipt cannot substitute for another machine’s evidence. Missing transport authority leaves work unsupported; it does not justify broader IAM.
+```
+
+Check the amended scratch spec:
+
+```python
+from pathlib import Path
+s = Path('docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md').read_text()
+for required in ['**rev 6.33**', 'Home placement and retained claim names',
+ 'no stored rewrite', 'SourceRegion and TargetRegion are engine values',
+ 'CARLOS_HOST_CLAIM', 'Cover*Predecessors']:
+ assert required in s, required
+```
+
+## Final verification
+
+Run on the exact replayed tree, using the shared cache and flags from bootstrap:
+
+```bash
+go test ./internal/park ./internal/hostbox ./internal/hostops ./internal/console ./internal/host -run TestPlan13 -count=1
+go test ./internal/provision ./internal/console -run 'TestPlan9B8|TestClaimAuthoritySourceCensus' -count=1
+go vet ./...
+GOOS=darwin GOARCH=amd64 go build ./...
+GOOS=windows GOARCH=amd64 go build ./...
+gofmt -l internal/park internal/hostbox internal/hostops internal/console internal/host
+```
+
+The focused tests cover aliases with and without a home binding, home and named placement mapping, invalid/unset inputs, an old admission chain, read-only projection, real chore acknowledgement, quiet-window refusal, actual enrolled move starts the real home-source loss declaration/record write, and real fenced-target recovery with home on either side. They do not substitute for both branches' combined home-move lifecycle witnesses or the repository's full landing gate.
+
+The authoring report records commands actually run, results and sandbox limits. Before landing, combine the moves prerequisite, run the repository gate and cross-platform CI on that branch, and review the authority comparisons above. No production activation is part of this plan.
+
+When verification is complete, leave the tree first, then remove the exact scratch directory:
+
+```bash
+cd "$CLAIM13_REPO"
+rm -rf -- "$CLAIM13_SCRATCH"
+```
+
+Never include GOCACHE in cleanup.
+
+## Copy inventory
+
+| Item | Disposition |
+| --- | --- |
+| UI text, CLI help, emails, member-facing errors | No additions or changes. Existing error values and refusal strings are reused. |
+| HomeRegion, HomePlacementCode, SameRegion, QuietUntil, HolderRegionView, HomeView, OwnsRegion, ClaimMoveRosterPlaces, MoveFenceRegions | Internal API identifiers; no product copy. |
+| `op.intent.home_region` | Internal additive operation-map key; no top-level wire field or strict-reader schema change. |
+| `source_region: "@home"` | Existing internal loss-intent field with a nonempty engine value; never shown as a placement code. |
+| Code comments, tests, this plan, report and rev 6.33 amendment | Implementation instructions for the controller. No new diagnostic wording is proposed for shipping. |