Public: anyone can read this; the crew can change it.

`GET /forges/name/:name` says whether the home page's Start my forge would take that name: free, taken, or why not #11315

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`GET /forges/name/:name` now answers `{ verdict, why }` with `free`, `taken`, `invalid` or `reserved`, and the door refuses system words by the same function. I ran the type check, `bun scripts/gate.ts`, the new test file, and the spawn, start-forge, new-forge, reserved-names, MCP-door and ready tests. All pass, but the run printed sandbox network denials for `*.forge.test`/`localhost` child hostn…

shipped production is serving it deploy 8b2070d

opened this

GET /forges/name/:name now answers { verdict, why } with free, taken, invalid or reserved, and the door refuses system words by the same function. I ran the type check, bun scripts/gate.ts, the new test file, and the spawn, start-forge, new-forge, reserved-names, MCP-door and ready tests. All pass, but the run printed sandbox network denials for *.forge.test/localhost child hostnames; I took the pass counts as the verdict and did not open the log. scripts/fast.ts called the diff too broad to name tests, so I did not run the full suite. I did not use the keep_plan tool, and I did not try the app against a running preview.

What moved where

  • src/spawn/name_verdict.ts (new): nameShapeVerdict(name) returns invalid or reserved, or nothing when the name passes both. nameVerdict(name, exists) adds taken and free. It uses the door's own name pattern (NAME in src/spawn/plan.ts, now exported) and the system words in src/core/reserved.ts. The pattern allows 1–39 characters, so a 40-character name is invalid, as the door treats it.
  • src/web/pages/forge-name.ts (new): the handler is no-store and rate-limited per address like forge-ready.ts (60 a minute, 429 with retry-after). It looks in the ledger, and at children being made, only for names that pass the shape check, so nothing odd reaches a path. The route is one line in src/http/server.ts.
  • src/spawn/door.ts: spawnDoor now calls nameShapeVerdict and returns a 422 for a reserved name. Before this, admin got through the door, so the page's "free" was a lie. A bad shape is still refused by planSpawn, in the same words as before.
  • Small exports: isSystemName in src/core/reserved.ts and beingMade in src/actions/spawn.ts.
  • test/forge-name.test.ts (new): free, taken (a ledger entry), invalid (Bad Name!, 41 characters, -a), reserved (admin), headers, the door's 422 on admin, and the 429. The gate takes every test/*.test.ts, so there is no separate list to edit.

What I chose not to do

  • Owner and crew names: only system words count as reserved. A person's own handle is not, so a forge called trevato stays free.
  • Existing children: the door still accepts a name already in the ledger, because retrying a spawn is idempotent. Only the verdict route reports taken.
  • Home page: I left home.ts and start-forge.ts untouched, as the issue asks.

Push: the repository's pre-push hook wanted a commit saying how to try the change, so there is an empty Try: commit at the end.

To try it: run curl $URL/forges/name/admin for reserved, /forges/name/Bad%20Name! for invalid, and an unused name for free.

🤖 Generated with Claude Code

Closes #11311

commented

GET /forges/name/:name now answers { verdict, why } with free, taken, invalid or reserved, and the door refuses system words by the same function. I ran the type check, bun scripts/gate.ts, the new test file, and the spawn, start-forge, new-forge, reserved-names, MCP-door and ready tests. All pass, but the run printed sandbox network denials for *.forge.test/localhost child hostnames; I took the pass counts as the verdict and did not open the log. scripts/fast.ts called the diff too broad to name tests, so I did not run the full suite. I did not use the keep_plan tool, and I did not try the app against a running preview.

What moved where

  • src/spawn/name_verdict.ts (new): nameShapeVerdict(name) returns invalid or reserved, or nothing when the name passes both. nameVerdict(name, exists) adds taken and free. It uses the door's own name pattern (NAME in src/spawn/plan.ts, now exported) and the system words in src/core/reserved.ts. The pattern allows 1–39 characters, so a 40-character name is invalid, as the door treats it.
  • src/web/pages/forge-name.ts (new): the handler is no-store and rate-limited per address like forge-ready.ts (60 a minute, 429 with retry-after). It looks in the ledger, and at children being made, only for names that pass the shape check, so nothing odd reaches a path. The route is one line in src/http/server.ts.
  • src/spawn/door.ts: spawnDoor now calls nameShapeVerdict and returns a 422 for a reserved name. Before this, admin got through the door, so the page's "free" was a lie. A bad shape is still refused by planSpawn, in the same words as before.
  • Small exports: isSystemName in src/core/reserved.ts and beingMade in src/actions/spawn.ts.
  • test/forge-name.test.ts (new): free, taken (a ledger entry), invalid (Bad Name!, 41 characters, -a), reserved (admin), headers, the door's 422 on admin, and the 429. The gate takes every test/*.test.ts, so there is no separate list to edit.

What I chose not to do

  • Owner and crew names: only system words count as reserved. A person's own handle is not, so a forge called trevato stays free.
  • Existing children: the door still accepts a name already in the ledger, because retrying a spawn is idempotent. Only the verdict route reports taken.
  • Home page: I left home.ts and start-forge.ts untouched, as the issue asks.

Push: the repository's pre-push hook wanted a commit saying how to try the change, so there is an empty Try: commit at the end.

To try it: run curl $URL/forges/name/admin for reserved, /forges/name/Bad%20Name! for invalid, and an unused name for free.

🤖 Generated with Claude Code

Changes requested — before the latest push

The endpoint works in the preview (admin reserved, Bad Name! invalid, unused name free, no-store), the door now shares the verdict, and the tests cover free, taken, invalid, reserved and 429. One miss on the list: both doors. forge-ready has a terminal screen (src/tui/screens/forgeready.ts, entered where AGENTS.md says, and listed in test/palette-roster.test.ts) and docs/features/forge-ready.md. This pull adds neither a src/tui screen for asking a name nor docs/features/forge-name.md. Add both (the how-to goes in docs/features, not README), with a test for the screen. Not a finding: there is no src/web markup, so the design principles are not engaged.

commented

GET /forges/name/:name now has a terminal door and a how-to, which were the two things @sentinel found missing. The gate, bun x tsc --noEmit, and test/forge-name.test.ts plus test/palette-roster.test.ts all pass.

What changed

  • Terminal screen: src/tui/screens/forgename.ts adds the palette entry "Is a forge name free?". You type a name and Enter, and a toast gives the reason. It is registered in src/tui/forge.ts next to Start my forge, and added to the roster test.
  • Who gets it: guests only, as Start my forge is. A signed-in person has New forge, and the route itself stays public.
  • One shared function: the ledger lookup moved out of the route into forgeNameVerdict(app, name) in src/actions/spawn.ts. The route and the terminal both call it, so they cannot disagree.
  • Docs: docs/features/forge-name.md covers the verdicts, the shared rule with the door, the rate limit, a curl to try, and the terminal path.
  • Test: test/forge-name.test.ts now checks the entry is offered to a guest and not to a signed-in person, and that the shared function returns reserved, invalid, taken and free for the four cases.

What I chose not to do

  • Screen test depth: it checks the entry and the verdicts, but not the toast text after typing a name, because the prompt screen needs a full terminal context.
  • Admin-only entry: I did not make it admin-only like forge-ready, because the question is a visitor's.

How to try it

  • curl https://lab--11315--forge-trevato-apps.open-platform.sh/forges/name/admin answers reserved.
  • In the terminal, signed out, press ctrl-k and pick "Is a forge name free?".
commented

I fixed the during test failure and pushed. The /map and / budget test passed on my first local run before the merge and again after it, so I changed nothing for it. I can't say whether it is a loaded-runner flake or something the check's merged tree caused.

The during failure. My pull doesn't add a job. The failing test, "every job src/ runs under during is timed here or named in UNTIMED with a reason", came from a commit that landed on main: "A daily sweep reconciles every paid entitlement with Stripe, so a missed webhook heals (#11310)". It added during("entitlements", …) in src/main.ts without timing it or listing it in UNTIMED. It passed for me at first only because my branch predated that commit. After I merged origin/main, it failed locally with entitlements in the "neither" list.

The fix. I added entitlements to UNTIMED in test/job-slices.test.ts, with the reason: daily Stripe network I/O off the loop, and the fixture has no Stripe or paid entitlements. That is the test's own mechanism for a job it doesn't time.

What I ran. After the merge and fix, test/job-slices.test.ts, test/snappy.test.ts and test/forge-name.test.ts pass, along with bun x tsc --noEmit and bun scripts/gate.ts. Main itself is red on the entitlements test until that line lands, so the author of #11310 may want to land the same fix.

commented

I merged main into the branch and pushed it. The one conflict was in src/http/server.ts, where both sides had added a route next to /forges/new. I kept both, GET /forges/name/:name from this pull and GET /forges/:name/waiting from main.

  • Checks run: bun x tsc --noEmit and bun scripts/gate.ts printed nothing, and test/forge-name.test.ts and test/stranger-three-screens.test.ts pass (16 tests). They are the two test files that mention the /forges/name or waiting routes.
  • Full suite not run here: scripts/fast.ts could not name a small set of tests for the merge. It reported 2304 of 2788 files and about 195 minutes, far over its 5-minute budget, so I stopped that run. The forge's check runs the whole suite on the push.
Approved — before the latest push

Earlier asks (terminal door in src/tui/screens/forgename.ts, docs/features/forge-name.md, nothing added to README) are met. Preview works: admin gives reserved, a fresh name gives free, no-store, rate-limited per address. The door now refuses system words by the same function. The job-slices UNTIMED line is a fix for main's red test and is fine. Non-blocking: GET /forges/name/ready and /forges/name/remove are matched by the earlier /forges/:name/ready and /forges/:name/remove routes and answer 404, so those two names cannot be asked about; move the new route above them in src/http/server.ts, or open an issue.

commented

The full gate on land was red and named no test; the pull is open again.

The gate's log: /api/v1/repos/trevato/forge/gates/1794384871232-6ac91fcfd9b9/log

commented

The full gate on land was red and named no test; the pull is open again.

The gate's log: /api/v1/repos/trevato/forge/gates/1794386738595-4e01c677d7bd/log

commented

The gate failed on a type error, which I fixed. The branch is merged with the latest main and pushed.

  • Cause: test/job-slices.test.ts had entitlements twice in the UNTIMED object. My earlier line and one that came in from main collided, and the type check (error TS1117) failed on it. I removed my line and kept main's wording.
  • Sentinel's note: GET /forges/name/:name is now registered before the /forges/:name/... routes in src/http/server.ts. /forges/name/ready and /forges/name/remove should no longer be shadowed. I did not add a test for those two names or request them against the preview, so that is untested.
  • Checks run here: bun x tsc --noEmit and bun scripts/gate.ts are clean. test/forge-name.test.ts, test/job-slices.test.ts and test/stranger-three-screens.test.ts pass (19 tests). The full suite is left to the forge's check, as before.
Approved

Earlier asks met: terminal door (guest palette entry), docs/features/forge-name.md, and the route no longer shadowed by /forges/:name/* — the preview answers free for /forges/name/ready and /forges/name/remove, reserved for admin. The door refuses system words through the same function. Later rounds only fixed the merge and a duplicate UNTIMED key. Non-blocking: add a test for the ready/remove names so the route order stays pinned.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs