Goldie Blocks

A Local Test-User Door, Locked Five Ways

Owner ruling 2026-10-04 (option A) — a dev-only /dev/sign-in door signs an agent or the owner in as one of two seeded test users without WorkOS's hosted page. It exists only in a dev build, answers only localhost, mints one-hour tokens, is honoured only by a deployment holding DEV_LOGIN_JWKS, and only for user_dev_ subjects.

In plain terms: a side entrance for practice. On a developer's own computer, /dev/sign-in hands out a one-hour pass for one of two pretend clients, so the app can be checked end to end without WorkOS's sign-in page. The live site has no such entrance, and the live database would not honour its passes.

Decision

Owner ruling, 2026-10-04 (option A).

  1. The door. http://localhost:3000/dev/sign-in offers two test users. Choosing one sets a one-hour cookie holding a signed token (a JWT); while it is held the app hands Convex that token instead of WorkOS's, and the account menu's sign-out (or the door's own button) clears it.
  2. The test users (bun run seed:test-users, idempotent): user_dev_tester owns a Workspace, the Test Matter Project, the Sample Advice Document (two Modules with Blocks) and two Libraries with guides and tags, Tester's Precedents and Tester's Private Bank. user_dev_guest has a Builder seat on Test Matter and a seat on Tester's Precedents only, so Library access is your own seat can be checked by hand.
  3. The locks. If any cannot be kept, the door is withdrawn, not weakened.
    • Dev build only: src/start.ts loads the door behind import.meta.env.DEV; a production build contains none of its code (checked 2026-10-04 by building and searching the output).
    • Localhost only: any other host name is refused; a POST from another site is refused.
    • Test users only: the door signs only the subjects in convex-dev/devLogin.ts, and convex/callerIdentity.ts treats a door token for any subject not starting user_dev_, or a user_dev_ subject on any other issuer's token, as nobody. Every read of the caller's identity goes through that one function.
    • One hour: token and cookie both expire after 3600 seconds.
    • Dev deployment only: convex/auth.config.ts adds the door's issuer only when DEV_LOGIN_JWKS is set. It is set on the dev deployment (Staging) and never on production, which therefore rejects every door token.

One-time setup

  1. bun run dev-login:keys writes the private key to .env.local (never committed) and prints the next line.
  2. Against the dev deployment only: bunx convex env set DEV_LOGIN_JWKS "$(bun scripts/dev-login-keys.mjs --print-jwks)".
  3. bun run seed:test-users, then restart bun run dev.

Status

  • Tested (convex/devLogin.convex.test.ts, convex/callerIdentity.test.ts): no dev issuer without DEV_LOGIN_JWKS; a door token for user_xyz is nobody; the guest cannot search or insert from the private Library while the tester can; the seed is idempotent. Breaking the lock fails the tests.
  • Checked live on the dev deployment (2026-10-04): tokens minted by the door were accepted for both test users; the guest's search listed only Tester's Precedents and an insert from the private Library was refused; a tampered token was rejected.
  • Not yet seen in a browser end to end. The cloud sandbox's proxy does not carry WebSocket connections, so the app's live data never loaded there. The first laptop run should open Sample Advice and the Add dialog as each test user.

On this page