data-lifecycle-336
Squashed from data-lifecycle-336 into main.
10 files changed,
+213
−1
docs/superpowers/notes/census-proposal-data-lifecycle-336.md+25 −0docs/superpowers/plans/2026-10-21-offline-move-runbook.md+1 −1infra/modules/deployment-buckets/main.tf+29 −0infra/modules/deployment-buckets/tests/data_lifecycle.tftest.hcl+47 −0infra/modules/deployment-buckets/variables.tf+11 −0infra/modules/region-remote-host/main.tf+29 −0infra/modules/region-remote-host/tests/data_lifecycle.tftest.hcl+47 −0infra/modules/region-remote-host/variables.tf+11 −0internal/provision/testdata/plan14-root-reads-chain.tsv+1 −0internal/provision/testdata/plan14-root-reads-data-lifecycle-336.tsv+12 −0
CI passed — run details
CI log
=== gate ===
.amadan/test-gate.sh
=== launchctx (1 s)
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]
=== vet (20 s)
=== gofmt (0 s)
gofmt -l .: clean
=== rest (74 s)
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 3.931s
ok github.com/carlosframework/platform/cmd/moveprobe 11.977s
ok github.com/carlosframework/platform/cmd/zddprobe 0.794s
ok github.com/carlosframework/platform/internal/activator 60.019s
ok github.com/carlosframework/platform/internal/agentsock 0.018s
ok github.com/carlosframework/platform/internal/alerting 0.010s
ok github.com/carlosframework/platform/internal/analytics 0.048s
ok github.com/carlosframework/platform/internal/appconfig 2.817s
ok github.com/carlosframework/platform/internal/apperr 0.121s
ok github.com/carlosframework/platform/internal/applog 0.198s
ok github.com/carlosframework/platform/internal/assetcache 0.116s
ok github.com/carlosframework/platform/internal/awsinv 0.004s
ok github.com/carlosframework/platform/internal/boxchoice 0.007s
ok github.com/carlosframework/platform/internal/boxid 0.856s
? 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.011s
ok github.com/carlosframework/platform/internal/caps 0.014s
ok github.com/carlosframework/platform/internal/certwarm 0.002s
ok github.com/carlosframework/platform/internal/cgroup 0.844s
ok github.com/carlosframework/platform/internal/changelog 0.494s
ok github.com/carlosframework/platform/internal/claimactivation 1.237s
ok github.com/carlosframework/platform/internal/clamd 0.066s
? github.com/carlosframework/platform/internal/cliflag [no test files]
? 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 7.964s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.012s
ok github.com/carlosframework/platform/internal/dbquery 0.008s
ok github.com/carlosframework/platform/internal/dbreads 2.142s
? github.com/carlosframework/platform/internal/dbreads/dbreadstest [no test files]
ok github.com/carlosframework/platform/internal/dbverify 0.011s
ok github.com/carlosframework/platform/internal/dnsrec 0.006s
ok github.com/carlosframework/platform/internal/docsite 0.013s
ok github.com/carlosframework/platform/internal/domains 2.967s
ok github.com/carlosframework/platform/internal/doorkey 0.010s
ok github.com/carlosframework/platform/internal/economics 0.012s
ok github.com/carlosframework/platform/internal/edge 45.489s
? github.com/carlosframework/platform/internal/edgeproof [no test files]
ok github.com/carlosframework/platform/internal/edgesfile 0.016s
ok github.com/carlosframework/platform/internal/fleetapi 0.149s
ok github.com/carlosframework/platform/internal/gate 1.655s
ok github.com/carlosframework/platform/internal/genselect 0.019s
ok github.com/carlosframework/platform/internal/geo 0.016s
ok github.com/carlosframework/platform/internal/geoip 0.033s
ok github.com/carlosframework/platform/internal/historyreadback 0.237s
ok github.com/carlosframework/platform/internal/hostactors 5.545s
ok github.com/carlosframework/platform/internal/hostactors/awsmembership 0.017s
ok github.com/carlosframework/platform/internal/hostbox 2.867s
ok github.com/carlosframework/platform/internal/hostclaim 0.619s
ok github.com/carlosframework/platform/internal/hostenv 0.016s
ok github.com/carlosframework/platform/internal/hostevidence 0.016s
ok github.com/carlosframework/platform/internal/hostexec 5.173s
ok github.com/carlosframework/platform/internal/hostfence 0.037s
ok github.com/carlosframework/platform/internal/hostfiles 10.552s
ok github.com/carlosframework/platform/internal/hostfloor 0.017s
ok github.com/carlosframework/platform/internal/hostguard 1.085s
ok github.com/carlosframework/platform/internal/hostops 1.839s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.017s
ok github.com/carlosframework/platform/internal/hostquarantine 0.019s
ok github.com/carlosframework/platform/internal/hostrelocate 0.549s
ok github.com/carlosframework/platform/internal/hostresolve 0.188s
ok github.com/carlosframework/platform/internal/hostwrite 0.017s
ok github.com/carlosframework/platform/internal/infraview 0.020s
ok github.com/carlosframework/platform/internal/instancectl 5.248s
ok github.com/carlosframework/platform/internal/instanceexec 2.786s
ok github.com/carlosframework/platform/internal/instancefs 0.009s
ok github.com/carlosframework/platform/internal/instancekey 0.003s
ok github.com/carlosframework/platform/internal/instanceupdates 0.012s
ok github.com/carlosframework/platform/internal/layout 1.631s
ok github.com/carlosframework/platform/internal/lbhealth 0.003s
ok github.com/carlosframework/platform/internal/lease 10.081s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.431s
ok github.com/carlosframework/platform/internal/ledger 0.012s
ok github.com/carlosframework/platform/internal/mail 0.204s
ok github.com/carlosframework/platform/internal/maintenance 0.085s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.577s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.119s
ok github.com/carlosframework/platform/internal/metrics 0.006s
ok github.com/carlosframework/platform/internal/moves 23.389s
ok github.com/carlosframework/platform/internal/moves/archive 0.875s
ok github.com/carlosframework/platform/internal/objstore 0.247s
ok github.com/carlosframework/platform/internal/ops 0.217s
ok github.com/carlosframework/platform/internal/park 0.229s
ok github.com/carlosframework/platform/internal/plans 0.006s
ok github.com/carlosframework/platform/internal/privatefile 0.004s
ok github.com/carlosframework/platform/internal/provision 26.292s
ok github.com/carlosframework/platform/internal/publicrelease 0.008s
? github.com/carlosframework/platform/internal/regionname [no test files]
ok github.com/carlosframework/platform/internal/registry 5.629s
ok github.com/carlosframework/platform/internal/relay 7.242s
ok github.com/carlosframework/platform/internal/release 27.403s
ok github.com/carlosframework/platform/internal/reqrec 4.813s
ok github.com/carlosframework/platform/internal/rollout 3.798s
ok github.com/carlosframework/platform/internal/rolloutwords 0.004s
ok github.com/carlosframework/platform/internal/rootaws 0.008s
ok github.com/carlosframework/platform/internal/rootfsimage 0.150s
ok github.com/carlosframework/platform/internal/rootparticipation 0.059s
ok github.com/carlosframework/platform/internal/rootstate 0.026s
ok github.com/carlosframework/platform/internal/runs 0.601s
ok github.com/carlosframework/platform/internal/s3compat 0.005s
ok github.com/carlosframework/platform/internal/safefile 0.005s
ok github.com/carlosframework/platform/internal/schedule 2.633s
ok github.com/carlosframework/platform/internal/seeds 0.011s
ok github.com/carlosframework/platform/internal/sim/engines 2.084s
ok github.com/carlosframework/platform/internal/sim/hostlife 10.787s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 4.257s
ok github.com/carlosframework/platform/internal/status 0.067s
ok github.com/carlosframework/platform/internal/steering 0.006s
ok github.com/carlosframework/platform/internal/stopevidence 0.150s
ok github.com/carlosframework/platform/internal/svcrole 0.002s
ok github.com/carlosframework/platform/internal/telemetry 0.006s
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.003s
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.618s
=== internal_console@0 (70 s)
ok github.com/carlosframework/platform/internal/console 68.569s
=== internal_console@1 (47 s)
ok github.com/carlosframework/platform/internal/console 45.717s
=== internal_console@2 (43 s)
ok github.com/carlosframework/platform/internal/console 41.599s
=== internal_console@3 (44 s)
ok github.com/carlosframework/platform/internal/console 42.210s
=== internal_console@4 (46 s)
ok github.com/carlosframework/platform/internal/console 44.410s
=== internal_console@5 (46 s)
ok github.com/carlosframework/platform/internal/console 44.316s
=== internal_console@6 (48 s)
ok github.com/carlosframework/platform/internal/console 46.347s
=== internal_console@7 (53 s)
ok github.com/carlosframework/platform/internal/console 51.235s
=== internal_host@0 (35 s)
ok github.com/carlosframework/platform/internal/host 32.400s
=== internal_host@1 (32 s)
ok github.com/carlosframework/platform/internal/host 29.981s
=== internal_host@2 (30 s)
ok github.com/carlosframework/platform/internal/host 28.010s
=== internal_host@3 (63 s)
ok github.com/carlosframework/platform/internal/host 61.060s
=== root@0 (72 s)
ok github.com/carlosframework/platform 69.747s
=== root@1 (50 s)
ok github.com/carlosframework/platform 48.140s
=== root@2 (62 s)
ok github.com/carlosframework/platform 59.820s
=== root@3 (55 s)
ok github.com/carlosframework/platform 52.993s
=== root@4 (61 s)
ok github.com/carlosframework/platform 58.729s
=== 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: 51118242 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.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.025s
=== RUN TestT4StorageProbeMinio
2026/10/25 10:09:46 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18E1BDF504998D47, 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
=== RUN TestS3StoreMinioContract
=== RUN TestS3StoreMinioContract/conditional_get_and_etag_listing
=== RUN TestS3StoreMinioContract/bump_through_every_mutation_path
--- PASS: TestS3StoreMinioContract (0.03s)
--- PASS: TestS3StoreMinioContract/conditional_get_and_etag_listing (0.01s)
--- PASS: TestS3StoreMinioContract/bump_through_every_mutation_path (0.02s)
PASS
ok github.com/carlosframework/platform/internal/release 0.032s
Bucket created successfully `local/dbf-deploy`.
Bucket created successfully `local/dbf-data-ie`.
Bucket created successfully `local/dbf-data-uk`.
local/dbf-deploy versioning is enabled
Created policy `dbf-console` successfully.
Created policy `dbf-ukbox` successfully.
Added user `dbfconsole` successfully.
Added user `dbfukbox` successfully.
Attached Policies: [dbf-console]
To User: dbfconsole
Attached Policies: [dbf-ukbox]
To User: dbfukbox
=== RUN TestDBForwardMinio
2026/10/25 10:09:49 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/25 10:09:49 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/25 10:09:49 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/25 10:09:49 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/25 10:09:49 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=370
2026/10/25 10:09:49 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/25 10:09:49 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000003
2026/10/25 10:09:49 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000003 maxTXID=0000000000000003 size=454
2026/10/25 10:09:49 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000003 txid.db=0000000000000003
2026/10/25 10:09:49 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/25 10:09:49 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/25 10:09:49 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/25 10:09:49 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/25 10:09:49 console: could not notify member@example.com of a CLI token issue: keymail: lookup member@example.com: keymail: GET /api/federation/lookup?addr=member%40example.com&from=console%40127.0.0.1%3A40101: could not determine target mailbox
agent.go:135: dbreads: dbr_b2qhm4ilkctubya6a3eazolvze: planned 3 files (1544 bytes) and restored to 3 in 10ms
agent.go:135: dbreads: dbr_b2qhm4ilkctubya6a3eazolvze: output 5 bytes in 1 confirmed chunks in 1ms
2026/10/25 10:09:50 console: db read dbr_b2qhm4ilkctubya6a3eazolvze: relayed 1 chunks (102 bytes) in 1.004s, ended done
agent.go:135: dbreads: dbr_jra2v6l5qt2vzneeikz4rribsm: planned 1 files (720 bytes) and restored to 1 in 10ms
agent.go:135: dbreads: dbr_jra2v6l5qt2vzneeikz4rribsm: output 5 bytes in 1 confirmed chunks in 1ms
2026/10/25 10:09:51 console: db read dbr_jra2v6l5qt2vzneeikz4rribsm: relayed 1 chunks (102 bytes) in 1.003s, ended done
agent.go:135: dbreads: dbr_ik7uxe44nwthboocofsydeb2z4: planned 3 files (1544 bytes) and held through 3 in 9ms
agent.go:135: dbreads: dbr_ik7uxe44nwthboocofsydeb2z4: output 1544 bytes in 1 confirmed chunks in 1ms
2026/10/25 10:09:52 console: db read dbr_ik7uxe44nwthboocofsydeb2z4: relayed 1 chunks (1641 bytes) in 1.004s, ended done
=== RUN TestDBForwardMinio/by_disconnect
=== NAME TestDBForwardMinio
agent.go:135: dbreads: dbr_s2nakzgtfjx3yehrhoc5aiqqxy: planned 3 files (1544 bytes) and restored to 3 in 9ms
2026/10/25 10:09:52 console: db read dbr_s2nakzgtfjx3yehrhoc5aiqqxy: relayed 0 chunks (0 bytes) in 55ms, ended client gone
agent.go:135: dbreads: dbr_s2nakzgtfjx3yehrhoc5aiqqxy: output 5 bytes in 1 confirmed chunks in 103ms
=== RUN TestDBForwardMinio/by_timeout
=== NAME TestDBForwardMinio
agent.go:135: dbreads: dbr_em7ea2cffrexuxuyphoysvzvqu: planned 3 files (1544 bytes) and restored to 3 in 10ms
2026/10/25 10:10:12 console: db read dbr_em7ea2cffrexuxuyphoysvzvqu: relayed 0 chunks (0 bytes) in 19.477s, ended no-result
agent.go:135: dbreads: dbr_em7ea2cffrexuxuyphoysvzvqu: output 5 bytes in 1 confirmed chunks in 19.453s
--- PASS: TestDBForwardMinio (22.94s)
--- PASS: TestDBForwardMinio/by_disconnect (0.24s)
--- PASS: TestDBForwardMinio/by_timeout (19.59s)
PASS
ok github.com/carlosframework/platform/internal/console 22.962s
minio contract: PASS
diff --git a/docs/superpowers/notes/census-proposal-data-lifecycle-336.md b/docs/superpowers/notes/census-proposal-data-lifecycle-336.md| new file mode 100644 |
| index 000000000..3de04afbe |
| --- /dev/null |
| +++ b/docs/superpowers/notes/census-proposal-data-lifecycle-336.md |
| @@ -0,0 +1,25 @@ |
| +# Census approval: data-lifecycle-336 |
| + |
| +Ruling `census-ruling-data-lifecycle-336`. Every change below keeps the disposition it already has, so it needs an approval, not a decision. |
| + |
| +| unit | change | read sites | what changed | |
| +|---|---|---|---| |
| +| `infra/modules/deployment-buckets/main.tf` `external` | body | unchanged (1) | adds aws_s3_bucket_lifecycle_configuration "data" on the data bucket: noncurrent versions expire after var.data_noncurrent_days, expired delete markers go, multipart aborts after 7 days; no read, no IAM change | |
| +| `infra/modules/deployment-buckets/variables.tf` `external` | body | unchanged (1) | adds variable data_noncurrent_days (number, default 7, validated whole and >= 1); no read | |
| +| `infra/modules/region-remote-host/main.tf` `external` | body | unchanged (1) | adds the same aws_s3_bucket_lifecycle_configuration "data" on the regional data bucket; no read, no IAM change | |
| +| `infra/modules/region-remote-host/variables.tf` `external` | body | unchanged (1) | adds variable data_noncurrent_days (number, default 7, validated whole and >= 1); no read | |
| + |
| +Envelopes re-pinned: `infra/modules/deployment-buckets`, `infra/modules/region-remote-host`. |
| + |
| +Review: a model from the other family than the author reads the branch's diff against main on the amadan.net remote (`origin` or `amadan`, whichever points at amadan.net) and gives a verdict for every unit and every re-pinned envelope above. For a unit, compare the change with its recorded disposition and basis below; for an envelope, read every changed function in the package and what it reaches. Escalate if the change lets root open, run, query or follow anything it did not before from a path or input a tenant, an app or a box agent can create, write or name; if it weakens a check, refusal, validation, argument or environment the basis relies on; or if the diff cannot settle either. Otherwise approve. Record each verdict on #280 with the tip. An escalation goes to the census owner as a decision. |
| + |
| +Recorded dispositions, which each change must still satisfy: |
| + |
| +- `infra/modules/deployment-buckets/main.tf` `external`: legacy, task accepted-legacy. Basis: accepted-legacy;rev3=J4;basis=root-input;rev3=census-ruling-db-region-forward-2;basis=the db-reads/ lifecycle rules (db region forward spec §5.7, J4): two rules beside geoip-noncurrent on the deployment bucket's lifecycle configuration, prepared and not applied until Paul approves docs/superpowers/notes/db-reads-lifecycle-approval.md; no read, no IAM change |
| +- `infra/modules/deployment-buckets/variables.tf` `external`: legacy, task accepted-legacy. Basis: accepted-legacy;rev3=J4;basis=root-input |
| +- `infra/modules/region-remote-host/main.tf` `external`: legacy, task accepted-legacy. Basis: accepted-legacy;rev3=J4;basis=root-input |
| +- `infra/modules/region-remote-host/variables.tf` `external`: legacy, task accepted-legacy. Basis: accepted-legacy;rev3=J4;basis=root-input |
| + |
| +Seal: `84cb1e17c2f9cabddcc1e6ed5b39b00ffa70395639b4418c2cc5cf9b444b3eee` → `539ae875b4a089bc6512ae2e047fa9e353e8b760c18312b81ec3400e5e1f6d48`. |
| + |
| +Overlay: `internal/provision/testdata/plan14-root-reads-data-lifecycle-336.tsv`, appended to `internal/provision/testdata/plan14-root-reads-chain.tsv`. |
diff --git a/docs/superpowers/plans/2026-10-21-offline-move-runbook.md b/docs/superpowers/plans/2026-10-21-offline-move-runbook.md| index c31df766c..8a8c7705d 100644 |
| --- a/docs/superpowers/plans/2026-10-21-offline-move-runbook.md |
| +++ b/docs/superpowers/plans/2026-10-21-offline-move-runbook.md |
| @@ -419,7 +419,7 @@ L "cleanup" "bash $W/cleanup.sh /data/.offline-moves/$H/<move-id>/source/instanc |
| 3. removes the parked local replica `.../source/instance/.activator-<host>.db-litestream/ltx` on the source box, by exact path (a few hundred KB); |
| 4. deletes every object under `instances/<host>/` **by exact key, keeping `lock.json`** (the lifted tombstone stays as the record), and logs each key with the version id it removed and its size. |
| -The data buckets are **versioned with no lifecycle rule**. A delete only adds a delete marker; the bytes stay as noncurrent versions, are still billed, and can be brought back from the logged version id. Removing the noncurrent versions is a separate decision. The parked database, env and `config.env` under `source/instance/` stay too. |
| +The data buckets are **versioned, and noncurrent versions expire 7 days after they are replaced** (platform #336). A delete only adds a delete marker; the bytes stay as noncurrent versions for 7 days, are billed until then, and can be brought back from the logged version id within that window. The parked database, env and `config.env` under `source/instance/` stay too. |
| Never run this for an instance whose move was aborted: its source replica is the live one. |
diff --git a/infra/modules/deployment-buckets/main.tf b/infra/modules/deployment-buckets/main.tf| index 91dd68c26..625635b54 100644 |
| --- a/infra/modules/deployment-buckets/main.tf |
| +++ b/infra/modules/deployment-buckets/main.tf |
| @@ -92,6 +92,35 @@ resource "aws_s3_bucket_versioning" "data" { |
| versioning_configuration { status = "Enabled" } |
| } |
| +# Old versions in the data bucket expire (platform #336). The bucket is |
| +# versioned and litestream rewrites and compacts its files constantly, so |
| +# without this every replaced or deleted object lived on as a noncurrent |
| +# version forever: on 2026-10-24 one flagship data bucket held 14.5 GB of |
| +# current objects and was billed for 1,657 GB. A noncurrent version expires |
| +# data_noncurrent_days after it is replaced (7 by default, the restore |
| +# window), delete markers with nothing behind them go, and abandoned |
| +# multipart uploads go after 7 days. Current objects never expire. |
| +resource "aws_s3_bucket_lifecycle_configuration" "data" { |
| + bucket = aws_s3_bucket.data.id |
| + |
| + rule { |
| + id = "expire-noncurrent" |
| + status = "Enabled" |
| + filter {} |
| + noncurrent_version_expiration { |
| + noncurrent_days = var.data_noncurrent_days |
| + } |
| + expiration { |
| + expired_object_delete_marker = true |
| + } |
| + abort_incomplete_multipart_upload { |
| + days_after_initiation = 7 |
| + } |
| + } |
| + |
| + depends_on = [aws_s3_bucket_versioning.data] |
| +} |
| + |
| resource "aws_s3_bucket_public_access_block" "deployment" { |
| bucket = aws_s3_bucket.deployment.id |
| block_public_acls = true |
diff --git a/infra/modules/deployment-buckets/tests/data_lifecycle.tftest.hcl b/infra/modules/deployment-buckets/tests/data_lifecycle.tftest.hcl| new file mode 100644 |
| index 000000000..287a9be87 |
| --- /dev/null |
| +++ b/infra/modules/deployment-buckets/tests/data_lifecycle.tftest.hcl |
| @@ -0,0 +1,47 @@ |
| +# Platform #336: the data bucket's noncurrent versions expire after |
| +# data_noncurrent_days (7 by default), expired delete markers go, and |
| +# current objects never expire. |
| + |
| +mock_provider "aws" {} |
| + |
| +variables { |
| + deployment_bucket_name = "dep" |
| + data_bucket_name = "data" |
| +} |
| + |
| +run "data_noncurrent_versions_expire_after_seven_days" { |
| + command = plan |
| + |
| + assert { |
| + condition = length([ |
| + for r in aws_s3_bucket_lifecycle_configuration.data.rule : r |
| + if r.id == "expire-noncurrent" && r.status == "Enabled" && |
| + one(r.noncurrent_version_expiration).noncurrent_days == 7 && |
| + one(r.expiration).expired_object_delete_marker == true |
| + ]) == 1 |
| + error_message = "the data bucket must expire noncurrent versions after 7 days and clear expired delete markers" |
| + } |
| +} |
| + |
| +run "data_noncurrent_days_is_configurable" { |
| + command = plan |
| + |
| + variables { |
| + data_noncurrent_days = 30 |
| + } |
| + |
| + assert { |
| + condition = one(one(aws_s3_bucket_lifecycle_configuration.data.rule).noncurrent_version_expiration).noncurrent_days == 30 |
| + error_message = "data_noncurrent_days must set the noncurrent expiry" |
| + } |
| +} |
| + |
| +run "data_noncurrent_days_refuses_zero" { |
| + command = plan |
| + |
| + variables { |
| + data_noncurrent_days = 0 |
| + } |
| + |
| + expect_failures = [var.data_noncurrent_days] |
| +} |
diff --git a/infra/modules/deployment-buckets/variables.tf b/infra/modules/deployment-buckets/variables.tf| index a3a542254..e1af55cd3 100644 |
| --- a/infra/modules/deployment-buckets/variables.tf |
| +++ b/infra/modules/deployment-buckets/variables.tf |
| @@ -27,3 +27,14 @@ variable "box_fence_writer_role_arns" { |
| error_message = "Fence writers must be exact IAM role ARNs without wildcards." |
| } |
| } |
| + |
| +variable "data_noncurrent_days" { |
| + description = "Days a replaced or deleted object in the data bucket is kept as a noncurrent version before it expires. Defaults to the 7-day restore window. Longer retention for a plan belongs in litestream's per-instance retention, not here." |
| + type = number |
| + default = 7 |
| + |
| + validation { |
| + condition = var.data_noncurrent_days >= 1 && floor(var.data_noncurrent_days) == var.data_noncurrent_days |
| + error_message = "data_noncurrent_days must be a whole number of days, at least 1." |
| + } |
| +} |
diff --git a/infra/modules/region-remote-host/main.tf b/infra/modules/region-remote-host/main.tf| index a5e4a203d..322fbee8d 100644 |
| --- a/infra/modules/region-remote-host/main.tf |
| +++ b/infra/modules/region-remote-host/main.tf |
| @@ -34,6 +34,35 @@ resource "aws_s3_bucket_versioning" "data" { |
| versioning_configuration { status = "Enabled" } |
| } |
| +# Old versions in the data bucket expire (platform #336). The bucket is |
| +# versioned and litestream rewrites and compacts its files constantly, so |
| +# without this every replaced or deleted object lived on as a noncurrent |
| +# version forever: on 2026-10-24 one flagship data bucket held 14.5 GB of |
| +# current objects and was billed for 1,657 GB. A noncurrent version expires |
| +# data_noncurrent_days after it is replaced (7 by default, the restore |
| +# window), delete markers with nothing behind them go, and abandoned |
| +# multipart uploads go after 7 days. Current objects never expire. |
| +resource "aws_s3_bucket_lifecycle_configuration" "data" { |
| + bucket = aws_s3_bucket.data.id |
| + |
| + rule { |
| + id = "expire-noncurrent" |
| + status = "Enabled" |
| + filter {} |
| + noncurrent_version_expiration { |
| + noncurrent_days = var.data_noncurrent_days |
| + } |
| + expiration { |
| + expired_object_delete_marker = true |
| + } |
| + abort_incomplete_multipart_upload { |
| + days_after_initiation = 7 |
| + } |
| + } |
| + |
| + depends_on = [aws_s3_bucket_versioning.data] |
| +} |
| + |
| resource "aws_s3_bucket_public_access_block" "data" { |
| bucket = aws_s3_bucket.data.id |
| block_public_acls = true |
diff --git a/infra/modules/region-remote-host/tests/data_lifecycle.tftest.hcl b/infra/modules/region-remote-host/tests/data_lifecycle.tftest.hcl| new file mode 100644 |
| index 000000000..fcc0bed6f |
| --- /dev/null |
| +++ b/infra/modules/region-remote-host/tests/data_lifecycle.tftest.hcl |
| @@ -0,0 +1,47 @@ |
| +# Platform #336: the data bucket's noncurrent versions expire after |
| +# data_noncurrent_days (7 by default), expired delete markers go, and |
| +# current objects never expire. |
| + |
| +mock_provider "aws" {} |
| + |
| +variables { |
| + region_code = "uk" |
| + data_bucket_name = "data" |
| +} |
| + |
| +run "data_noncurrent_versions_expire_after_seven_days" { |
| + command = plan |
| + |
| + assert { |
| + condition = length([ |
| + for r in aws_s3_bucket_lifecycle_configuration.data.rule : r |
| + if r.id == "expire-noncurrent" && r.status == "Enabled" && |
| + one(r.noncurrent_version_expiration).noncurrent_days == 7 && |
| + one(r.expiration).expired_object_delete_marker == true |
| + ]) == 1 |
| + error_message = "the data bucket must expire noncurrent versions after 7 days and clear expired delete markers" |
| + } |
| +} |
| + |
| +run "data_noncurrent_days_is_configurable" { |
| + command = plan |
| + |
| + variables { |
| + data_noncurrent_days = 30 |
| + } |
| + |
| + assert { |
| + condition = one(one(aws_s3_bucket_lifecycle_configuration.data.rule).noncurrent_version_expiration).noncurrent_days == 30 |
| + error_message = "data_noncurrent_days must set the noncurrent expiry" |
| + } |
| +} |
| + |
| +run "data_noncurrent_days_refuses_zero" { |
| + command = plan |
| + |
| + variables { |
| + data_noncurrent_days = 0 |
| + } |
| + |
| + expect_failures = [var.data_noncurrent_days] |
| +} |
diff --git a/infra/modules/region-remote-host/variables.tf b/infra/modules/region-remote-host/variables.tf| index 9fe68ecd6..0dfa71070 100644 |
| --- a/infra/modules/region-remote-host/variables.tf |
| +++ b/infra/modules/region-remote-host/variables.tf |
| @@ -19,3 +19,14 @@ variable "sibling_staging_arns" { |
| type = list(string) |
| default = [] |
| } |
| + |
| +variable "data_noncurrent_days" { |
| + description = "Days a replaced or deleted object in the data bucket is kept as a noncurrent version before it expires. Defaults to the 7-day restore window. Longer retention for a plan belongs in litestream's per-instance retention, not here." |
| + type = number |
| + default = 7 |
| + |
| + validation { |
| + condition = var.data_noncurrent_days >= 1 && floor(var.data_noncurrent_days) == var.data_noncurrent_days |
| + error_message = "data_noncurrent_days must be a whole number of days, at least 1." |
| + } |
| +} |
diff --git a/internal/provision/testdata/plan14-root-reads-chain.tsv b/internal/provision/testdata/plan14-root-reads-chain.tsv| index a520c01fd..c439211d0 100644 |
| --- a/internal/provision/testdata/plan14-root-reads-chain.tsv |
| +++ b/internal/provision/testdata/plan14-root-reads-chain.tsv |
| @@ -18,3 +18,4 @@ plan14-root-reads-rootfs-apps-1-new.tsv bd1e86c9dc0a0d33ea9b9b66483c58f79d71cfec |
| plan14-root-reads-rootfs-apps-1.tsv 9cf49b684449c9b4c8b2356afa1ec0a7bb6403011de005f12e4877101bb74ddc |
| plan14-root-reads-edge-request-records-decisions.tsv c6dfb1753f33c96aee894496c2e7e8e9d87cea4f0ff61cb15dbbd8e78ed5021b |
| plan14-root-reads-edge-request-records.tsv 84cb1e17c2f9cabddcc1e6ed5b39b00ffa70395639b4418c2cc5cf9b444b3eee |
| +plan14-root-reads-data-lifecycle-336.tsv 539ae875b4a089bc6512ae2e047fa9e353e8b760c18312b81ec3400e5e1f6d48 |
diff --git a/internal/provision/testdata/plan14-root-reads-data-lifecycle-336.tsv b/internal/provision/testdata/plan14-root-reads-data-lifecycle-336.tsv| new file mode 100644 |
| index 000000000..b0cfab2a7 |
| --- /dev/null |
| +++ b/internal/provision/testdata/plan14-root-reads-data-lifecycle-336.tsv |
| @@ -0,0 +1,12 @@ |
| +# Census overlay data-lifecycle-336, written by census-propose (#369). census-ruling-data-lifecycle-336, approved on #280 before the merge; the approval card is docs/superpowers/notes/census-proposal-data-lifecycle-336.md. |
| +# 4 body, 0 sites, 2 envelope: each changed unit re-frozen at its existing disposition with its claim appended. |
| +@legacy ddfe0f10ea8352c2ac2a2332d376052927882c84f3c8dd9d0739e0247bb8a995 infra/modules/deployment-buckets/main.tf external legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;rev3=census-ruling-db-region-forward-2;basis=the db-reads/ lifecycle rules (db region forward spec §5.7, J4): two rules beside geoip-noncurrent on the deployment bucket's lifecycle configuration, prepared and not applied until Paul approves docs/superpowers/notes/db-reads-lifecycle-approval.md; no read, no IAM change;rev3=census-ruling-data-lifecycle-336;basis=adds aws_s3_bucket_lifecycle_configuration "data" on the data bucket: noncurrent versions expire after var.data_noncurrent_days, expired delete markers go, multipart aborts after 7 days; no read, no IAM change file:external@0 3774b0e24145e89430c5acb0ad4508e6949f4b34ec6cbdb97700e5c5c35ba7d9 |
| +@legacy c0491444351fffe26584b361757b54814460c5c5af36f62a957b4b64597ca3e1 infra/modules/deployment-buckets/variables.tf external legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;rev3=census-ruling-data-lifecycle-336;basis=adds variable data_noncurrent_days (number, default 7, validated whole and >= 1); no read file:external@0 234c8f2a6977a296bfdea78a3e2753914af7f806381b36aa2f4cd2630a15d60e |
| +@legacy bff551ee559255589fffecd7b6ab2ee529eba4e13df6ff6eb9bb1e359248b7c3 infra/modules/region-remote-host/main.tf external legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;rev3=census-ruling-data-lifecycle-336;basis=adds the same aws_s3_bucket_lifecycle_configuration "data" on the regional data bucket; no read, no IAM change file:external@0 ee699245585c40adacdc3cfcd6a7e1bf264825ac9ad55aab702332b278d86a1c |
| +@legacy fba1279b4a22e1291957e331c37dca458c8f80342014e5a4b8ce3bcf5780c0a4 infra/modules/region-remote-host/variables.tf external legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;rev3=census-ruling-data-lifecycle-336;basis=adds variable data_noncurrent_days (number, default 7, validated whole and >= 1); no read file:external@0 467eae2e74081b304e441203590730945212e43555ab3af45fecb70d22a0e744 |
| +infra/modules/deployment-buckets/main.tf external legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;rev3=census-ruling-db-region-forward-2;basis=the db-reads/ lifecycle rules (db region forward spec §5.7, J4): two rules beside geoip-noncurrent on the deployment bucket's lifecycle configuration, prepared and not applied until Paul approves docs/superpowers/notes/db-reads-lifecycle-approval.md; no read, no IAM change;rev3=census-ruling-data-lifecycle-336;basis=adds aws_s3_bucket_lifecycle_configuration "data" on the data bucket: noncurrent versions expire after var.data_noncurrent_days, expired delete markers go, multipart aborts after 7 days; no read, no IAM change file:external@0 3774b0e24145e89430c5acb0ad4508e6949f4b34ec6cbdb97700e5c5c35ba7d9 |
| +infra/modules/deployment-buckets/variables.tf external legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;rev3=census-ruling-data-lifecycle-336;basis=adds variable data_noncurrent_days (number, default 7, validated whole and >= 1); no read file:external@0 234c8f2a6977a296bfdea78a3e2753914af7f806381b36aa2f4cd2630a15d60e |
| +infra/modules/region-remote-host/main.tf external legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;rev3=census-ruling-data-lifecycle-336;basis=adds the same aws_s3_bucket_lifecycle_configuration "data" on the regional data bucket; no read, no IAM change file:external@0 ee699245585c40adacdc3cfcd6a7e1bf264825ac9ad55aab702332b278d86a1c |
| +infra/modules/region-remote-host/variables.tf external legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;rev3=census-ruling-data-lifecycle-336;basis=adds variable data_noncurrent_days (number, default 7, validated whole and >= 1); no read file:external@0 467eae2e74081b304e441203590730945212e43555ab3af45fecb70d22a0e744 |
| +@coverage infra/modules/deployment-buckets 90574aa9cab03043c37aabbaf70e0fc852bbce5a428f38085a752f52f4e6747b |
| +@coverage infra/modules/region-remote-host 58a1425a489a6d1b1dbe3dbec3c28b8c99e799ab134dd03d0db9fd60badbf957 |