carlos / platform Public

plan: self-serve moves 3, rev 4 (Astra plan review 3: F1 to F10; holds suppress their target's stale publications and the fold decides the rest, one table per request, holds in their own snapshot, tunnel and warming kept, self-contained)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 6fe2b7a3993d9d520e57f2a6f07b03ea10ff5e76 parent 94a3828
1 file changed, +69 −56
  • docs/superpowers/plans/2026-10-22-self-serve-moves-3-holding-page.md +69 −56

CI passed — run details

CI log
=== gate ===
.amadan/test-gate.sh
=== launchctx (0 s)
ok  	amadan.net/carlos/platform.git/launchctx	0.015s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
=== vet (1 s)
=== gofmt (0 s)
gofmt -l .: clean
=== rest (140 s)
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	4.030s
ok  	github.com/carlosframework/platform/cmd/moveprobe	12.169s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.766s
ok  	github.com/carlosframework/platform/internal/activator	59.164s
ok  	github.com/carlosframework/platform/internal/agentsock	0.039s
ok  	github.com/carlosframework/platform/internal/alerting	0.004s
ok  	github.com/carlosframework/platform/internal/analytics	0.029s
ok  	github.com/carlosframework/platform/internal/appconfig	3.099s
ok  	github.com/carlosframework/platform/internal/apperr	0.126s
ok  	github.com/carlosframework/platform/internal/applog	0.240s
ok  	github.com/carlosframework/platform/internal/awsinv	0.004s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.010s
ok  	github.com/carlosframework/platform/internal/boxid	1.543s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.011s
ok  	github.com/carlosframework/platform/internal/caps	0.026s
ok  	github.com/carlosframework/platform/internal/certwarm	0.005s
ok  	github.com/carlosframework/platform/internal/cgroup	0.822s
ok  	github.com/carlosframework/platform/internal/changelog	0.617s
ok  	github.com/carlosframework/platform/internal/claimactivation	1.421s
ok  	github.com/carlosframework/platform/internal/clamd	0.065s
?   	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	6.222s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.010s
ok  	github.com/carlosframework/platform/internal/dbverify	0.006s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.004s
ok  	github.com/carlosframework/platform/internal/docsite	0.009s
ok  	github.com/carlosframework/platform/internal/domains	3.242s
ok  	github.com/carlosframework/platform/internal/doorkey	0.014s
ok  	github.com/carlosframework/platform/internal/economics	0.007s
ok  	github.com/carlosframework/platform/internal/edge	45.276s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.009s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.193s
ok  	github.com/carlosframework/platform/internal/gate	2.029s
ok  	github.com/carlosframework/platform/internal/genselect	0.022s
ok  	github.com/carlosframework/platform/internal/geo	0.019s
ok  	github.com/carlosframework/platform/internal/geoip	0.035s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.267s
ok  	github.com/carlosframework/platform/internal/host	137.686s
ok  	github.com/carlosframework/platform/internal/hostactors	6.446s
ok  	github.com/carlosframework/platform/internal/hostactors/awsmembership	0.008s
ok  	github.com/carlosframework/platform/internal/hostbox	3.339s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.860s
ok  	github.com/carlosframework/platform/internal/hostenv	0.007s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.009s
ok  	github.com/carlosframework/platform/internal/hostexec	5.909s
ok  	github.com/carlosframework/platform/internal/hostfence	0.065s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.346s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.032s
ok  	github.com/carlosframework/platform/internal/hostguard	1.121s
ok  	github.com/carlosframework/platform/internal/hostops	2.465s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.003s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.006s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.692s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.196s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.003s
ok  	github.com/carlosframework/platform/internal/infraview	0.007s
ok  	github.com/carlosframework/platform/internal/instancectl	5.249s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.806s
ok  	github.com/carlosframework/platform/internal/instancefs	0.011s
ok  	github.com/carlosframework/platform/internal/instancekey	0.004s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.011s
ok  	github.com/carlosframework/platform/internal/layout	2.140s
ok  	github.com/carlosframework/platform/internal/lbhealth	0.004s
ok  	github.com/carlosframework/platform/internal/lease	10.129s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.636s
ok  	github.com/carlosframework/platform/internal/ledger	0.024s
ok  	github.com/carlosframework/platform/internal/mail	0.306s
ok  	github.com/carlosframework/platform/internal/maintenance	0.092s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.604s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.120s
ok  	github.com/carlosframework/platform/internal/metrics	0.005s
ok  	github.com/carlosframework/platform/internal/moves	21.595s
ok  	github.com/carlosframework/platform/internal/moves/archive	1.055s
ok  	github.com/carlosframework/platform/internal/objstore	0.333s
ok  	github.com/carlosframework/platform/internal/ops	0.293s
ok  	github.com/carlosframework/platform/internal/park	0.238s
ok  	github.com/carlosframework/platform/internal/plans	0.008s
ok  	github.com/carlosframework/platform/internal/privatefile	0.005s
ok  	github.com/carlosframework/platform/internal/provision	13.791s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.009s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.566s
ok  	github.com/carlosframework/platform/internal/relay	7.207s
ok  	github.com/carlosframework/platform/internal/release	25.723s
ok  	github.com/carlosframework/platform/internal/rollout	3.935s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.007s
ok  	github.com/carlosframework/platform/internal/rootaws	0.013s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.064s
ok  	github.com/carlosframework/platform/internal/rootstate	0.028s
ok  	github.com/carlosframework/platform/internal/runs	0.593s
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.837s
ok  	github.com/carlosframework/platform/internal/seeds	0.018s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	11.659s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.705s
ok  	github.com/carlosframework/platform/internal/status	0.061s
ok  	github.com/carlosframework/platform/internal/steering	0.011s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.197s
ok  	github.com/carlosframework/platform/internal/svcrole	0.003s
ok  	github.com/carlosframework/platform/internal/telemetry	0.005s
ok  	github.com/carlosframework/platform/internal/termfence	0.006s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.006s
ok  	github.com/carlosframework/platform/internal/unixdial	0.003s
ok  	github.com/carlosframework/platform/internal/updatewords	0.004s
ok  	github.com/carlosframework/platform/internal/usage	0.006s
ok  	github.com/carlosframework/platform/internal/version	0.002s
ok  	github.com/carlosframework/platform/internal/vet	1.646s
=== internal_console@0 (78 s)
ok  	github.com/carlosframework/platform/internal/console	76.181s
=== internal_console@1 (37 s)
ok  	github.com/carlosframework/platform/internal/console	35.580s
=== internal_console@2 (42 s)
ok  	github.com/carlosframework/platform/internal/console	40.115s
=== internal_console@3 (37 s)
ok  	github.com/carlosframework/platform/internal/console	35.190s
=== internal_console@4 (37 s)
ok  	github.com/carlosframework/platform/internal/console	35.154s
=== internal_console@5 (35 s)
ok  	github.com/carlosframework/platform/internal/console	33.357s
=== internal_console@6 (37 s)
ok  	github.com/carlosframework/platform/internal/console	34.952s
=== internal_console@7 (43 s)
ok  	github.com/carlosframework/platform/internal/console	41.062s
=== root@0 (33 s)
ok  	github.com/carlosframework/platform	30.946s
=== root@1 (45 s)
ok  	github.com/carlosframework/platform	42.584s
=== root@2 (42 s)
ok  	github.com/carlosframework/platform	40.227s
=== 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: 50069666 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/22 09:13:23 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18E0CF23B43C7C1C, 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
minio contract: PASS
diff --git a/docs/superpowers/plans/2026-10-22-self-serve-moves-3-holding-page.md b/docs/superpowers/plans/2026-10-22-self-serve-moves-3-holding-page.md
index b83b28939..49596d873 100644
--- a/docs/superpowers/plans/2026-10-22-self-serve-moves-3-holding-page.md
+++ b/docs/superpowers/plans/2026-10-22-self-serve-moves-3-holding-page.md
@@ -2,130 +2,143 @@
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
-Rev 3, 2026-10-22. Reviews: rev 1 (`after236/selfserve-plan3-review.md`, 15 findings), rev 2 (`selfserve-plan3-review2.md`, 14 findings). Rev 3 answers rev 2's fourteen: the arbitration is restated as a **ranking in two stages** (real candidates first, as today's rule; then alias candidates grouped by target, each group resolved on its own, live groups beating held ones, groups compared only by stamp and today's order, never by pair across targets) and pinned by a small-model **property test** over every candidate set, so the three-publication alias witness and the real-route precedence test hold by construction; exact resolution and request resolution are separate, so wildcard fallback works after an exact hold expires while the TLS predicates stay exact and reject wildcard labels; the decision carries the upstream snapshot it chose, so dispatch, path selection, login re-resolution, owner checks and dialing all consume one decision; local candidates come from the upstream table Reload builds, so no eligibility changes; explicit-alias provenance is part of the reconciler's equality and the fingerprint; the offline view carries a revision and both its refresh and its failure paths wake the edge; hops and doors have a stated answer for every decision; the probe wrapper ignores informational responses, delegates `Hijack`, and probe requests bypass the cache; the renderer's `until` and the client's branches are stated; the decoder audit separates the reviewed inventory from a type-resolved discovery check; and the census and reconstruction witnesses are completed.
+Rev 4, 2026-10-22. Reviews: rev 1 (`after236/selfserve-plan3-review.md`, 15 findings), rev 2 (`selfserve-plan3-review2.md`, 14), rev 3 (`selfserve-plan3-review3.md`, F1 to F10). Rev 4 answers rev 3's ten and is self-contained (nothing is "as rev 2"; F8). The ranking is restated so that it **cannot** disagree with plan 1's fold (F1, F2): a holding candidate's only power over ready candidates is to **suppress** the stale ready publications of its own target (same `targetHost`, unstamped, lower pair); the ready candidates that survive are folded by the **same function** `foldHomes` uses, so ready-only routing is plan 1's by construction and a differential test pins it; only when no ready candidate survives does a hold answer. The invariants are restated to be consistent with that algorithm and with §6.4 ("eligible under ordinary routing" = not a suppressed stale publication of the moving target); the "real hold beats a stamped alias" test is withdrawn as contradicting §6.4. The decision carries the whole local table it was resolved against (F3). Candidate construction is complete for local ready, local holding (aliases expanded through the writer's own predicate) and remote, with the existing admissions kept (F4). Local holds live in their own atomic snapshot that `Reload` never touches, and the view publishes revision and contents in one immutable value (F5). `Allowed` keeps the tunnel exception; a separate certificate-name enumeration covers holds for warming without making them served evidence (F6). `AliasPublishable` includes the primary, the name exclusions, and no owner parameter (F7). The countdown arithmetic and the inherited contracts are restated in full (F8). The discovery check includes `internal/steering` and a reviewed exclusion list (F9). The census names `internal/edgesfile` and the loader (F10).
-Builds on plan 1 (rev 9: `(incarnation, serving_seq)` on rows and publications, `target_host` on alias publications, the fold's pair rule, `edgesFileWriteClaims(..., lifetimes bool)`) and plan 2 (rev 4: `SelfServeMove{DisplayName, OfflineAt, EstimateSeconds}`, `Participants`, `moveCfg.BoxID`, `probeTarget`).
+Builds on plan 1 (rev 10: `(incarnation, serving_seq)` on rows and publications, `target_host` on alias publications, the fold's pair rule in `dialBeats`/`foldHomes`, `edgesFileWriteClaims(..., lifetimes bool)`, the cutover gate) and plan 2 (rev 4: `SelfServeMove{DisplayName, OfflineAt, EstimateSeconds}`, `Participants`, `moveCfg.BoxID`, `probeTarget`).
**Goal:** From `start` until the target's row is ready and published, every edge answers the moving host and its explicit aliases with a 503 holding page, ordered against ready routes by the pair, never believed for more than five minutes after its last successful fetch; and the console's health probe can prove the target box itself answered.
-**Architecture:** Every lifetimes-mode box publishes its held offline-move rows as holding entries in a new edges-file field `offline_moves`, separate from `hosts`. The edge keeps every candidate per name (ready routes, holding entries, certificate-only entries; local and remote) and resolves a request by one ranking. The same ranking's exact form answers certificate admission. A `X-Carlos-Probe: local` request is refused with 503 at the forwarding boundary and stamped `X-Carlos-Served-By: <box id>` on the final response where served locally.
+**Architecture:** Every lifetimes-mode box publishes its held offline-move rows as holding entries in a new edges-file field `offline_moves`, separate from `hosts`. The edge keeps every candidate per name (ready routes, holding entries, certificate-only entries; local and remote) and resolves a request by one ranking whose ready-only path is today's fold. The same ranking's exact form answers certificate admission. A `X-Carlos-Probe: local` request is refused with 503 at the forwarding boundary and stamped `X-Carlos-Served-By: <box id>` on the final response where served locally.
-**Tech Stack:** Go. `internal/host/edgesfile.go`, `internal/edge`, `internal/edgesfile`, `internal/domains`, `internal/registry`, `internal/console`, the agent's wiring.
+**Tech Stack:** Go. `internal/host/edgesfile.go`, `internal/edge`, `internal/edgesfile`, `internal/domains`, `internal/registry`, `internal/console`, `internal/steering` (audit only), the agent's wiring.
**Spec:** `docs/superpowers/specs/2026-10-21-self-serve-offline-moves-design.md` §6.4, §7.4, §11.
## Global Constraints
-- `offline_moves` is never a reason to count a host as served (Task 4's inventory).
-- A remote holding candidate is believed only while `now - fetchedAt < holdingValidity` (5 min); expiry is applied at ranking time from the retained candidates, never by store I/O; the answer after expiry is what the remaining candidates give, including the wildcard fallback.
+- `offline_moves` is never a reason to count a host as served (Task 4's inventory). The claims-on writer publishes no `offline_moves`.
+- A remote holding candidate is believed only while `now.Sub(fetchedAt) < holdingValidity` (5 min; at exactly 5 m 0 s it is expired); expiry is applied at ranking time from the retained candidates, never by store I/O; the answer after expiry is what the remaining candidates give, including the wildcard fallback.
- **Exact resolution and request resolution are different questions.** `Allowed`, `KnownHost` and `ExactHome` use exact candidates only and reject `*.` names as today; request resolution tries the exact name and falls back to the wildcard parent only when the exact name has no **effective** routing candidate (a live exact holding winner blocks the fallback).
+- **Ready-only routing is plan 1's fold.** With no holding candidate in play, `rank` returns exactly what `foldHomes` plus the local-first rule return today (Task 3's differential test); local ready candidates win over remote ones as today (the upstream table is consulted first).
- Local ready candidates are the entries of the upstream table `Reload` builds (`e.current()`): its eligibility (ready status, a usable backend, no sidecars, aliases whenever their primary has an upstream, shadowing) is unchanged. DNS pointing gates **publication** of aliases, not local service.
-- Explicit aliases only in holding publications: the alias row is `Explicit` and eligible for publication (`dns_status == "pointing"`, not shadowed, the owner rule).
-- The display name is the only request-supplied text on the page, rendered through `html/template`.
-- The page: 503, `Retry-After: 30`, `Cache-Control: no-store`, `X-Robots-Tag: noindex`; the client reloads by `location.replace` of the current URL every 30 s while `until` is ahead, shows the taking-longer sentence at `until` and reloads every 60 s after; with no `until` it keeps the no-estimate sentence and reloads every 60 s; its sessionStorage key is `carlos-holding-<host>`; storage failures never stop the reload.
-- No change to a claims-on box's publication or to claim-stamp precedence among ready candidates; the real-route-over-alias rule (#287, `TestHomeMapAStampedAliasDoesNotBeatAnUnstampedRoute`) stays before stamps.
-- Copy rows pending Paul's review: the four sentences of rev 2 and "not served here".
-- Census: `plan14-root-reads-self-serve-moves-3.tsv` loaded last by a new block at the end of `TestPlan14RootReadCensus`, `rev3=PROPOSED-self-serve-moves-3`, envelopes `internal/host`, `internal/edge`, `internal/domains`, `internal/registry`, `internal/console` (Task 6's probe), the tracked field `route_aliases.explicit`, reviewed dispositions for changed frozen units; `TestPlan9B8RecordWriteCensus` unchanged and green.
+- Explicit aliases only in holding publications: the alias row is `Explicit` and `AliasPublishable` (below).
+- The display name is the only request-supplied text on the page, rendered through `html/template`; no other request text (URL, headers, host) appears in the body.
+- **The page.** Status 503, `Retry-After: 30`, `Cache-Control: no-store`, `X-Robots-Tag: noindex`, `Content-Type: text/html; charset=utf-8`. Sentences (copy rows pending Paul's review): with a name and an estimate ahead: "<display name> is temporarily offline. Please check back in approximately N minutes."; without a name and an estimate ahead: "This account is temporarily offline. Please check back in approximately N minutes."; no estimate (`until == 0`): "This account is temporarily offline. Please check back in a few minutes."; estimate passed (`until <= now`): "This is taking longer than expected. Please keep checking back."; the probe refusal: "not served here". N = `ceil((until - now) / 60 s)` with a minimum of 1, computed by the server at render time; the client never recomputes N (each reload renders it afresh). The client script: `until` (unix seconds, 0 for none) and `host` are embedded as JSON; while `until > 0 && until > now` it reloads by `location.replace(location.href)` (a GET, so a POST never repeats) every 30 s, and at the instant `now >= until` it replaces the sentence with the taking-longer sentence locally and reloads every 60 s thereafter; with `until == 0` it keeps the no-estimate sentence and reloads every 60 s; the sessionStorage key `carlos-holding-<host>` records the first-seen time (informational; distinct from the pending page's key); any storage exception is caught and never stops the reload. HEAD writes the headers and no body.
+- No change to a claims-on box's publication or to claim-stamp precedence among ready candidates; the real-route-over-alias rule (#287, `TestHomeMapAStampedAliasDoesNotBeatAnUnstampedRoute`) stays before stamps; a stamped ready candidate is never suppressed (holds are never stamped).
+- Census: `internal/provision/testdata/plan14-root-reads-self-serve-moves-3.tsv`, loaded by a new `loadRootReadInventory(root+"/internal/provision/testdata/plan14-root-reads-self-serve-moves-3.tsv", &inv, true)` block in `internal/provision/plan14_legacy_identity_test.go` **after every existing overlay block and before the comparison**; `rev3=PROPOSED-self-serve-moves-3`; envelopes `internal/host`, `internal/edge`, `internal/edgesfile` (Task 1's `eligible.go`), `internal/domains`, `internal/registry`, `internal/console` (Task 6's probe), `internal/steering` only if its test file counts (verify; test files do not); the tracked field `route_aliases.explicit`; reviewed dispositions for changed frozen units; `TestPlan9B8RecordWriteCensus` unchanged and green.
- Local tests: touched packages with `-run`; CI runs the full suite.
## Review Focus
-1. Expiry: a remote exact hold over a retained exact ready `(1, 0)`, over a local wildcard upstream, and over a remote wildcard route, each at 4 m 59 s and 5 m 1 s with no refresh in between, and after a whole refresh fails: the page, then today's answer (the retained route, or the wildcard) (Task 5, `TestHoldingEntriesExpireFromTheFetchClock`).
-2. The alias ranking on every candidate set of the small model (up to four candidates, two targets, pairs up to 2, stamps 0 or 1, fresh or expired): the stated invariants hold (Task 3, `TestAliasRankingProperties`).
+1. Expiry: a remote exact hold over a retained exact ready `(1, 0)`, over a local wildcard upstream, and over a remote wildcard route, each at 4 m 59 s, 5 m 0 s and 5 m 1 s with no refresh in between, and after a whole refresh fails: the page, then today's answer (the retained route, or the wildcard) (Task 5, `TestHoldingEntriesExpireFromTheFetchClock`).
+2. The ranking on every candidate set of the small model (up to four candidates, two targets, pairs up to 2, stamps 0 or 1, fresh or expired, local or remote): the stated invariants hold, and with no live hold the answer equals the fold's (Task 3, `TestRankingProperties`, `TestRankEqualsFoldWithoutHolds`).
3. The stale ready alias of the moving instance loses to its own hold; a legacy alias publication without `target_host` is a different target and wins (Task 3, `TestAliasOfTheMovingInstanceLosesToItsHold`).
-4. The source box's own edge: a local hold `(1, 1)` against a retained remote ready `(1, 0)` → the page; a remote ready `(1, 2)` → backhaul; a local ready alias to A against a fetched hold for A at a higher pair → the page; a non-pointing local alias keeps serving (Task 2, `TestLocalAndRemoteArbitrateTogether`).
-5. A probe through every forwarding path → 503 and no dial; served locally through the real writer stack with gzip and the cache on, with a `103` before the final response and a forged final `X-Carlos-Served-By` → the final response carries this box's id and is not cached (Task 6, `TestProbeModeThroughTheRealStack`).
+4. The source box's own edge: a local hold `(1, 1)` against a retained remote ready `(1, 0)` → the page; a remote ready `(1, 2)` → backhaul; a local ready alias to A against a fetched hold for A at a higher pair → the page; a non-pointing local alias keeps serving; the source's own explicit alias is served the page with the own publication excluded from the home map (Task 2, `TestLocalAndRemoteArbitrateTogether`).
+5. A probe through every forwarding path → 503 and no dial; served locally through the real writer stack with gzip and the cache on and a populated cache entry, with a `103` before the final response and a forged final `X-Carlos-Served-By` → the final response carries this box's id, reaches the backend, and is not cached (Task 6, `TestProbeModeThroughTheRealStack`).
---
### Task 1: Alias provenance and the writer's holding entries
**Files:**
-- Modify: `internal/registry/registry.go` (`route_aliases.explicit INTEGER NOT NULL DEFAULT 0`; `RouteAlias.Explicit bool`; `UpsertAlias`'s conflict clause sets `explicit=excluded.explicit`; `ListAliases` scans it; `Fingerprint`'s alias part includes it), `internal/domains/aliases.go` (claim-backed aliases `Explicit: true`; `deriveInstanceAliases` writes `Explicit: false` **and its skip test includes `Explicit`**: a row with the same target and owner but `Explicit: true` is rewritten to false when the claim is gone; a legacy row matching a current claim is upgraded), `internal/host/edgesfile.go` (`edgesFile.OfflineMoves []holdingEntry \`json:"offline_moves,omitempty"\``; `edgesFileWriteClaims(ctx, s, reg, ident, eip, last, refresh, claims, lifetimes bool, holds holdingSource)`), `internal/edgesfile/eligible.go` (new, importable: `func AliasPublishable(a registry.RouteAlias, primaryServed bool, shadowed bool, ownerOK bool) bool` = `a.DNSStatus == "pointing" && !shadowed && ownerOK` with `primaryServed` meaning "the primary is a served **or held** row": the writer passes its own served/held sets and owner projection; the ownership validation itself stays in `domains.ReconcileAliases`), `internal/host/movesoffline.go` (`offlineView.holding(host)`; `offlineView.rev uint64` incremented by `refresh` **and** `fail`; `offlineView.onChange func(rev uint64)` the edge subscribes to), `internal/host/movespass.go` (the listing error path at line 1081 calls `w.cfg.Offline.fail()` before returning), `internal/host/agent.go` (wiring)
-- Test: `internal/host/edgesfile_holding_test.go`, `internal/domains/aliases_explicit_test.go`, `internal/registry/alias_explicit_test.go`
+- Modify: `internal/registry/registry.go` (`route_aliases.explicit INTEGER NOT NULL DEFAULT 0`; `RouteAlias.Explicit bool`; `UpsertAlias`'s conflict clause sets `explicit=excluded.explicit`; `ListAliases` scans it; `Fingerprint`'s alias part includes it), `internal/domains/aliases.go` (claim-backed aliases `Explicit: true`; `deriveInstanceAliases` writes `Explicit: false` **and its skip test includes `Explicit`**: a row with the same target and owner but `Explicit: true` is rewritten to false when the claim is gone; a legacy row matching a current claim is upgraded), `internal/host/edgesfile.go` (`edgesFile.OfflineMoves []holdingEntry \`json:"offline_moves,omitempty"\``; `edgesFileWriteClaims(ctx, s, reg, ident, eip, last, refresh, claims *edgesClaims, lifetimes bool, holds holdingSource)`), `internal/edgesfile/eligible.go` (new, importable), `internal/host/movesoffline.go` (the view's immutable snapshot, below), `internal/host/movespass.go` (the listing error path at line 1081 calls `w.cfg.Offline.fail()` before returning; a nil `Offline` is tolerated as today), `internal/host/agent.go` (wiring)
+- Test: `internal/host/edgesfile_holding_test.go`, `internal/domains/aliases_explicit_test.go`, `internal/registry/alias_explicit_test.go`, `internal/edgesfile/eligible_test.go`
**Interfaces:**
-- `type holdingEntry struct { Host string \`json:"host"\`; Platform bool \`json:"platform,omitempty"\`; Account string \`json:"account,omitempty"\`; App string \`json:"app,omitempty"\`; Aliases []string \`json:"aliases,omitempty"\`; MoveID string \`json:"move_id"\`; Incarnation uint64 \`json:"incarnation,omitempty"\`; ServingSeq uint64 \`json:"serving_seq,omitempty"\`; OfflineAt int64 \`json:"offline_at,omitempty"\`; EstimateSeconds int \`json:"estimate_seconds,omitempty"\`; DisplayName string \`json:"display_name,omitempty"\` }` (`Account`/`App` from the row's owner projection, so a door can authorise); `type holdingSource func(host string) (provision.MoveState, bool)`.
-- The rule per row and the stop conditions as rev 2; `Aliases` = the `route_aliases` rows with `Host == r.Host && Explicit && AliasPublishable(a, true, shadowed, ownerOK)`.
+- `func AliasPublishable(a registry.RouteAlias, primaryServed, shadowed bool) bool` = `a.Host != "" && !strings.HasPrefix(a.Host, "*.") && a.DNSStatus == "pointing" && primaryServed && !shadowed`. `primaryServed` means "the alias's primary row is in this writer's served set or is a held row it publishes". There is **no owner parameter**: ownership validation is `domains.ReconcileAliases`'s (a row that exists has passed it); the owner projection (`servedOwners`) may be empty for a hostname spanning apps and that hostname still publishes and routes publicly (the door refuses it, as today). The writer's alias loop for `hosts` and the holding entries' `Aliases` both call this function.
+- `type holdingEntry struct { Host string \`json:"host"\`; Platform bool \`json:"platform,omitempty"\`; Account string \`json:"account,omitempty"\`; App string \`json:"app,omitempty"\`; Aliases []string \`json:"aliases,omitempty"\`; MoveID string \`json:"move_id"\`; Incarnation uint64 \`json:"incarnation,omitempty"\`; ServingSeq uint64 \`json:"serving_seq,omitempty"\`; OfflineAt int64 \`json:"offline_at,omitempty"\`; EstimateSeconds int \`json:"estimate_seconds,omitempty"\`; DisplayName string \`json:"display_name,omitempty"\` }` (`Account`/`App` from the row's owner projection, possibly empty).
+- `type holdingSource func(host string) (provision.MoveState, bool)` = `offlineView.holding`.
+- **The rule per registry row `r`:** `registry.IsHold(r.Status) && r.HoldOp != ""` and `holds(r.Host)` returns a move with `ID == r.HoldOp` → one entry `{Host: r.Host, Platform: r.Platform || r.Provisioned, Account/App: the owner projection of r, Aliases, MoveID: mv.ID, Incarnation: r.Incarnation, ServingSeq: r.ServingSeq, OfflineAt: mv.SelfServe.OfflineAt.Unix() (0 when `SelfServe == nil` or the time is zero), EstimateSeconds: mv.SelfServe.EstimateSeconds (0 when nil), DisplayName: mv.SelfServe.DisplayName ("" when nil)}`; `Aliases` = the `route_aliases` rows with `a.Host == r.Host && a.Explicit && AliasPublishable(a, true, shadowedByServed(a.Alias))`, sorted. **Stop conditions:** a ready row publishes none; a held row whose `HoldOp` the view does not know (no move, or a move with another ID) publishes none; the entry stops when the record no longer carries that move id (the view) or the row turns ready. The target's `landing` row publishes from flip until `ReleaseHoldSeq`. Entries sorted by host. Only in lifetimes mode (`lifetimes == true`); region HA on or off makes no difference.
+- **The view's snapshot (F5):** `type offlineHolds struct { Rev uint64; Holds map[string]provision.MoveState; Failed bool }` immutable once published; `offlineView.snapshot() *offlineHolds` (atomic pointer load); `refresh` publishes `{Rev: prev+1, Holds: new, Failed: false}`; `fail` publishes `{Rev: prev+1, Holds: nil, Failed: true}`; `holding(host)` reads the current snapshot; `offlineView.onChange func(*offlineHolds)` is called after every publication with the snapshot it published (never with a reconstructed one).
-- [ ] **Step 1: Write the failing tests**: rev 2's plus `TestDerivedAliasClearsExplicitOnTheSameTarget` (a detached explicit claim whose name is then derived to the same target → the row becomes `Explicit: false`, the fingerprint changes, the next holding publication omits it); `TestAliasFingerprintIncludesExplicit`; `TestViewRevisionBumpsOnRefreshAndFail` (and the moves pass's listing error marks the view failed).
-- [ ] **Step 2: Run to verify they fail; Step 3: implement; Step 4: run** `go test ./internal/host -run 'Holding|ViewRevision' -count=1`, `go test ./internal/domains -run 'Explicit|Provenance' -count=1`, `go test ./internal/registry -run 'Alias' -count=1`.
-- [ ] **Step 5: Commit** `edges: explicit alias provenance in the registry and the reconciler; the lifetimes writer publishes held rows as holding entries; the offline view carries a revision`.
+- [ ] **Step 1: Write the failing tests**: `TestAliasReconcilerWritesProvenance` (a claim-backed alias → explicit; a derived instance alias → not; a legacy row upgraded); `TestDerivedAliasClearsExplicitOnTheSameTarget` (a detached explicit claim whose name is then derived to the same target → `Explicit: false`, the fingerprint changes, the next holding publication omits it); `TestAliasFingerprintIncludesExplicit`; `TestAliasPublishable` (absent primary → false; `*.` name → false; empty name → false; not pointing → false; shadowed → false; pointing with an empty owner projection → true); `TestWriterPublishesHoldingEntriesOnlyInLifetimesMode` (lifetimes on with region HA off and on → entries; lifetimes off with either → none; `applyEdgesClaims` still runs before the served alias construction); `TestHoldingEntryListsOnlyExplicitPublishableAliases` (explicit pointing → listed; explicit not pointing → not; derived exact name → not; shadowed by a served row → not; the synthetic catch-all row → not; a mixed-app prefix host → listed with empty `Account/App`); `TestTargetPublishesWhileLanding`; `TestHoldingEntryStopsWhenTheRecordClearsOrChangesID`; `TestViewSnapshotIsImmutableAndRevisioned` (refresh then fail then refresh → revs 1, 2, 3; `onChange` receives each published value; a reader holding snapshot 1 still sees snapshot 1's holds; the moves pass's listing error publishes a failed snapshot).
+- [ ] **Step 2: Run to verify they fail; Step 3: implement; Step 4: run** `go test ./internal/host -run 'Holding|ViewSnapshot' -count=1`, `go test ./internal/domains -run 'Explicit|Provenance' -count=1`, `go test ./internal/registry -run 'Alias' -count=1`, `go test ./internal/edgesfile -run 'AliasPublishable' -count=1`.
+- [ ] **Step 5: Commit** `edges: explicit alias provenance in the registry and the reconciler; the lifetimes writer publishes held rows as holding entries; the offline view publishes immutable revisioned snapshots`.
### Task 2: Candidates, the ranking, the page, and the local edge
**Files:**
-- Modify: `internal/edge/homemap.go` (`edgesFileView.OfflineMoves []holdingView`, `fetchedAt`; `homeSnapshot.cands map[string][]candidate`; `foldHomes` builds candidates; `rank(name, cands, now) decision`; `resolveExact`, `resolveRequest`; `Home`, `ExactHome`, `Owner`, `KnownHost`, `Hosts`, `ServedHosts` over the exact resolution), `internal/edge/holding.go` (new), `internal/edge/edge.go` (`Reload` builds `localCands` **from the upstream table it just built**: one ready candidate per table entry with `up` attached (alias flag, target = the primary's host for an alias entry, owner from `up.route`, the pair from `up.route.Incarnation/ServingSeq`), plus one holding candidate per held row the holding source knows (owner from the row); `e.resolve(host, now) decision`; the request paths below), `internal/edge/certsync.go`, `internal/edge/wildcard.go` (exact predicates), the agent (`edge.SetHoldingSource`, the view's `onChange` → `edge.RefreshHolds(rev)`, `edge.WithBoxID`)
-- Test: `internal/edge/rank_test.go`, `internal/edge/holding_test.go`, `internal/edge/homemap_test.go`
+- Modify: `internal/edge/homemap.go` (`edgesFileView.OfflineMoves []holdingView \`json:"offline_moves"\`` and `fetchedAt time.Time`; `homeSnapshot.cands map[string][]candidate`; `foldHomes` is **kept as the ready-only fold** and gains the pair rule from plan 1; `buildCandidates(files, self, privateDial) map[string][]candidate` runs the same per-file admission loop; `rank`, `resolveExact`, `resolveRequest`; `Home`, `ExactHome`, `Owner`, `KnownHost`, `Hosts`, `ServedHosts` over the exact resolution; `HoldingHosts()` new), `internal/edge/holding.go` (new: `renderHoldingPage`, `holdingAnswer`, `holdSnapshot`, `RefreshHolds`), `internal/edge/edge.go` (`resolve`, the request paths, `Allowed`, `certNames`), `internal/edge/certsync.go`, `internal/edge/certwarm.go` (`warmSet` adds `certNames()`), `internal/edge/wildcard.go` (exact predicates; `currentHosts` adds held platform rows), the agent (`edge.SetHoldingSource(view)`, `view.onChange = func(s *offlineHolds) { edge.RefreshHolds(s) }`, `edge.WithBoxID(boxID)` where `boxID` is the same value plan 2 gives `moveCfg.BoxID`)
+- Test: `internal/edge/rank_test.go`, `internal/edge/holding_test.go`, `internal/edge/homemap_test.go`, `internal/edge/decision_test.go`
**Interfaces:**
- `type holdingView struct { Host string \`json:"host"\`; Platform bool \`json:"platform"\`; Account string \`json:"account"\`; App string \`json:"app"\`; Aliases []string \`json:"aliases"\`; MoveID string \`json:"move_id"\`; Incarnation uint64 \`json:"incarnation"\`; ServingSeq uint64 \`json:"serving_seq"\`; OfflineAt int64 \`json:"offline_at"\`; EstimateSeconds int \`json:"estimate_seconds"\`; DisplayName string \`json:"display_name"\` }`.
-- `type candidate struct { kind candKind (candReady, candHolding, candCert); local bool; order int (the file's sorted index; local is -1); eip, box string; account, app string; alias, wildcard, platform bool; targetHost string; incarnation, servingSeq uint64; stamp int64; fetchedAt time.Time; hold *holdingView; up *upstream (local only) }`. Remote constructors copy the file's `fetchedAt`, `box`, `order` and `eip`; a holding entry yields one real `candHolding` for its host (`targetHost = host`) and one alias `candHolding` per listed alias (`targetHost = host`), all with the entry's owner.
-- `type decision struct { kind decKind (decLocal, decRemote, decHolding, decNone); up *upstream; eip, box, account, app string; stamp int64; platform, wildcard bool; hold *holdingView }`.
-- **`rank(name, cands, now)`**: (1) remote `candHolding` with `now.Sub(fetchedAt) >= holdingValidity` are removed; (2) `platform`/`wildcard` are the OR over all candidates; `candCert` never decides; nothing else → `decNone`; (3) **stage one, real candidates** (non-alias ready or holding): if any exist, alias candidates are dropped and the real winner is: the highest-stamped stamped ready candidate if any is stamped (holds are never stamped); else the highest `(incarnation, servingSeq)`; a tie between ready and holding → ready; a tie among ready → the lowest `order`; among holding → the lowest `order`; (4) **stage two, alias candidates only**: group by `targetHost` (an empty target is its own group); within a group the winner is: a stamped ready if any (highest stamp); else the highest pair, ready beating holding on a tie, lowest `order` among equals; a group whose winner is ready is **live**, otherwise **held**; if any live group exists → the winner among live groups' winners by (highest stamp, then lowest `order`) **never by pair across targets**, and every held group is dropped; if no live group → the held winner with the lowest `order`; (5) the winner's whole state is the decision (`decLocal` carries `up`; `decRemote` carries `eip, box, account, app, stamp`; `decHolding` carries `hold`).
-- Invariants the property test asserts (Task 3): a real candidate always beats every alias candidate; a stamped ready real candidate beats every other real candidate; a holding candidate never beats a stamped candidate; a ready alias of target T beats a holding alias of a different target whatever the pairs; a holding alias of T beats a ready alias of T with a lower pair and loses to one with a higher or equal pair; an expired remote hold decides nothing; pairs are never compared across different targets; the decision is independent of input order except through `order`.
-- `func (m *homeMap) resolveExact(name string, now time.Time) decision` (the exact candidates; never a wildcard); `func (m *homeMap) resolveRequest(name string, now time.Time) decision` (exact; if `decNone` and the exact name has no live `decHolding` → `rank` over `*.<parent>`'s candidates); `Home`/`ExactHome` answer only `decRemote` from `resolveExact`/`resolveRequest` as today's split (`Home` request, `ExactHome` exact); `Owner` from the same decision; `KnownHost(name, now)` = `resolveExact` is `decRemote` or `decHolding` and `name` is not a `*.` label; `Hosts()` adds a `decHolding` winner's host when `platform`.
-- `func (e *Edge) resolve(host string, now time.Time) decision`: local exact candidates (and local wildcard entries when the exact name has none) merged with the remote candidates, through `rank`, with the wildcard fallback rule above; **the handler uses `dec.up`** for local dispatch: path-prefix selection runs over `dec.up.paths`, the login re-resolution looks up the path in the same snapshot, the door's `doorOwnsRoute` checks `dec.up.route`, a remote decision's `Owner` is `dec.account/app`, and `backhaulTo` is called with `dec.eip, dec.box`: one decision for the whole request.
-- **Request paths**: HTTPS `ServeHTTP`: after `acceptHop`: `decLocal` → the local path (as today from `dec.up`); `decRemote` → an accepted hop → `staleHopRefusal`; a door → `Owner` must match else `unknownHost`; else `backhaulTo`; `decHolding` → an accepted hop → the page (no forwarding); a door → the page only when `hold.Account/App` equal the door's, else `unknownHost`; else the page; `decNone` → an accepted hop → `staleHopRefusal` (today); else `unknownHost` (today). HTTP fallback: `decLocal`/`decRemote`/`decHolding` → the HTTPS redirect; `decNone` → today. `Allowed(host)` = a local exact ready or local exact holding candidate and not a `*.` name; `deploymentHost` = `Allowed || KnownHost`; `currentHosts` = the local exact ready **and held** platform/provisioned non-alias rows (so wildcard parents persist through a hold); `homeMap.Hosts()` as above.
-- The page: `func renderHoldingPage(displayName string, untilUnix int64, now time.Time) []byte` with `untilUnix = OfflineAt + EstimateSeconds` when both are non-zero, else 0; the sentences and client branches as the Global Constraints state; `func (e *Edge) holdingAnswer(w, r, h holdingView)`.
-- `RefreshHolds(rev uint64)`: rebuilds the local holding candidates; rebuilds are serialised by a mutex and a rebuild for an older `rev` than the installed one is discarded; `rev` from a failed view → no local holding candidates.
-
-- [ ] **Step 1: Write the failing tests**: `TestHoldingViewDecodesTheWriterFixture`; `TestRankStageOne` (the real rules incl. the existing `TestHomeMapAStampedAliasDoesNotBeatAnUnstampedRoute` still passing, and a stamped alias against a real holding candidate → the hold); `TestRankStageTwo` (the three-publication case in all six orders; the `(1,2)`-ready-A/`(1,1)`-hold-A/`(9,1)`-hold-B case → A; two holds → the lower `order`); `TestResolveExactVersusRequest` (an expired exact hold → the wildcard; a live exact hold blocks it; `KnownHost` false for an invented wildcard label; `Allowed` rejects `*.`); `TestDecisionCarriesTheUpstream` (path prefixes and the login re-resolution use `dec.up`; a reload between resolution and dispatch does not change the served upstream); `TestLocalCandidatesAreTheUpstreamTable` (a non-pointing local alias serves; sidecars and backend-less rows absent); `TestLocalAndRemoteArbitrateTogether`; `TestEveryRequestPathHonoursTheDecision` (the table above including hops and doors with and without ownership); `TestLocalHoldsFollowTheViewRevision` (a failed view after an installed hold drops it; an older rebuild is discarded); `TestHoldingPageRendersEveryCase` (name, no name, no estimate, past until, escaping, HEAD; the script's `until`, cadences, storage key and no-estimate branch asserted by text); `TestTLSCoverageForHoldingHosts` (real handshakes; a wildcard-only platform host held keeps its parent; an invented label gets no issuance and no challenge read).
-- [ ] **Step 2: Run to verify they fail; Step 3: implement; Step 4: run** `go test ./internal/edge -run 'Rank|Holding|Resolve|Decision|LocalCandidates|LocalAndRemote|RequestPath|TLSCoverage|HomeMap' -count=1`.
-- [ ] **Step 5: Commit** `edge: one ranking over local and remote candidates decides routing, the holding page and certificate admission, exact and request resolution apart`.
-
-### Task 3: The alias ranking's witnesses
+- `type candidate struct { kind candKind (candReady, candHolding, candCert); local bool; order int (the file's sorted index; local is -1); eip, box string; account, app string; alias, wildcard, platform bool; targetHost string; incarnation, servingSeq uint64; stamp int64; fetchedAt time.Time; hold *holdingView; up *upstream (local only); table map[string]upstream (local only: the table `up` came from) }`.
+- **Remote constructors** (`buildCandidates`): the per-file admission is `foldHomes`'s today, unchanged: files iterated in sorted key order (`order` = index); `f.Box == ""` or `f.Box == self` skipped (own-box exclusion); `addr = dialAddress(f, own, ownKnown, privateDial)` (so same-region private dialling is retained); `hosts` entries: role `home` with `addr == ""` → cert-only only for a `*.` wildcard key else skipped; role `wildcard` only on a `*.` key else skipped; unknown roles skipped; a ready candidate `{candReady, eip: addr, box, account, app, alias: h.Alias, wildcard: h.Wildcard, platform: h.Platform && !h.Alias && !h.Wildcard, targetHost: h.TargetHost (plan 1; "" when absent) for an alias, the host itself for a route, incarnation/servingSeq (plan 1), stamp: h.Stamp, fetchedAt}`; `offline_moves` entries (only when `addr != ""`, the same requirement a dial target has): one `candHolding` for `Host` `{targetHost: Host, platform: entry.Platform, account/app: entry's, incarnation/servingSeq: entry's, alias: false, hold: &entry}` and one alias `candHolding` per listed alias `{alias: true, targetHost: Host, platform: false, the same owner and pair, hold: &entry}`; holds carry `stamp: 0` always.
+- **Local constructors** (`Reload` for ready, `RefreshHolds` for holds): for every entry `(name, up)` of the table `Reload` just built: a `candReady{local: true, order: -1, alias: up.alias, targetHost: up.route.Host when up.alias else name, account/app: from up.route, incarnation/servingSeq: up.route's pair, platform: up.route.Platform || up.route.Provisioned, wildcard: strings.HasPrefix(name, "*."), stamp: 0 (local candidates' stamps are not consulted: local-first is the rule, as today), up: &up, table: the new table}`. Local holds: for every registry row the hold snapshot knows (`registry.IsHold(r.Status) && r.HoldOp == mv.ID`): one real `candHolding{local, targetHost: r.Host, platform: r.Platform || r.Provisioned, owner from the row, pair from the row, hold: a holdingView built from the row and the move}` and one alias `candHolding{alias: true, targetHost: r.Host, platform: false}` per `route_aliases` row with `a.Host == r.Host && a.Explicit && edgesfile.AliasPublishable(a, true, shadowed)` (the same predicate the writer uses, so the source box serves its own alias the page even though the home map excludes its own publication).
+- `type decision struct { kind decKind (decLocal, decRemote, decHolding, decNone); up *upstream; table map[string]upstream; eip, box, account, app string; stamp int64; platform, wildcard bool; hold *holdingView }`.
+- **`rank(name string, cands []candidate, now time.Time) decision`:**
+ 1. remote `candHolding` with `now.Sub(fetchedAt) >= holdingValidity` are removed;
+ 2. `platform`/`wildcard` are the OR over all candidates; `candCert` never decides; nothing else present → `decNone`;
+ 3. **suppression:** for every remaining `candHolding` h: every `candReady` c with `c.targetHost == h.targetHost && c.targetHost != "" && c.stamp == 0 && (c.incarnation, c.servingSeq) < (h.incarnation, h.servingSeq)` is removed (a stale publication of the moving target). Nothing else is removed: a stamped ready, a ready of another target, a ready with an empty target, a ready with an equal or higher pair all survive;
+ 4. **ready:** if any `candReady` survives: a local one → `decLocal` with its `up` and `table` (local first, as today; two local candidates for one name do not occur; the table has one entry per name); else the remote survivors folded by `foldHomes`'s rule applied in `order` (`dialBeats` with plan 1's pair rule: a route beats an alias; a dial target beats none; between two of the same kind sharing a **non-empty** `targetHost` the higher pair wins and an equal pair falls to today's rule; otherwise today's rule: a higher stamp wins, else the earlier `order` keeps it) → `decRemote` with the winner's `eip, box, account, app, stamp`;
+ 5. **holding:** no ready survives: among `candHolding`, a real (non-alias) hold beats an alias hold; among equals of the same `targetHost` the higher pair wins; otherwise the lowest `order` (local `-1` first) → `decHolding` with `hold`.
+- **Invariants (Task 3 asserts them on the small model):** (a) with no live `candHolding`, `rank` equals the fold-plus-local-first answer; (b) a stamped ready candidate is never suppressed and never loses to a hold; (c) a ready candidate of a different target than every hold is never suppressed and always beats every hold; (d) a hold suppresses exactly the unstamped ready candidates of its own non-empty target with a lower pair; (e) a ready candidate of the hold's target with an equal or higher pair beats it; (f) an expired remote hold neither suppresses nor decides; (g) pairs are compared only between candidates sharing a non-empty target; (h) a real ready beats an alias ready whatever the pairs and stamps (#287); (i) the decision is independent of input order except through `order`; (j) `platform`/`wildcard` and the owner come from the winner's own candidate (classes OR'd), never from an alias for the certificate parent set.
+- `func (m *homeMap) resolveExact(name string, now time.Time) decision` (the exact candidates; never a wildcard); `func (m *homeMap) resolveRequest(name string, now time.Time) decision` (exact; if `decNone` and the exact name has no live `decHolding` → `rank` over `*.<parent>`'s candidates); `Home`/`ExactHome` answer only `decRemote` from `resolveRequest`/`resolveExact` as today's split; `Owner` from the same decision; `KnownHost(name, now)` = `resolveExact` is `decRemote` or `decHolding` and `name` is not a `*.` label; `Hosts()` adds a `decHolding` winner's host when `platform`; `ServedHosts()` unchanged (ready winners only; **a hold is never served evidence**); `HoldingHosts(now)` new: the exact names (hosts and aliases) whose `resolveExact` is a live `decHolding`.
+- **The local edge:** `type holdSnapshot struct { rev uint64; cands map[string][]candidate }` in `e.holds atomic.Pointer[holdSnapshot]`, written **only** by `RefreshHolds(s *offlineHolds)`: builds the local holding candidates from `s.Holds` (none when `s.Failed` or `s == nil`) and the registry, and installs under `e.holdsMu` only if `s.Rev` is greater than the installed rev (an older rebuild is discarded); after installing, outside the lock, it sends the wildcard manager the same parent-set notification `Reload` sends after installing a new table. `Reload` never writes `e.holds`, so it cannot reinstall a stale hold. `func (e *Edge) resolve(host string, now time.Time) decision`: the exact candidates = `e.current()`'s entry for `host` (as a local ready candidate), `e.holds.Load().cands[host]`, and the home map's exact candidates; through `rank`; `decNone` and no live exact hold → the same over `*.<parent>` (the local wildcard entry and the remote wildcard candidates). **The handler uses `dec.up` and `dec.table`:** path-prefix selection runs over `dec.up.paths` and resolves the target hostname in `dec.table` (`selectUpstream(dec, r.URL.Path) *upstream`, preserving today's cleaned/raw segment matching, exact target lookup and one-hop selection); the login re-resolution looks its path up in the same `dec.table`; the door's `doorOwnsRoute` checks the **finally selected** upstream's route; a remote decision's owner is `dec.account/app`; `backhaulTo` is called with `dec.eip, dec.box`: one decision and one table for the whole request.
+- **Request paths:** HTTPS `ServeHTTP`: after `acceptHop`: `decLocal` → the local path from `selectUpstream`; `decRemote` → an accepted hop → `staleHopRefusal`; a door → `Owner` must match else `unknownHost`; else `backhaulTo`; `decHolding` → an accepted hop → the page (no forwarding); a door → the page only when `hold.Account/App` equal the door's and are non-empty, else `unknownHost`; else the page; `decNone` → an accepted hop → `staleHopRefusal`; else `unknownHost`. HTTP fallback: `decLocal`/`decRemote`/`decHolding` → the HTTPS redirect; `decNone` → today.
+- **TLS (F6):** `Allowed(host)`: `*.` → false; the tunnel hostname (`e.tunnels != nil && host == e.tunnels.Host()`) → true, as today; else a local exact `candReady` or a local exact `candHolding` exists for `host`. `deploymentHost = Allowed || KnownHost`. `currentHosts()` = the platform/provisioned non-alias names among the local table **and** the local real holds (so wildcard parents persist through a hold). `func (e *Edge) certNames() []string` = the local hold names (hosts and aliases) plus `homes.HoldingHosts(now)`; `warmSet` considers `certNames()` after `ServedHosts()` under the same exclusions (apps domain, wildcard-covered, `*.`); the issuer election is unchanged. `ServedHosts` is not widened.
+- The page: `func renderHoldingPage(displayName string, untilUnix int64, now time.Time) []byte` and `func (e *Edge) holdingAnswer(w, r, h *holdingView)` with `untilUnix = h.OfflineAt + int64(h.EstimateSeconds)` when both are non-zero, else 0; the sentences, N, the headers and the client's branches exactly as the Global Constraints state.
+
+- [ ] **Step 1: Write the failing tests**: `TestHoldingViewDecodesTheWriterFixture` (`internal/edgesfile/testdata/offline-moves.json`); `TestRankSuppressionAndFold` (the hold suppresses only its target's lower unstamped pairs; the F1 four-candidate set (A-ready `(1,0)`, A-hold `(1,1)`, B-ready `(1,0)`, B-hold `(1,1)`) → the lower-`order` hold; the F2 three-publication set (A-old `(1,0)` at 0, B-ready `(1,0)` at 1, A-new `(1,2)` at 2, no holds) → A-new, as `foldHomes`; `TestHomeMapAStampedAliasDoesNotBeatAnUnstampedRoute` still passing; a stamped alias against a real hold → the stamped alias (§6.4)); `TestResolveExactVersusRequest` (an expired exact hold → the wildcard; a live exact hold blocks it; `KnownHost` false for an invented wildcard label; `Allowed` rejects `*.` and admits the tunnel host); `TestDecisionCarriesTheTable` (a prefix target and the login re-resolution come from `dec.table`; a reload between resolution and dispatch that changes the prefix target's owner and backend does not change what this request serves; the door check runs against the finally selected route); `TestLocalCandidatesAreTheUpstreamTable` (a non-pointing local alias serves; sidecars and backend-less rows absent); `TestLocalHoldsIncludeExplicitAliases` (the own publication excluded from the home map; the source's explicit pointing alias → the page; its non-pointing alias → serves as today; a derived alias → not held); `TestRemoteCandidatesKeepTheAdmissions` (own box excluded; `Box == ""` skipped; unknown role skipped; a file without an address yields cert-only wildcards and no holds; same-region private dialling); `TestLocalAndRemoteArbitrateTogether` (Review Focus 4); `TestEveryRequestPathHonoursTheDecision` (the table above including hops and doors with and without ownership and with an empty-owner hold); `TestLocalHoldsFollowTheViewSnapshot` (a failed snapshot after an installed hold drops it; an older snapshot is discarded; a `Reload` that overlaps a `RefreshHolds` never reinstalls a hold: Reload at rev 7 captured, RefreshHolds(8 failed), Reload finishes → no hold; success → failure → recovery without registry changes; DNS eligibility change); `TestHoldingPageRendersEveryCase` (name, no name, no estimate, past `until`, N = ceil with minimum 1 at 61 s, 60 s, 59 s, 1 s, escaping of `<b>`, HEAD; the script's `until`, both cadences, the local sentence switch at `until`, the storage key and the try/catch asserted by text); `TestTLSCoverageForHoldingHosts` (real handshakes; a wildcard-only platform host held keeps its parent; an invented label gets no issuance and no challenge read; the tunnel host still admitted; a holding-only custom domain is in `certNames` and not in `ServedHosts`; an issuer warms it, then a non-issuer's handshake for it succeeds from the shared cache).
+- [ ] **Step 2: Run to verify they fail; Step 3: implement; Step 4: run** `go test ./internal/edge -run 'Rank|Holding|Resolve|Decision|LocalCandidates|LocalHolds|RemoteCandidates|LocalAndRemote|RequestPath|TLSCoverage|HomeMap' -count=1`.
+- [ ] **Step 5: Commit** `edge: holds suppress their target's stale publications and the fold decides the rest; one decision and one table per request; local holds in their own snapshot`.
+
+### Task 3: The ranking's witnesses
**Files:**
- Test: `internal/edge/alias_test.go`, `internal/edge/rank_property_test.go`
-- [ ] **Step 1: Write the tests**: `TestAliasOfTheMovingInstanceLosesToItsHold`; `TestAliasRepointedDuringAMove` (B wins in all six orders, fresh and retained, with unequal unrelated pairs on A); `TestTwoHoldsForOneAlias` (the lower `order`); `TestHoldingAliasNeverBeatsARealRoute`; `TestStampedAliasBeatsEveryHoldInItsGroup`; `TestAliasRankingProperties` (enumerate every candidate set of the small model and assert Task 2's invariants).
-- [ ] **Step 2: Run; fix `rank` if anything fails; Step 3: Commit** `edge: the alias ranking pinned by witnesses and a small-model property test`.
+- [ ] **Step 1: Write the tests**: `TestAliasOfTheMovingInstanceLosesToItsHold` (Review Focus 3); `TestAliasRepointedDuringAMove` (B wins in all six orders, fresh and retained, with unequal unrelated pairs on A; detached, the alias falls to a derived entry of another target); `TestTwoHoldsForOneAlias` (the lower `order`; local first); `TestHoldingAliasNeverBeatsAReadyRouteOfAnotherTarget`; `TestStampedCandidateIsNeverSuppressed`; `TestRankEqualsFoldWithoutHolds` (every candidate set of the small model with no live hold: `rank` == the local-first-then-`foldHomes` answer, including the empty-target exception); `TestRankingProperties` (every candidate set of the small model: invariants (a) to (j)).
+- [ ] **Step 2: Run; fix `rank` if anything fails; Step 3: Commit** `edge: the ranking pinned by witnesses, a differential test against the fold and a small-model property test`.
### Task 4: The decoder inventory and its discovery check
**Files:**
-- Test: as rev 2 plus `internal/host/movesreport_offline_moves_test.go`, `internal/host/movespass_offline_moves_test.go`
-- Create: `internal/edgesfile/testdata/offline-moves.json`
+- Test: `internal/edgesfile/offline_moves_test.go`, `internal/console/edges_offline_moves_test.go`, `internal/domains/aliases_offline_moves_test.go`, `internal/steering/offline_moves_test.go`, `internal/host/edgesfile_decoders_test.go`, `internal/host/movesreport_offline_moves_test.go`, `internal/host/movespass_offline_moves_test.go`, `internal/hostactors/references_offline_moves_test.go`, `internal/edgesfile/audit_test.go`
+- Create: `internal/edgesfile/testdata/offline-moves.json` (one served route, one holding entry for another host with one alias, one certificate-only wildcard entry)
-The reviewed inventory with one assertion each: `edgesfile.Parse`, `ReadSnapshot`/`ReadLiveSnapshot`, `DeploymentRoutes` (with fresh status evidence), `EdgeIPs` and `LiveSnapshot` (classified unaffected, asserted unchanged on the fixture), the console's `loadEdgesJoin`, `offlineServes`, `boxchoice` and `regioncandidates` through their decision functions, `apidomains.go`'s read, `domains.ReconcileAliases` through its `DeploymentRoutes` input, `internal/steering`, `internal/host`'s `moveTargets`, `loadMoveFleet`, the writer's prior-publication recovery and `seedEdgesFileGate` (an unchanged-gate round trip after `offline_moves` is added), the home map, `internal/hostactors/references.go`. The discovery check `TestEveryEdgesFileReadSiteIsInventoried` (go/ast with type resolution): every call to an `edgesfile` reader, and every `json.Unmarshal`/`(*json.Decoder).Decode` whose target type is declared in `edgesfile`, `edge`, `host`, `hostactors` or `console` and either carries a top-level `hosts` or `box_id` JSON tag or is reached through a wrapper of those readers, must be in the inventory; fixtures for the narrow box-id reader and for an unrelated `hosts` struct prove the detector's shape.
+The reviewed inventory, each with one assertion over the fixture: `edgesfile.Parse` (`File.Hosts` has the route, not the hold); `ReadSnapshot`/`ReadLiveSnapshot` (`PlacementView.Hosts`); `DeploymentRoutes` (with fresh status evidence so the served route is present; the hold absent); `EdgeIPs` and `LiveSnapshot` (classified unaffected, asserted unchanged); the console's `loadEdgesJoin`, `offlineServes`, `boxchoice` and `regioncandidates` through their decision functions (a candidate box is chosen for the served route's region; the held host contributes nothing), `apidomains.go`'s read (the held alias is not "taken"); `domains.ReconcileAliases` through its `DeploymentRoutes` input; `internal/steering` (`parseEdgesFileView`: the desired state ignores the hold); `internal/host`'s `moveTargets`, `loadMoveFleet`, the writer's prior-publication recovery and `seedEdgesFileGate` (an unchanged-gate round trip after `offline_moves` is added); the home map (the hold is a candidate, never in `ServedHosts`); `internal/hostactors/references.go` (the box-id reader ignores it). The decisions are compared with identical liveness and capability evidence, varying only `offline_moves`.
-- [ ] **Step 1: Write the tests. Step 2: Commit** `edges: every edges-file read site is inventoried and ignores offline_moves for served-ness`.
+**The discovery check** `TestEveryEdgesFileReadSiteIsInventoried` (`go/ast` with `go/types` resolution over `./internal/...`): a read site is (i) any call to an `edgesfile` reader (`Parse`, `ReadSnapshot`, `ReadLiveSnapshot`, `DeploymentRoutes`, `EdgeIPs`, `LiveSnapshot`), (ii) any `json.Unmarshal`/`(*json.Decoder).Decode` whose target type (after pointer and alias resolution, including named wrappers) has a top-level struct field tagged `json:"hosts"` or `json:"box_id"`, in **any** package under `internal/` (no package restriction; this finds `steering.parseEdgesFileView`), or (iii) a function that wraps a site of (i) or (ii) and is itself called elsewhere. Every site must be in the inventory list in the test, or in the **reviewed exclusion list** (types with a `hosts` field that are not edges files, each named with a one-line reason). Fixture packages under `internal/edgesfile/testdata/audit/` prove the detector: an unrelated `hosts` struct in an inventoried package (must be flagged until excluded), the anonymous box-id reader, a wrapper and a type alias (all flagged), and the real `steering.parseEdgesFileView` (flagged). The list and the exclusion list are both pinned (a removal from code fails the test too).
+
+- [ ] **Step 1: Write the tests. Step 2: Commit** `edges: every edges-file read site is inventoried, discovered by type, and ignores offline_moves for served-ness`.
### Task 5: Freshness
**Files:**
-- Modify: `internal/edge/homemap.go` (`fetchedAt` on a successful parse, carried forward otherwise; `rank` applies the rule)
+- Modify: `internal/edge/homemap.go` (`fetchedAt` set by `refreshHosts` on a successful parse of that file and carried forward with a carried-forward file; `rank` applies the rule)
- Test: `internal/edge/homemap_test.go`
-- [ ] **Step 1: Write the failing tests**: `TestHoldingEntriesExpireFromTheFetchClock` (Review Focus 1's cases including the local and remote wildcard fallbacks and a failed `List`, every `Get`, and a partial failure); `TestExpiredHoldFallsBackToTheRetainedRoute`; `TestExpiredHoldNoLongerAdmitsACertificate`.
+- [ ] **Step 1: Write the failing tests**: `TestHoldingEntriesExpireFromTheFetchClock` (Review Focus 1's cases at 4 m 59 s, 5 m 0 s and 5 m 1 s including the local and remote wildcard fallbacks, a failed `List`, every `Get` failing, and a partial failure); `TestExpiredHoldFallsBackToTheRetainedRoute`; `TestExpiredHoldNoLongerAdmitsACertificate` (`KnownHost` and `certNames` drop it; `Allowed` for a local hold is unaffected by the remote clock).
- [ ] **Step 2: Run; Step 3: implement; Step 4: run** `go test ./internal/edge -run 'Expire|Holding' -count=1`. **Step 5: Commit** `edge: holding entries expire five minutes after their fetch, with today's fallback including wildcards`.
### Task 6: Probe mode and the console's probe
**Files:**
-- Modify: `internal/edge/edge.go` (`probe` detection; the refusal in `backhaulTo`; for `decLocal` with `probe`: `cacheable = false` (no cache lookup, no `maybeStore`) and `w = &servedByWriter{...}` installed **outermost**, before the gzip writer and `countingWriter`; `Edge.WithBoxID`), `internal/edge/servedby.go` (new), `internal/console/selfserve_health.go` (`probeTarget` requires `X-Carlos-Served-By == row.Participants.Target.BoxID` and the sealed build)
+- Modify: `internal/edge/edge.go` (`probe := r.Header.Get("X-Carlos-Probe") == "local"`; the refusal in `backhaulTo` (503 "not served here", no dial) and in every path that would forward: ordinary, wildcard-exact and door-authorised; for `decLocal` with `probe`: `cacheable = false` (no cache lookup, no `maybeStore`) and `w = &servedByWriter{...}` installed **outermost**, before the gzip writer and `countingWriter`), `internal/edge/options.go` (`func WithBoxID(id string) RunOption`, package level, the convention of the other `RunOption`s; the agent passes plan 2's `moveCfg.BoxID`), `internal/edge/servedby.go` (new), `internal/console/selfserve_health.go` (`probeTarget` requires `X-Carlos-Served-By == row.Participants.Target.BoxID` and the sealed build's `x-carlos-version`)
- Test: `internal/edge/probe_test.go`, `internal/console/selfserve_health_test.go`
**Interfaces:**
-- `type servedByWriter struct { http.ResponseWriter; box string; final bool }`: `WriteHeader(code)`: `code < 200` → pass through (informational, no commit); otherwise commit once (`Del` every `X-Carlos-Served-By`, `Set` the box id, `Set Cache-Control: no-store`) and pass through; the first `Write` without a prior final `WriteHeader` → commit with 200 then write; `Flush()` → commit (200) if not yet, then flush when the underlying is an `http.Flusher`; `Hijack()` → commit is skipped (an upgrade's 101 is written on the connection by the hijacker; the probe never upgrades), delegate to the underlying `http.Hijacker` **directly** (so `countingWriter`'s assertion finds it); `Unwrap()` for `http.ResponseController`.
+- `type servedByWriter struct { http.ResponseWriter; box string; final bool }`: `WriteHeader(code)`: `code < 200` → pass through (informational, no commit); otherwise commit once (`Del` every `X-Carlos-Served-By`, `Set` the box id, `Set Cache-Control: no-store`) and pass through; the first `Write` without a prior final `WriteHeader` → commit with 200 then write; `Flush()` → commit (200) if not yet, then flush when the underlying is an `http.Flusher`; `Hijack()` → no commit, delegate to the underlying `http.Hijacker` **directly** (so `countingWriter`'s assertion finds it); `Unwrap()` for `http.ResponseController`.
-- [ ] **Step 1: Write the failing tests**: `TestProbeModeNeverPassesABackhauledAnswer` (the three forwarding paths); `TestProbeModeThroughTheRealStack` (`Edge.ServeHTTP` with gzip and the cache on: an implicit 200, a HEAD, a `Flush`, a `103` then `200` with a forged final header → the final response carries this box's id; the response is not stored and a later ordinary request misses the cache; an upgrade request on a non-probe path still hijacks); `TestConsoleProbeRequiresServedByAndVersion`.
+- [ ] **Step 1: Write the failing tests**: `TestProbeModeNeverPassesABackhauledAnswer` (the three forwarding paths → 503 "not served here", no dial recorded; ordinary forwarding on the same paths without the header still carries hop authentication, the forwarding headers, the no-key fallback and containment); `TestProbeModeThroughTheRealStack` (`Edge.ServeHTTP` with gzip and the cache on and **a populated cache entry for the URL**: an implicit 200, a HEAD, a `Flush`, a `103` then `200` with a forged final header → the final response carries this box's id and the backend saw the request (the cache was bypassed); the response is not stored and a later ordinary request still hits the old entry; an upgrade request on a non-probe path still hijacks); `TestConsoleProbeRequiresServedByAndVersion` (missing or wrong box id, a box label instead of an id, missing or wrong version, a redirect → each fails with its own sentence).
- [ ] **Step 2: Run; Step 3: implement; Step 4: run** `go test ./internal/edge -run 'Probe|ServedBy' -count=1`, `go test ./internal/console -run 'ConsoleProbe' -count=1`. **Step 5: Commit** `edge: probe mode refuses to forward and stamps the serving box on the final response through the real writer stack`.
### Task 7: End to end
**Files:**
-- Test: `internal/e2e/selfserve/holding_test.go`
+- Test: `internal/e2e/selfserve/holding_test.go` (plan 2's harness with a third edge process reading the edges files, and a custom domain attached as an explicit alias)
-- [ ] **Step 1: Write the tests**: rev 2's list with the reconstruction witness made exact: `TestAbortAfterLandingThenSourceReconstructionWithRetainedTargetHold` (start → source held `(1, 1)`; flip; abort → source reopened `(1, 2)`; finish → `move_seq` 3; **after finish** delete the source row; reconcile mints `(1, 3)` and the edges file publishes it; the target's holding publication at `(1, 1)` is retained on the third edge; the reconstructed route wins the request).
-- [ ] **Step 2: Run them; fix what they find within this plan's scope. Step 3: both census tests and the cross-builds; the census note with the console envelope. Step 4: Commit** `self-serve moves: the holding page end to end`; push.
+- [ ] **Step 1: Write the tests**: `TestHoldingPageOnAThirdEdgeAndACustomDomain` (from start to the target's opened publication the third edge answers 503 with the page for the host and the alias; after the open the page is replaced by the backhaul to the target **before** the health phase passes; an app that then fails on the target answers as the app does, no hold returns; the source box's own edge answers the page for its held row and its explicit alias); `TestAbortAfterLandingThenSourceReconstructionWithRetainedTargetHold` (start → source held `(1, 1)`; flip; abort → source reopened `(1, 2)`; finish → `move_seq` 3; **after finish** delete the source row; reconcile mints `(1, 3)` and the edges file publishes it; the target's holding publication at `(1, 1)` is retained on the third edge; the reconstructed route wins the request); `TestReadyRouteWinsOverStaleHoldAndHoldOverStaleRoute`; `TestFirstMoveWithPairlessRetainedPublication`; `TestRecreatedHostOutranksOldHoldingAndReadyPublications`; `TestCurrentHoldBeatsPreviousLifetimesReadyRoute`; `TestEdgeRestartedIntoMixedPublications`; `TestExpiredHoldingEntryFallsBackIncludingAfterAWholeRefreshFails`; `TestAliasWitnessesEndToEnd` (the explicit-to-derived **different-target** conversion: the alias is detached from the moving instance and derived for another target → the other target's route wins; and the same-target provenance conversion of Task 1).
+- [ ] **Step 2: Run them; fix what they find within this plan's scope. Step 3: both census tests and the cross-builds; write `docs/superpowers/notes/census-proposal-self-serve-moves-3.md` with the overlay's loader block, the changed envelopes (incl. `internal/edgesfile` and `internal/console`) and the registry field. Step 4: Commit** `self-serve moves: the holding page end to end on a third edge and a custom domain`; push.
---
## Self-review notes
-- Spec coverage as rev 2; the arbitration is now a ranking with invariants (T2, T3) and exact-versus-request resolution (T2, T5).
+- Spec coverage: §6.4 (T2 ranking, T3 witnesses, T5 expiry), §7.4 (T2 page, T6 probe), §11 (T7 reconstruction witness).
+- The ranking's ready-only path is the fold itself, so plan 1 and plan 3 cannot disagree (T3's differential test).
- Known limit, stated: the page's client script is pinned by text assertions on each branch; no browser runs in CI.
-- Names used across tasks: `RouteAlias.Explicit`, `edgesfile.AliasPublishable`, `holdingEntry`, `holdingSource`, `offlineView.rev/onChange`, `holdingView`, `candidate`/`candKind`/`order`, `decision`/`decKind`, `rank`, `resolveExact`, `resolveRequest`, `Edge.resolve`, `holdingValidity`, `renderHoldingPage`, `holdingAnswer`, `SetHoldingSource`, `RefreshHolds(rev)`, `WithBoxID`, `servedByWriter`, `probeTarget` (plan 2).
+- Names used across tasks: `RouteAlias.Explicit`, `edgesfile.AliasPublishable`, `holdingEntry`, `holdingSource`, `offlineHolds`, `offlineView.snapshot/onChange`, `holdingView`, `candidate`/`candKind`/`order`/`table`, `decision`/`decKind`, `buildCandidates`, `rank`, `resolveExact`, `resolveRequest`, `HoldingHosts`, `Edge.resolve`, `selectUpstream`, `holdSnapshot`, `RefreshHolds(*offlineHolds)`, `certNames`, `holdingValidity`, `renderHoldingPage`, `holdingAnswer`, `SetHoldingSource`, `WithBoxID` (package-level `RunOption`), `servedByWriter`, `probeTarget` (plan 2).
- Review Focus 1 to 5 are pinned by T5, T3, T3, T2, T6.