carlos / platform Public

db-region-forward-copy: Luna-approved copy for forwarded db reads, docs

Squashed from db-region-forward-copy into main.
claude agent, operated by paul@keymail.dev 2a2d7cc9493a694c7dc2f2b1810af89ae8539647 parent 6e8564a
10 files changed, +103 −58
  • approved_db_forward_copy_test.go +9 −6
  • cmd/console/dbforward_test.go +2 −4
  • dbforward.go +13 −15
  • dbforward_e2e_test.go +2 −2
  • docs/site/cli.md +7 −4
  • docs/superpowers/notes/census-proposal-db-region-forward-copy.md +30 −0
  • docs/superpowers/plans/2026-10-22-db-region-forward-copy.md +21 −21
  • internal/console/apidbforward.go +4 −6
  • internal/provision/plan14_root_reads_test.go +12 −0
  • internal/provision/testdata/plan14-root-reads-db-region-forward-copy.tsv +3 −0

CI passed — run details

CI log
=== gate ===
.amadan/test-gate.sh
=== launchctx (0 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 (17 s)
=== gofmt (0 s)
gofmt -l .: clean
=== rest (167 s)
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	3.651s
ok  	github.com/carlosframework/platform/cmd/moveprobe	11.878s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.785s
ok  	github.com/carlosframework/platform/internal/activator	59.639s
ok  	github.com/carlosframework/platform/internal/agentsock	0.022s
ok  	github.com/carlosframework/platform/internal/alerting	0.007s
ok  	github.com/carlosframework/platform/internal/analytics	0.043s
ok  	github.com/carlosframework/platform/internal/appconfig	2.870s
ok  	github.com/carlosframework/platform/internal/apperr	0.186s
ok  	github.com/carlosframework/platform/internal/applog	0.319s
ok  	github.com/carlosframework/platform/internal/awsinv	0.005s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.007s
ok  	github.com/carlosframework/platform/internal/boxid	0.729s
?   	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.020s
ok  	github.com/carlosframework/platform/internal/caps	0.023s
ok  	github.com/carlosframework/platform/internal/certwarm	0.006s
ok  	github.com/carlosframework/platform/internal/cgroup	0.814s
ok  	github.com/carlosframework/platform/internal/changelog	0.523s
ok  	github.com/carlosframework/platform/internal/claimactivation	1.205s
ok  	github.com/carlosframework/platform/internal/clamd	0.062s
?   	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	8.291s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.016s
ok  	github.com/carlosframework/platform/internal/dbquery	0.010s
ok  	github.com/carlosframework/platform/internal/dbreads	2.150s
?   	github.com/carlosframework/platform/internal/dbreads/dbreadstest	[no test files]
ok  	github.com/carlosframework/platform/internal/dbverify	0.015s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.011s
ok  	github.com/carlosframework/platform/internal/docsite	0.017s
ok  	github.com/carlosframework/platform/internal/domains	2.897s
ok  	github.com/carlosframework/platform/internal/doorkey	0.015s
ok  	github.com/carlosframework/platform/internal/economics	0.004s
ok  	github.com/carlosframework/platform/internal/edge	45.518s
?   	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.133s
ok  	github.com/carlosframework/platform/internal/gate	1.657s
ok  	github.com/carlosframework/platform/internal/genselect	0.015s
ok  	github.com/carlosframework/platform/internal/geo	0.017s
ok  	github.com/carlosframework/platform/internal/geoip	0.029s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.159s
ok  	github.com/carlosframework/platform/internal/host	139.042s
ok  	github.com/carlosframework/platform/internal/hostactors	6.264s
ok  	github.com/carlosframework/platform/internal/hostactors/awsmembership	0.006s
ok  	github.com/carlosframework/platform/internal/hostbox	2.972s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.799s
ok  	github.com/carlosframework/platform/internal/hostenv	0.005s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.009s
ok  	github.com/carlosframework/platform/internal/hostexec	5.284s
ok  	github.com/carlosframework/platform/internal/hostfence	0.067s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.319s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.026s
ok  	github.com/carlosframework/platform/internal/hostguard	1.104s
ok  	github.com/carlosframework/platform/internal/hostops	1.957s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.005s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.004s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.646s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.177s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.006s
ok  	github.com/carlosframework/platform/internal/infraview	0.008s
ok  	github.com/carlosframework/platform/internal/instancectl	5.234s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.789s
ok  	github.com/carlosframework/platform/internal/instancefs	0.016s
ok  	github.com/carlosframework/platform/internal/instancekey	0.016s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.024s
ok  	github.com/carlosframework/platform/internal/layout	1.829s
ok  	github.com/carlosframework/platform/internal/lbhealth	0.011s
ok  	github.com/carlosframework/platform/internal/lease	10.321s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.558s
ok  	github.com/carlosframework/platform/internal/ledger	0.024s
ok  	github.com/carlosframework/platform/internal/mail	0.236s
ok  	github.com/carlosframework/platform/internal/maintenance	0.090s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.581s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.081s
ok  	github.com/carlosframework/platform/internal/metrics	0.005s
ok  	github.com/carlosframework/platform/internal/moves	22.492s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.958s
ok  	github.com/carlosframework/platform/internal/objstore	0.270s
ok  	github.com/carlosframework/platform/internal/ops	0.184s
ok  	github.com/carlosframework/platform/internal/park	0.272s
ok  	github.com/carlosframework/platform/internal/plans	0.009s
ok  	github.com/carlosframework/platform/internal/privatefile	0.004s
ok  	github.com/carlosframework/platform/internal/provision	23.383s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.015s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.590s
ok  	github.com/carlosframework/platform/internal/relay	7.240s
ok  	github.com/carlosframework/platform/internal/release	25.754s
ok  	github.com/carlosframework/platform/internal/rollout	3.794s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.008s
ok  	github.com/carlosframework/platform/internal/rootaws	0.006s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.062s
ok  	github.com/carlosframework/platform/internal/rootstate	0.036s
ok  	github.com/carlosframework/platform/internal/runs	0.574s
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.612s
ok  	github.com/carlosframework/platform/internal/seeds	0.012s
ok  	github.com/carlosframework/platform/internal/sim/engines	2.613s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	10.342s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.679s
ok  	github.com/carlosframework/platform/internal/status	0.081s
ok  	github.com/carlosframework/platform/internal/steering	0.007s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.283s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.004s
ok  	github.com/carlosframework/platform/internal/termfence	0.004s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.004s
ok  	github.com/carlosframework/platform/internal/unixdial	0.005s
ok  	github.com/carlosframework/platform/internal/updatewords	0.007s
ok  	github.com/carlosframework/platform/internal/usage	0.007s
ok  	github.com/carlosframework/platform/internal/version	0.002s
ok  	github.com/carlosframework/platform/internal/vet	1.693s
=== internal_console@0 (84 s)
ok  	github.com/carlosframework/platform/internal/console	83.010s
=== internal_console@1 (41 s)
ok  	github.com/carlosframework/platform/internal/console	39.605s
=== internal_console@2 (45 s)
ok  	github.com/carlosframework/platform/internal/console	43.156s
=== internal_console@3 (43 s)
ok  	github.com/carlosframework/platform/internal/console	41.363s
=== internal_console@4 (45 s)
ok  	github.com/carlosframework/platform/internal/console	43.635s
=== internal_console@5 (42 s)
ok  	github.com/carlosframework/platform/internal/console	40.437s
=== internal_console@6 (40 s)
ok  	github.com/carlosframework/platform/internal/console	38.180s
=== internal_console@7 (36 s)
ok  	github.com/carlosframework/platform/internal/console	34.308s
=== root@0 (111 s)
ok  	github.com/carlosframework/platform	109.686s
=== root@1 (89 s)
ok  	github.com/carlosframework/platform	87.256s
=== root@2 (80 s)
ok  	github.com/carlosframework/platform	78.739s
=== 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: 50462882 bytes (ceiling 52000000)
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
  minio: cached, digest verified
  mc: cached, digest verified
  minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
  buckets and scoped user ready
=== RUN   TestDeliveredContractMinio
=== RUN   TestDeliveredContractMinio/put
=== RUN   TestDeliveredContractMinio/get_round-trips_the_body
=== RUN   TestDeliveredContractMinio/list_shows_the_key
=== RUN   TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN   TestDeliveredContractMinio/delete_removes_it
=== RUN   TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
    --- PASS: TestDeliveredContractMinio/put (0.00s)
    --- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
    --- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
    --- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
    --- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
    --- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok  	github.com/carlosframework/platform/internal/objstore	0.022s
=== RUN   TestT4StorageProbeMinio
2026/10/23 08:21:25 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18E11AE2364786E9, 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.00s)
PASS
ok  	github.com/carlosframework/platform/internal/host	0.020s
=== RUN   TestS3StoreMinioContract
=== RUN   TestS3StoreMinioContract/conditional_get_and_etag_listing
=== RUN   TestS3StoreMinioContract/bump_through_every_mutation_path
--- PASS: TestS3StoreMinioContract (0.02s)
    --- PASS: TestS3StoreMinioContract/conditional_get_and_etag_listing (0.01s)
    --- PASS: TestS3StoreMinioContract/bump_through_every_mutation_path (0.01s)
PASS
ok  	github.com/carlosframework/platform/internal/release	0.028s
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/23 08:21:27 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/23 08:21:27 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/23 08:21:27 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/23 08:21:27 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/23 08:21:27 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/23 08:21:27 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/23 08:21:27 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/23 08:21:27 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=370
2026/10/23 08:21:27 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/23 08:21:27 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000003
2026/10/23 08:21:27 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000003 maxTXID=0000000000000003 size=454
2026/10/23 08:21:27 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000003 txid.db=0000000000000003
2026/10/23 08:21:27 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/23 08:21:27 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%3A43155: could not determine target mailbox
    agent.go:123: dbreads: dbr_t7rt6cz2skxxvcxyc6pi5k53ny: planned 3 files (1544 bytes) and restored to 3 in 9ms
    agent.go:123: dbreads: dbr_t7rt6cz2skxxvcxyc6pi5k53ny: output 5 bytes in 1 confirmed chunks in 1ms
2026/10/23 08:21:28 console: db read dbr_t7rt6cz2skxxvcxyc6pi5k53ny: relayed 1 chunks (102 bytes) in 1.003s, ended done
    agent.go:123: dbreads: dbr_jlmlowivecbbtselb3kdzrpdse: planned 1 files (720 bytes) and restored to 1 in 8ms
    agent.go:123: dbreads: dbr_jlmlowivecbbtselb3kdzrpdse: output 5 bytes in 1 confirmed chunks in 1ms
2026/10/23 08:21:29 console: db read dbr_jlmlowivecbbtselb3kdzrpdse: relayed 1 chunks (102 bytes) in 1.003s, ended done
=== RUN   TestDBForwardMinio/by_disconnect
=== NAME  TestDBForwardMinio
    agent.go:123: dbreads: dbr_u5mbafx5mpndenfimuyhkdwhyq: planned 3 files (1544 bytes) and restored to 3 in 9ms
2026/10/23 08:21:29 console: db read dbr_u5mbafx5mpndenfimuyhkdwhyq: relayed 0 chunks (0 bytes) in 14ms, ended client gone
    agent.go:123: dbreads: dbr_u5mbafx5mpndenfimuyhkdwhyq: output 5 bytes in 1 confirmed chunks in 43ms
=== RUN   TestDBForwardMinio/by_timeout
=== NAME  TestDBForwardMinio
    agent.go:123: dbreads: dbr_dc72w7g7f2e7vorpukgz7oeceu: planned 3 files (1544 bytes) and restored to 3 in 9ms
2026/10/23 08:21:49 console: db read dbr_dc72w7g7f2e7vorpukgz7oeceu: relayed 0 chunks (0 bytes) in 19.021s, ended no-result
    agent.go:123: dbreads: dbr_dc72w7g7f2e7vorpukgz7oeceu: output 5 bytes in 1 confirmed chunks in 19s
--- PASS: TestDBForwardMinio (21.37s)
    --- PASS: TestDBForwardMinio/by_disconnect (0.16s)
    --- PASS: TestDBForwardMinio/by_timeout (19.13s)
PASS
ok  	github.com/carlosframework/platform/internal/console	21.384s
minio contract: PASS
diff --git a/approved_db_forward_copy_test.go b/approved_db_forward_copy_test.go
index a13eedb37..fc172bf0e 100644
--- a/approved_db_forward_copy_test.go
+++ b/approved_db_forward_copy_test.go
@@ -37,7 +37,11 @@ var dbForwardCopyIDs = func() []string {
// dbForwardCopyBuilt is every id whose text the code or docs carry. The task
// that writes a row's text adds its id here in the same commit, once Paul has
// approved the row.
-var dbForwardCopyBuilt = []string{}
+var dbForwardCopyBuilt = []string{
+ "DBF-1", "DBF-2", "DBF-14", "DBF-9", "DBF-10", "DBF-21", "DBF-22", // the console
+ "DBF-3", "DBF-4", "DBF-5", "DBF-6", "DBF-7", "DBF-8", "DBF-11", "DBF-12", "DBF-13", "DBF-15", "DBF-16", "DBF-32", // the CLI
+ "DBF-30", "DBF-31", // docs/site/cli.md
+}
// dbForwardCopyWired is every id whose PENDING text the code already
// carries, behind the console's CARLOS_DB_FORWARD switch, which is off
@@ -48,11 +52,10 @@ var dbForwardCopyBuilt = []string{}
// from a console that forwards, so none reaches a person on a deployment
// that has not turned forwarding on. The copy review moves each id from here
// to dbForwardCopyBuilt, applying Paul's edits in the same commit. Docs rows
-// are public the moment the site syncs, so they are never wired.
-var dbForwardCopyWired = []string{
- "DBF-1", "DBF-2", "DBF-14", "DBF-9", "DBF-10", "DBF-21", "DBF-22", // the console
- "DBF-3", "DBF-4", "DBF-5", "DBF-6", "DBF-7", "DBF-8", "DBF-11", "DBF-12", "DBF-13", "DBF-15", "DBF-16", "DBF-32", // the CLI
-}
+// are public the moment the site syncs, so they are never wired. Luna
+// approved every row on 2026-10-23, so the list is empty and every row is
+// built.
+var dbForwardCopyWired = []string{}
func mustParseDBForwardRecord(t *testing.T) []copyRow {
t.Helper()
diff --git a/cmd/console/dbforward_test.go b/cmd/console/dbforward_test.go
index 15dfa6c10..f72ea1c25 100644
--- a/cmd/console/dbforward_test.go
+++ b/cmd/console/dbforward_test.go
@@ -2,10 +2,8 @@ package main
import "testing"
-// Forwarding is off unless CARLOS_DB_FORWARD is exactly 1: the switch the
-// pending DBF copy waits behind (approved_db_forward_copy_test.go,
-// dbForwardCopyWired) and the condition for answering byte-identically to
-// main (db region forward spec §9.2).
+// Forwarding is off unless CARLOS_DB_FORWARD is exactly 1: the condition for
+// answering byte-identically to main (db region forward spec §9.2).
func TestDBForwardIsOffUnlessSet(t *testing.T) {
t.Setenv("CARLOS_DB_FORWARD", "")
if dbForward() {
diff --git a/dbforward.go b/dbforward.go
index 6e2b479b3..491df7887 100644
--- a/dbforward.go
+++ b/dbforward.go
@@ -29,22 +29,20 @@ import (
)
// The forwarded read's CLI sentences (copy record DBF-3 to DBF-8, DBF-11 to
-// DBF-13, DBF-15, DBF-16, DBF-32). PENDING copy review: each is reached only
-// after a console with CARLOS_DB_FORWARD=1 answered the forward marker
-// (approved_db_forward_copy_test.go, dbForwardCopyWired).
+// DBF-13, DBF-15, DBF-16, DBF-32), approved by Luna on 2026-10-23.
const (
- dbfNotPicked = "no server in region %s picked up the read within 60 seconds. try again in a few minutes." // DBF-3
- dbfTooLarge = "%s's database is %s once restored. carlos db can query at most %s in region %s." // DBF-4
- dbfNoRoom = "the server in region %s does not have room to restore %s's database right now. try again later." // DBF-5
- dbfOutputCap = "the result was cut off at 64 MB. narrow the query with WHERE or LIMIT and run it again." // DBF-6
- dbfTimeout = "the query ran for 5 minutes and was stopped. narrow it and run it again." // DBF-7
- dbfNoPull = "%s keeps its data in region %s. carlos db pull cannot fetch a copy from there yet. carlos db query can read it." // DBF-8
- dbfReading = "reading a copy of %s's database in region %s." // DBF-11
- dbfUnfinished = "the read of %s's database in region %s did not finish, so nothing was printed. try again." // DBF-12
- dbfUnchecked = "the answer from region %s did not check out, so carlos db showed none of it. try again." // DBF-13
- dbfChanged = "%s changed while the read was waiting. try again." // DBF-15
- dbfMovingCLI = "%s is moving between regions. read its database once the move has finished." // DBF-16
- dbfNotUTF8 = "this query cannot be sent to region %s, because it holds bytes that are not UTF-8 text or the character U+FFFD. fix the query and run it again." // DBF-32
+ dbfNotPicked = "no server in region %s started the read within 60 seconds. try again in a few minutes." // DBF-3
+ dbfTooLarge = "the database for %s is %s once restored. carlos db can query at most %s in region %s." // DBF-4
+ dbfNoRoom = "the server in region %s does not have room to restore %s's database right now. try again later." // DBF-5
+ dbfOutputCap = "the result was cut off at 64 MB. narrow the query with WHERE or LIMIT and run it again." // DBF-6
+ dbfTimeout = "the query ran for 5 minutes and was stopped. narrow it and run it again." // DBF-7
+ dbfNoPull = "%s keeps its data in region %s. carlos db pull cannot fetch a copy from there yet. carlos db query can read it." // DBF-8
+ dbfReading = "reading a copy of %s's database in region %s." // DBF-11
+ dbfUnfinished = "the read of %s's database in region %s did not finish, so nothing was printed. try again." // DBF-12
+ dbfUnchecked = "the answer from region %s was invalid, so carlos db printed nothing. try again." // DBF-13
+ dbfChanged = "%s changed while the read was waiting. try again." // DBF-15
+ dbfMovingCLI = "%s is moving between regions. read its database once the move has finished." // DBF-16
+ dbfNotUTF8 = "this query cannot be sent to region %s, because it contains invalid UTF-8 or the replacement character U+FFFD. fix the query and run it again." // DBF-32
)
// errDBForward is the db door's forward marker, handed back by dbRestore so
diff --git a/dbforward_e2e_test.go b/dbforward_e2e_test.go
index b57b98f16..e94c1ac38 100644
--- a/dbforward_e2e_test.go
+++ b/dbforward_e2e_test.go
@@ -661,12 +661,12 @@ func TestDBForwardPrintsNothingForABadAnswer(t *testing.T) {
b.picked(time.Now().Add(time.Minute))
b.chunk(1, false, "n\n")
b.result(1, "n\n")
- }, "did not check out"},
+ }, "was invalid, so carlos db printed nothing"},
"a summary hash mismatch": {func(b *e2eScript) {
b.picked(time.Now().Add(time.Minute))
b.chunk(1, true, "n\n31\n")
b.result(1, "n\n30\n")
- }, "did not check out"},
+ }, "was invalid, so carlos db printed nothing"},
// The console waits the owner's deadline plus its 1-minute grace.
"no result": {func(b *e2eScript) {
b.picked(time.Now().Add(2 * time.Second))
diff --git a/docs/site/cli.md b/docs/site/cli.md
index a964ca5ab..f001dd39e 100644
--- a/docs/site/cli.md
+++ b/docs/site/cli.md
@@ -536,10 +536,9 @@ own error, `attempt to write a readonly database`. That is on purpose.
Changing a copy would change nothing on the instance, and a command that
seemed to succeed would say otherwise.
-If the app has more than one instance, name one with `--instance`. Only
-instances that sleep have a replica. An instance on your own fleet, in
-another region, or in a pool cannot be read this way, and the command says
-which.
+If the app has more than one instance, name one with `--instance`. Only sleeping instances have a database copy. An instance on your own fleet or in a pool cannot be read this way, and the command says which.
+
+When an instance keeps its data in a different region from the console's, `carlos db query` runs in that region. A server there restores the copy, runs your query and sends back only the rows. The rows are encrypted for your terminal on the way, so nothing in between can read them, and nothing is printed until all of them have arrived and been checked. In that region, the copy can be at most 2 GB once restored, the query runs for at most 5 minutes, and the rows can total at most 64 MB. `carlos db pull` cannot fetch a copy from another region yet.
### carlos run
@@ -2205,6 +2204,10 @@ image before starting `apply`, `recover`, `edge` or `console`. Once armed,
the local floor remains enforced independently of `CARLOS_HOST_CLAIM`.
The member command remains `carlos ops host resolve`.
+#### carlos system db-worker
+
+`carlos system db-worker` runs on platform boxes only. systemd starts it for each forwarded database query. You never run it yourself.
+
#### carlos system claims
`carlos system claims` prepares the dev claim mechanics trial. Run `help` for the commands and required inputs. `run` executes one revision-checked phase from a protected plan on the trusted signing host; `inspect` returns the verified roster and its signed envelope. Box installers run as root. Add `--dry-run` to preview an operation.
diff --git a/docs/superpowers/notes/census-proposal-db-region-forward-copy.md b/docs/superpowers/notes/census-proposal-db-region-forward-copy.md
new file mode 100644
index 000000000..6508a8c57
--- /dev/null
+++ b/docs/superpowers/notes/census-proposal-db-region-forward-copy.md
@@ -0,0 +1,30 @@
+# Census: db region forward copy (Luna's approved copy, 2026-10-23)
+
+Status: PROPOSED, for the census owner's ruling on #280.
+
+Branch `db-region-forward-copy`, cut from main at 6e8564ad. It applies Luna's
+copy verdicts for the forwarded db read (fourteen approved as drafted, seven
+rewritten: DBF-3, DBF-4, DBF-9, DBF-13, DBF-30, DBF-31, DBF-32), moves every
+DBF row to `dbForwardCopyBuilt`, and writes DBF-30 and DBF-31 into
+`docs/site/cli.md`.
+
+The code change is string constants only: five sentence constants in
+`dbforward.go` (the CLI) and one in `internal/console/apidbforward.go`, plus
+their comments. No read is added, removed or changed, and no census unit's
+digest moves.
+
+Root-read census: one overlay,
+`internal/provision/testdata/plan14-root-reads-db-region-forward-copy.tsv`,
+loaded last after `plans-3a-creation-caps`, holding only two envelope re-pins:
+
+| envelope | why |
+|---|---|
+| `.` | `dbforward.go`'s DBF-3, DBF-4, DBF-13 and DBF-32 constants |
+| `internal/console` | `apidbforward.go`'s DBF-9 constant |
+
+The legacy seal does not move
+(`dc7dbb4f562fe31556e7ed1e09fcfb380f551b45a444e94e72662f0b202c605b`, checked
+before the overlay loads and after it).
+
+Record-write census (9B8): unchanged. Claim routes: unchanged. go.mod:
+unchanged.
diff --git a/docs/superpowers/plans/2026-10-22-db-region-forward-copy.md b/docs/superpowers/plans/2026-10-22-db-region-forward-copy.md
index b248786fc..8c7a8e5d1 100644
--- a/docs/superpowers/plans/2026-10-22-db-region-forward-copy.md
+++ b/docs/superpowers/plans/2026-10-22-db-region-forward-copy.md
@@ -36,34 +36,34 @@ RFC 3339 time.
| id | file | text | status |
|---|---|---|---|
-| DBF-1 | internal/console/apidbforward.go | <host> keeps its data in region <region>. no server in that region can read databases yet, so carlos db cannot read it for now. | pending |
-| DBF-2 | internal/console/apidbforward.go | <host> keeps its data in region <region>. no server in that region is answering right now. try again in a few minutes. | pending |
-| DBF-9 | internal/console/apidbforward.go | too many database reads are open. wait a minute and try again. | pending |
-| DBF-10 | internal/console/apidbforward.go | <host> is moving between regions. read its database once the move has finished. | pending |
-| DBF-14 | internal/console/apidbforward.go | <host> keeps its data in region <region>. this version of carlos cannot read it from there. run carlos update, then try again. | pending |
-| DBF-21 | internal/console/apidbforward.go | forwarded a database query to region <region>. the query was <n> bytes, sha256 <sha> (cli) | pending |
-| DBF-22 | internal/console/apidbforward.go | forwarded a database query to region <region>, as of <time>. the query was <n> bytes, sha256 <sha> (cli) | pending |
+| DBF-1 | internal/console/apidbforward.go | <host> keeps its data in region <region>. no server in that region can read databases yet, so carlos db cannot read it for now. | approved |
+| DBF-2 | internal/console/apidbforward.go | <host> keeps its data in region <region>. no server in that region is answering right now. try again in a few minutes. | approved |
+| DBF-9 | internal/console/apidbforward.go | too many database reads are already running. wait a minute and try again. | approved |
+| DBF-10 | internal/console/apidbforward.go | <host> is moving between regions. read its database once the move has finished. | approved |
+| DBF-14 | internal/console/apidbforward.go | <host> keeps its data in region <region>. this version of carlos cannot read it from there. run carlos update, then try again. | approved |
+| DBF-21 | internal/console/apidbforward.go | forwarded a database query to region <region>. the query was <n> bytes, sha256 <sha> (cli) | approved |
+| DBF-22 | internal/console/apidbforward.go | forwarded a database query to region <region>, as of <time>. the query was <n> bytes, sha256 <sha> (cli) | approved |
## CLI (plan 2)
| id | file | text | status |
|---|---|---|---|
-| DBF-3 | dbforward.go | no server in region <region> picked up the read within 60 seconds. try again in a few minutes. | pending |
-| DBF-4 | dbforward.go | <host>'s database is <size> once restored. carlos db can query at most <cap> in region <region>. | pending |
-| DBF-5 | dbforward.go | the server in region <region> does not have room to restore <host>'s database right now. try again later. | pending |
-| DBF-6 | dbforward.go | the result was cut off at 64 MB. narrow the query with WHERE or LIMIT and run it again. | pending |
-| DBF-7 | dbforward.go | the query ran for 5 minutes and was stopped. narrow it and run it again. | pending |
-| DBF-8 | dbforward.go | <host> keeps its data in region <region>. carlos db pull cannot fetch a copy from there yet. carlos db query can read it. | pending |
-| DBF-11 | dbforward.go | reading a copy of <host>'s database in region <region>. | pending |
-| DBF-12 | dbforward.go | the read of <host>'s database in region <region> did not finish, so nothing was printed. try again. | pending |
-| DBF-13 | dbforward.go | the answer from region <region> did not check out, so carlos db showed none of it. try again. | pending |
-| DBF-15 | dbforward.go | <host> changed while the read was waiting. try again. | pending |
-| DBF-16 | dbforward.go | <host> is moving between regions. read its database once the move has finished. | pending |
-| DBF-32 | dbforward.go | this query cannot be sent to region <region>, because it holds bytes that are not UTF-8 text or the character U+FFFD. fix the query and run it again. | pending |
+| DBF-3 | dbforward.go | no server in region <region> started the read within 60 seconds. try again in a few minutes. | approved |
+| DBF-4 | dbforward.go | the database for <host> is <size> once restored. carlos db can query at most <cap> in region <region>. | approved |
+| DBF-5 | dbforward.go | the server in region <region> does not have room to restore <host>'s database right now. try again later. | approved |
+| DBF-6 | dbforward.go | the result was cut off at 64 MB. narrow the query with WHERE or LIMIT and run it again. | approved |
+| DBF-7 | dbforward.go | the query ran for 5 minutes and was stopped. narrow it and run it again. | approved |
+| DBF-8 | dbforward.go | <host> keeps its data in region <region>. carlos db pull cannot fetch a copy from there yet. carlos db query can read it. | approved |
+| DBF-11 | dbforward.go | reading a copy of <host>'s database in region <region>. | approved |
+| DBF-12 | dbforward.go | the read of <host>'s database in region <region> did not finish, so nothing was printed. try again. | approved |
+| DBF-13 | dbforward.go | the answer from region <region> was invalid, so carlos db printed nothing. try again. | approved |
+| DBF-15 | dbforward.go | <host> changed while the read was waiting. try again. | approved |
+| DBF-16 | dbforward.go | <host> is moving between regions. read its database once the move has finished. | approved |
+| DBF-32 | dbforward.go | this query cannot be sent to region <region>, because it contains invalid UTF-8 or the replacement character U+FFFD. fix the query and run it again. | approved |
## Docs
| id | file | text | status |
|---|---|---|---|
-| DBF-30 | docs/site/cli.md | If the app has more than one instance, name one with `--instance`. Only instances that sleep have a replica. An instance on your own fleet or in a pool cannot be read this way, and the command says which.\n\nWhen an instance keeps its data in another region than the console's, `carlos db query` runs in that region. A server there restores the copy, runs your query and sends back only the rows. The rows are encrypted for your terminal on the way, so nothing in between can read them, and nothing is printed until all of them have arrived and checked out. There, the copy can be at most 2 GB once restored, the query runs for at most 5 minutes, and the rows can come to at most 64 MB. `carlos db pull` cannot fetch a copy from another region yet. | pending |
-| DBF-31 | docs/site/cli.md | carlos system db-worker runs on platform boxes only. systemd starts it for each forwarded database query. you never run it yourself. | pending |
+| DBF-30 | docs/site/cli.md | If the app has more than one instance, name one with `--instance`. Only sleeping instances have a database copy. An instance on your own fleet or in a pool cannot be read this way, and the command says which.\n\nWhen an instance keeps its data in a different region from the console's, `carlos db query` runs in that region. A server there restores the copy, runs your query and sends back only the rows. The rows are encrypted for your terminal on the way, so nothing in between can read them, and nothing is printed until all of them have arrived and been checked. In that region, the copy can be at most 2 GB once restored, the query runs for at most 5 minutes, and the rows can total at most 64 MB. `carlos db pull` cannot fetch a copy from another region yet. | approved |
+| DBF-31 | docs/site/cli.md | `carlos system db-worker` runs on platform boxes only. systemd starts it for each forwarded database query. You never run it yourself. | approved |
diff --git a/internal/console/apidbforward.go b/internal/console/apidbforward.go
index 2f06f0246..6a2181415 100644
--- a/internal/console/apidbforward.go
+++ b/internal/console/apidbforward.go
@@ -106,9 +106,8 @@ func dbForwardEvidence(snap edgesSnapshot, err error, region string) string {
return ""
}
-// The console's forwarded-read sentences (copy record DBF-1, DBF-2, DBF-14).
-// PENDING copy review: wired behind CARLOS_DB_FORWARD, which is off unless a
-// deployment sets it (approved_db_forward_copy_test.go, dbForwardCopyWired).
+// The console's forwarded-read sentences (copy record DBF-1, DBF-2, DBF-14),
+// approved by Luna on 2026-10-23.
const (
dbfNoCapable = "%s keeps its data in region %s. no server in that region can read databases yet, so carlos db cannot read it for now."
dbfNoneLive = "%s keeps its data in region %s. no server in that region is answering right now. try again in a few minutes."
@@ -150,10 +149,9 @@ func (s *Server) dbResidency(w http.ResponseWriter, r *http.Request, rec provisi
}
// The forwarded read's other console sentences (copy record DBF-9, DBF-10,
-// DBF-21, DBF-22). PENDING copy review, wired behind CARLOS_DB_FORWARD as
-// the three above are.
+// DBF-21, DBF-22), approved by Luna on 2026-10-23.
const (
- dbfTooMany = "too many database reads are open. wait a minute and try again."
+ dbfTooMany = "too many database reads are already running. wait a minute and try again."
dbfMoving = "%s is moving between regions. read its database once the move has finished."
dbfAudit = "forwarded a database query to region %s. the query was %d bytes, sha256 %s (cli)"
dbfAuditAt = "forwarded a database query to region %s, as of %s. the query was %d bytes, sha256 %s (cli)"
diff --git a/internal/provision/plan14_root_reads_test.go b/internal/provision/plan14_root_reads_test.go
index ba87f3e80..26b190b37 100644
--- a/internal/provision/plan14_root_reads_test.go
+++ b/internal/provision/plan14_root_reads_test.go
@@ -1395,6 +1395,18 @@ func TestPlan14RootReadCensus(t *testing.T) {
t.Fatal(err)
}
seal = "dc7dbb4f562fe31556e7ed1e09fcfb380f551b45a444e94e72662f0b202c605b"
+ // Census overlay db-region-forward-copy (Luna's approved copy for the
+ // forwarded db read, 2026-10-23), loaded last, after
+ // plans-3a-creation-caps. Envelope re-pins only (. and internal/console:
+ // changed sentence constants); no read is added, removed or changed, so
+ // the seal does not move. PROPOSED-db-region-forward-copy
+ // (docs/superpowers/notes/census-proposal-db-region-forward-copy.md).
+ if errs := checkRootReadLegacy(inv, seal); len(errs) != 0 {
+ t.Fatal(errs)
+ }
+ if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-db-region-forward-copy.tsv"), &inv, true); err != nil {
+ t.Fatal(err)
+ }
errs := append(compareRootReads(got, inv), checkRootReadLegacy(inv, seal)...)
errs = append(errs, checkRootReadDeclarations(got, inv)...)
if len(errs) > 0 {
diff --git a/internal/provision/testdata/plan14-root-reads-db-region-forward-copy.tsv b/internal/provision/testdata/plan14-root-reads-db-region-forward-copy.tsv
new file mode 100644
index 000000000..5150558db
--- /dev/null
+++ b/internal/provision/testdata/plan14-root-reads-db-region-forward-copy.tsv
@@ -0,0 +1,3 @@
+# Census overlay db-region-forward-copy (the Luna-approved copy for forwarded db reads, 2026-10-23: seven DBF sentences rewritten in dbforward.go and internal/console/apidbforward.go, and the CLI page), loaded LAST in TestPlan14RootReadCensus, after plans-3a-creation-caps, the overlay main loads last at 6e8564ad. Envelope re-pins only: string constants changed, no read was added, removed or changed, and no unit row changes, so the legacy seal does not move. rev3=PROPOSED-db-region-forward-copy, asked of the census owner (docs/superpowers/notes/census-proposal-db-region-forward-copy.md).
+@coverage . 75498b97f7b25e2271172bb4cefc673c457c5a7f35ef1b5ad42bbd16a619ceef
+@coverage internal/console f018180ef9eb83a66b739d6eea2cb26a1c14a6631997a62a0810f71ed4575aa3