carlos / platform Public

spec+plans: final-review amendments — /img leg, status-route precedence, waitForTXT target, platform-parents-win

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 49d8890f4ff8841ddbfbeb552c6e87116d39b688 parent cfad842
2 files changed, +31 −8
  • docs/superpowers/plans/2026-08-12-woodstar-w0-relay-fanout.md +9 −5
  • docs/superpowers/specs/2026-08-12-woodstar-carloku-migration-design.md +22 −3
diff --git a/docs/superpowers/plans/2026-08-12-woodstar-w0-relay-fanout.md b/docs/superpowers/plans/2026-08-12-woodstar-w0-relay-fanout.md
index 7de62c891..f7e111893 100644
--- a/docs/superpowers/plans/2026-08-12-woodstar-w0-relay-fanout.md
+++ b/docs/superpowers/plans/2026-08-12-woodstar-w0-relay-fanout.md
@@ -30,7 +30,8 @@ behavior is opt-in via new flags; unset flags = byte-identical behavior.
unaffected (Caddy keeps the fan-out there).
- The fan-out route set mirrors `infra/devbox-init.sh`'s Caddy block
(the truthful superset of `infra/prod-Caddyfile`): `/media*`→media,
- `/api/*`→indexer, `/stream/*`→notify, `/unfurl*`→unfurl,
+ `/api/*`→indexer, `/stream/*`→notify, `/unfurl*` + `/img*`→unfurl
+ (`/img` serves every link-card image; added by final-review fix),
`/.well-known/webfinger` + `/.well-known/host-meta` + `/users/*` +
`/inbox` + `/ap/*`→apbridge, everything else→relay.
- Wildcard-host behavior mirrors `infra/prod-Caddyfile`'s
@@ -353,10 +354,13 @@ handler-only. If `go vet` flags it unused before Task 3 lands in the
same branch, that is expected within this task only — it is used two
tasks later; alternatively fold this function into Task 3's diff.)
Note: `/media*` and `/unfurl*` in Caddy are literal-prefix wildcards;
-the deployed client only ever requests `/media`, `/media/<hash>`, and
-`/unfurl?url=…`, so exact + `/`-prefix matching above is the honest
-equivalent (a hypothetical `/mediafoo` falling to the relay 404s in
-both worlds — the relay mux has no such route).
+the deployed client only ever requests `/media`, `/media/<hash>`,
+`/unfurl?url=…`, and `/img?u=…`, so exact + `/`-prefix matching above
+is the honest equivalent. (Correction from final review: a hypothetical
+`/mediafoo` falling to the relay does NOT 404 — extension-less unknown
+paths take the SPA fallback and return 200 HTML. The narrowing stands
+because the client never emits such paths, not because the fallback is
+harmless.)
- [ ] **Step 5: Run the tests to verify they pass**
diff --git a/docs/superpowers/specs/2026-08-12-woodstar-carloku-migration-design.md b/docs/superpowers/specs/2026-08-12-woodstar-carloku-migration-design.md
index e819bf46f..80af54a52 100644
--- a/docs/superpowers/specs/2026-08-12-woodstar-carloku-migration-design.md
+++ b/docs/superpowers/specs/2026-08-12-woodstar-carloku-migration-design.md
@@ -162,8 +162,18 @@ Mechanics (all provider-blind, the `dnsProvider` seam is untouched):
- `wildcard.go` computes `challengeFQDN = _acme-challenge.<parent>`
today. Delegated parents get a parent → delegation-target mapping;
- `solveDNS01` publishes at the target. `waitForTXT` polls the original
- name — resolvers follow the CNAME, so it already works.
+ `solveDNS01` publishes at the target. **`waitForTXT` polls the
+ delegation target, not the original name** (amended per final
+ review): we confirm the write we control, and the customer zone's
+ negative-cache TTL is not ours to bound. Consequence, stated:
+ `waitForTXT` is NOT a delegation-setup check — a missing customer
+ CNAME sails through the wait and fails at Let's Encrypt, one failed
+ validation per parent per hour (inside LE limits); the attach-surface
+ CNAME gate below is the real setup check.
+- **Platform parents always win** (final-review ruling): a delegated
+ entry colliding with a static domain or sitting under the apps
+ domain is dropped at `refresh()`, never preferred — one bad claim
+ must not degrade a live platform wildcard's renewal.
- **Declaration — two additive claim fields** (the #82 session's
catch: one bool must not fuse two orthogonal capabilities):
- `wildcard: true` — this domain is a **delegated wildcard parent
@@ -249,7 +259,9 @@ world expect Caddy's path fan-out on one origin. Fix is relay-side
`-notify-upstream`, `-unfurl-upstream`, `-apbridge-upstream` on
`cmd/relay`. When set, the relay reverse-proxies:
`/media` + `/media/*` → media (including `POST /media` uploads);
- `/api/*` → indexer; `/stream/*` → notify; `/unfurl*` → unfurl;
+ `/api/*` → indexer; `/stream/*` → notify; `/unfurl*` AND `/img*` →
+ unfurl (`/img` serves every link-card image — omitting it fails as a
+ 200 SPA page inside `<img>` tags, caught in final review);
`/.well-known/webfinger`, `/.well-known/host-meta`, `/users/*`,
`/inbox`, `/ap/*` → apbridge — the devbox Caddyfile's route set,
which is the truthful superset of prod's. `httputil.ReverseProxy`
@@ -317,6 +329,13 @@ units, data, routes.
**Cutover (HELD for Paul's explicit go; quiet hour; TTLs already 300):**
+Pre-flight invariant (final-review catch): **`status.woodstar.app`'s
+exact-host route MUST exist before the wildcard alias serves** — with
+the alias live and the status route missing, status requests 302 to
+`woodstar.app/status` (an SPA client route) and the status page
+silently becomes the app. Exact-host beats wildcard; verify the
+precedence in rehearsal.
+
1. Old box (SSH): stop the six `woodstar-*` units → run one final
media-backup pass → wait ~30 s for litestream's final WAL frames →
stop litestream → copy `apbridge.seed` off the box → verify newest