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

An admin's refusals (self-suspend, scorecard, front page) live in one module, and the API and MCP read them from there #5576

Merged opened this

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

Merged

The three admin refusals (self-suspend, scorecard, front page) now come from one place, `src/actions/admin_words.ts`, as `ADMIN_WORDS.selfSuspend`, `.scorecard` and `.frontPage`. The words are unchanged.

shipped production is serving it deploy 2fa0c24

opened this

The three admin refusals (self-suspend, scorecard, front page) now come from one place, src/actions/admin_words.ts, as ADMIN_WORDS.selfSuspend, .scorecard and .frontPage. The words are unchanged.

What moved:

  • src/http/api.ts: the account refusal, the GET /api/v1/crew/scorecard 403 and the PUT /api/v1/front 403 now read from the module.
  • src/http/mcp.ts: account_suspend, scorecard and front_page now read from the module. Each change is on the literal's own line, plus one import line, because other MCP work edits this file at the same time.
  • src/contract/openapi.ts: I also changed this file, which the issue didn't name. Its PUT /api/v1/front description typed out the front-page sentence, and the issue asks that nothing outside the module does. It's now ` ${ADMIN_WORDS.frontPage} (403) `, which produces the same text.

grep -rn for each sentence under src/ now finds only the module. The one other hit is the doc comment at the top of src/actions/accounts.ts, which starts with a capital "An" so the grep misses it; I left it alone.

New test: test/admin-words.test.ts triggers the refusals through each door and checks the exact answer:

  • self-suspend through both the API and MCP;
  • scorecard through the API;
  • front page through both the API and MCP.

It fails if a door types its own wording. The MCP scorecard refusal isn't covered: the tool is admin-only and hidden from non-admins, so no request can reach that refusal. It still reads from the module.

Tests: the new test, the existing tests for each door (account suspend, accounts, scorecard, OpenAPI, and every suite that touches the front page — 137 tests in 15 files) and bun x tsc --noEmit all pass. No existing test was changed.

🤖 Generated with Claude Code

Closes #5561

commented

The three admin refusals (self-suspend, scorecard, front page) now come from one place, src/actions/admin_words.ts, as ADMIN_WORDS.selfSuspend, .scorecard and .frontPage. The words are unchanged.

What moved:

  • src/http/api.ts: the account refusal, the GET /api/v1/crew/scorecard 403 and the PUT /api/v1/front 403 now read from the module.
  • src/http/mcp.ts: account_suspend, scorecard and front_page now read from the module. Each change is on the literal's own line, plus one import line, because other MCP work edits this file at the same time.
  • src/contract/openapi.ts: I also changed this file, which the issue didn't name. Its PUT /api/v1/front description typed out the front-page sentence, and the issue asks that nothing outside the module does. It's now ` ${ADMIN_WORDS.frontPage} (403) `, which produces the same text.

grep -rn for each sentence under src/ now finds only the module. The one other hit is the doc comment at the top of src/actions/accounts.ts, which starts with a capital "An" so the grep misses it; I left it alone.

New test: test/admin-words.test.ts triggers the refusals through each door and checks the exact answer:

  • self-suspend through both the API and MCP;
  • scorecard through the API;
  • front page through both the API and MCP.

It fails if a door types its own wording. The MCP scorecard refusal isn't covered: the tool is admin-only and hidden from non-admins, so no request can reach that refusal. It still reads from the module.

Tests: the new test, the existing tests for each door (account suspend, accounts, scorecard, OpenAPI, and every suite that touches the front page — 137 tests in 15 files) and bun x tsc --noEmit all pass. No existing test was changed.

🤖 Generated with Claude Code

Approved — before the latest push

A clean move that matches #5561. The three sentences now live only in src/actions/admin_words.ts, and the API (api.ts:1461, 1746, 5353), MCP (account_suspend, front_page, scorecard) and the OpenAPI PUT /api/v1/front refusal all read them from there. The wording is byte-for-byte the same, and each edit stays on its literal's own line, as the issue asked because of the parallel mcp.ts work. On main, a grep under src/ finds the three typed copies in each door and the OpenAPI one. After this pull only the module holds them, plus the capitalised doc comment in accounts.ts, which is prose and fine to leave.

Security: nothing changes. No new route or input, and the authorization paths are untouched. The terminal is not involved because src/tty.ts carries none of these refusals.

Tests: test/admin-words.test.ts sends real requests through the API and MCP and checks the exact {error, message} and code: message answers against the module. A door that retypes or changes the words fails it. Leaving the MCP scorecard refusal uncovered is right: the tool is adminOnly, so no request reaches it.

Preview: / answers 200. Unauthenticated GET /api/v1/crew/scorecard and PUT /api/v1/front refuse with 401, as before. Approved.

commented

An admin's refusals (self-suspend, scorecard, front page) now live in one place, src/actions/admin_words.ts. The REST API, the MCP tools and the OpenAPI document all read them from there, so each refusal is written once instead of in each surface. Behaviour does not change; test/admin-words.test.ts checks that every surface returns the same words.

This push merges main back in. The only conflicts were import lines: src/contract/openapi.ts needed both the ADMIN_WORDS import and main's new SELF_WAIT import, and src/http/api.ts needed both ADMIN_WORDS and main's new SPEND_WORDS. I kept both lines in each file and touched nothing else. The type check is clean, and 106 tests across 27 files pass (the admin-words, contract, OpenAPI, MCP account-suspend and plan/spend-words suites); the forge's check runs the rest.

To try it, ask to self-suspend as an admin, or ask for the scorecard or the front page in a way that is refused, through the API and through MCP. Both should give the same words, and /api/v1/openapi.json should describe them in those words too.

🤖 Generated with Claude Code

Approved

Clean, and it does what #5561 asks. It's a pure move: the three sentences sit in src/actions/admin_words.ts, and each door reads them in one line without touching any auth path. That covers account_suspend/remove, the scorecard and the front page in both the API and MCP, plus the OpenAPI refusal. No check moved, no wording changed, and no new input or output path.

Verified:

  • At the pull head, grep -rn for each sentence under src/ finds only the module, plus the capitalised doc comment in src/actions/accounts.ts, which is prose, not an answer.
  • bun test test/admin-words.test.ts passes, 3/3. The test pins each door's exact answer against the module, so a door that retypes the words fails it.
  • The preview's /contract/openapi.json still reads "the front page is a site admin's to set (403)".
  • Unauthenticated PUT /api/v1/front and GET /api/v1/crew/scorecard on the preview still answer 401 before any refusal.

No terminal or page surface is involved, so there's nothing to mirror in src/tty.ts. The MCP scorecard refusal can't be reached because the tool is adminOnly, and the pull says so honestly.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs