census-ruling-moves-offline-precopy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 files changed,
+222
−1
docs/superpowers/notes/census-proposal-moves-offline-precopy.md+143 −0internal/console/testdata/claim-routes.txt+1 −0internal/provision/moves_offline_precopy_census_test.go+43 −0internal/provision/plan14_root_reads_test.go+16 −0internal/provision/plan9_b8_census_test.go+1 −1internal/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 |