carlos / platform Public

spec: edge asset cache rev 7 (corrections against the code)

Summary

Corrections found while planning: per-app records confirmed time from reconcile itself (record view optional), installed plus serving build for the paths union, manifests from the bucket, the full list of ReconcileAliases silent failures, carlos instances set-assets, citation fixes.
claude agent, operated by paul@keymail.dev b55ff714555f42f38fe9e30cae00b72a3b113dd3 parent 377a239
1 file changed, +74 −31
  • docs/superpowers/specs/2026-10-24-edge-asset-cache-design.md +74 −31

CI passed — run details

CI log
=== gate ===
.amadan/test-gate.sh
=== launchctx (0 s)
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]
=== vet (18 s)
=== gofmt (0 s)
gofmt -l .: clean
=== rest (174 s)
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	3.816s
ok  	github.com/carlosframework/platform/cmd/moveprobe	11.864s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.799s
ok  	github.com/carlosframework/platform/internal/activator	57.717s
ok  	github.com/carlosframework/platform/internal/agentsock	0.027s
ok  	github.com/carlosframework/platform/internal/alerting	0.004s
ok  	github.com/carlosframework/platform/internal/analytics	0.023s
ok  	github.com/carlosframework/platform/internal/appconfig	3.124s
ok  	github.com/carlosframework/platform/internal/apperr	0.094s
ok  	github.com/carlosframework/platform/internal/applog	0.221s
ok  	github.com/carlosframework/platform/internal/awsinv	0.006s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.008s
ok  	github.com/carlosframework/platform/internal/boxid	1.398s
?   	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.018s
ok  	github.com/carlosframework/platform/internal/caps	0.024s
ok  	github.com/carlosframework/platform/internal/certwarm	0.004s
ok  	github.com/carlosframework/platform/internal/cgroup	0.831s
ok  	github.com/carlosframework/platform/internal/changelog	0.547s
ok  	github.com/carlosframework/platform/internal/claimactivation	1.263s
ok  	github.com/carlosframework/platform/internal/clamd	0.059s
?   	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.176s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.017s
ok  	github.com/carlosframework/platform/internal/dbquery	0.008s
ok  	github.com/carlosframework/platform/internal/dbreads	2.098s
?   	github.com/carlosframework/platform/internal/dbreads/dbreadstest	[no test files]
ok  	github.com/carlosframework/platform/internal/dbverify	0.007s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.007s
ok  	github.com/carlosframework/platform/internal/docsite	0.006s
ok  	github.com/carlosframework/platform/internal/domains	3.135s
ok  	github.com/carlosframework/platform/internal/doorkey	0.016s
ok  	github.com/carlosframework/platform/internal/economics	0.004s
ok  	github.com/carlosframework/platform/internal/edge	45.792s
?   	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.134s
ok  	github.com/carlosframework/platform/internal/gate	1.656s
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.157s
ok  	github.com/carlosframework/platform/internal/host	144.697s
ok  	github.com/carlosframework/platform/internal/hostactors	6.053s
ok  	github.com/carlosframework/platform/internal/hostactors/awsmembership	0.016s
ok  	github.com/carlosframework/platform/internal/hostbox	3.001s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.845s
ok  	github.com/carlosframework/platform/internal/hostenv	0.016s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.017s
ok  	github.com/carlosframework/platform/internal/hostexec	5.387s
ok  	github.com/carlosframework/platform/internal/hostfence	0.054s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.355s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.024s
ok  	github.com/carlosframework/platform/internal/hostguard	1.066s
ok  	github.com/carlosframework/platform/internal/hostops	2.020s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.016s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.005s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.624s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.198s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.016s
ok  	github.com/carlosframework/platform/internal/infraview	0.018s
ok  	github.com/carlosframework/platform/internal/instancectl	5.239s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.769s
ok  	github.com/carlosframework/platform/internal/instancefs	0.005s
ok  	github.com/carlosframework/platform/internal/instancekey	0.017s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.015s
ok  	github.com/carlosframework/platform/internal/layout	1.773s
ok  	github.com/carlosframework/platform/internal/lbhealth	0.017s
ok  	github.com/carlosframework/platform/internal/lease	10.188s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.599s
ok  	github.com/carlosframework/platform/internal/ledger	0.034s
ok  	github.com/carlosframework/platform/internal/mail	0.261s
ok  	github.com/carlosframework/platform/internal/maintenance	0.119s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.599s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.081s
ok  	github.com/carlosframework/platform/internal/metrics	0.011s
ok  	github.com/carlosframework/platform/internal/moves	22.756s
ok  	github.com/carlosframework/platform/internal/moves/archive	1.044s
ok  	github.com/carlosframework/platform/internal/objstore	0.298s
ok  	github.com/carlosframework/platform/internal/ops	0.234s
ok  	github.com/carlosframework/platform/internal/park	0.298s
ok  	github.com/carlosframework/platform/internal/plans	0.011s
ok  	github.com/carlosframework/platform/internal/privatefile	0.008s
ok  	github.com/carlosframework/platform/internal/provision	23.927s
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.667s
ok  	github.com/carlosframework/platform/internal/relay	7.240s
ok  	github.com/carlosframework/platform/internal/release	25.919s
ok  	github.com/carlosframework/platform/internal/rollout	3.855s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.006s
ok  	github.com/carlosframework/platform/internal/rootaws	0.009s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.066s
ok  	github.com/carlosframework/platform/internal/rootstate	0.028s
ok  	github.com/carlosframework/platform/internal/runs	0.566s
ok  	github.com/carlosframework/platform/internal/s3compat	0.009s
ok  	github.com/carlosframework/platform/internal/safefile	0.012s
ok  	github.com/carlosframework/platform/internal/schedule	2.677s
ok  	github.com/carlosframework/platform/internal/seeds	0.011s
ok  	github.com/carlosframework/platform/internal/sim/engines	2.603s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	10.893s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.907s
ok  	github.com/carlosframework/platform/internal/status	0.083s
ok  	github.com/carlosframework/platform/internal/steering	0.012s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.345s
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.007s
ok  	github.com/carlosframework/platform/internal/unixdial	0.003s
ok  	github.com/carlosframework/platform/internal/updatewords	0.005s
ok  	github.com/carlosframework/platform/internal/usage	0.009s
ok  	github.com/carlosframework/platform/internal/version	0.002s
ok  	github.com/carlosframework/platform/internal/vet	1.707s
=== internal_console@0 (81 s)
ok  	github.com/carlosframework/platform/internal/console	79.472s
=== internal_console@1 (43 s)
ok  	github.com/carlosframework/platform/internal/console	41.571s
=== internal_console@2 (41 s)
ok  	github.com/carlosframework/platform/internal/console	39.669s
=== internal_console@3 (42 s)
ok  	github.com/carlosframework/platform/internal/console	40.200s
=== internal_console@4 (43 s)
ok  	github.com/carlosframework/platform/internal/console	41.164s
=== internal_console@5 (41 s)
ok  	github.com/carlosframework/platform/internal/console	39.890s
=== internal_console@6 (38 s)
ok  	github.com/carlosframework/platform/internal/console	36.302s
=== internal_console@7 (47 s)
ok  	github.com/carlosframework/platform/internal/console	45.230s
=== root@0 (110 s)
ok  	github.com/carlosframework/platform	108.291s
=== root@1 (83 s)
ok  	github.com/carlosframework/platform	81.878s
=== root@2 (93 s)
ok  	github.com/carlosframework/platform	91.330s
=== 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: 50528418 bytes (ceiling 52000000)
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
  minio: cached, digest verified
  mc: cached, digest verified
  minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
  buckets and scoped user ready
=== RUN   TestDeliveredContractMinio
=== RUN   TestDeliveredContractMinio/put
=== RUN   TestDeliveredContractMinio/get_round-trips_the_body
=== RUN   TestDeliveredContractMinio/list_shows_the_key
=== RUN   TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN   TestDeliveredContractMinio/delete_removes_it
=== RUN   TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
    --- PASS: TestDeliveredContractMinio/put (0.00s)
    --- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
    --- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
    --- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
    --- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
    --- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok  	github.com/carlosframework/platform/internal/objstore	0.024s
=== RUN   TestT4StorageProbeMinio
2026/10/24 18:32:39 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18E18AD186FF3253, 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.030s
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/24 18:32:41 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 18:32:41 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/24 18:32:41 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 18:32:41 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/24 18:32:41 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=370
2026/10/24 18:32:41 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/24 18:32:41 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000003
2026/10/24 18:32:41 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000003 maxTXID=0000000000000003 size=454
2026/10/24 18:32:41 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000003 txid.db=0000000000000003
2026/10/24 18:32:41 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 18:32:41 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/24 18:32:41 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 18:32:41 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 18:32:41 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%3A33055: could not determine target mailbox
    agent.go:135: dbreads: dbr_iruc3qos3j4hjpbfvxjhb42kze: planned 3 files (1544 bytes) and restored to 3 in 10ms
    agent.go:135: dbreads: dbr_iruc3qos3j4hjpbfvxjhb42kze: output 5 bytes in 1 confirmed chunks in 1ms
2026/10/24 18:32:42 console: db read dbr_iruc3qos3j4hjpbfvxjhb42kze: relayed 1 chunks (102 bytes) in 1.004s, ended done
    agent.go:135: dbreads: dbr_gsbn5vixx3k3mzprsulsfknks4: planned 1 files (720 bytes) and restored to 1 in 8ms
    agent.go:135: dbreads: dbr_gsbn5vixx3k3mzprsulsfknks4: output 5 bytes in 1 confirmed chunks in 1ms
2026/10/24 18:32:43 console: db read dbr_gsbn5vixx3k3mzprsulsfknks4: relayed 1 chunks (102 bytes) in 1.003s, ended done
    agent.go:135: dbreads: dbr_5mmlttc54dhz2wjtxdhofjksfi: planned 3 files (1544 bytes) and held through 3 in 9ms
    agent.go:135: dbreads: dbr_5mmlttc54dhz2wjtxdhofjksfi: output 1544 bytes in 1 confirmed chunks in 1ms
2026/10/24 18:32:44 console: db read dbr_5mmlttc54dhz2wjtxdhofjksfi: relayed 1 chunks (1641 bytes) in 1.004s, ended done
=== RUN   TestDBForwardMinio/by_disconnect
=== NAME  TestDBForwardMinio
    agent.go:135: dbreads: dbr_raryplh27ew6kwba765omv7u4e: planned 3 files (1544 bytes) and restored to 3 in 9ms
2026/10/24 18:32:44 console: db read dbr_raryplh27ew6kwba765omv7u4e: relayed 0 chunks (0 bytes) in 13ms, ended client gone
    agent.go:135: dbreads: dbr_raryplh27ew6kwba765omv7u4e: output 5 bytes in 1 confirmed chunks in 12ms
=== RUN   TestDBForwardMinio/by_timeout
=== NAME  TestDBForwardMinio
    agent.go:135: dbreads: dbr_x4zbwdeu2dkzk3xwaor2h3fgkm: planned 3 files (1544 bytes) and restored to 3 in 9ms
2026/10/24 18:33:04 console: db read dbr_x4zbwdeu2dkzk3xwaor2h3fgkm: relayed 0 chunks (0 bytes) in 19.218s, ended no-result
    agent.go:135: dbreads: dbr_x4zbwdeu2dkzk3xwaor2h3fgkm: output 5 bytes in 1 confirmed chunks in 19.188s
--- PASS: TestDBForwardMinio (22.55s)
    --- PASS: TestDBForwardMinio/by_disconnect (0.13s)
    --- PASS: TestDBForwardMinio/by_timeout (19.33s)
PASS
ok  	github.com/carlosframework/platform/internal/console	22.569s
minio contract: PASS
diff --git a/docs/superpowers/specs/2026-10-24-edge-asset-cache-design.md b/docs/superpowers/specs/2026-10-24-edge-asset-cache-design.md
index ce6d8618f..2952a8983 100644
--- a/docs/superpowers/specs/2026-10-24-edge-asset-cache-design.md
+++ b/docs/superpowers/specs/2026-10-24-edge-asset-cache-design.md
@@ -1,6 +1,6 @@
# Edge asset cache: versioned build assets served from every edge
-Branch `edge-asset-cache`. Status: rev 6, 2026-10-24, builder check folded in; rulings R1 to R3 (§13). Approved by Paul 2026-10-24; Astra accepted rev 6 in round 4.
+Branch `edge-asset-cache`. Status: rev 7, 2026-10-24, builder check folded in; rulings R1 to R3 (§13). Approved by Paul 2026-10-24; Astra accepted rev 6 in round 4; rev 7 corrections under review (round 5).
Related: media roadmap (`2026-09-07-media-platform-roadmap-design.md`),
which this does not amend; #368 (the shared cache stores 206 responses).
@@ -164,9 +164,11 @@ prefix. (My call.)
The instance record gains `Assets *bool` (`omitempty`; absent means
"follow the release", `false` means "catch nothing for this instance").
Set through the existing PATCH door (`apiinstances.go`, the PATCH body
-beside `gate` and `steering`) and `carlos instances update <host>
---assets=off|on`. It reaches the route row through `RouteFor` and
-`convergeOwned` like `Steering` and `Gate`. Use: an instance whose traffic
+beside `gate` and `steering`) and a new verb, `carlos instances
+set-assets <host> on|off`, beside `set-upstreams` and `set-channel`
+(`carlos instances update` is already "install the newest build now",
+`instances_updates.go:201`). It reaches the route row through `RouteFor`
+and `convergeOwned` like `Steering`. Use: an instance whose traffic
must always reach the app, or a quick off switch while debugging.
(R1.)
@@ -186,7 +188,7 @@ The home box publishes no prefixes for a hostname when any of these hold:
hostname can serve different releases, gates and opt-outs, and one
hostname-wide declaration cannot describe them. The hostname must also
have a single owner by the rule that decides `edgeHost.Account`/`App`
- (`edgesfile.go:73-80`) **(astra B2)**;
+ (`edgesfile.go:77-85`, fold at `:93-116`) **(astra B2)**;
- the route is pooled (`Route.Pool != ""`): pooled routes are a tenancy
boundary only their pool serves, the same reason steering refuses them
(`apiinstances.go:2112-2116`). A pooled route still uses the local disk
@@ -214,16 +216,29 @@ that catches gains:
generation-managed route `Route.Version` names only what is installed,
not what answers (`internal/edge/edge.go:842-845`; the served build comes
from the generation, `internal/edge/genproxy.go:298-302`), so the home
- publishes the **union** of the installed build's declared paths and those
- of every generation currently live on the route **(astra B5)**. The
+ publishes the **union** of the installed build's declared paths and the
+ current serving generation's (`ServingGen(host).Build`,
+ `internal/activator/genservice.go:68`) **(astra B5)**. The activator
+ keeps one current generation per host (`actEntry.gen`,
+ `internal/activator/activator.go:469`); a generation being replaced only
+ finishes requests already sent to it, so no third build can answer a new
+ request. The
installed build is in the union so that a sleeping instance, which has no
live generation (`ServingGen` is false while asleep,
`internal/edge/gensource.go:24-28`), keeps catching: the installed build
is the one a wake would start, and its assets are what a page served
before the sleep asks for **(astra R1-5)**. During an update both builds'
assets are caught, and the old build's prefix drops out when its
- generation retires. Reading those manifests happens on the home box,
- which already holds them; no other box reads them.
+ generation retires. The home reads each build's manifest from the
+ bucket with `release.GetManifest` (`internal/release/release.go:710`)
+ once per version and keeps it in memory, since a manifest is immutable.
+ It does not use the staged copy under `/opt/carlos/versions`
+ (`release.ReadStagedManifest`, `versions.go:261`), which an older binary
+ may have re-marshalled without the `assets` key. A manifest that cannot
+ be read publishes no paths for that build until it can. No other box
+ reads manifests. The edges-file writer gets the serving build through a
+ narrow function the agent hands it, since it has no activator handle
+ today.
- `gen` is the instance's asset generation (§6), copied from the instance
record through the route row.
- Aliases (custom domains, and the exact names instance catch-alls
@@ -254,16 +269,26 @@ are failing still writes its file from its local registry rows
obsolete generation as fresh **(astra R1-3)**.
So `at` is the host's **authority time**: the last moment the home box
-knew the records behind that host were current and applied. The record
-view (`internal/provision/recordview.go`) gains a per-scope confirmed
-time: the time of the last change-index read (or backstop walk) that
-succeeded **and** left the app's scope `apps/<acct>/<app>` applied rather
-than pending, **and** after which the registry rows for that scope were
-converged. A refresh that returns nil while a scope stays pending
-(`recordview.go:164-175`: a failed scope read is logged and left pending,
-and the snapshot is published incomplete) does not advance that scope's
-time. Publishing never advances it. The publisher copies the scope's
-confirmed time into each host's `at`.
+knew the records behind that host were current and applied. The box keeps
+a per-app **records confirmed time**: the start time of the last
+reconcile pass (`provision.Reconcile`, run by
+`reconcileSerializedWith`, `internal/host/agent.go:1604`) that read the
+app's records completely and applied them to the registry without error.
+Reconcile today returns one error for the whole pass, so it must also
+report which app scopes it read completely and applied. Where those reads
+come from depends on how the box is configured:
+
+- with the change-index record view armed (`CARLOS_CHANGE_INDEX=read`,
+ `internal/host/changeindex.go:57-78`), a scope counts as read
+ completely only when it is applied and not pending in the snapshot the
+ pass used. A refresh that returns nil while a scope stays pending
+ (`internal/provision/recordview.go:164-175`) does not count;
+- without it (`armedRecordView` nil, the bucket read directly), a scope
+ counts only when its listing and every record body under it were read
+ without error.
+
+The pass's **start** time is the one recorded, because a record written
+during the pass may not have been seen. Publishing never advances it.
**The invariant.** A host's `at` may only be a time at which *every*
input the catch decision for that host reads was known complete and
@@ -273,19 +298,28 @@ path rows), so the spec names the inputs instead of the cases
**(astra R2-1, R3-1, R3-2)**. The catch decision (§3.3, §4.1) reads:
1. **The app's records** (the instance record, its route row, gate,
- import gate, opt-out, generation): bounded by the record view's
- per-scope confirmed time above.
+ import gate, opt-out, generation): bounded by the app's records
+ confirmed time above.
2. **Domain claims**, which decide whether a hostname has path rows,
which aliases exist, and which aliases instance catch-alls derive
(`internal/domains/aliases.go`, `ReconcileAliases`): bounded by the
box's **domain confirmed time**, the completion time of the last
- `ReconcileAliases` pass that was *complete*. Today the pass returns an
- error only when the claim listing fails; a failed label count for a
- catch-all (`aliases.go:161`), a failed alias upsert (`:310`) or a
- failed path delete (`:590`) is logged and the pass still returns nil.
- So `ReconcileAliases` must also report completeness, and any of those
- failures makes the pass incomplete. An incomplete pass does not
- advance the domain confirmed time.
+ `ReconcileAliases` pass that was *complete* (again its start time).
+ Today the pass returns an error only when the claim listing fails.
+ Everything else is logged and the pass returns nil: the label count for
+ a catch-all (`aliases.go:162-171`), instance alias upsert (`:204`),
+ alias upsert (`:310`), catch-all upsert (`:531`), alias delete
+ (`:541`), wildcard upsert and delete (`:561`, `:568`), path upsert
+ (`:584`) and path delete (`:590`). Two inputs fail silently too: claim
+ bodies the claim cache could not read are dropped
+ (`internal/domains/claimcache.go:137-145`), and a failed
+ `edgesfile.DeploymentRoutes` read comes back nil and is treated as
+ "allow". So `ReconcileAliases` must also report completeness, and
+ **any** of those makes the pass incomplete. An incomplete pass does not
+ advance the domain confirmed time. Existing callers keep today's error
+ behaviour; `TestInstanceCatchallKeepsNamesWhenTheListingFails`
+ (`instancecatchall_test.go:507`) still sees nil and now also sees
+ "incomplete".
3. **Release manifests**: immutable once read, so they add no bound.
Every published host, primary or alias, gets `at` = the older of (1) for
@@ -538,8 +572,8 @@ sha256, identical on every edge.
the asset path writes the chosen representation itself.
- Added on every caught response, hit or not: `Carlos-Cache: hit`, `miss`
or `bypass` (the last for anything forwarded uncached). A hit carries
- no `Carlos-Version`: an entry can outlive many deploys, so a version on
- it would be a false "verified" for the deploy watch (`edge.go:630-660`
+ no `X-Carlos-Version`: an entry can outlive many deploys, so a version on
+ it would be a false "verified" for the deploy watch (`edge.go:813-847`
explains the header's freshness contract).
- `Age` on hits: the lifetime minus the time left to the entry's expiry.
@@ -655,7 +689,7 @@ any touched package; a test runs the full fill/hit/purge path against
AU) to watch disk, then the rest one at a time.
- **CLI.** `carlos ship --assets`, `carlos deploy --assets` and
`--no-assets`, the `assets` project default, `carlos assets purge`,
- `carlos assets status`, `carlos instances update --assets` get
+ `carlos assets status`, `carlos instances set-assets` get
`docs/site/cli.md`
headings (docsite gate). Every user-facing string goes through copy
review before it is written.
@@ -811,3 +845,12 @@ On flagship, the same from a small box with the disk figures recorded.
waiter deadlines; R3-4 HTML is never stored; R3-5 arm the home first.
- round 4, 2026-10-24: Astra **accept** at 287a59c4, no new findings
(`CODEX-FINDINGS-ASSET-CACHE-R4.md`).
+- rev 7, 2026-10-24: corrections found while writing the plan, against
+ the code: the records confirmed time comes from reconcile's own
+ per-app completeness, with or without the record view (which is armed
+ only under `CARLOS_CHANGE_INDEX=read`); the paths union is installed
+ build plus `ServingGen`'s build (the activator keeps one current
+ generation); manifests are read from the bucket, not the staged copy;
+ every `ReconcileAliases` log-and-continue point and silent input is
+ listed; the opt-out verb is `carlos instances set-assets`; two citation
+ fixes.