edge asset cache plans: Astra plan review round 4 folded in
Summary
Round 4 fixes: the record-write census joins the landing task, unconfirmed retirements make an app incomplete, the upstream must match the policy before a fill or acknowledgement, a buffered cold initiator reports miss, and the e2e refusal and generation matrices are complete.
Agent notes
Findings committed unedited as CODEX-FINDINGS-ASSET-CACHE-PLANS-R4.md.
3 files changed,
+33
−12
docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-1-declare-publish.md+6 −5docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md+10 −7docs/superpowers/specs/CODEX-FINDINGS-ASSET-CACHE-PLANS-R4.md+17 −0
CI passed — run details
CI log
=== gate ===
.amadan/test-gate.sh
=== launchctx (1 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 (5 s)
=== gofmt (0 s)
gofmt -l .: clean
=== rest (152 s)
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 3.923s
ok github.com/carlosframework/platform/cmd/moveprobe 11.550s
ok github.com/carlosframework/platform/cmd/zddprobe 0.704s
ok github.com/carlosframework/platform/internal/activator 58.431s
ok github.com/carlosframework/platform/internal/agentsock 0.022s
ok github.com/carlosframework/platform/internal/alerting 0.003s
ok github.com/carlosframework/platform/internal/analytics 0.020s
ok github.com/carlosframework/platform/internal/appconfig 2.783s
ok github.com/carlosframework/platform/internal/apperr 0.080s
ok github.com/carlosframework/platform/internal/applog 0.134s
ok github.com/carlosframework/platform/internal/awsinv 0.005s
ok github.com/carlosframework/platform/internal/boxchoice 0.006s
ok github.com/carlosframework/platform/internal/boxid 0.754s
? 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.010s
ok github.com/carlosframework/platform/internal/caps 0.021s
ok github.com/carlosframework/platform/internal/certwarm 0.004s
ok github.com/carlosframework/platform/internal/cgroup 0.707s
ok github.com/carlosframework/platform/internal/changelog 0.360s
ok github.com/carlosframework/platform/internal/claimactivation 0.879s
ok github.com/carlosframework/platform/internal/clamd 0.057s
? 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.618s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.010s
ok github.com/carlosframework/platform/internal/dbquery 0.005s
ok github.com/carlosframework/platform/internal/dbreads 2.068s
? github.com/carlosframework/platform/internal/dbreads/dbreadstest [no test files]
ok github.com/carlosframework/platform/internal/dbverify 0.005s
ok github.com/carlosframework/platform/internal/dnsrec 0.005s
ok github.com/carlosframework/platform/internal/docsite 0.008s
ok github.com/carlosframework/platform/internal/domains 2.823s
ok github.com/carlosframework/platform/internal/doorkey 0.010s
ok github.com/carlosframework/platform/internal/economics 0.004s
ok github.com/carlosframework/platform/internal/edge 45.537s
? github.com/carlosframework/platform/internal/edgeproof [no test files]
ok github.com/carlosframework/platform/internal/edgesfile 0.008s
ok github.com/carlosframework/platform/internal/fleetapi 0.132s
ok github.com/carlosframework/platform/internal/gate 1.801s
ok github.com/carlosframework/platform/internal/genselect 0.016s
ok github.com/carlosframework/platform/internal/geo 0.016s
ok github.com/carlosframework/platform/internal/geoip 0.032s
ok github.com/carlosframework/platform/internal/historyreadback 0.157s
ok github.com/carlosframework/platform/internal/host 141.963s
ok github.com/carlosframework/platform/internal/hostactors 6.240s
ok github.com/carlosframework/platform/internal/hostactors/awsmembership 0.007s
ok github.com/carlosframework/platform/internal/hostbox 3.339s
ok github.com/carlosframework/platform/internal/hostclaim 0.821s
ok github.com/carlosframework/platform/internal/hostenv 0.006s
ok github.com/carlosframework/platform/internal/hostevidence 0.012s
ok github.com/carlosframework/platform/internal/hostexec 5.509s
ok github.com/carlosframework/platform/internal/hostfence 0.050s
ok github.com/carlosframework/platform/internal/hostfiles 10.293s
ok github.com/carlosframework/platform/internal/hostfloor 0.019s
ok github.com/carlosframework/platform/internal/hostguard 1.047s
ok github.com/carlosframework/platform/internal/hostops 2.217s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.006s
ok github.com/carlosframework/platform/internal/hostquarantine 0.005s
ok github.com/carlosframework/platform/internal/hostrelocate 0.717s
ok github.com/carlosframework/platform/internal/hostresolve 0.274s
ok github.com/carlosframework/platform/internal/hostwrite 0.008s
ok github.com/carlosframework/platform/internal/infraview 0.004s
ok github.com/carlosframework/platform/internal/instancectl 5.247s
ok github.com/carlosframework/platform/internal/instanceexec 2.811s
ok github.com/carlosframework/platform/internal/instancefs 0.009s
ok github.com/carlosframework/platform/internal/instancekey 0.006s
ok github.com/carlosframework/platform/internal/instanceupdates 0.010s
ok github.com/carlosframework/platform/internal/layout 2.004s
ok github.com/carlosframework/platform/internal/lbhealth 0.002s
ok github.com/carlosframework/platform/internal/lease 10.108s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.694s
ok github.com/carlosframework/platform/internal/ledger 0.013s
ok github.com/carlosframework/platform/internal/mail 0.295s
ok github.com/carlosframework/platform/internal/maintenance 0.094s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.577s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.104s
ok github.com/carlosframework/platform/internal/metrics 0.006s
ok github.com/carlosframework/platform/internal/moves 21.294s
ok github.com/carlosframework/platform/internal/moves/archive 1.060s
ok github.com/carlosframework/platform/internal/objstore 0.301s
ok github.com/carlosframework/platform/internal/ops 0.237s
ok github.com/carlosframework/platform/internal/park 0.285s
ok github.com/carlosframework/platform/internal/plans 0.007s
ok github.com/carlosframework/platform/internal/privatefile 0.003s
ok github.com/carlosframework/platform/internal/provision 14.900s
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.693s
ok github.com/carlosframework/platform/internal/relay 7.239s
ok github.com/carlosframework/platform/internal/release 26.077s
ok github.com/carlosframework/platform/internal/rollout 3.877s
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.064s
ok github.com/carlosframework/platform/internal/rootstate 0.027s
ok github.com/carlosframework/platform/internal/runs 0.618s
ok github.com/carlosframework/platform/internal/s3compat 0.005s
ok github.com/carlosframework/platform/internal/safefile 0.009s
ok github.com/carlosframework/platform/internal/schedule 2.669s
ok github.com/carlosframework/platform/internal/seeds 0.014s
ok github.com/carlosframework/platform/internal/sim/engines 2.644s
ok github.com/carlosframework/platform/internal/sim/hostlife 10.462s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 4.373s
ok github.com/carlosframework/platform/internal/status 0.072s
ok github.com/carlosframework/platform/internal/steering 0.006s
ok github.com/carlosframework/platform/internal/stopevidence 0.198s
ok github.com/carlosframework/platform/internal/svcrole 0.002s
ok github.com/carlosframework/platform/internal/telemetry 0.007s
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.005s
ok github.com/carlosframework/platform/internal/usage 0.006s
ok github.com/carlosframework/platform/internal/version 0.002s
ok github.com/carlosframework/platform/internal/vet 1.634s
=== internal_console@0 (86 s)
ok github.com/carlosframework/platform/internal/console 84.195s
=== internal_console@1 (44 s)
ok github.com/carlosframework/platform/internal/console 41.552s
=== internal_console@2 (41 s)
ok github.com/carlosframework/platform/internal/console 38.753s
=== internal_console@3 (41 s)
ok github.com/carlosframework/platform/internal/console 38.873s
=== internal_console@4 (43 s)
ok github.com/carlosframework/platform/internal/console 41.056s
=== internal_console@5 (41 s)
ok github.com/carlosframework/platform/internal/console 38.544s
=== internal_console@6 (37 s)
ok github.com/carlosframework/platform/internal/console 34.849s
=== internal_console@7 (47 s)
ok github.com/carlosframework/platform/internal/console 44.489s
=== root@0 (109 s)
ok github.com/carlosframework/platform 107.618s
=== root@1 (83 s)
ok github.com/carlosframework/platform 81.938s
=== root@2 (91 s)
ok github.com/carlosframework/platform 89.488s
=== 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.023s
=== RUN TestT4StorageProbeMinio
2026/10/24 19:17:21 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18E18D420D5ED134, 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.021s
=== 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.029s
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 19:17:23 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 19:17:23 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/24 19:17:23 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 19:17:23 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/24 19:17:23 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=370
2026/10/24 19:17:23 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/24 19:17:23 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000003
2026/10/24 19:17:23 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000003 maxTXID=0000000000000003 size=454
2026/10/24 19:17:23 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000003 txid.db=0000000000000003
2026/10/24 19:17:23 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 19:17:23 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/24 19:17:23 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 19:17:23 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 19:17:23 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%3A40785: could not determine target mailbox
agent.go:135: dbreads: dbr_mucuezq6qot3lzo4me64usiyp4: planned 3 files (1544 bytes) and restored to 3 in 10ms
agent.go:135: dbreads: dbr_mucuezq6qot3lzo4me64usiyp4: output 5 bytes in 1 confirmed chunks in 1ms
2026/10/24 19:17:24 console: db read dbr_mucuezq6qot3lzo4me64usiyp4: relayed 1 chunks (102 bytes) in 1.004s, ended done
agent.go:135: dbreads: dbr_lm6qo4a57xnwoobwygw2fm7eay: planned 1 files (720 bytes) and restored to 1 in 8ms
agent.go:135: dbreads: dbr_lm6qo4a57xnwoobwygw2fm7eay: output 5 bytes in 1 confirmed chunks in 1ms
2026/10/24 19:17:25 console: db read dbr_lm6qo4a57xnwoobwygw2fm7eay: relayed 1 chunks (102 bytes) in 1.003s, ended done
agent.go:135: dbreads: dbr_yfkpk5fdjmmnpi5ww72apunegy: planned 3 files (1544 bytes) and held through 3 in 8ms
agent.go:135: dbreads: dbr_yfkpk5fdjmmnpi5ww72apunegy: output 1544 bytes in 1 confirmed chunks in 1ms
2026/10/24 19:17:26 console: db read dbr_yfkpk5fdjmmnpi5ww72apunegy: relayed 1 chunks (1641 bytes) in 1.004s, ended done
=== RUN TestDBForwardMinio/by_disconnect
=== NAME TestDBForwardMinio
agent.go:135: dbreads: dbr_k6dbsb5o5w27natvzoci25pp24: planned 3 files (1544 bytes) and restored to 3 in 9ms
2026/10/24 19:17:26 console: db read dbr_k6dbsb5o5w27natvzoci25pp24: relayed 0 chunks (0 bytes) in 33ms, ended client gone
agent.go:135: dbreads: dbr_k6dbsb5o5w27natvzoci25pp24: output 5 bytes in 1 confirmed chunks in 103ms
=== RUN TestDBForwardMinio/by_timeout
=== NAME TestDBForwardMinio
agent.go:135: dbreads: dbr_fpu5p7txtddwe5ohfhuvranenm: planned 3 files (1544 bytes) and restored to 3 in 9ms
2026/10/24 19:17:47 console: db read dbr_fpu5p7txtddwe5ohfhuvranenm: relayed 0 chunks (0 bytes) in 19.851s, ended no-result
agent.go:135: dbreads: dbr_fpu5p7txtddwe5ohfhuvranenm: output 5 bytes in 1 confirmed chunks in 19.826s
--- PASS: TestDBForwardMinio (23.29s)
--- PASS: TestDBForwardMinio/by_disconnect (0.24s)
--- PASS: TestDBForwardMinio/by_timeout (19.96s)
PASS
ok github.com/carlosframework/platform/internal/console 23.306s
minio contract: PASS
diff --git a/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-1-declare-publish.md b/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-1-declare-publish.md| index e8d39caf6..293a2ae86 100644 |
| --- a/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-1-declare-publish.md |
| +++ b/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-1-declare-publish.md |
| @@ -41,6 +41,7 @@ Every task's requirements include this section. |
| ## Census impact |
| +- **Record-write census (`TestPlan9B8RecordWriteCensus`, `internal/provision/plan9_b8_census_test.go:360`):** this plan adds a typed record writer and changes record creators, which that census rejects until its inventory is reviewed (astra P4-1). Add `"setrecordassetgen": true` to `censusKeyExact` (`:56`); add `SetRecordAssetGen` (class `record-writer`, kinds `key:SetRecordAssetGen,key:updateRecord`) and the purge door and `mintMissingAssetGens` (class `record-writer`, kinds naming `RecordsPrefix` and `SetRecordAssetGen`) to a new `edgeAssetCache1WriteCensusInventory()` that extends whichever inventory function the test calls at the merge (`dbRegionForward3WriteCensusInventory` at the time of writing); re-digest every record creator Task 5 changes, each with a one-line review comment in the style of `:721`; point the test at the new function. |
| - **Plan 14 root-read census:** one overlay, `plan14-root-reads-edge-asset-cache-1.tsv`, rows `rev3=PROPOSED-edge-asset-cache-1`. |
| - Changed frozen units keep their rows' form and are re-digested with class, task and basis unchanged: `internal/host/edgesfile.go` `edgesFileWriteClaims` stays a plain row of class `authority`, task `F,J1`, basis `root-route;kept=F;scope=protected-decision-only` (the `plan14-root-reads-region-ha-2a.tsv:26` precedent; the loader requires an authority row's task to name a kept family, `plan14_root_reads_test.go:507-515`); `internal/domains/aliases.go` `ReconcileAliases` and `deriveInstanceAliases` keep their `@legacy` rows (class `legacy`, re-fingerprinted by `rootReadLegacyFingerprint`). |
| - New unit `internal/host/assetmanifests.go` `assetManifestCache.paths` (reads `release.GetManifest` for the same protected publication decision): a plain row of class `authority`, task `F,J1`, basis `root-route;kept=F;scope=protected-decision-only`, the writer's own disposition. |
| @@ -226,9 +227,9 @@ Task 0 drafts every row as `pending` and runs the copy review. Rows (draft text |
| - `type ReconcileReport struct { Complete map[string]bool }` (keys `"<acct>/<app>"`) |
| - `ReconcileConfig.Report *ReconcileReport` (nil is today's behaviour; when set, `Reconcile` fills it before returning) |
| -An app is complete when: the listing was not `partial`; no record body under the app failed to read; no record under it was `protect`ed; and **every** in-scope record was positively applied, meaning every convergence step the pass runs for it (`convergeOwned`, the gate convergence at `reconcile.go:1366-1374`, a mint or an adoption) ran and returned nil, including steps that found nothing to change. There is no equality shortcut on a subset of columns: a record whose gate convergence failed is not applied even if its asset columns already match (astra P2-R1-7). Retirements count too: when the pass decides to remove a retired route of the app and `claimRetire` fails (`reconcile.go:1597-1599` logs and continues), the app is incomplete, so a deleted hostname whose row survives cannot keep a renewed `at` through a sibling's healthy convergence (astra P3-3). A record the pass skipped for any other reason (a trash marker that could not be read sets `declineMint`, `reconcile.go:1137`, and the loop skips the record at `:1174` with no failing write; a held claim; a deferred adoption) makes its app incomplete (astra P1-7). `readFailed` from an unreadable account manifest makes every app of that account incomplete. A pass that returns an error leaves `Complete` empty. |
| +An app is complete when: the listing was not `partial`; no record body under the app failed to read; no record under it was `protect`ed; and **every** in-scope record was positively applied, meaning every convergence step the pass runs for it (`convergeOwned`, the gate convergence at `reconcile.go:1366-1374`, a mint or an adoption) ran and returned nil, including steps that found nothing to change. There is no equality shortcut on a subset of columns: a record whose gate convergence failed is not applied even if its asset columns already match (astra P2-R1-7). Retirements count too: when the pass decides to remove a retired route of the app and `claimRetire` fails (`reconcile.go:1597-1599` logs and continues), or the removal is withheld because the fresh bucket walk that must confirm it could not be read cleanly (`reconcile.go:1613-1617`, which logs and returns nil), every app with a pending retirement in that pass is incomplete (astra P4-R3-3), so a deleted hostname whose row survives cannot keep a renewed `at` through a sibling's healthy convergence (astra P3-3). A record the pass skipped for any other reason (a trash marker that could not be read sets `declineMint`, `reconcile.go:1137`, and the loop skips the record at `:1174` with no failing write; a held claim; a deferred adoption) makes its app incomplete (astra P1-7). `readFailed` from an unreadable account manifest makes every app of that account incomplete. A pass that returns an error leaves `Complete` empty. |
| -- [ ] **Step 1: Write the failing tests** (`setup`, `acmeRecord`, `putReplace` helpers): `TestReportCompleteOnACleanPass`; `TestReportOmitsAnAppWithAnUnreadableRecord` (a `viewCountingStore`-style wrapper failing one Get; the other app is still complete); `TestReportOmitsAProtectedRecordsApp` (malformed body); `TestReportOmitsEveryAppOnAPartialListing`; `TestReportEmptyWhenThePassFails`; `TestReportOmitsAnAppWhoseRecordWasSkipped` (an unreadable trash marker with the record's `AssetGen` changed: the row keeps the old generation and the app is not complete); `TestReportOmitsAnAppWhoseGateConvergenceFailed` (asset columns already equal, `SetGate` failing through a SQLite trigger as in Task 7); `TestReportOmitsAnAppWhoseRetirementFailed` (two instances; one record deleted and its route's delete failing through a trigger; the sibling converges; the app is not complete); `TestReportNilKeepsTodaysBehaviour`. |
| +- [ ] **Step 1: Write the failing tests** (`setup`, `acmeRecord`, `putReplace` helpers): `TestReportCompleteOnACleanPass`; `TestReportOmitsAnAppWithAnUnreadableRecord` (a `viewCountingStore`-style wrapper failing one Get; the other app is still complete); `TestReportOmitsAProtectedRecordsApp` (malformed body); `TestReportOmitsEveryAppOnAPartialListing`; `TestReportEmptyWhenThePassFails`; `TestReportOmitsAnAppWhoseRecordWasSkipped` (an unreadable trash marker with the record's `AssetGen` changed: the row keeps the old generation and the app is not complete); `TestReportOmitsAnAppWhoseGateConvergenceFailed` (asset columns already equal, `SetGate` failing through a SQLite trigger as in Task 7); `TestReportOmitsAnAppWhoseRetirementFailed` (two instances; one record deleted and its route's delete failing through a trigger; the sibling converges; the app is not complete); `TestReportOmitsAnAppWhoseRetirementCouldNotBeConfirmed` (the same, with the fresh walk failing instead); `TestReportNilKeepsTodaysBehaviour`. |
| - [ ] **Step 2:** Run `go test ./internal/provision/ -run 'TestReport' -count=1`. Expected: FAIL. |
| - [ ] **Step 3:** Implement by tracking per-app failure inside the existing scan and convergence loops; no new reads. |
| - [ ] **Step 4:** Run the same command. Expected: PASS. |
| @@ -329,7 +330,7 @@ Publication rule for a host, applied in `edgesFileWriteClaims` through `assetPol |
| - Modify: `internal/provision/plan14_root_reads_test.go` (load the overlay last, check the previous seal), `.amadan/test-weights.tsv` |
| - [ ] **Step 1:** `git fetch origin && git merge origin/main`. Re-read which overlay loads last. |
| -- [ ] **Step 2:** Run `go test ./internal/provision/ -run TestPlan14RootReadCensus -count=1`. Expected: FAIL naming exactly the units and envelopes in "Census impact". |
| -- [ ] **Step 3:** Write the overlay rows and the notes file; post the ruling request on #280. |
| -- [ ] **Step 4:** Run the census test. Expected: PASS. Run the cross-compiles and `gofmt -l .`. Expected: clean. |
| +- [ ] **Step 2:** Run `go test ./internal/provision/ -run 'TestPlan14RootReadCensus|TestPlan9B8RecordWriteCensus' -count=1`. Expected: FAIL naming exactly the units, sites and envelopes in "Census impact". |
| +- [ ] **Step 3:** Write the overlay rows, the record-write inventory function and the notes file (which covers both censuses); post the ruling request on #280. |
| +- [ ] **Step 4:** Run both census tests and `TestPlan9B8CensusRejectsNewSites`. Expected: PASS. Run the cross-compiles and `gofmt -l .`. Expected: clean. |
| - [ ] **Step 5:** Add weights for any new top-level test over a second (time it with `-run` and `-v`). Commit, push, wait for CI on the tip, then merge per Global Constraints. |
diff --git a/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md b/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md| index 033e9b0e9..6913a8cca 100644 |
| --- a/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md |
| +++ b/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md |
| @@ -117,9 +117,9 @@ Added to `docs/superpowers/plans/2026-10-24-edge-asset-cache-copy.md` by Task 0 |
| **Interfaces:** |
| - Consumes: `assetcache.Entry`, `Meta.Age` (plan 2), `acceptsGzip`, `weakenETag` (`compress.go`). |
| -- Produces: `func serveAssetEntry(w http.ResponseWriter, r *http.Request, e *assetcache.Entry, now time.Time) error`: copies `Meta.Header`; sets `Age`, `Carlos-Cache: hit`, `Vary: Accept-Encoding` when the entry has a gzip copy; chooses gzip when the entry has a copy, `acceptsGzip(r.Header)` and no `Range` header, then sets `Content-Encoding: gzip` and weakens the `ETag`; answers through `http.ServeContent` over the chosen file (conditional requests, a single `Range`, `If-Range`, 416); never sets `X-Carlos-Version`. |
| +- Produces: `func serveAssetEntry(w http.ResponseWriter, r *http.Request, e *assetcache.Entry, now time.Time, result string) error`: copies `Meta.Header`; sets `Age`, `Carlos-Cache: <result>` (`hit` for a hit, `miss` for a buffered initiator answered from the entry it just filled, astra P4-3), `Vary: Accept-Encoding` when the entry has a gzip copy; chooses gzip when the entry has a copy, `acceptsGzip(r.Header)` and no `Range` header, then sets `Content-Encoding: gzip` and weakens the `ETag`; answers through `http.ServeContent` over the chosen file (conditional requests, a single `Range`, `If-Range`, 416); never sets `X-Carlos-Version`. |
| -- [ ] **Step 1: Write the failing tests** (entries committed into a real plan 2 store on `t.TempDir()`). `TestServeAssetEntryHit` (body, `Carlos-Cache: hit`, `Age`); `TestServeAssetEntryConditional` (304 for the identity `ETag`; 304 for the weak gzip `ETag` with gzip accepted); `TestServeAssetEntryRange` (206 with the right bytes, identity even when gzip is accepted); `TestServeAssetEntryIfRangeWithWeakETagGetsWholeBody`; `TestServeAssetEntryVaryOnBothRepresentations`; `TestServeAssetEntryNoTransformHasNoGzip` (an entry committed without a copy); `TestServeAssetEntryKeepsCSPAndCORP`; `TestServeAssetEntryHead`; `TestServeAssetEntryCarriesNoVersion`. |
| +- [ ] **Step 1: Write the failing tests** (entries committed into a real plan 2 store on `t.TempDir()`). `TestServeAssetEntryHit` (body, `Carlos-Cache: hit`, `Age`); `TestServeAssetEntryConditional` (304 for the identity `ETag`; 304 for the weak gzip `ETag` with gzip accepted); `TestServeAssetEntryRange` (206 with the right bytes, identity even when gzip is accepted); `TestServeAssetEntryIfRangeWithWeakETagGetsWholeBody`; `TestServeAssetEntryVaryOnBothRepresentations`; `TestServeAssetEntryNoTransformHasNoGzip` (an entry committed without a copy); `TestServeAssetEntryKeepsCSPAndCORP`; `TestServeAssetEntryHead`; `TestServeAssetEntryCarriesNoVersion`; `TestServeAssetEntryReportsTheResultItIsGiven`. |
| - [ ] **Step 2:** Run `go test ./internal/edge/ -run 'TestServeAssetEntry' -count=1`. Expected: FAIL. |
| - [ ] **Step 3:** Implement. |
| - [ ] **Step 4:** Run the same command. Expected: PASS. |
| @@ -159,7 +159,9 @@ Added to `docs/superpowers/plans/2026-10-24-edge-asset-cache-copy.md` by Task 0 |
| **The initiating request.** When the initiator is a `GET` with no `Range`, `If-None-Match`, `If-Modified-Since` or `If-Range`, the body is teed to it as it fills. When it is a `HEAD`, or has any of those headers, it is **buffered** (astra P3-5): nothing is teed, the initiator waits for the commit and is answered by `serveAssetEntry`, so its conditional or range is honoured on a cold miss as on a hit (astra P1-10). If the verdict refuses at header time, the full response is streamed to it unmodified (a 200 to a range request is valid HTTP) with `Carlos-Cache: bypass`; a `HEAD` gets the headers and no body. If the fill is abandoned after the headers (deadline, short body, trailer veto), what happens depends on whether the initiator's response has started: a **streamed** initiator's delivery simply continues to the end of the upstream body exactly as an uncached response would, and only the caching is abandoned; nothing is ever replayed onto a started response. A **buffered** initiator (a `HEAD`, or one with `Range` or a conditional, which nothing has been written to yet) has its request forwarded again as today and answered `bypass` (astra P2-1). **Billing follows the delivery** (astra P3-4): the discarded attempt is metered by no one (the home did not meter an acknowledged fill, and the far edge meters an acknowledged response only when it is the one delivered), and the re-forwarded request is metered by whoever meters it today, so the client's one delivery is one request with only the delivered bytes. |
| -On the home, `serveAssetFillForHop` runs only for a verified hop carrying `X-Carlos-Asset-Fill`; it checks `verifyAssetFill`, that `e.assets.local.For(host)` gives the asserted account, app and gen and catches the path; on any failure it returns false and the request continues as an ordinary hop. It answers a hit from the store with `X-Carlos-Asset-Expires` set to the entry's expiry, or fills; in both cases it deletes any app-set `X-Carlos-Asset-Fill-Ack` and `X-Carlos-Asset-Expires`, sets `X-Carlos-Asset-Expires` (`0` when the verdict does not store) and `X-Carlos-Asset-Fill-Ack`, and never touches the shared LRU. |
| +**The upstream must be the policy's.** `LocalAssets.For` reads the registry, while `up` comes from the edge's route snapshot, which reloads on its own two-second poll (`edge.go:2324`), so the two can disagree for a moment after an alias changes owner. Before filling, and before acknowledging a far fill, the edge requires `up.route.Account`, `up.route.App` and `up.route.AssetGen` to equal the policy's account, app and gen for the key, and the request's host to resolve to the same primary route in both; any mismatch is an ordinary uncached request (astra P4-2). |
| + |
| +On the home, `serveAssetFillForHop` runs only for a verified hop carrying `X-Carlos-Asset-Fill`; it checks `verifyAssetFill`, that `e.assets.local.For(host)` gives the asserted account, app and gen and catches the path, and the upstream rule above; on any failure it returns false and the request continues as an ordinary hop. It answers a hit from the store with `X-Carlos-Asset-Expires` set to the entry's expiry, or fills; in both cases it deletes any app-set `X-Carlos-Asset-Fill-Ack` and `X-Carlos-Asset-Expires`, sets `X-Carlos-Asset-Expires` (`0` when the verdict does not store) and `X-Carlos-Asset-Fill-Ack`, and never touches the shared LRU. |
| - [ ] **Step 1: Write the failing tests** (one `Edge` with a registry route, a fake `LocalAssets`, a real plan 2 store; "not acceptance"). |
| - `TestLocalMissThenHit` (`miss`, then `hit`, the backend saw one request). |
| @@ -172,14 +174,15 @@ On the home, `serveAssetFillForHop` runs only for a verified hop carrying `X-Car |
| - `TestFallbackETagIsTheBodyHash` (no app `ETag`: the entry's is `"<sha256>"`, a conditional hit with it is 304). |
| - `TestSetCookieTrailerVetoesTheFill` (announced and unannounced). |
| - `TestStreamedInitiatorIsNotReplayedAfterATrailerVeto` and `TestStreamedInitiatorIsNotReplayedAfterTheDeadline` (the client receives exactly one status line and one body, byte-identical to the upstream's). |
| - - `TestColdHeadIsBuffered` (a cold `HEAD`: answered from the entry after commit with the fallback `ETag`; on refusal, headers and no body). |
| + - `TestColdHeadIsBuffered` (a cold `HEAD`: answered from the entry after commit with the fallback `ETag` and `Carlos-Cache: miss`; the next request reports `hit`; on refusal, headers and no body). |
| + - `TestPolicyAndSnapshotDisagreeingFillsNothing` (the registry's alias target changes to another app while `Edge.Reload` is withheld: the request is forwarded uncached and a far fill gets no acknowledgement). |
| - `TestStalledFillIsAbandonedAtTheDeadline` (deadline hook `assetFillDeadline`; reservation released, `Stats().Reserved == 0`, waiters forwarded). |
| - `TestNonCanonicalPathBypasses`, `TestPostUnderAPrefixPassesThrough`. |
| - `TestHomeFillAcknowledgesAndSkipsTheMeter` (a verified hop with a valid assertion: response has a verifiable ack, the usage meter recorded nothing, the shared LRU has no entry). |
| - `TestHomeFillWithStaleGenGetsNoAck` and `TestHomeFillWithChangedOwnerGetsNoAck` (metered as an ordinary hop). |
| - `TestHomeFillUncacheableIsAcknowledgedWithZeroExpiry`. |
| - `TestAppSetAckHeadersAreStripped` (the backend sets both headers; the home's own replace them; on a plain request neither reaches the client). |
| -- [ ] **Step 2:** Run `go test ./internal/edge/ -run 'TestLocal|TestHibernatingRouteHit|TestHeadFills|TestQueryIsPart|TestFillSurvives|TestShortBody|TestStalledFill|TestNonCanonicalPath|TestPostUnder|TestHomeFill|TestAppSetAck|TestColdMiss|TestColdHead|TestFallbackETag|TestSetCookieTrailer|TestStreamedInitiator' -count=1`. Expected: FAIL. |
| +- [ ] **Step 2:** Run `go test ./internal/edge/ -run 'TestLocal|TestHibernatingRouteHit|TestHeadFills|TestQueryIsPart|TestFillSurvives|TestShortBody|TestStalledFill|TestNonCanonicalPath|TestPostUnder|TestHomeFill|TestAppSetAck|TestColdMiss|TestColdHead|TestFallbackETag|TestSetCookieTrailer|TestStreamedInitiator|TestPolicyAndSnapshot' -count=1`. Expected: FAIL. |
| - [ ] **Step 3:** Implement. |
| - [ ] **Step 4:** Run the same command. Expected: PASS. |
| - [ ] **Step 5:** Commit. |
| @@ -254,11 +257,11 @@ The authority table itself runs in plan 1 Task 8 (`internal/host`, the real `ass |
| - `TestAssetE2EOldFarEdgeForgedMarkerIsBilledAsAHop`: an old far edge is simulated explicitly, because a new edge strips the client's marker whether or not it is armed: the test sends the home a hop it builds itself, with a valid `X-Carlos-Hop-Auth` from `signHop` under the scene's mesh key and a forged `X-Carlos-Asset-Fill` (a well-formed timestamp with a random MAC), which is exactly what an old edge forwarding a client header would send. The home's backend observes that the hop verified and the marker arrived; the home bills it as an ordinary hop (`TestHopServedRequestMetersOnceWithHopCounters`'s counters) and the response carries no acknowledgement (astra P2-5). |
| - `TestAssetE2EDisarmedHomeFillsNothing`: the home unarmed with a stale published entry still on the far edge: responses arrive `bypass`, metered once at the home, nothing stored. |
| - `TestAssetE2EFillCarriesNoCredentials`: a client `Cookie` and `Authorization` sent to the far edge never reach the home's backend. |
| - - `TestAssetE2ERefusedFillsAreStoredNowhere`: `Set-Cookie`, a `Set-Cookie` trailer, `Vary: Cookie`, `Vary: Origin`, `Cache-Control: private`, `no-cache`, a 206, a long-lived HTML fallback and a 404, each through the far edge: answered `bypass`, both stores empty, metered once (astra P3-R1-15). |
| + - `TestAssetE2ERefusedFillsAreStoredNowhere`: `Set-Cookie`, a `Set-Cookie` trailer, `Vary: Cookie`, `Vary: Origin`, `Cache-Control: private`, `no-cache`, `no-store`, no `Cache-Control` at all, a 206, a long-lived HTML fallback and a 404, each through the far edge: answered `bypass`, both stores empty, metered once (astra P3-R1-15). |
| - `TestAssetE2ELateVetoOnABufferedRangeBillsOnce`: a cold range request through the far edge whose acknowledged fill gets a `Set-Cookie` trailer: the client gets one response from the re-forward, and the two edges' meters together record one request and only the delivered bytes. |
| - `TestAssetE2ECoalescedWaitersAndRefusals`: concurrent far requests for one key share an admitted entry; with a refused response, each waiter reaches the home on its own. |
| - `TestAssetE2EStalledBodyIsAbandoned`: the home's backend stalls after headers; the far fill is abandoned at the deadline with both reservations released. |
| - - `TestAssetE2EUndeclaringBuildIsNotStored`: during a generation switch where only the installed build declares the prefix, the serving build's cacheable fallback is stored on neither edge. |
| + - `TestAssetE2EUndeclaringBuildIsNotStored`: during a generation switch where only the installed build declares the prefix, the serving build's cacheable fallback is stored on neither edge; and the reverse, where only the retiring (still serving) build declares it, a response from the installed build after the switch is stored on neither edge (astra P4-R1-15). |
| - `TestAssetE2EWarmSleepRestart`: a generation-managed instance warms, sleeps and restarts its edge process; hits continue on both edges with no wake. |
| - `TestAssetE2EOversizedResponseIsStoredNowhere` (a 33 MiB body, with and without `Content-Length`). |
| - `TestAssetE2EGenerationOrOwnerMismatchGetsNoAck` (the far edge's published view names a generation, then an owner, that the home no longer holds: no acknowledgement, metered once at the home, stored nowhere) (astra P2-R1-15). |
diff --git a/docs/superpowers/specs/CODEX-FINDINGS-ASSET-CACHE-PLANS-R4.md b/docs/superpowers/specs/CODEX-FINDINGS-ASSET-CACHE-PLANS-R4.md| new file mode 100644 |
| index 000000000..a4c15c29a |
| --- /dev/null |
| +++ b/docs/superpowers/specs/CODEX-FINDINGS-ASSET-CACHE-PLANS-R4.md |
| @@ -0,0 +1,17 @@ |
| +| Earlier finding | Disposition | Why | |
| +|---|---|---| |
| +| R3-1: Metadata encoded before final fields | Fixed | [Plan 2:128](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-2-disk-store.md:128) finalizes fields before encoding and adds a commit/reopen/read test. | |
| +| R3-2: Provider-test paths | Fixed | [Plan 3:272](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md:272) uses the correct full import path and package-local filenames. | |
| +| R3-3: Failed retirement advances authority | Partly fixed | Failed deletes are covered, but failed retirement revalidation still has a [nil-return path](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/internal/provision/reconcile.go:1615) with no explicit completeness rule or test. | |
| +| R3-4: Buffered fallback double billing | Fixed | [Plan 3:160](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md:160) assigns billing to the delivered response; Task 9 tests the combined meters. | |
| +| R3-5: Plain HEAD streamed | Fixed | [Plan 3:160](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md:160) explicitly buffers HEAD and requires bodyless refusal delivery. | |
| +| R3-6: Re-ship cannot retry generation backfill | Fixed | [Plan 1:202](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-1-declare-publish.md:202) requires backfill before both successful responses and adds the partial-failure retry test. | |
| +| R1-15: Missing production-wired attacks | Partly fixed | The named refusal gaps are closed, but [Task 9](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md:250) still omits explicit two-edge cases for missing Cache-Control, `no-store`, and the retiring-build-only declaration required by spec §12. | |
| + |
| +1. **P1 — The record-write census is missing from the implementation and landing tasks.** Plan 1 Tasks 5 and 11. Task 5 introduces `SetRecordAssetGen` through `updateRecord` and changes existing record creators ([plan 1:192](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-1-declare-publish.md:192)). Those changes trigger the separate record-write census, which rejects both unlisted declarations and changed source digests ([plan9_b8_census_test.go:262](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/internal/provision/plan9_b8_census_test.go:262)). Task 11 updates only the root-read census; that cannot satisfy `TestPlan9B8RecordWriteCensus`. **Smallest fix:** assign review and updates of the record-write inventory, register `SetRecordAssetGen` in its typed-writer recognizer, and run the record-write census alongside the root-read gate. |
| + |
| +2. **P2 — A new owner’s cache key can be filled through the previous owner’s backend.** Plan 1 Task 8 and Plan 3 Task 5. `assetPolicy` reads the registry ([plan 1:268](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-1-declare-publish.md:268)), while `fillFromApp` dispatches through the independently captured `up.proxy`; the signed-fill check compares the assertion only against `LocalAssets.For` ([plan 3:158](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md:158), [plan 3:162](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md:162)). The edge reloads its route snapshot on a separate [two-second poll](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/internal/edge/edge.go:2324). After an alias changes owners, policy can therefore name the new app while `up` still targets the old one. If both apps use the same version string and declare the prefix, the declaration check does not distinguish them. **Smallest fix:** require the selected upstream’s owner, primary route and asset generation to match the policy used for the key before acknowledging or filling. Add a test that changes the registry’s alias target while deliberately withholding `Edge.Reload`. |
| + |
| +3. **P3 — Buffered initiating misses are labelled as hits.** Plan 3 Tasks 3, 5 and 6. `serveAssetEntry` unconditionally sets `Carlos-Cache: hit` ([plan 3:120](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md:120)), but buffered cold HEAD, range and conditional requests must also use that helper after filling ([plan 3:160](/home/paulca/amadan.net/carlos/platform-edge-asset-cache/docs/superpowers/plans/2026-10-24-edge-asset-cache-plan-3-serve.md:160)). No interface permits the initiating response to retain `miss`. **Smallest fix:** pass the cache result into the serving helper, or assign that header in its callers; test that the cold initiator reports `miss` and subsequent requests report `hit`. |
| + |
| +reject |
| \ No newline at end of file |