carlos / platform Public

Plan 2a rev 4: census ruling region-ha-2a item 5

platform-1e approved Server.chooseCreateFirst in the current structure
with one condition, met here: the early choice carries the claim
observation it was decided on (ETag or absence), and beginClaimCreate
keeps it only when its own claim read is that observation. On a change
the create is refused with errChoiceStale (hostclaim.ErrConflict, main's
retryable busy answer) before anything is registered or written; the
old choice is never used. TestAStaleEarlyChoiceIsNeverUsed proves it and
fails without the check. Census values updated (chooseCreateFirst and
beginClaimCreate digests and fingerprints, console coverage, seals);
open question 2 closed as decided. Every code block dry-run and replayed
onto a clean export.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 2544243c65d47377b725ed2a499ba64953568e81 parent e5a3a3d
1 file changed, +140 −32
  • docs/superpowers/plans/2026-10-16-region-ha-2a-core.md +140 −32

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	292.332s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.818s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.174s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.702s
ok  	github.com/carlosframework/platform/internal/activator	249.590s
ok  	github.com/carlosframework/platform/internal/agentsock	0.015s
ok  	github.com/carlosframework/platform/internal/alerting	0.003s
ok  	github.com/carlosframework/platform/internal/analytics	0.018s
ok  	github.com/carlosframework/platform/internal/appconfig	2.511s
ok  	github.com/carlosframework/platform/internal/apperr	0.070s
ok  	github.com/carlosframework/platform/internal/applog	0.119s
ok  	github.com/carlosframework/platform/internal/awsinv	0.003s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.004s
ok  	github.com/carlosframework/platform/internal/boxid	0.805s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.008s
ok  	github.com/carlosframework/platform/internal/caps	0.017s
ok  	github.com/carlosframework/platform/internal/certwarm	0.002s
ok  	github.com/carlosframework/platform/internal/cgroup	0.698s
ok  	github.com/carlosframework/platform/internal/changelog	0.309s
ok  	github.com/carlosframework/platform/internal/clamd	0.056s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	391.865s
?   	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	5.977s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.009s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.002s
ok  	github.com/carlosframework/platform/internal/docsite	0.005s
ok  	github.com/carlosframework/platform/internal/domains	2.398s
ok  	github.com/carlosframework/platform/internal/doorkey	0.005s
ok  	github.com/carlosframework/platform/internal/economics	0.003s
ok  	github.com/carlosframework/platform/internal/edge	26.626s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.008s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.131s
ok  	github.com/carlosframework/platform/internal/gate	1.655s
ok  	github.com/carlosframework/platform/internal/genselect	0.014s
ok  	github.com/carlosframework/platform/internal/geo	0.015s
ok  	github.com/carlosframework/platform/internal/geoip	0.030s
ok  	github.com/carlosframework/platform/internal/host	134.751s
ok  	github.com/carlosframework/platform/internal/hostactors	0.267s
ok  	github.com/carlosframework/platform/internal/hostbox	2.316s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.516s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.012s
ok  	github.com/carlosframework/platform/internal/hostexec	4.721s
ok  	github.com/carlosframework/platform/internal/hostfence	0.030s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.375s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.012s
ok  	github.com/carlosframework/platform/internal/hostguard	1.013s
ok  	github.com/carlosframework/platform/internal/hostops	1.616s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.012s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.012s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.441s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.154s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.012s
ok  	github.com/carlosframework/platform/internal/infraview	0.013s
ok  	github.com/carlosframework/platform/internal/instancectl	4.156s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.722s
ok  	github.com/carlosframework/platform/internal/instancefs	0.016s
ok  	github.com/carlosframework/platform/internal/instancekey	0.002s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.007s
ok  	github.com/carlosframework/platform/internal/layout	1.526s
ok  	github.com/carlosframework/platform/internal/lease	9.791s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.006s
ok  	github.com/carlosframework/platform/internal/ledger	0.017s
ok  	github.com/carlosframework/platform/internal/mail	0.162s
ok  	github.com/carlosframework/platform/internal/maintenance	0.097s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.502s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.063s
ok  	github.com/carlosframework/platform/internal/metrics	0.005s
ok  	github.com/carlosframework/platform/internal/moves	21.034s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.818s
ok  	github.com/carlosframework/platform/internal/objstore	0.192s
ok  	github.com/carlosframework/platform/internal/ops	0.174s
ok  	github.com/carlosframework/platform/internal/park	0.203s
ok  	github.com/carlosframework/platform/internal/privatefile	0.004s
ok  	github.com/carlosframework/platform/internal/provision	11.233s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.007s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	4.891s
ok  	github.com/carlosframework/platform/internal/relay	7.226s
ok  	github.com/carlosframework/platform/internal/release	19.862s
ok  	github.com/carlosframework/platform/internal/rollout	3.615s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.003s
ok  	github.com/carlosframework/platform/internal/rootaws	0.006s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.006s
ok  	github.com/carlosframework/platform/internal/rootstate	0.025s
ok  	github.com/carlosframework/platform/internal/runs	0.575s
ok  	github.com/carlosframework/platform/internal/s3compat	0.005s
ok  	github.com/carlosframework/platform/internal/safefile	0.004s
ok  	github.com/carlosframework/platform/internal/schedule	2.483s
ok  	github.com/carlosframework/platform/internal/seeds	0.011s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	8.496s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.903s
ok  	github.com/carlosframework/platform/internal/status	0.040s
ok  	github.com/carlosframework/platform/internal/steering	0.005s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.245s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.004s
ok  	github.com/carlosframework/platform/internal/termfence	0.004s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.004s
ok  	github.com/carlosframework/platform/internal/unixdial	0.002s
ok  	github.com/carlosframework/platform/internal/updatewords	0.004s
ok  	github.com/carlosframework/platform/internal/usage	0.005s
ok  	github.com/carlosframework/platform/internal/version	0.002s
ok  	github.com/carlosframework/platform/internal/vet	1.546s
=== 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
linux/arm64 carlos binary: 46399650 bytes (ceiling 52000000)
--- 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.024s
=== RUN   TestT4StorageProbeMinio
2026/10/16 06:29:36 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DEEEB8412699E7, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.01s)
PASS
ok  	github.com/carlosframework/platform/internal/host	0.022s
minio contract: PASS
diff --git a/docs/superpowers/plans/2026-10-16-region-ha-2a-core.md b/docs/superpowers/plans/2026-10-16-region-ha-2a-core.md
index 4e9e74137..b7c18c96c 100644
--- a/docs/superpowers/plans/2026-10-16-region-ha-2a-core.md
+++ b/docs/superpowers/plans/2026-10-16-region-ha-2a-core.md
@@ -1,4 +1,4 @@
-# Region HA 2a: The Provider-Neutral Core Implementation Plan (rev 3)
+# Region HA 2a: The Provider-Neutral Core Implementation Plan (rev 4)
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
@@ -44,6 +44,16 @@ Rev 3 answers Astra's round 2 (`docs/superpowers/plans/CODEX-REVIEW-REGION-HA-2A
One new census item: `Server.chooseCreateFirst` (Task 11), a new accepted-legacy unit whose one root read is the claim read `beginClaimCreate` makes, made before the binding. The census tooling's `unitdigest` build line is corrected (rev 2 built it over its own source directory).
+## Rev 4: census ruling region-ha-2a item 5
+
+Astra's round 3 (`docs/superpowers/plans/CODEX-REVIEW-REGION-HA-2A-3.md`) found nothing and calls the plan ready to build. platform-1e approved item 5, `Server.chooseCreateFirst`, in the current structure (not the hook alternative), with one condition (amadan #236), which rev 4 meets:
+
+| # | Condition | Rev 4 | Where |
+|---|---|---|---|
+| C6 | `chooseCreateFirst` returns its choice with the claim observation it decided on; `beginClaimCreate` requires its own claim read to be that observation, and refuses or re-chooses otherwise, never proceeding on the old choice | `chooseCreateFirst` carries the choice and the observation (the claim's ETag, or its absence) in the context. `beginClaimCreate` passes its own read to `chooseExecutorFirst`, which keeps a carried choice only when that read is the same observation. Otherwise it **refuses**: `errChoiceStale`, which is `hostclaim.ErrConflict`, so the door answers main's retryable busy refusal (409, `Retry-After: 2`) before anything is registered or written. Refusing is the smaller change: no new copy, and no second choice to prove. `TestAStaleEarlyChoiceIsNeverUsed`: a claim write landing between the choice and begin is refused, with nothing chosen again, registered or written; without the check the test fails (begin goes on past `RegisterStart` with the old choice) | Task 11 |
+
+Census values move with it: new digests for `Server.chooseCreateFirst` and `Server.beginClaimCreate` (kinds and read sites unchanged), their `@legacy` fingerprints, `internal/console`'s `@coverage` row in Tasks 11 to 13, and the seal in Tasks 11 and 12. Open question 2 is closed as decided.
+
## Scope
Spec 2 is four plans. This is **2a**. The others, and the seams 2a leaves them:
@@ -78,8 +88,8 @@ Approved in principle with conditions (amadan #236). The overlay asks for exactl
|---|---|---|---|---|
| 7 | `internal/host/edgesfile.go:edgesFileWriteClaims` | authority, F,J1 (kept) | Re-digest, class, task, basis and sites unchanged: with region HA on it reads the address source before every publication | stands |
| 10 | `internal/console/apiboxroles.go:Server.boxRoster` | accepted-legacy, J4 root-input | New: the role doors' read of the signed roster (`Directory.Read`), operator-only, read-only | moves |
-| 11 | `internal/console/claim_create.go:Server.beginClaimCreate` | accepted-legacy, J4 root-input | Re-freeze: one call (`chooseExecutorFirst`) before `RegisterStart`, which keeps a choice the context carries; its one read site unchanged (approved, ruling rev 2 item 3) | moves |
-| 11 | `internal/console/nocapacity.go:Server.chooseCreateFirst` | accepted-legacy, J4 root-input | **New in rev 3:** the instance create door's choice before its first write. Its one root read is the host's claim (`hostclaim.Read`), the read `beginClaimCreate` makes, feeding only the placement choice; it takes no lease and writes nothing | moves |
+| 11 | `internal/console/claim_create.go:Server.beginClaimCreate` | accepted-legacy, J4 root-input | Re-freeze: one call (`chooseExecutorFirst`) before `RegisterStart`, given this unit's own claim read, which keeps a carried choice only when that read is the observation the choice was decided on; its one read site unchanged (approved, ruling rev 2 item 3) | moves |
+| 11 | `internal/console/nocapacity.go:Server.chooseCreateFirst` | accepted-legacy, J4 root-input | New in rev 3, approved (ruling item 5) with its condition met in rev 4: the instance create door's choice before its first write. Its one root read is the host's claim (`hostclaim.Read`), the read `beginClaimCreate` makes, feeding only the placement choice; it takes no lease and writes nothing, and returns its choice with the claim observation it decided on | moves |
| 12 | `internal/console/apiinstancemove.go:Server.handleAPIInstanceMoveRequest` | accepted-legacy | Re-freeze: two refusals added before any write, both no room in the destination (`roomIn` before the placement checks, `moveRoomCheck` through `selectMoveBoxes`); both only refuse; read sites unchanged, offsets moved (approved, ruling rev 2 item 4; the basis text names both refusals) | moves |
`Server.attachClaimFirstRoute` is not changed: the first route's refusal comes through `beginClaimCreate` and `claimResponseRefusal`, which is not a root-read unit. Record-write census (B8) digests change for `Directory.Replace` (Task 3), `registerCLIRoutes` (Task 10), `handleAPIInstanceCreate` (Task 11) and `handleAPIInstanceMoveRequest` (Task 12). **No write site changes and none is added** (ruling rev 2): each re-digest is a change of the unit's other lines; the census reports no new or changed site.
@@ -283,12 +293,12 @@ nice -n 19 go test ./internal/provision/ -run 'TestPlan9B8RecordWriteCensus|Test
3. **An edges file stamped by a box whose clock runs ahead** is believed for a minute of skew and no more; a stale or unreadable generation is never trusted. Task 4's `TestPlaceableAppliesEachRule` (the future and unreadable cases).
4. **A bucket outage after a box has armed** must leave its health answer at 200 (no termination for a shared outage), while a refresh stuck inside one attempt turns it 503. Task 2's `TestBucketOutageAfterArmingStaysHealthy` and Task 9's `TestHomeMapFeedsTheHealthState`.
5. **A client retrying a refused create in a loop** must not page the operator on every retry, and must leave nothing for recovery each time. Task 11's `TestCreateDoorsRefuseWithNoRoomBeforeRegistering` (three refusals, one page, no registration, no claim after a recovery pass).
-6. **A create refused for want of room must change nothing, even when the fleet changes under it.** The choice comes before the environment binding, and the create names the box that was chosen. Task 11's `TestRefusedCreateLeavesTheEnvironmentsStoreAlone` (the last box goes between the region check and the choice) and `TestChooseFirstWritesNothingAndChoosesOnce`.
+6. **A create refused for want of room must change nothing, even when the fleet changes under it.** The choice comes before the environment binding, and the create names the box that was chosen. Task 11's `TestRefusedCreateLeavesTheEnvironmentsStoreAlone` (the last box goes between the region check and the choice) and `TestChooseFirstWritesNothingAndChoosesOnce`. Nor may a create go on with a choice whose claim changed: `TestAStaleEarlyChoiceIsNeverUsed`.
7. **The move door's room check only refuses.** A regression that narrows the candidates would quietly pick a destination main's selection refuses (a region with several boxes). Task 12's `TestMoveRoomCheckOnlyRefuses`.
## Dry run
-Every code block below was written into this branch on main `70e57449` (Tasks 1 to 10 at `aa5b6b07` for rev 2, replayed unchanged for rev 3; Tasks 11 to 13 rewritten at `0a31a455`), task by task in order, built, vetted with `GOOS=linux` and `GOOS=darwin` (and built with `GOOS=windows`), and its tests run as each task's steps say; the census values are those runs'. The copy gate was checked without approving anything: with every row pending, `TestRegionHABuiltCopyIsApplied`, `TestRegionHACopyRecordIsComplete` and `TestRegionHACopyHasNoEmDash` pass, and `TestRegionHABuiltCopyIsApproved` fails naming exactly the built ids (RHA-C1 to RHA-C16 after Task 10, RHA-C1 to RHA-C19 after Task 11). That failure is what an implementer sees until Paul approves the rows. Each rev 3 regression test was first run against rev 2's code and failed (the store test's refused create wrote the store; the move door answered 400; a legacy box beside a default one was picked; the metric went to the edges file's region). The whole `TestClaimConsoleMutationInvariant` ran once on the final tree. The plan text was then replayed onto a clean export, task by task, and every intermediate state built and passed both censuses. The code was then removed.
+Every code block below was written into this branch on main `70e57449` (Tasks 1 to 10 at `aa5b6b07` for rev 2, replayed unchanged for rev 3; Tasks 11 to 13 rewritten at `0a31a455` for rev 3, and again at `e5a3a3de` for rev 4), task by task in order, built, vetted with `GOOS=linux` and `GOOS=darwin` (and built with `GOOS=windows`), and its tests run as each task's steps say; the census values are those runs'. The copy gate was checked without approving anything: with every row pending, `TestRegionHABuiltCopyIsApplied`, `TestRegionHACopyRecordIsComplete` and `TestRegionHACopyHasNoEmDash` pass, and `TestRegionHABuiltCopyIsApproved` fails naming exactly the built ids (RHA-C1 to RHA-C16 after Task 10, RHA-C1 to RHA-C19 after Task 11). That failure is what an implementer sees until Paul approves the rows. Each rev 3 regression test was first run against rev 2's code and failed (the store test's refused create wrote the store; the move door answered 400; a legacy box beside a default one was picked; the metric went to the edges file's region). Rev 4's `TestAStaleEarlyChoiceIsNeverUsed` was run with the observation check removed and failed. The whole `TestClaimConsoleMutationInvariant` ran once on the final tree. The plan text was then replayed onto a clean export, task by task, and every intermediate state built and passed both censuses. The code was then removed.
---
@@ -4994,12 +5004,13 @@ git commit -m "region HA 2a: box and group role doors and the carlos system verb
**Interfaces:**
- Consumes: Task 6's `ExecutorChooser.PlaceableBoxes` and `Choose` (through `ProductionAuthority.SelectExecutor`); `hostops.ErrNoEligibleBox`; `alerting.Page`; `Server.homeRegionOut`.
-- Produces: `func hostops.WithChosenExecutor(ctx, host string, sel hostclaim.ExecutorSelection) context.Context`; `func hostops.ChosenExecutor(ctx, host string) (hostclaim.ExecutorSelection, bool)`; `type noCapacityError struct{ Region string }` (`errors.Is(err, hostops.ErrNoEligibleBox)`); `type noCapacityAlerts` (`due(region string, now time.Time) bool`); `func (s *Server) noRoom(ctx, region string) error`; `func (s *Server) placementChooser(pool string) *hostops.ExecutorChooser` (Tasks 12 and 13 use it); `func (s *Server) roomIn(ctx, region, pool string) error` (Task 12 uses it); `func (s *Server) chooseExecutorFirst(ctx, host string, before hostclaim.Observed, build func(time.Time, string) (hostops.Record, error)) (context.Context, error)`; `func (s *Server) chooseCreateFirst(ctx, host string, build ...) (context.Context, error)`; `var pageNoCapacity = alerting.Page` (the test seam).
+- Produces: `func hostops.WithChosenExecutor(ctx, host string, sel hostclaim.ExecutorSelection) context.Context`; `func hostops.ChosenExecutor(ctx, host string) (hostclaim.ExecutorSelection, bool)`; `type noCapacityError struct{ Region string }` (`errors.Is(err, hostops.ErrNoEligibleBox)`); `type noCapacityAlerts` (`due(region string, now time.Time) bool`); `func (s *Server) noRoom(ctx, region string) error`; `func (s *Server) placementChooser(pool string) *hostops.ExecutorChooser` (Tasks 12 and 13 use it); `func (s *Server) roomIn(ctx, region, pool string) error` (Task 12 uses it); `func (s *Server) chooseExecutorFirst(ctx, host string, before hostclaim.Observed, build func(time.Time, string) (hostops.Record, error)) (context.Context, error)`; `func (s *Server) chooseCreateFirst(ctx, host string, build ...) (context.Context, error)` (the returned context carries the choice and the claim observation it was decided on); `var errChoiceStale` (`errors.Is(err, hostclaim.ErrConflict)`); `var pageNoCapacity = alerting.Page` (the test seam).
**Wait for the copy.** RHA-C17 to RHA-C19 are the refusal and the alert. Start once Paul has approved them, and use his text.
H27 (spec rev 15), as Astra's findings ask (round 1: 1, 2, 3 and 6; round 2: 1 and 2):
- **Choose before the first write.** Every create through the claim starts in `beginClaimCreate`. With region HA on, `chooseExecutorFirst` builds the record the create would write, asks `SelectExecutor` for its executor, and returns before `RegisterStart` when there is none. Otherwise it puts the choice in the context, and `SelectExecutor` returns that choice to `StartCreate` (claim Plan 5's checks still run on it), so the create names exactly the box the door checked. The instance create door has a write before `beginClaimCreate`: `bindEnvironmentCreate` can clear a seed's mark on the environment's store (`takeSeedStore`). So that door calls `chooseCreateFirst` before the binding; it reads the host's claim and chooses the same way, and `beginClaimCreate` finds the choice in the context and keeps it. The domain attach and standalone sidecar doors write nothing before `beginClaimCreate`, and the seed doors refuse on a claim console (`seedRegionReady`).
+- **The observation travels with the choice (ruling item 5).** `chooseCreateFirst` returns the choice together with the claim observation it decided on (the claim's ETag, or its absence). `beginClaimCreate` hands its own claim read to `chooseExecutorFirst`, which keeps the carried choice only when that read is the same observation. If the claim changed in between, the create is **refused** with `errChoiceStale` (`hostclaim.ErrConflict`: main's retryable busy answer) before anything is registered or written; the old choice is never used, and nothing is chosen again. Refusing is the smaller of the two options the ruling allows: it adds no copy and no second choice.
- **No lease, no write, chosen once (ruling rev 2, item 3).** The choice is `SelectExecutor`'s read of the roster, the fences and the edges files. It takes no lease and writes nothing; a choice the context carries for the host is never chosen again; a refusal leaves no registration, no claim, no store change and nothing for a recovery pass.
- **Every choosing door.** One error, `noCapacityError`, answered in one place, `claimResponseRefusal`: instance create, domain attach's first route (the API's `handleAPIDomainAttach` and the browser's `handleAppDomainAttach`, both through `attachClaimFirstRoute`, which is unchanged), and standalone sidecar create. The move door's destination is Task 12. A sidecar attached to an instance chooses nothing (it lives on its instance's box) and is unchanged.
- **The earlier region checks.** `handleAPIInstanceCreate` judges an app-derived region (`regionCandidates`) by the chooser's eligible set instead, and asks `roomIn` before the armed-placement gate for an explicit region. `roomIn` only refuses: a region the roster places boxes in, with no placeable box (no edges file, no public address, stale, full or excluded), is the no-room refusal rather than a 400; a region the roster does not know is left to the gate.
@@ -5007,7 +5018,7 @@ H27 (spec rev 15), as Astra's findings ask (round 1: 1, 2, 3 and 6; round 2: 1 a
With region HA off nothing here runs: `roomIn`, `chooseCreateFirst` and `chooseExecutorFirst` return at once, and every create is main's.
-**Census (platform-1e):** `Server.beginClaimCreate` is an accepted-legacy unit (J4 root input). It gains one call before `RegisterStart`; its one read site is unchanged. The overlay re-freezes it with the ruling's tag. `Server.chooseCreateFirst` is new: its one root read is the host's claim, the read `beginClaimCreate` makes, made before the binding and feeding only the placement choice. The overlay asks for it as accepted-legacy (J4 root input). The seal moves. `Server.attachClaimFirstRoute` does not change. The record-write census digest of `handleAPIInstanceCreate` changes; no write site is added or changed.
+**Census (platform-1e):** `Server.beginClaimCreate` is an accepted-legacy unit (J4 root input). It gains one call before `RegisterStart`; its one read site is unchanged. The overlay re-freezes it with the ruling's tag. `Server.chooseCreateFirst` is new: its one root read is the host's claim, the read `beginClaimCreate` makes, made before the binding and feeding only the placement choice. It returns its choice with that observation, and `beginClaimCreate` refuses when its own read differs (ruling item 5). The overlay asks for it as accepted-legacy (J4 root input), as approved. The seal moves. `Server.attachClaimFirstRoute` does not change. The record-write census digest of `handleAPIInstanceCreate` changes; no write site is added or changed.
- [ ] **Step 1: Write the failing tests**
@@ -5463,6 +5474,69 @@ func TestChooseFirstWritesNothingAndChoosesOnce(t *testing.T) {
t.Fatalf("the create's selection %+v %v %v, want the door's %+v", sel, chosen, err, want)
}
}
+
+// Census ruling region-ha-2a item 5 (amadan #236): the early choice
+// carries the claim observation it was decided on, and beginClaimCreate
+// keeps it only if its own read of the claim is that observation. A claim
+// write landing between the choice and begin refuses the create with
+// main's retryable busy answer: the old choice is never used, nothing is
+// chosen again, and nothing is registered or written.
+func TestAStaleEarlyChoiceIsNeverUsed(t *testing.T) {
+ cx := haClaimCase(t, "")
+ cx.instanceWorld(t)
+ publishHABox(t, cx, "box-ie-tito", false, nil)
+ ctx := context.Background()
+ const host = "acme.go.tito.io"
+ acct, err := cx.st.AccountBySqid("bab")
+ if err != nil || acct == nil {
+ t.Fatalf("account: %+v %v", acct, err)
+ }
+ build := func(now time.Time, acquisition string) (hostops.Record, error) {
+ rec := provision.Record{Schema: 1, Host: host, App: "titogo", Account: "bab", Channel: "stable", Kind: "static", Pool: "tito", Region: "ie",
+ CreatedBy: "bob@keymail.dev", Acquisition: acquisition}
+ return hostops.PrepareInstance(rec, now, cx.srv.cfg.claimPlacementRegion("ie"))
+ }
+ chosen, err := cx.srv.chooseCreateFirst(ctx, host, build)
+ if _, ok := hostops.ChosenExecutor(chosen, host); err != nil || !ok {
+ t.Fatalf("early choice: %v (chosen %v)", err, ok)
+ }
+
+ // A claim write lands between the choice and begin.
+ created := time.Now().UTC().Truncate(time.Second)
+ inc, err := hostclaim.Incarnation("bab", "titogo", host, created)
+ if err != nil {
+ t.Fatal(err)
+ }
+ writeServingClaim(t, cx.rel, host, inc, created, "box-ie-tito", "ie")
+ written, _, err := cx.rel.Get(ctx, "control/hosts/"+host+".json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ before := registrations(t, cx, "bab", "titogo")
+ mark := cx.rec.mark()
+ // The build runs only to choose again or, past RegisterStart, to write
+ // the record: either is the old choice's create going on.
+ req, err := cx.srv.beginClaimCreate(chosen, acct, "titogo", host, "bob@keymail.dev", func(time.Time, string) (hostops.Record, error) {
+ t.Fatal("begin went on after the claim changed")
+ return hostops.Record{}, nil
+ })
+ if req != nil {
+ req.Close()
+ }
+ if !errors.Is(err, errChoiceStale) || !errors.Is(err, hostclaim.ErrConflict) {
+ t.Fatalf("begin on a changed claim = %v, want the stale-choice refusal", err)
+ }
+ if ref := claimResponseRefusal(err); ref.Status != http.StatusConflict || ref.Msg != claimRetireBusyMsg {
+ t.Fatalf("refusal %+v, want main's retryable busy answer", ref)
+ }
+ wroteNothing(t, cx, mark)
+ if n := registrations(t, cx, "bab", "titogo"); n != before {
+ t.Fatalf("registrations %d, want %d", n, before)
+ }
+ if now, _, err := cx.rel.Get(ctx, "control/hosts/"+host+".json"); err != nil || string(now) != string(written) {
+ t.Fatalf("the claim changed under the refusal: %v", err)
+ }
+}
````
````diff
@@ -5504,7 +5578,7 @@ func TestChooseFirstWritesNothingAndChoosesOnce(t *testing.T) {
- [ ] **Step 2: Run them to see them fail**
-Run: `nice -n 19 go test ./internal/console/ -run 'NoCapacity|ClaimRefusalAnswersNoRoom|CreateDoorsRefuse|AppRegionWith|RefusedCreateLeaves|ChooseFirstWrites|CreateRegionWithNoReachable' -count=1`
+Run: `nice -n 19 go test ./internal/console/ -run 'NoCapacity|ClaimRefusalAnswersNoRoom|CreateDoorsRefuse|AppRegionWith|RefusedCreateLeaves|ChooseFirstWrites|CreateRegionWithNoReachable|AStaleEarlyChoice' -count=1`
Expected: FAIL to build (`undefined: pageNoCapacity`, `undefined: noCapacityError`). The hostops test fails to build on `undefined: WithChosenExecutor`.
- [ ] **Step 3: Write the early choice and the refusal**
@@ -5705,20 +5779,43 @@ func (s *Server) roomIn(ctx context.Context, region, pool string) error {
return nil
}
-// chooseExecutorFirst is beginClaimCreate's first step with region HA on:
-// it chooses the executor for the record build would make, before anything
-// is registered, and carries the choice to the create
+// errChoiceStale is an early choice whose claim observation is no longer
+// the claim: a write landed between the choice and beginClaimCreate's own
+// read. It is hostclaim.ErrConflict, so the door answers main's retryable
+// busy refusal, and nothing was registered or written.
+var errChoiceStale = errors.New("console: the host's claim changed after its executor was chosen")
+
+// chosenOnKey carries the claim observation an early choice was decided
+// on, beside the choice itself (hostops.WithChosenExecutor).
+type chosenOnKey struct{}
+
+type chosenOn struct {
+ host, etag string
+ absent bool
+}
+
+// chooseExecutorFirst is beginClaimCreate's first step with region HA on,
+// given beginClaimCreate's own read of the claim (before). It chooses the
+// executor for the record build would make, before anything is
+// registered, and carries the choice to the create
// (hostops.WithChosenExecutor). It takes no lease and writes nothing: the
// choice is SelectExecutor's read of the roster and the edges files. No
// eligible box is noRoom's refusal. A choice ctx already carries for host
-// (chooseCreateFirst's) is kept, and nothing is chosen again. With region
-// HA off it returns ctx unchanged, and the create is main's.
+// (chooseCreateFirst's) is kept only when before is the claim observation
+// it was decided on (census ruling region-ha-2a, item 5): otherwise the
+// create is refused with errChoiceStale, and the old choice is never used
+// and nothing is chosen again. With region HA off it returns ctx
+// unchanged, and the create is main's.
func (s *Server) chooseExecutorFirst(ctx context.Context, host string, before hostclaim.Observed, build func(time.Time, string) (hostops.Record, error)) (context.Context, error) {
a := s.cfg.ClaimAuthority
if !s.cfg.RegionHA || a == nil || a.Executors == nil {
return ctx, nil
}
if _, ok := hostops.ChosenExecutor(ctx, host); ok {
+ on, ok := ctx.Value(chosenOnKey{}).(chosenOn)
+ if !ok || on.host != host || on.etag != before.ETag || on.absent != before.Absent {
+ return ctx, fmt.Errorf("%w: %w: %s", hostclaim.ErrConflict, errChoiceStale, host)
+ }
return ctx, nil
}
probe, err := build(time.Now().UTC(), "")
@@ -5739,9 +5836,11 @@ func (s *Server) chooseExecutorFirst(ctx context.Context, host string, before ho
// door's first write (Astra 2a round 2 finding 2): the environment binding
// (bindEnvironmentCreate) can move a seed's store to the new instance, so
// a create refused for want of room must be refused before it. It reads
-// host's claim and chooses as chooseExecutorFirst does; beginClaimCreate
-// then finds the choice in ctx and keeps it. With region HA off it
-// returns ctx unchanged and reads nothing.
+// host's claim and chooses as chooseExecutorFirst does, and returns the
+// choice together with the claim observation it decided on (its ETag, or
+// its absence). beginClaimCreate keeps the choice only if its own read is
+// that same observation. With region HA off it returns ctx unchanged and
+// reads nothing.
func (s *Server) chooseCreateFirst(ctx context.Context, host string, build func(time.Time, string) (hostops.Record, error)) (context.Context, error) {
a := s.cfg.ClaimAuthority
if !s.cfg.RegionHA || a == nil || a.Executors == nil {
@@ -5751,7 +5850,14 @@ func (s *Server) chooseCreateFirst(ctx context.Context, host string, build func(
if err != nil {
return ctx, err
}
- return s.chooseExecutorFirst(ctx, host, before, build)
+ chosen, err := s.chooseExecutorFirst(ctx, host, before, build)
+ if err != nil {
+ return ctx, err
+ }
+ if _, ok := hostops.ChosenExecutor(chosen, host); !ok {
+ return chosen, nil
+ }
+ return context.WithValue(chosen, chosenOnKey{}, chosenOn{host: host, etag: before.ETag, absent: before.Absent}), nil
}
````
@@ -5774,13 +5880,15 @@ func (s *Server) chooseCreateFirst(ctx context.Context, host string, build func(
````diff
--- a/internal/console/claim_create.go
+++ b/internal/console/claim_create.go
-@@ -37,6 +37,10 @@
+@@ -37,6 +37,12 @@
}
before, err := hostclaim.Read(ctx, s.cfg.Releases, host)
if err != nil {
+ return nil, err
+ }
-+ // Region HA (spec 2 §7, H27): choose before registering anything.
++ // Region HA (spec 2 §7, H27): choose before registering anything. A
++ // door's earlier choice is kept only if this read is the claim it was
++ // decided on (census ruling region-ha-2a, item 5).
+ if ctx, err = s.chooseExecutorFirst(ctx, host, before, build); err != nil {
return nil, err
}
@@ -5899,7 +6007,7 @@ func (s *Server) chooseCreateFirst(ctx context.Context, host string, build func(
- [ ] **Step 4: Run the tests**
```bash
-nice -n 19 go test ./internal/console/ -run 'NoCapacity|ClaimRefusalAnswersNoRoom|CreateDoorsRefuse|AppRegionWith|RefusedCreateLeaves|ChooseFirstWrites|CreateRegionWithNoReachable|ClaimCreate|InstanceCreate|DomainAttach|Sidecar|EnvironmentCreate|EnvironmentBinding' -count=1
+nice -n 19 go test ./internal/console/ -run 'NoCapacity|ClaimRefusalAnswersNoRoom|CreateDoorsRefuse|AppRegionWith|RefusedCreateLeaves|ChooseFirstWrites|CreateRegionWithNoReachable|AStaleEarlyChoice|ClaimCreate|InstanceCreate|DomainAttach|Sidecar|EnvironmentCreate|EnvironmentBinding' -count=1
nice -n 19 go test ./internal/hostops/ -run 'Executor|Chooser|SelectExecutor|StartCreate|ProductionCreate' -count=1
```
Expected: PASS.
@@ -5958,18 +6066,18 @@ The `Server.chooseCreateFirst` rows are census ruling region-ha-2a's (platform-1
Add this comment line after the overlay's last comment line:
````text
-# Task 11: H27 on the create doors (internal/console nocapacity.go; hostops chosen.go): beginClaimCreate chooses the executor before it registers anything (chooseExecutorFirst) and carries the choice to the create (hostops.WithChosenExecutor, honoured by ProductionAuthority.SelectExecutor), claimResponseRefusal answers no room with a 503 on instance create, domain attach's first route (attachClaimFirstRoute, unchanged) and standalone sidecar create, and handleAPIInstanceCreate judges an app-derived region by the chooser's eligible set. handleAPIInstanceCreate chooses before its first write, the environment binding (chooseCreateFirst), and beginClaimCreate keeps that choice; with region HA on, an explicit region with no box that has room is the no-room refusal before the armed-placement gate (roomIn). Census ruling region-ha-2a covers two accepted-legacy units: Server.beginClaimCreate re-frozen (one call added before RegisterStart, its one root-read site, the claim read file:Read@470, unchanged), and Server.chooseCreateFirst, new: the same claim read, made before the binding, feeding only the placement choice. The seal moves.
+# Task 11: H27 on the create doors (internal/console nocapacity.go; hostops chosen.go): beginClaimCreate chooses the executor before it registers anything (chooseExecutorFirst) and carries the choice to the create (hostops.WithChosenExecutor, honoured by ProductionAuthority.SelectExecutor), claimResponseRefusal answers no room with a 503 on instance create, domain attach's first route (attachClaimFirstRoute, unchanged) and standalone sidecar create, and handleAPIInstanceCreate judges an app-derived region by the chooser's eligible set. handleAPIInstanceCreate chooses before its first write, the environment binding (chooseCreateFirst), and beginClaimCreate keeps that choice; with region HA on, an explicit region with no box that has room is the no-room refusal before the armed-placement gate (roomIn). Census ruling region-ha-2a covers two accepted-legacy units: Server.beginClaimCreate re-frozen (one call added before RegisterStart, its one root-read site, the claim read file:Read@470, unchanged), and Server.chooseCreateFirst, new: the same claim read, made before the binding, feeding only the placement choice; it returns its choice with the claim observation it decided on, and beginClaimCreate keeps the choice only if its own read is that observation (ruling item 5). The seal moves.
````
Then set these rows. A `@coverage` row replaces the overlay's row for the same directory, or is appended when there is none; every other row is appended:
````text
@coverage internal/hostops 6e494cc9e53bf4fa4433fad798ecd3493c972f65083b0b2153b253f0bd50dcba
-@coverage internal/console 2cbd830ba2753a8a6e844ce413220e89b2c85fecd0c0f891d4afd44c68cfeb7f
-@legacy d3df5cf0504d03e83d6b4d9ac0d5f7f96ce83f2be9edde6fa45f6b53aac0a28d internal/console/claim_create.go Server.beginClaimCreate legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@470 c1f1d4ad231ad74c975cd99178f8c0856b38690e0b25c9ea38a1888324953499
-internal/console/claim_create.go Server.beginClaimCreate legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@470 c1f1d4ad231ad74c975cd99178f8c0856b38690e0b25c9ea38a1888324953499
-@legacy 6e1630474813d7844c18d06016a19d55081a63e5b60223fa2641b9c198439bb8 internal/console/nocapacity.go Server.chooseCreateFirst legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@271 a867392e56072cf16b712a56d7ea613c2a36e0380c304aee33f6ab8b4939d135
-internal/console/nocapacity.go Server.chooseCreateFirst legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@271 a867392e56072cf16b712a56d7ea613c2a36e0380c304aee33f6ab8b4939d135
+@coverage internal/console 24ef1664e0251ac26a5cfe4cb2f7f4efd56a3881dc1eee413f5d172f0f72690d
+@legacy 546fc7978037e417ac9df597a95f21add19b025e2d569f8fef7ed4695f0b8bfd internal/console/claim_create.go Server.beginClaimCreate legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@470 ff9b246aa3dcfe180ea1901ecd9fc1b3b18561dc309e08c6a922870d89179705
+internal/console/claim_create.go Server.beginClaimCreate legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@470 ff9b246aa3dcfe180ea1901ecd9fc1b3b18561dc309e08c6a922870d89179705
+@legacy 5aa99a1b92eb73601585a25265c5285bcfd0c6b6ae7f402248eb1a299bc79831 internal/console/nocapacity.go Server.chooseCreateFirst legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@271 7cc51e7812cc59f39ea23d5285c621d912557f4890d6e76d566f6182b0fe3ffd
+internal/console/nocapacity.go Server.chooseCreateFirst legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@271 7cc51e7812cc59f39ea23d5285c621d912557f4890d6e76d566f6182b0fe3ffd
````
````diff
@@ -5980,7 +6088,7 @@ internal/console/nocapacity.go Server.chooseCreateFirst legacy accepted-legacy a
t.Fatal(err)
}
- seal = "a57250c350c20e4116e5c87b559cfda71f3e0c65d59a2858d83c5f78bea4e9b5"
-+ seal = "5013c9ebddabb1d84a57cc2199991fd0f75b97bc5a955c13d80ccf48efb1c235"
++ seal = "2170924f126b744e720ee9e7c9c4c7d7f916bbe89509267219e10a1b1a5ae986"
errs := append(compareRootReads(got, inv), checkRootReadLegacy(inv, seal)...)
errs = append(errs, checkRootReadDeclarations(got, inv)...)
if len(errs) > 0 {
@@ -6384,7 +6492,7 @@ Add this comment line after the overlay's last comment line:
Then set these rows. A `@coverage` row replaces the overlay's row for the same directory, or is appended when there is none; every other row is appended:
````text
-@coverage internal/console 2a3739453eafdeda8cc9bb91fa2ae37b11ae6e83508e9210d93d1d370b324732
+@coverage internal/console e9e63edfe287fef5599d0c40a4e7538fbe8462cefaa208875ffb786720dde6f9
@legacy 88952543956a1b090e6b819c05be0504a899945d5c039dee8373fbaa805881a9 internal/console/apiinstancemove.go Server.handleAPIInstanceMoveRequest legacy accepted-legacy accepted-legacy;rev3=census-ruling-region-ha-2a;basis=the console (cmd/console serve under console.service, user carlos-console): main's body with two refusals added before any write, both no room in the destination (H27): roomIn before the placement checks and moveRoomCheck's through selectMoveBoxes; read sites unchanged, offsets moved file:Exec@7439,file:Exec@7527,file:Exec@8518,file:Exec@8695,file:Exec@8846,file:Exec@9846,file:Read@5925,file:Read@8237 867ed0feabca3d5c72990474887d5f88820b9b0e7ef7f72e48703ac4dfee10a8
internal/console/apiinstancemove.go Server.handleAPIInstanceMoveRequest legacy accepted-legacy accepted-legacy;rev3=census-ruling-region-ha-2a;basis=the console (cmd/console serve under console.service, user carlos-console): main's body with two refusals added before any write, both no room in the destination (H27): roomIn before the placement checks and moveRoomCheck's through selectMoveBoxes; read sites unchanged, offsets moved file:Exec@7439,file:Exec@7527,file:Exec@8518,file:Exec@8695,file:Exec@8846,file:Exec@9846,file:Read@5925,file:Read@8237 867ed0feabca3d5c72990474887d5f88820b9b0e7ef7f72e48703ac4dfee10a8
````
@@ -6396,8 +6504,8 @@ internal/console/apiinstancemove.go Server.handleAPIInstanceMoveRequest legacy a
if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-region-ha-2a.tsv"), &inv, true); err != nil {
t.Fatal(err)
}
-- seal = "5013c9ebddabb1d84a57cc2199991fd0f75b97bc5a955c13d80ccf48efb1c235"
-+ seal = "9581ac17ef7967d92e3a97fa873792e1da3367f517b62e2bdbdf2603aca5212b"
+- seal = "2170924f126b744e720ee9e7c9c4c7d7f916bbe89509267219e10a1b1a5ae986"
++ seal = "8bb6e5f26df334676dddf16387e78206db872cc46ebaaa5565cabf1dc50f40c6"
errs := append(compareRootReads(got, inv), checkRootReadLegacy(inv, seal)...)
errs = append(errs, checkRootReadDeclarations(got, inv)...)
if len(errs) > 0 {
@@ -6904,7 +7012,7 @@ Then set these rows. A `@coverage` row replaces the overlay's row for the same d
````text
@coverage internal/hostops a86df6341b8b1d44676da9ff1310a204f2b34380cb438cc05fe71ae1834834f3
-@coverage internal/console 745590987511f5758f0af07f63851275c30334825379af7f0dde62fa05c991e0
+@coverage internal/console 0a6e6c1d77b0c56955bf1b090a450ac6ec5d12f0ed60313e44049cb341a6788c
````
Run the census checks and `go test . -run 'TestRegionHA(Core|Adapters|S3)' -count=1`. Expected: PASS: `regioncapacity.go` imports no AWS package, and `awsregionmetric.go` is named for AWS.
@@ -6930,5 +7038,5 @@ git commit -m "region HA 2a: publish each region's eligible capacity"
## Open questions
1. **Hosting before 2b.** Task 9 marks a box hosting when the claim agent's wiring starts, which proves enrolment and a matching roster row but does not itself check that the floor guard is armed for that enrolment. 2b's mode selection makes it exact. Is that acceptable for 2a, given no load balancer reads the answer until 2c?
-2. **`Server.chooseCreateFirst` (new in rev 3).** It is asked of platform-1e as accepted-legacy, with the basis `beginClaimCreate` has. If the ruling prefers no second claim read, the alternative is a hook inside `beginClaimCreate` that runs the binding after its choice and before `RegisterStart`; that changes the signature of a re-frozen unit and its three callers, so rev 3 does not take it.
+2. **`Server.chooseCreateFirst`: decided.** platform-1e approved it in the current structure, not the hook inside `beginClaimCreate`, on condition that the choice carries the claim observation it was decided on and `beginClaimCreate` never proceeds on a changed claim. Rev 4 does that and refuses on a change (Task 11).
3. **The census values (condition 3).** Every value here was computed at main `70e57449`. Plan 14 Parts 2 and 3 will move the base; the implementer recomputes every value there with the tooling above, and platform-1e's ruling covers the rows, not these hashes.