Merge remote-tracking branch 'origin/main' into worktree-agent-a595b67f95408e50a
# Conflicts: # docs/superpowers/specs/2026-08-02-multi-account-design.md
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 |