carlos / platform Public

spec: account ids are sequential — 40-bit random gave 9-char sqids

Paul's call after seeing kass.nyfbskdye.oncarlos.com. Durability comes
from minted-once-into-the-bucket, not from randomness, so sequential is
exactly as safe as random here — the original bug was the REGISTRY
assigning by listing order. Also corrects the anti-enumeration rationale:
Certificate Transparency publishes every minted hostname, so sqids were
never secret; the only real disclosure is the account count.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 4875560dc0e14a198c6364216d5defa01fb0ab13 parent 5b4e8ff
1 file changed, +20 −3
  • docs/superpowers/specs/2026-08-02-multi-account-design.md +20 −3
diff --git a/docs/superpowers/specs/2026-08-02-multi-account-design.md b/docs/superpowers/specs/2026-08-02-multi-account-design.md
index 35d935064..171053d21 100644
--- a/docs/superpowers/specs/2026-08-02-multi-account-design.md
+++ b/docs/superpowers/specs/2026-08-02-multi-account-design.md
@@ -13,7 +13,12 @@ in §8 — which is what currently blocks enabling `PLATFORM_APPS_DOMAIN`.
An **account** is `{id, sqid, name, created_at}`:
-- **`id`** — a random 40-bit integer minted at creation. Not sequential.
+- **`id`** — a **sequential integer** minted at creation (next free id;
+ `1` is reserved for ops). Corrected 2026-08-02 after seeing the result:
+ 40-bit random ids encode to NINE-character sqids
+ (`kass.nyfbskdye.oncarlos.com`), which is not a URL anyone can read or
+ type. Sequential ids keep them short and grow the length linearly with
+ account count, with no cap.
- **`sqid`** — the frozen encoder (`internal/domains`) over that id; the
account's public identity, appearing in every platform domain
(`kass.<sqid>.oncarlos.com`) and every bucket key.
@@ -53,8 +58,20 @@ it into the bucket**, at `accounts/<sqid>/account.json`:
The registry only ever *materializes* what the bucket says — it never
assigns. The bucket is the control plane; identity belongs there like
-everything else. Randomness also means sqids are **not enumerable**: you
-cannot walk `bab`, `bac`, `bad` to discover who else is on a deployment.
+everything else. **Durability comes from minted-once-and-recorded, not
+from randomness** — which is why sequential ids are safe here and the
+original AUTOINCREMENT approach was not: that one let the *registry*
+assign in bucket-listing order, so a rebuild reassigned them.
+
+Minting must use a **create-only write**, retrying with the next id on
+conflict, so two concurrent minters can never claim the same integer.
+
+Sequential ids are enumerable, and that is accepted: every platform
+domain gets a Let's Encrypt certificate, and Certificate Transparency
+publishes those hostnames publicly regardless of how the sqid is
+generated — so sqids were never secret. The only genuine disclosure is
+the rough account count, which for a platform that publishes a fleet
+status page is not a secret worth an unreadable URL.
`ops` keeps the fixed id `1` (sqid `bab`), created by migration on every
deployment — so a console-less self-hoster is unaffected and every app