Public: anyone can read this; nobody can change it.

No sign-up makes the site admin, not even a forge's first account: only the operator's first-admin code, a claim or a seed makes one #57

Merged opened this view on origin

Check · Review · Land done · Live — Landed; nothing deploys from this branch, so this is the end of its path.

Merged

`AdminOrigin` is `claim`, `first-admin` and `seed`. createUser takes the admin flags only from one of those: they are not in any other origin's input type, and createUser drops them for any other origin at run time. So the signup form, the API, the passkey door, the terminal, the parent forge's sign-in and the platform edge each make a person who is not an admin, even on a forge with no admin at a…

shipped merged

opened this view on origin

AdminOrigin is claim, first-admin and seed. createUser takes the admin flags only from one of those: they are not in any other origin's input type, and createUser drops them for any other origin at run time. So the signup form, the API, the passkey door, the terminal, the parent forge's sign-in and the platform edge each make a person who is not an admin, even on a forge with no admin at all. A standalone forge's admin comes from the operator's --first-admin code (#41); a spawned child's from its claim link.

With no sign-up an admin, welcome no longer proves an admin's address by being one, and the edge's first account is not proven by being first: both start unproven and follow the link like anyone else.

Test suites whose admin was their first sign-up make it with test/helpers/admin.ts (seedAdmin, or adminCookie for a signed-in browser) instead. test/no-signup-admin.test.ts fails on main: the API, passkey, terminal and edge doors each made the first account an admin.

Why

Trevor's rule (2026-10-17): a Forge site admin comes only from the operator's first-admin link (#41, merged), a spawned child's claim handover, or the seed origin tests use, never from any signup path, even when the forge has no admin. This PR depends on #41, which is already on main: without it a standalone forge would have no way to make its admin.

What changed

  • src/core/users.ts: AdminOrigin = claim | first-admin | seed. Only those origins' inputs carry isAdmin/adminIfFirst (a type error anywhere else), and createUser drops them at run time for every other origin.
  • The signup, passkey and edge doors no longer ask for adminIfFirst. welcome and ensureEdgeUser no longer mark an account proven for being the first/admin, so every sign-up follows the email link.
  • test/helpers/admin.ts (seedAdmin, adminCookie): the 82 suites whose admin was their first sign-up make that same account as seed instead. The tests that asserted the old rule now assert the new one.
  • test/no-signup-admin.test.ts: on main 18917b93 it fails 4 of 6 (API, passkey door, terminal and edge each made the first account an admin). On this branch it passes 6/6.
  • Docs: README, ARCHITECTURE, APPS.

Beyond validate, every test file was also run on its own on this head (525 files, CI-shaped env, bun 1.3.14). All are green except contain.test.ts (DNS), crew-interrupted.test.ts and snapshot-destination.test.ts, and those three fail the same way alone on main on this box.

Left for a decision

Not changed here: while signup is closed/invite, a fresh forge still lets in its first account (newAccountsClosed), and the terminal still skips Turnstile for that first account. Both were there so the owner could sign up as the admin. Now all they do is let a stranger make an unproven guest before the operator gets there. Closing them is a small follow-up if Trevor wants it.

<!-- pipeline:validation -->

Validation

pipeline/validate on ea16896f: baseline in 609s — bun 1.3.14, CI-shaped env (scrubbed vars, runner PATH), exact check.yml shard + forge.json commands.

stepresulttime
bun install --frozen-lockfileok (exit 0)0s
forge.json check.fastBASELINE (exit 1)137s
check.yml gate (1) pull_requestBASELINE (exit 1)15s
check.yml gate (2) pull_requestok (exit 0)273s
check.yml gate (3) pull_requestok (exit 0)184s
  • ⚠️ node v20.19.2 here, v22 on CI (tsc runs under node)
  • ⚠️ check.yml gate (1) pull_request stopped early at a BASELINE failure (fail-fast): its later files were not run in this step
test output (failures, totals)
▶ bun install --frozen-lockfile
  ok  exit 0  0s
▶ forge.json check.fast
(fail) reviews, threads and the merge gate > require-review refuses the merge until the head is approved [309.29ms] [shard 9]
(fail) egress, reported: an undeclared public host goes through and is counted; a private one never does > an undeclared public host is let through and counted [1.47ms] [shard 10]
Ran 3146 tests across 315 files in 133.4s; 2 failed.
classify: reran test/contain.test.ts test/pull-mergeable.test.ts test/tui-workflows.test.ts  alone on origin/main 098fafdf and on this branch
  BASELINE  exit 1  137s
▶ check.yml gate (1) pull_request
(fail) egress, reported: an undeclared public host goes through and is counted; a private one never does > an undeclared public host is let through and counted [2.42ms] [shard 3]
Ran 110 tests across 3 files in 14.4s; 1 failed.
classify: reran test/contain.test.ts  alone on origin/main 098fafdf and on this branch
  BASELINE  exit 1  15s
▶ check.yml gate (2) pull_request
Ran 1455 tests across 144 files in 273.3s (144 shards).
  ok  exit 0  273s
▶ check.yml gate (3) pull_request
Ran 1541 tests across 145 files in 183.4s (145 shards).
  ok  exit 0  184s
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs