carlos / platform Public

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>
Paul Campbell 6d42d94134d465704f58c47a3b12b0a4549e62e6 parent 11cdddf
2 files changed, +49 −6
  • README.md +23 −4
  • docs/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).