Replaces #2927. Most of #2927 is already on main: src/spawn/door.ts lets a stranger spawn (rate-limited), refuses an agent, and calls an unreadable app "not a known app". The one part not on main is a per-account limit for a signed-in person.
Done when:
[ ] spawnDoor counts the children a signed-in person already owns. Once they have FORGE_FORGES_PER_ACCOUNT (default 3; an unset value or one that is not a whole number above 0 means 3), the next POST /api/v1/forges with dryRun:false is a 429 or 422 with one sentence, for example "you have 3 forges, the most one account may make here; delete one to make another". The same sentence shows on /forges/new.
[ ] An admin is not limited. A dry run (the plan) is never refused by the limit.
[ ] Tests in the existing spawn-door test file: the limit refuses the 4th, an admin passes, a dry run passes, and an agent is still refused.
Files: src/spawn/door.ts, maybe src/spawn/quota.ts and its test. Risks: no new MCP tool and no new tool field, because test/mcp-size.test.ts holds tools/list under 37,000 bytes, and that budget failed #2927's second try.
Replaces #2927. Most of #2927 is already on main:
src/spawn/door.tslets a stranger spawn (rate-limited), refuses an agent, and calls an unreadable app "not a known app". The one part not on main is a per-account limit for a signed-in person.Done when:
spawnDoorcounts the children a signed-in person already owns. Once they haveFORGE_FORGES_PER_ACCOUNT(default 3; an unset value or one that is not a whole number above 0 means 3), the nextPOST /api/v1/forgeswithdryRun:falseis a 429 or 422 with one sentence, for example "you have 3 forges, the most one account may make here; delete one to make another". The same sentence shows on/forges/new.Files:
src/spawn/door.ts, maybesrc/spawn/quota.tsand its test. Risks: no new MCP tool and no new tool field, becausetest/mcp-size.test.tsholds tools/list under 37,000 bytes, and that budget failed #2927's second try.