carlos / platform Public

census-ruling-moves-offline-precopy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 3bb06baaaba05b0101fa0449cb612794d6341a69 parent 21cd69a
6 files changed, +222 −1
  • docs/superpowers/notes/census-proposal-moves-offline-precopy.md +143 −0
  • internal/console/testdata/claim-routes.txt +1 −0
  • internal/provision/moves_offline_precopy_census_test.go +43 −0
  • internal/provision/plan14_root_reads_test.go +16 −0
  • internal/provision/plan9_b8_census_test.go +1 −1
  • internal/provision/testdata/plan14-root-reads-moves-offline-precopy.tsv +18 −0

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.012s
?   	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	331.689s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.831s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.304s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.716s
ok  	github.com/carlosframework/platform/internal/activator	250.196s
ok  	github.com/carlosframework/platform/internal/agentsock	0.028s
ok  	github.com/carlosframework/platform/internal/alerting	0.007s
ok  	github.com/carlosframework/platform/internal/analytics	0.019s
ok  	github.com/carlosframework/platform/internal/appconfig	2.508s
ok  	github.com/carlosframework/platform/internal/apperr	0.073s
ok  	github.com/carlosframework/platform/internal/applog	0.130s
ok  	github.com/carlosframework/platform/internal/awsinv	0.004s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.005s
ok  	github.com/carlosframework/platform/internal/boxid	0.894s
?   	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.013s
ok  	github.com/carlosframework/platform/internal/certwarm	0.002s
ok  	github.com/carlosframework/platform/internal/cgroup	0.716s
ok  	github.com/carlosframework/platform/internal/changelog	0.338s
ok  	github.com/carlosframework/platform/internal/claimactivation	0.825s
ok  	github.com/carlosframework/platform/internal/clamd	0.055s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	439.988s
?   	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.743s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.010s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.003s
ok  	github.com/carlosframework/platform/internal/docsite	0.006s
ok  	github.com/carlosframework/platform/internal/domains	2.352s
ok  	github.com/carlosframework/platform/internal/doorkey	0.006s
ok  	github.com/carlosframework/platform/internal/economics	0.004s
ok  	github.com/carlosframework/platform/internal/edge	45.233s
?   	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.132s
ok  	github.com/carlosframework/platform/internal/gate	1.655s
ok  	github.com/carlosframework/platform/internal/genselect	0.015s
ok  	github.com/carlosframework/platform/internal/geo	0.016s
ok  	github.com/carlosframework/platform/internal/geoip	0.030s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.155s
ok  	github.com/carlosframework/platform/internal/host	194.501s
ok  	github.com/carlosframework/platform/internal/hostactors	6.413s
ok  	github.com/carlosframework/platform/internal/hostactors/awsmembership	0.005s
ok  	github.com/carlosframework/platform/internal/hostbox	2.759s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.571s
ok  	github.com/carlosframework/platform/internal/hostenv	0.004s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.007s
ok  	github.com/carlosframework/platform/internal/hostexec	5.045s
ok  	github.com/carlosframework/platform/internal/hostfence	0.042s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.499s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.016s
ok  	github.com/carlosframework/platform/internal/hostguard	1.041s
ok  	github.com/carlosframework/platform/internal/hostops	1.639s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.003s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.002s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.466s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.156s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.005s
ok  	github.com/carlosframework/platform/internal/infraview	0.003s
ok  	github.com/carlosframework/platform/internal/instancectl	5.225s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.748s
ok  	github.com/carlosframework/platform/internal/instancefs	0.006s
ok  	github.com/carlosframework/platform/internal/instancekey	0.002s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.008s
ok  	github.com/carlosframework/platform/internal/layout	1.581s
ok  	github.com/carlosframework/platform/internal/lbhealth	0.004s
ok  	github.com/carlosframework/platform/internal/lease	10.115s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.378s
ok  	github.com/carlosframework/platform/internal/ledger	0.015s
ok  	github.com/carlosframework/platform/internal/mail	0.181s
ok  	github.com/carlosframework/platform/internal/maintenance	0.086s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.550s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.090s
ok  	github.com/carlosframework/platform/internal/metrics	0.006s
ok  	github.com/carlosframework/platform/internal/moves	21.200s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.820s
ok  	github.com/carlosframework/platform/internal/objstore	0.206s
ok  	github.com/carlosframework/platform/internal/ops	0.175s
ok  	github.com/carlosframework/platform/internal/park	0.217s
ok  	github.com/carlosframework/platform/internal/privatefile	0.004s
ok  	github.com/carlosframework/platform/internal/provision	36.480s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.010s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.500s
ok  	github.com/carlosframework/platform/internal/relay	7.245s
ok  	github.com/carlosframework/platform/internal/release	24.355s
ok  	github.com/carlosframework/platform/internal/rollout	3.806s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.004s
ok  	github.com/carlosframework/platform/internal/rootaws	0.009s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.059s
ok  	github.com/carlosframework/platform/internal/rootstate	0.027s
ok  	github.com/carlosframework/platform/internal/runs	0.585s
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.602s
ok  	github.com/carlosframework/platform/internal/seeds	0.011s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	9.940s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	4.314s
ok  	github.com/carlosframework/platform/internal/status	0.064s
ok  	github.com/carlosframework/platform/internal/steering	0.008s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.257s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.005s
ok  	github.com/carlosframework/platform/internal/termfence	0.004s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.006s
ok  	github.com/carlosframework/platform/internal/unixdial	0.004s
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.573s
=== 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: 49873058 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: downloading https://github.com/minio/minio/releases/download/RELEASE.2025-09-07T16-13-09Z/minio.linux-arm64.RELEASE.2025-09-07T16-13-09Z
  minio: downloaded, digest verified
  mc: downloading https://github.com/minio/mc/releases/download/RELEASE.2025-08-13T08-35-41Z/mc.linux-arm64.RELEASE.2025-08-13T08-35-41Z
  mc: downloaded, 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.03s)
    --- 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.02s)
        --- 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.01s)
        --- 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.01s)
        --- 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.040s
=== RUN   TestT4StorageProbeMinio
2026/10/21 13:17:47 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18E08DE53CE94FAF, 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.033s
minio contract: PASS
diff --git a/docs/superpowers/notes/census-proposal-moves-offline-precopy.md b/docs/superpowers/notes/census-proposal-moves-offline-precopy.md
new file mode 100644
index 000000000..285840b0d
--- /dev/null
+++ b/docs/superpowers/notes/census-proposal-moves-offline-precopy.md
@@ -0,0 +1,143 @@
+# Census: parallel mirror rounds and the offline move's stage door
+
+Status: RULED. census-ruling-moves-offline-precopy, by the coordinator as census
+owner for this branch, 2026-10-21: accepted as proposed. Every row carries
+`rev3=census-ruling-moves-offline-precopy`.
+
+The branch is `moves-offline-precopy`, from main 3d444a1b, ruled at 21cd69a4
+(after Astra's round 1 fixes, 692d41cf). It does two things. It runs a mirror
+round's copies and deletes side by side in `lease.MirrorPrefixKeep`. And it
+adds `carlos move offline stage`, which pre-copies the replica while the
+instance keeps serving, as a new record state, offline-staging.
+
+The ruling adds:
+
+- `internal/provision/testdata/plan14-root-reads-moves-offline-precopy.tsv`,
+ loaded last in `TestPlan14RootReadCensus`, after scan-demand-revival, once
+ scan-demand-revival's seal has been checked;
+- `internal/provision/moves_offline_precopy_census_test.go`, the record-write
+ (9B8) overlay on scan-demand-revival's inventory, which
+ `TestPlan9B8RecordWriteCensus` now reads;
+- one line in `internal/console/testdata/claim-routes.txt`.
+
+No earlier overlay is edited.
+
+## The footing
+
+Every unit here runs in the box agent (`/opt/carlos/carlos agent`,
+carlos-edge.service, `User=carlos`) or the console (cmd/console serve, user
+carlos-console). None makes a root decision. So each stays accepted legacy at
+the disposition census-ruling-moves-offline gave it or its sibling: J1
+(root-route) for registry and route reads, J4 (root-input) for file reads.
+
+## Root-read census
+
+| unit | change | executing identity | disposition |
+|---|---|---|---|
+| `internal/host/movesoffline.go` `movesWorker.offlineSourceRevert` | changed legacy: calls `offlineUnstage` before it reports reverted. Read sites unchanged; `whole:Route@701` moved to `@843` (the same `rt` read, shifted by the inserted lines) | the agent, on abort, through the armed moves worker | re-frozen, J1 |
+| `internal/provision/offline_move.go` `offlineBegin` | new: OfflineStart's body moved unchanged, with the state as a parameter. Its one site, `file:Open@198`, is `rec.ActiveRun.Open()`, i.e. `RunState.Open()` (queued, picked or cancel), a method, not a file read | the console, through `SetRecordMove` | accepted legacy, J4 (as OfflineStart) |
+| `internal/provision/offline_move.go` `OfflineStart` | retired: still declared, reads nothing. It calls `offlineBegin(…, OfflineStopping)` and is behaviour-identical to main: same refusals in the same order, same stamps, and the `StagedAt` stamp fires only for staging | the console | H3 census-ruling removal (the carlos-run `getJSON` form) |
+
+### What offlineUnstage reads and writes
+
+It does nothing unless the record carries `staged_at` and this box is the
+stager the stage door pinned on the record (`move.stager`). On that one box it
+reads only the object store:
+
+- its own stager report (`OfflineReportKey`);
+- the fleet's edges files, for the target's coordinates (`offlineTarget`, the
+ same helper the seal uses);
+- this box's receipts object.
+
+It then:
+
+- deletes `moves/<host>/` in the target's data bucket. It refuses a bare
+ `moves/`, and never deletes in this box's own bucket. `offlineSealRevert`
+ already deletes the same prefix the same way;
+- clears `move-receipts/<host>.json` in `cfg.OwnBucket`, the same bucket the
+ stage round writes them to (`tombstoneAt`);
+- writes its stager report as reverted.
+
+It reads no local file. The host comes from the record and the target from
+the edges files: the operator names only a region code, and the tenant
+controls none of these paths.
+
+### Envelopes re-pinned (at 21cd69a4)
+
+None of these changes adds a read site. Every changed production file was
+read for this ruling.
+
+| envelope | what changed | hash |
+|---|---|---|
+| `.` | move.go: the stage subcommand and its status and abort lines; no os, file, exec or env use | `44fc0f3e…8bdd` |
+| `internal/console` | the stage door; `offlineBeginChecks`, shared by start and stage; the stager pin; the finish gate on the stager's reverted report; the status view | `6ae90250…92d6` |
+| `internal/host` | `movesoffline_stage.go`, new: reads only the object store, and imports `os` only for `os.ErrNotExist`. `edgesfile.go` publishes `offline-stage-1`. offlineRound has a staging branch | `e9234db8…8f47` |
+| `internal/lease` | mirror.go: `forEachParallel`; copies, then deletes, side by side; the receipts book read before the workers start | `980f38c4…62a14` |
+| `internal/moves` | the stager role, the staged phase, the report's progress fields, `OfflineStager` | `33c7b507…e071` |
+| `internal/provision` | `OfflineStage` (which needs a stager), `OfflineRestage`, `OfflineStartStaged`; `MoveState.StagedAt` and `MoveState.Stager` | `8c868695…4b64` |
+
+## Record-write census (9B8)
+
+Two sites change, each keeping main's class. No site is added.
+
+| file:function | kinds | class | why |
+|---|---|---|---|
+| `internal/console/authcli.go:Server.registerCLIRoutes` | lit:instances,lit:sidecars | key-site (main's) | one route line for the stage door, beside the six offline doors; no write added. Digest `2aee959d…c459` |
+| `internal/lease/mirror.go:MirrorPrefixKeep` | sel:CopyVersioned | versioned-transport (main's) | see below. Digest `dff4d03f…fb19` |
+
+MirrorPrefixKeep plans its skips in key order first. It then runs the copies
+on up to `DefaultMirrorWorkers` (8) goroutines and, only once every copy has
+landed, the deletes. Its writes are the same as before:
+
+- Copy or CopyVersioned into the destination prefix;
+- Delete of destination keys the source no longer has;
+- receipts through the book (`set`, `drop`, `save`). Every update is under one
+ mutex, and `receiptLedger.save` is still the one Put, unchanged.
+
+After round 1, the book is read once, before the workers start, whenever the
+round has copies to make. So `set` and `drop` touch memory only, and no
+request is made under the mutex.
+
+The stager pin is a new field on `MoveState`. It is written through the typed
+`provision.SetRecordMove` (`offlineApply`), like every other move field, under
+the same one-move lock. That is not a new write site.
+
+## Claim route census
+
+`POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/stage` joins
+`claim-routes.txt`.
+
+- **Access.** The door is operator-only: `offlineGate` calls
+ `moveOperatorGate`, and any token that is not an operator's gets a 404.
+- **Claims consoles.** With host claims or Phase 1 on, it is refused with
+ MV-O20.
+- **Tests.** Its claim-refusal fixture and its `svcroutes_test.go` exemption
+ match the six sibling offline doors exactly.
+
+## The seal
+
+The two accepted-legacy rows add frozen declarations, so the legacy seal moves
+from `a21dba12…f262` (main, after scan-demand-revival) to
+`965fda6e58a16662833fcfe5435e9f45b525eb1abdd1f1f1715d611cb699bc81`. A removal
+is not in the seal. The test checks scan-demand-revival's seal before it loads
+this overlay. The seal was read from a temporary `t.Logf` of
+`rootReadLegacySeal(inv)`, which was then removed.
+
+## Ruling (the coordinator, 2026-10-21): census-ruling-moves-offline-precopy, ACCEPTED as proposed
+
+1. offlineSourceRevert and offlineUnstage: ACCEPTED. Object-store reads only,
+ on the pinned stager only; there is no root read of an operator- or
+ tenant-controlled path.
+2. offlineBegin: ACCEPTED (J4, as OfflineStart). OfflineStart is retired by
+ the H3 removal, since it is behaviour-identical.
+3. offlineBegin's `file:Open@198` is `rec.ActiveRun.Open()`: ACCEPTED.
+4. The six envelopes: ACCEPTED, re-pinned at 21cd69a4.
+5. MirrorPrefixKeep: ACCEPTED at versioned-transport. Its record writes still
+ go through the book.
+6. registerCLIRoutes: ACCEPTED at key-site.
+7. The stage route: ACCEPTED. It is operator-only, refused with MV-O20 on
+ claims consoles, and has its fixture.
+
+The first verification was at 25c0db26. It was re-verified at 21cd69a4 for the
+round 1 fixes: the stager pin, cleanup by `staged_at`, and the receipts
+preload.
diff --git a/internal/console/testdata/claim-routes.txt b/internal/console/testdata/claim-routes.txt
index e154bb8c4..ec5289de4 100644
--- a/internal/console/testdata/claim-routes.txt
+++ b/internal/console/testdata/claim-routes.txt
@@ -193,6 +193,7 @@ route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/finish
route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/flip
route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/open
route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/seal
+route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/stage
route POST /api/cli/apps/{account}/{app}/instances/{host}/retry
route POST /api/cli/apps/{account}/{app}/instances/{host}/runs
route POST /api/cli/apps/{account}/{app}/instances/{host}/runs/{id}/ack
diff --git a/internal/provision/moves_offline_precopy_census_test.go b/internal/provision/moves_offline_precopy_census_test.go
new file mode 100644
index 000000000..4a0bb6d20
--- /dev/null
+++ b/internal/provision/moves_offline_precopy_census_test.go
@@ -0,0 +1,43 @@
+package provision
+
+// movesOfflinePrecopyWriteCensusOverlay is moves-offline-precopy's
+// record-write sites (parallel copies in lease.MirrorPrefix and the carlos
+// move offline stage door), census-ruling-moves-offline-precopy: RULED by the
+// coordinator, as census owner for this branch (2026-10-21), accepted as
+// classified (docs/superpowers/notes/census-proposal-moves-offline-precopy.md).
+//
+// Two sites change and keep their class; no site is added.
+// registerCLIRoutes gains the stage door's route line beside the six
+// offline doors (key-site, main's). MirrorPrefixKeep plans its skips first
+// and then runs the copies and, after every copy has landed, the deletes on
+// up to DefaultMirrorWorkers goroutines; it still writes only the
+// destination prefix (Copy or CopyVersioned, Delete) and the receipts
+// through the book (set, drop and save, every update under one mutex,
+// receiptLedger.save the one Put, unchanged), so versioned-transport stands.
+// After Astra's round 1 (692d41cf) the book is read once before the workers
+// start whenever the round copies anything, so set and drop touch memory
+// only and no request is made under the mutex; the write set is unchanged.
+var movesOfflinePrecopyWriteCensusOverlay = []censusEntry{
+ {"internal/console/authcli.go", "Server.registerCLIRoutes", "key-site", "lit:instances,lit:sidecars", "2aee959d5c58a81b47f10629f6954b3d0b6d6860f999d9fe9eead9155f12c459"},
+ {"internal/lease/mirror.go", "MirrorPrefixKeep", "versioned-transport", "sel:CopyVersioned", "dff4d03fa4e56219c5a3e08862f24ad2a6bce7a7e0df806571fa52e8fb29fb19"},
+}
+
+// movesOfflinePrecopyWriteCensusInventory is scan-demand-revival's inventory
+// with the moves-offline-precopy overlay: a changed site replaces its entry,
+// a new one is added.
+func movesOfflinePrecopyWriteCensusInventory() []censusEntry {
+ entries := append([]censusEntry(nil), scanDemandWriteCensusInventory()...)
+ for _, replacement := range movesOfflinePrecopyWriteCensusOverlay {
+ found := false
+ for i, entry := range entries {
+ if entry.File == replacement.File && entry.Unit == replacement.Unit {
+ entries[i], found = replacement, true
+ break
+ }
+ }
+ if !found {
+ entries = append(entries, replacement)
+ }
+ }
+ return entries
+}
diff --git a/internal/provision/plan14_root_reads_test.go b/internal/provision/plan14_root_reads_test.go
index b56965763..7f587add8 100644
--- a/internal/provision/plan14_root_reads_test.go
+++ b/internal/provision/plan14_root_reads_test.go
@@ -1034,6 +1034,22 @@ func TestPlan14RootReadCensus(t *testing.T) {
t.Fatal(err)
}
seal = "a21dba12b0f3cd0811c624ec352d6ff5c46936ef0da1d4ce9d31cdd13cc0f262"
+ // Census overlay moves-offline-precopy (parallel copies in
+ // lease.MirrorPrefix and the carlos move offline stage door), loaded
+ // last, under census-ruling-moves-offline-precopy (the coordinator, as
+ // census owner, 2026-10-21); the ruling names each unit's executing
+ // identity (docs/superpowers/notes/census-proposal-moves-offline-precopy.md).
+ // offlineSourceRevert is re-frozen at its disposition, offlineBegin
+ // (OfflineStart's body) is new accepted legacy, and OfflineStart is
+ // retired by an H3 census-ruling removal. Two frozen declarations are
+ // added, so the seal moves; scan-demand-revival's is checked first.
+ if errs := checkRootReadLegacy(inv, seal); len(errs) != 0 {
+ t.Fatal(errs)
+ }
+ if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-moves-offline-precopy.tsv"), &inv, true); err != nil {
+ t.Fatal(err)
+ }
+ seal = "965fda6e58a16662833fcfe5435e9f45b525eb1abdd1f1f1715d611cb699bc81"
errs := append(compareRootReads(got, inv), checkRootReadLegacy(inv, seal)...)
errs = append(errs, checkRootReadDeclarations(got, inv)...)
if len(errs) > 0 {
diff --git a/internal/provision/plan9_b8_census_test.go b/internal/provision/plan9_b8_census_test.go
index 5b08dd5dd..e9a1ef5bc 100644
--- a/internal/provision/plan9_b8_census_test.go
+++ b/internal/provision/plan9_b8_census_test.go
@@ -366,7 +366,7 @@ func TestPlan9B8RecordWriteCensus(t *testing.T) {
if len(got) < 100 {
t.Fatalf("census found only %d units under %s; the walk is broken", len(got), root)
}
- inventory := scanDemandWriteCensusInventory()
+ inventory := movesOfflinePrecopyWriteCensusInventory()
problems := append(censusDiff(inventory, got), censusPolicy(inventory)...)
if len(problems) > 0 {
t.Fatalf("record-write census rejected %d site(s); update the explicit inventory only after reviewing the source:\n %s", len(problems), strings.Join(problems, "\n "))
diff --git a/internal/provision/testdata/plan14-root-reads-moves-offline-precopy.tsv b/internal/provision/testdata/plan14-root-reads-moves-offline-precopy.tsv
new file mode 100644
index 000000000..a58345c27
--- /dev/null
+++ b/internal/provision/testdata/plan14-root-reads-moves-offline-precopy.tsv
@@ -0,0 +1,18 @@
+# Census overlay moves-offline-precopy (parallel copies in lease.MirrorPrefix and the carlos move offline stage door, an offline-staging record state), loaded LAST, after scan-demand-revival. RULED as census-ruling-moves-offline-precopy (the coordinator, as census owner for this branch, 2026-10-21), accepted as proposed: every row is rev3=census-ruling-moves-offline-precopy. The ruling, with each unit's executing identity: docs/superpowers/notes/census-proposal-moves-offline-precopy.md.
+# Every unit here runs in the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos) or the console (user carlos-console) and makes no root decision, so each stays accepted legacy at the disposition census-ruling-moves-offline gave it or its sibling (J1 root-route for registry and route reads, J4 root-input for file reads).
+# movesWorker.offlineSourceRevert re-frozen with its new digest: it calls offlineUnstage, which reads only the object store; its read sites are unchanged and one offset moved. offlineBegin is new: OfflineStart's body, moved unchanged with the state as a parameter; its one site is rec.ActiveRun.Open(), a method, not a file read. OfflineStart is retired by an H3 census-ruling removal (the carlos-run getJSON form); a removal is not in the seal. Two frozen declarations are added, so the seal moves.
+# Round 1 (Astra P2s, 692d41cf): the stage door pins the stager on the record (MoveState.Stager, written through SetRecordMove like every other move field); offlineUnstage cleans whenever staged_at is set, on the pinned stager only; the console finish of a staged move waits for that stager's reverted report; MirrorPrefixKeep reads the receipts book before its workers start. No unit's read sites change; offlineSourceRevert's basis names offlineUnstage's reads as they now are; the envelopes below are pinned at 21cd69a4.
+# Envelopes re-pinned: . (move.go: the stage subcommand, its status and abort lines; no read), internal/console (the stage door, offlineBeginChecks, the status view; no read site but offlineClaimEvidence's, unchanged), internal/host (movesoffline_stage.go, new, reads only the object store; edgesfile.go publishes offline-stage-1; the box-side stager election is gone, only the pinned box stages or cleans; offlineRound's staging branch), internal/lease (mirror.go: the round's copies and deletes run side by side; forEachParallel), internal/moves (the stager role, staged phase, report fields, OfflineStager) and internal/provision (offline_move.go: OfflineStage, which needs a stager, OfflineRestage, OfflineStartStaged; provision.go: MoveState.StagedAt and MoveState.Stager).
+@legacy 20c1c784429a572d5bd14513c5905d569e06ab0c4007922bf3383816b61636a9 internal/host/movesoffline.go movesWorker.offlineSourceRevert legacy accepted-legacy accepted-legacy;rev3=census-ruling-moves-offline-precopy;basis=root-route (J1, census-ruling-moves-offline's disposition); the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos), on abort: census-ruling-moves-offline's body (reads the row's hold and releases only this move's hold, under reconcileMu), now calling offlineUnstage before it reports reverted. offlineUnstage acts only for a move carrying staged_at and only on the stager the stage door pinned on the record (move.stager); it reads only the object store (its own stager report, the fleet's edges files for the target's coordinates, this box's receipts object) and deletes the target's moves/<host>/ staging prefix (never in this box's own bucket) and this box's move-receipts/<host>.json, then writes its stager report reverted; it reads no local file and makes no root decision. Read sites unchanged; one offset moved (whole:Route@701 to @843) registry:HoldOp@562,whole:Route@549,whole:Route@843 b917ac2d1976eec14839ed65e3e562327d54af956e86f5218ed011731182aa1b
+internal/host/movesoffline.go movesWorker.offlineSourceRevert legacy accepted-legacy accepted-legacy;rev3=census-ruling-moves-offline-precopy;basis=root-route (J1, census-ruling-moves-offline's disposition); the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos), on abort: census-ruling-moves-offline's body (reads the row's hold and releases only this move's hold, under reconcileMu), now calling offlineUnstage before it reports reverted. offlineUnstage acts only for a move carrying staged_at and only on the stager the stage door pinned on the record (move.stager); it reads only the object store (its own stager report, the fleet's edges files for the target's coordinates, this box's receipts object) and deletes the target's moves/<host>/ staging prefix (never in this box's own bucket) and this box's move-receipts/<host>.json, then writes its stager report reverted; it reads no local file and makes no root decision. Read sites unchanged; one offset moved (whole:Route@701 to @843) registry:HoldOp@562,whole:Route@549,whole:Route@843 b917ac2d1976eec14839ed65e3e562327d54af956e86f5218ed011731182aa1b
+@legacy 5e6a7aa9d23e5559c9b93f3a5c038e019a9a5d3bbcfbcf9229725ce7682b936a internal/provision/offline_move.go offlineBegin legacy accepted-legacy accepted-legacy;rev3=census-ruling-moves-offline-precopy;basis=root-input (J4, as OfflineStart); the console (cmd/console serve under console.service, user carlos-console): new, OfflineStart's body moved unchanged with the state as a parameter (offline-stopping for OfflineStart, behaviour-identical; offline-staging plus staged_at for OfflineStage), the record transition the start and stage doors apply through SetRecordMove; its one site is rec.ActiveRun.Open(), a method on the record's run state (RunState.Open), not a file read file:Open@198 66e0216314538687d102a4cee1089a19896c7693260f748a046a5ff16e76dc1e
+internal/provision/offline_move.go offlineBegin legacy accepted-legacy accepted-legacy;rev3=census-ruling-moves-offline-precopy;basis=root-input (J4, as OfflineStart); the console (cmd/console serve under console.service, user carlos-console): new, OfflineStart's body moved unchanged with the state as a parameter (offline-stopping for OfflineStart, behaviour-identical; offline-staging plus staged_at for OfflineStage), the record transition the start and stage doors apply through SetRecordMove; its one site is rec.ActiveRun.Open(), a method on the record's run state (RunState.Open), not a file read file:Open@198 66e0216314538687d102a4cee1089a19896c7693260f748a046a5ff16e76dc1e
+# OfflineStart retired (H3, the carlos-run form). A removal is not in the seal.
+@remove internal/provision/offline_move.go OfflineStart
+@removed internal/provision/offline_move.go OfflineStart H3 census-ruling-moves-offline-precopy: still declared, and reads nothing now: its body and its one site (rec.ActiveRun.Open(), a method, not a file read) moved unchanged into offlineBegin, an accepted-legacy row in this overlay; OfflineStart calls offlineBegin with offline-stopping and is behaviour-identical. It runs in the console as carlos-console.
+@coverage . 44fc0f3ea061670389aae27fb0ef47397adecba16951a173a4b0bbbe728a8bdd
+@coverage internal/console 6ae90250ebff821c8e64d0e64827860ddf80213177a86dc9cba6db9ec68592d6
+@coverage internal/host e9234db89d1fc6b9a966c8a0c66b983c4e4bb18a7c8222ccbf91a46318c98f47
+@coverage internal/lease 980f38c4804ae1485c0c0df77bade8990b1d0248c0f44e41e0194c7731562a14
+@coverage internal/moves 33c7b507f13b4f4081c3bdb0a84e31d875e2cf1be61b480a7ca856193125e071
+@coverage internal/provision 8c86869568a559329a0d8dcbc1df5cac18b4bcabd5ca3b4f403949ebd56f4b64