diff --git a/CLAUDE.md b/CLAUDE.md index e13ac457..d946be26 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,10 +38,15 @@ anything under `execution`. **Mounting a router without a registry entry makes the server refuse to boot** — see "Permissions" below before adding one. -**Still single-user: account creation.** `createUser` has exactly one call site, `auth/bootstrap.ts`, -gated on an empty table. There is no signup route, no invite flow and no admin create-user handler, so -every existing member was inserted into Postgres by hand. That is the largest gap in the model, not a -deliberate boundary. +**Account creation exists.** `POST /api/users` (owner-gated), with a form under Settings → User Management: +it validates the password with the same rules bootstrap uses, refuses a role that is not a row in `roles`, +refuses `Super Admin` outright because there is exactly one owner, optionally takes an inbound SSH key, and +provisions the Linux account. `POST /users/:id/provision-linux` retries that half if the host was not ready. + +This file said "there is no way to create a second account" for some days after `69a31051` shipped one, and +`docs/roles-and-teams.md` sequenced a whole phase around building it. Neither is checkable by +`bun run docs:check` — it compares numbers, and this was prose. Worth knowing as the shape of the failure: +the counts stay right and the sentences rot. ## Architecture diff --git a/docs/roles-and-teams.md b/docs/roles-and-teams.md index 8818f813..44b44bed 100644 --- a/docs/roles-and-teams.md +++ b/docs/roles-and-teams.md @@ -179,14 +179,21 @@ D8's warning in front of whoever writes the first script. code — `permissions/authorize.test.ts` is already pure and mockless, which is what makes that possible. 4. Teams UI, and the account screen showing which teams a user is in and as what. -### Phase 5 — the thing that actually unblocks all of it +### Phase 5 — account creation — **DONE, and it was done before this phase was written** -**There is still no way to create a second account.** `createUser` has one call site, `auth/bootstrap.ts`, -gated on an empty table; the existing accounts were inserted by hand. Roles and teams are a permission model -for accounts that cannot yet exist. +`POST /api/users` is owner-gated and live, with a form under Settings → User Management: same password +validator as bootstrap, a role checked against the `roles` table, `Super Admin` refused outright, an +optional inbound SSH key, and Linux provisioning with `POST /users/:id/provision-linux` to retry it. +Alongside it: role change, password reset, delete. -Sequenced last here because it is orthogonal, and flagged because a reader will otherwise finish phase 4 and -find nothing to test it with. **It may be worth doing first.** +It shipped in `69a31051`. This section spent days telling readers it did not exist and sequencing work +around building it — and on 2026-08-23 it produced a concrete cost, when a recommendation to "do phase 5 +first" was made from this text and withdrawn only because someone opened `api/users/` to write the spec. + +The general lesson, since it is the second time this exact shape has bitten: `bun run docs:check` compares +numbers, so counts stay honest while prose rots quietly. **A phase marked as not built is worth verifying +against the tree before it is planned around**, not because the plan was careless but because a plan is +written once and the code moves every day. ---