carlos / platform Public

Merge remote-tracking branch 'origin/main' into worktree-agent-a595b67f95408e50a

# Conflicts:
#	docs/superpowers/specs/2026-08-02-multi-account-design.md
Claude acc4509a3d88349a9527fcee3864643ada265e3c parent 7a9056eparent 4875560
1 file changed, +25 −20
  • docs/superpowers/specs/2026-08-02-multi-account-design.md +25 −20
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 6cb753e36..906eb9248 100644
--- a/docs/superpowers/specs/2026-08-02-multi-account-design.md
+++ b/docs/superpowers/specs/2026-08-02-multi-account-design.md
@@ -13,8 +13,12 @@ in §8 — which is what currently blocks enabling `PLATFORM_APPS_DOMAIN`.
An **account** is `{id, sqid, name, created_at}`:
-- **`id`** — the next free integer, allocated at creation by a create-only
- write into the bucket (§ "Durable identity"). Never a registry row number.
+- **`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.
@@ -54,24 +58,25 @@ Instead: **the id is minted once, into the bucket**, at
The registry only ever *materializes* what the bucket says — it never
assigns. The bucket is the control plane; identity belongs there like
-everything else. Durability comes from *minted once, recorded in the manifest,
-materialized-never-assigned* — not from how the number is chosen.
-
-**Sequential, not random** (`domains.MintAccount`): list the manifests, take
-the highest id, claim `max+1` with a **create-only** `Put`. Create-only is
-what makes the listing safe without a lock — two boxes minting at the same
-instant both pick the same integer, exactly one write wins, and the loser
-re-lists and takes the next one rather than overwriting a live account's
-identity. Retries are bounded; exhausting them fails loudly.
-
-Random ids were tried first and were a mistake: the encoder is base-23, so a
-random 40-bit id encodes to a **nine-character** sqid —
-`kass.nyfbskdye.oncarlos.com` — which nobody can read out, type, or recognise
-as theirs. Sequential gives `bab`, `bac`, `bad`…, growing one character per
-23× accounts. It gives up nothing: sqids are **not secret and never were**,
-because every platform hostname's certificate is published in Certificate
-Transparency within seconds of issue. Account existence is hidden where it can
-actually be hidden — a non-member gets the same 404 as a nonexistent page.
+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.
+Built as `domains.MintAccount`: list the manifests, take `max+1`, claim it
+with a create-only `Put`; the loser of a race re-lists and takes the next id
+rather than overwriting a live account's identity. Retries are bounded and
+exhausting them fails loudly, because a store that conflicts forever is a
+broken deployment, not something to spin on.
+
+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