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

Email verification's sentences live in one module, and the API and MCP read them from there #5574

Merged opened this

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

Merged

The three sentences a person gets when a request for another verification link is refused now live in one place, the new `src/actions/verify_words.ts`. It holds them in a `VERIFY_WORDS` object, keyed by the refusal code (`too_soon`, `failed`, `no_mail`). Both doors read them from there: `POST /api/v1/user/email/verify` in `src/http/api.ts` and the MCP `email_verify` tool in `src/http/mcp.ts`. Each…

shipped production is serving it deploy aa26afa

opened this

The three sentences a person gets when a request for another verification link is refused now live in one place, the new src/actions/verify_words.ts. It holds them in a VERIFY_WORDS object, keyed by the refusal code (too_soon, failed, no_mail). Both doors read them from there: POST /api/v1/user/email/verify in src/http/api.ts and the MCP email_verify tool in src/http/mcp.ts. Each door changed only on the three lines that held a sentence, plus one import line, so it should merge cleanly with the other MCP work going on in mcp.ts.

This is a pure move: no wording changed and the answers are byte-for-byte the same. Running grep -rn under src/ for each sentence now finds only the new module. The short "429: a link was sent a moment ago" in src/contract/openapi.ts is a one-line summary in the API description, not the sentence itself, so it stays where it is.

The new test/verify-words.test.ts triggers two refusals through each door:

  • Too soon: asking again straight after signing up.
  • Failed: a send the mailer reports as failed.

On the API side it checks the whole answer exactly (status, error, message); on the MCP side it checks that the tool reports an error and that its text contains the sentence. If a door types its own words instead of reading the module, the test fails as soon as the two drift apart.

To try it, run bun test test/verify-words.test.ts. That file, the existing tests for both doors (mcp-email-verify, verify-email, mail-caps, openapi-account, openapi-oa-acct, mcp) and bun x tsc --noEmit all pass.

🤖 Generated with Claude Code

Closes #5558

commented

The three sentences a person gets when a request for another verification link is refused now live in one place, the new src/actions/verify_words.ts. It holds them in a VERIFY_WORDS object, keyed by the refusal code (too_soon, failed, no_mail). Both doors read them from there: POST /api/v1/user/email/verify in src/http/api.ts and the MCP email_verify tool in src/http/mcp.ts. Each door changed only on the three lines that held a sentence, plus one import line, so it should merge cleanly with the other MCP work going on in mcp.ts.

This is a pure move: no wording changed and the answers are byte-for-byte the same. Running grep -rn under src/ for each sentence now finds only the new module. The short "429: a link was sent a moment ago" in src/contract/openapi.ts is a one-line summary in the API description, not the sentence itself, so it stays where it is.

The new test/verify-words.test.ts triggers two refusals through each door:

  • Too soon: asking again straight after signing up.
  • Failed: a send the mailer reports as failed.

On the API side it checks the whole answer exactly (status, error, message); on the MCP side it checks that the tool reports an error and that its text contains the sentence. If a door types its own words instead of reading the module, the test fails as soon as the two drift apart.

To try it, run bun test test/verify-words.test.ts. That file, the existing tests for both doors (mcp-email-verify, verify-email, mail-caps, openapi-account, openapi-oa-acct, mcp) and bun x tsc --noEmit all pass.

🤖 Generated with Claude Code

Approved — before the latest push

Clean, pure move that does what #5558 asked. src/actions/verify_words.ts holds the three sentences as plain data. POST /api/v1/user/email/verify and MCP email_verify read them from there, and each edit stays on the literal's own line, so it merges cleanly beside the parallel mcp.ts work. Status codes, error codes and auth paths are unchanged. No new input, write or dependency, so nothing new on the security side.

The new test drives too_soon and failed through both doors against a real server. The API answer is checked exactly. The MCP side checks isError plus the sentence, so a door that retypes its words fails there.

I checked the preview: / answers 200, and both doors still refuse an unauthenticated caller with 401 / unauthorized.

Nits, not blocking:

  • In the test, expect(api.status).toBe(429) / toBe(502) repeat what the next toEqual already checks.
  • no_mail has no door test. The issue asked for only one refusal, so this is fine.
  • The account page (src/web/pages/account.ts:254-257) keeps its own capitalised, full-stop sentences. That is out of scope here. If the page should share them later, that is a separate issue.
commented

The refusal sentences for email verification now live in src/actions/verify_words.ts. The API (src/http/api.ts) and the MCP (src/http/mcp.ts) import VERIFY_WORDS from there instead of each writing their own copy. Behaviour is unchanged: the API and MCP still answer with the same sentences, and test/verify-words.test.ts pins them.

This update merges main into the branch. The conflicts were only in the import lists. In each file this branch added the VERIFY_WORDS import and main added its own beside it: editMessage from edit_words.ts in api.ts, SPEND_WORDS from spend_words.ts in mcp.ts. The merge keeps both sides' imports.

After the merge, bun x tsc --noEmit is clean. The tests for verification, edit words, spend words and API errors pass (62 tests across 9 files).

🤖 Generated with Claude Code

Approved

Clean, behaviour-preserving move. The three refusal sentences now live once in src/actions/verify_words.ts. POST /api/v1/user/email/verify and MCP email_verify both read them from there. Status codes, error codes, auth and the unauthorized/capped branches are unchanged.

Security: no new input, write or identity path, and no secrets. The sentences are constants sent back as JSON. On the preview, an unauthenticated POST to the endpoint still answers 401.

Tests: I applied the diff to a scratch copy of main. test/verify-words.test.ts passes, and so do mcp-email-verify, verify-email and mail-caps (31 pass, 0 fail). Before this change, grepping src/ found each sentence twice. Now each is in one place.

A minor point, not blocking: no_mail is the one refusal the new test doesn't drive through either door (see line comment). This pull is a refactor with no new page or terminal surface, so the tty/README parity points don't apply.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs