carlos / platform Public

docs: fix round 1 — corrected #250 reproduction, restored two rollout minors

Review round 1 (spec ✅, quality 4 Important): rollout item 11's #250
reproduction was the ledger's pre-correction phrasing (-grant/-backing
bare); rewritten to the actual reproduction (public-addr instance route
via -backing unit then -grant; any everywhere-class row incl. static via
-tunnel, which has no kind rail at all). Restored two rollout-relevant
minors dropped from the original sweep: Task 2's corrupt-mesh-key
operator-delete-only recovery path (beside item 2), and Task 3's silent
staleHopRefusal (beside item 14).
Paul Campbell pushed by paul@keymail.dev e0ebd88bdc2983ce0386a9eff99a3737b5802ba9 parent d5705d8
1 file changed, +43 −9
  • docs/superpowers/plans/2026-08-22-multi-region-backhaul.md +43 −9
diff --git a/docs/superpowers/plans/2026-08-22-multi-region-backhaul.md b/docs/superpowers/plans/2026-08-22-multi-region-backhaul.md
index 44ff9d203..20c1ef82d 100644
--- a/docs/superpowers/plans/2026-08-22-multi-region-backhaul.md
+++ b/docs/superpowers/plans/2026-08-22-multi-region-backhaul.md
@@ -518,7 +518,14 @@ plan's own spec Part 4 describes.
cadence) per Task 2's ledgered honest-header fix (`0c07490`) — during
that window some edges verify hops under the old key, some under
`.next`; dual-accept on both is what makes that safe, not a race to
- avoid.
+ avoid. **A corrupt key is operator-delete only.** `mintMeshKey` is
+ create-only (Task 2) — it never overwrites an existing
+ `control/edge-mesh-key`, so a key that gets corrupted in the bucket
+ (a bad manual edit, a truncated write) is never self-repaired by any
+ box's `mintMeshKeyLoop`. Recovery is manual: delete the object; the
+ next box to hold the housekeeper lease re-mints on its next tick.
+ Worth knowing before touching that key by hand during an arming
+ window, not just at a real rotation.
3. **Exercise the backhaul path on dev/Kass before flagship AU.**
Flagship AU is the spec's designated first target because a steering
mistake there "burns nobody's ticket sales" (spec Part 1 decisions) —
@@ -674,13 +681,29 @@ plan's own spec Part 4 describes.
materialized on every platform box, and the removal phase reads the
class off the ROW (`everywhereRoute`), which deliberately answers
"home-boxed" for any row carrying `Backing`, `Tunnel` or `Caps` — a
- conservative choice that also means: mint a static record on a far
- box, `carlos route -grant`/`-backing`/`-tunnel` that far copy, delete
- the record — the home box retires its route, the far box keeps
- serving the retired site forever with its grants intact and no record
- anywhere to explain why. The gate stays conservative on purpose (the
- alternative hands an unattended bucket-record deletion the
- capability-grant purge's human-gated contract), so the fix is
+ conservative choice, but it means such a row is never retired once
+ touched. **Corrected reproduction** (the plain `-grant`/`-backing`
+ phrasing this note originally carried does not hold: both rails
+ refuse non-instance rows — `grantRail`, `cli.go:1197`, refuses
+ anything but a unit-backed instance route; `backingRail`, `cli.go:1370`,
+ refuses any non-instance kind outright — so neither reaches a static
+ row directly):
+ - a public-addr **instance** route (the Everywhere-probed class) on a
+ far box: `carlos route -backing unit` succeeds (kind `instance`,
+ not Platform), then `-grant <token>` succeeds (kind `instance`,
+ `Backing == unit`) — both rails clear because the row really is an
+ instance row, just a far-box copy of one;
+ - **or any everywhere-class row at all, static included**, touched
+ with `-tunnel <name>`: there is no `tunnelRail` — the tunnel axis
+ (`cli.go:951-974`) checks only `platformCanaryOwnedRow`, no kind
+ check exists, so a far box's static copy takes a tunnel stamp as
+ readily as an instance row.
+ Either path leaves `Backing`/`Tunnel` non-empty on the far copy, and
+ deleting the record then retires the home box's route while the far
+ box keeps serving the retired site forever, grants intact, with no
+ record left anywhere to explain why. The gate stays conservative on
+ purpose (the alternative hands an unattended bucket-record deletion
+ the capability-grant purge's human-gated contract), so the fix is
visibility + a supported cleanup verb, not a gate change. Relevant to
this rollout because everywhere-class adoption (this plan) is what
makes far-box copies routine for the first time — #250 was inert
@@ -717,7 +740,18 @@ plan's own spec Part 4 describes.
dead box will produce one line per failed request rather than a
throttled cadence — expected noise during the arming window, not a
defect, but flagged so it isn't mistaken for something worse the
- first time it's seen live.
+ first time it's seen live. **`staleHopRefusal` (`internal/edge/homemap.go:551`)
+ is silent in the same neighbourhood, the other direction** — a
+ sender stuck dialing a home box whose map has moved on gets its 503
+ answered correctly but nothing is LOGGED on either end, so a sender
+ looping into that refusal (map propagation stuck, a box that never
+ converged the move) produces zero signal that anything is wrong.
+ Task 3's review named this the "converge is not healing this" case
+ and wanted the same first-and-every-tenth cadence the mirror's
+ partial-poll logging already uses elsewhere in this branch — not
+ fixed here, but worth doing the next time anyone is in that file
+ rather than left to be rediscovered blind during a real stuck-move
+ incident.
**Full gate** (env `GOCACHE=/tmp/claude-1001/gocache GOFLAGS=-mod=mod
GOPROXY=off`): `go build ./...`, `go vet ./...`, `GOOS=darwin go build