docs: amend the incident-fallback story to actual behavior — the footer link needs the bucket origin, and S3-served HTML currently downloads
README and the fleet-status-page design spec overstated the footer's "if this page is unreachable" link: it's built from location.origin client-side, so it only helps once you're already viewing the page from the bucket's own origin — it can't rescue a reader whose only view was the deployment's normal origin going dark. The real fallback for a down deployment is fetching status/fleet.json / index.html straight out of the bucket (aws s3 cp or the raw bucket URL). Also documented: index.html fetched directly from S3 currently downloads rather than renders, because Put sets no Content-Type — queued as a "content-type on publish" follow-up in the spec's §5 rollout list. store.go itself is untouched in this pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 files changed,
+49
−6
README.md+23 −4docs/superpowers/specs/2026-08-01-fleet-status-page-design.md+26 −2
diff --git a/README.md b/README.md| index 8b4deeba0..d7e445a60 100644 |
| --- a/README.md |
| +++ b/README.md |
| @@ -81,10 +81,29 @@ then reads every object it just published back out and fails loudly if any |
| of them leaked. There is no live-publish CLI verb; the 5-minute loop inside |
| `carlos agent` is the only publisher. |
| -**Honest limit.** A fully-private deployment (`STATUS_PUBLIC` unset) whose |
| -console happens to be down has no status page at all — the operator's |
| -fallback is reading the object straight out of the bucket: |
| +**Incident fallback, and its actual limits.** The page footer links "if this |
| +page is unreachable" straight at the bucket's own copy of `fleet.json`, but |
| +that link is built client-side from `location.origin` (see |
| +`internal/status/assets/index.html`) — it is only meaningful once you are |
| +*already* viewing the page served from the bucket's own origin (the public |
| +`STATUS_HOST`, or the raw bucket URL). It is not a link that rescues you from |
| +a *down* deployment; if the edge/console are unreachable you won't be looking |
| +at this page's footer in the first place. The real operational fallback for |
| +a down deployment is fetching the objects directly, either the raw bucket |
| +URL in a browser or: |
| aws s3 cp s3://<bucket>/status/fleet.json - |
| + aws s3 cp s3://<bucket>/status/index.html - |
| -That's the whole story; nothing papers over it. |
| +One current wrinkle: `index.html` fetched straight from S3 currently |
| +**downloads instead of rendering** in a browser, because the store's `Put` |
| +sets no `Content-Type` and S3 falls back to `application/octet-stream`. It |
| +still opens fine as plain text in an editor, and `aws s3 cp ... - | less` |
| +works, but "open the bucket URL in a browser and read it" doesn't work yet |
| +for the HTML — see the design spec's rollout list (§5) for the queued fix |
| +(setting content-type on publish). `fleet.json` is unaffected (any client |
| +reading raw JSON already expects to view its own headers). |
| + |
| +A fully-private deployment (`STATUS_PUBLIC` unset) whose console is down has |
| +no page at all — the `aws s3 cp` fallback above is the entire story there; |
| +nothing papers over it. |
diff --git a/docs/superpowers/specs/2026-08-01-fleet-status-page-design.md b/docs/superpowers/specs/2026-08-01-fleet-status-page-design.md| index 2f61eaf3a..77f7f78ae 100644 |
| --- a/docs/superpowers/specs/2026-08-01-fleet-status-page-design.md |
| +++ b/docs/superpowers/specs/2026-08-01-fleet-status-page-design.md |
| @@ -144,8 +144,25 @@ them: |
| litestream convention: install everywhere, inert until configured). |
| - **Incident fallback** — the reason this architecture won: on a public |
| deployment the raw bucket URL serves the identical page even when every |
| - box is down; the page footer links it as "if this page is unreachable". |
| - The status page outlives the deployment it reports on. |
| + box is down. The page footer links it as "if this page is unreachable", |
| + but that link is generated client-side from `location.origin` |
| + (`internal/status/assets/index.html`) — it is only a meaningful escape |
| + hatch once you're *already viewing the page from the bucket's own |
| + origin* (`STATUS_HOST`, or the raw bucket URL directly); it cannot help |
| + someone who only ever sees the deployment's normal origin go dark. The |
| + actual operational fallback for a down deployment — public or private — |
| + is fetching `status/fleet.json` / `status/index.html` straight out of |
| + the bucket by hand: the raw bucket URL, or `aws s3 cp |
| + s3://<bucket>/status/fleet.json -`. The status page's *data* outlives |
| + the deployment it reports on; the page's own convenience links do not |
| + rescue you from that deployment being unreachable in the first place. |
| +- **Known wrinkle, not yet fixed**: `index.html` fetched straight from S3 |
| + currently **downloads instead of rendering** in a browser, because |
| + `release.Store.Put` sets no `Content-Type` and S3 defaults the object to |
| + `application/octet-stream`. It still reads fine as plain text (`aws s3 |
| + cp ... - | less`, or any editor); it just doesn't open as a page yet. |
| + `fleet.json` is unaffected. Tracked as a rollout follow-up (§5) — |
| + content-type on publish — rather than fixed in this pass. |
| - **Honest limit**: a fully-private deployment whose console is down has |
| no status page — the operator falls back to `aws s3 cp` of |
| `fleet.json`; documented in the runbook, not papered over. |
| @@ -183,3 +200,10 @@ All build-failing, ported from Eleven's pattern: |
| grant, verify page + fallback URL. |
| 4. Tito: private by default, with the console-less fallback noted; the |
| operator can flip it public or deploy a console later. |
| +5. Queued follow-up, not blocking this rollout: **content-type on |
| + publish** — `release.Store.Put` sets no `Content-Type`, so |
| + `status/index.html` fetched straight from the bucket currently |
| + downloads instead of rendering in a browser (§3's "known wrinkle"). |
| + Needs a Put-time content-type either inferred from the key's extension |
| + or passed explicitly by the status publisher; out of scope for this |
| + pass (`store.go` is not touched here). |