# AGENTS.md ## Operating Rules - Product name: `tessera`, lowercase in code, comments, docs, and user-facing copy unless grammar requires otherwise. - Ask first: "Can we do more with less code?" Prefer the smallest correct change that preserves clarity, performance, and security. - If a local fix creates known fragility or a security-sensitive dead end, pay the structural cost now. Optional refactors should be proposed, not made by default. - Read before editing. For non-local changes, inspect the target file plus a caller, consumer, sibling module, or focused test. - Work from the live source tree. Docs and prior context are secondary when they drift. - Keep edits scoped. Multiple agents may be working in parallel, so never overwrite or revert unrelated changes. - Assume the dev server may already be running. Do not start `npm run dev` unless asked, clearly needed, or no relevant server is running. - Prefer explicit code over clever code. Reuse existing helpers, route patterns, schemas, UI primitives, and auth utilities before adding new ones. - Preserve the `src/client/`, `src/worker/`, generated-code, and migration boundaries. - Prefer the `@/` alias for imports from `src`. - For current Cloudflare behavior, prefer official Cloudflare docs via `cloudflare-docs` MCP when available. For package signatures, read local `.d.ts` files first. ## Source Of Truth - `README.md`: architecture and local quickstart. - `OPERATOR.md`: deployment, secrets, and production operation. - The live source tree is authoritative when docs drift. - `docs/phase-1-design.md` and `docs/tessera-phase-1-design.md`: historical security-sensitive design context for issuer, callbacks, cookies, signing keys, invites, and `sub` claims. Confirm endpoint paths, env vars, schema details, and generated output against current source before treating them as current. - `src/worker/db/schema/`: D1 schema source. `drizzle/d1/` is generated by `npm run db:generate`. - `src/worker/auth/cli-config.ts`: Better Auth CLI source. `src/worker/db/schema/auth.ts` is generated by `npm run auth:generate`. - `worker-configuration.d.ts`, `dist/`, and `.wrangler/` are generated outputs. Regenerate them; do not hand-edit them. ## Project Map tessera is a Cloudflare Workers OIDC provider with a React frontend. - Worker: `src/worker/index.ts` mounts Hono; `auth/` configures Better Auth; `api/` owns tessera routes; `db/` owns Drizzle; `middleware/` owns auth gates, origin checks, security headers, admin allowlisting, and rate limiting; `services/` owns crypto, Turnstile, and URL helpers. - Client: `src/client/app.tsx` and `main.tsx` boot the React app; pages live in `pages/`, UI in `components/`, admin-specific components in `components/admin/`, hooks in `hooks/`, HTTP/auth/navigation helpers in `lib/`, and styles in `styles/app.css`. - Tests: worker and client Vitest specs in `tests/worker/` and `tests/client/`, Playwright in `tests/e2e/`, local OIDC simulators in `scripts/test-client/` and `scripts/test-consent/`. - Runtime config and generated output: `wrangler.jsonc`, `drizzle/d1/`, `worker-configuration.d.ts`, `dist/`, and `.wrangler/`. ## Security And Runtime Invariants - tessera is invite-only. Do not introduce open signup. - Stable opaque `sub` claims are core product behavior. Account linking, issuer URLs, callback paths, cookie behavior, signing keys, token claims, and token lifetimes are security-sensitive. - D1 is authoritative for users, accounts, sessions, OAuth clients, signing keys, invites, roles, and bans. Better Auth state stays in D1. - `/api/*`, `/.well-known/*`, and `/healthz` must run through the Worker before static asset handling. OAuth endpoints live under `/api/auth/oauth2/*`; JWKS is `/api/auth/jwks`. - OIDC discovery must advertise endpoints and JWKS paths that the Worker actually serves. - Turnstile-gated flows fail closed on missing config, missing tokens, and failed verification. - Sign-in, social sign-in, invite acceptance, and `/api/auth/*` remain rate-limited through `RL_AUTH` / `RL_API`; public JWKS and auth utility endpoints stay explicitly exempt. - Admin routes use `requireAdmin`; protected user routes use `requireUser`. - Keep OAuth client secret creation, rotation, and revocation fail-closed. - Do not log secrets, bearer tokens, OAuth client secrets, session cookies, password material, invite tokens, signing keys, or raw Turnstile responses. ## Change Guidance - Worker routes belong in `src/worker/api/` and are mounted from `src/worker/index.ts`. - Keep reusable worker logic in helpers or middleware rather than route handlers. - Reuse `src/worker/http.ts` for HTTP errors/responses, `src/worker/config.ts` for base URL and issuer logic, `src/worker/services/turnstile.ts` for Turnstile, and `src/worker/middleware/rate-limit.ts` for rate limiting. - If request or response shapes change, update the worker route, matching client helper in `src/client/lib/`, and affected tests together. - Frontend changes should reuse `src/client/components/ui/` and preserve accessible semantics, keyboard behavior, focus states, labels, and contrast. - Treat `TurnstileWidget` and `/api/config` as the authoritative client Turnstile path. - Update `src/worker/db/schema/` before generating D1 migrations. Do not hand-edit `drizzle/d1/` unless explicitly asked. - Cross-check `README.md`, `OPERATOR.md`, current source, and focused tests before shipping security-sensitive auth or OIDC changes. Treat phase design docs as context, not current implementation authority. ## Commands - `npm run dev`: start Vite + Cloudflare Worker dev. - `npm run build`: production bundle in `dist/`. - `npm run typecheck`: generate Worker types and run TypeScript checks. - `npm test`: run Vitest with the Cloudflare worker pool. - `npm run test:e2e`: run Playwright e2e coverage. - `npm run test:client`: run the local OIDC RP simulator. - `npm run test:consent`: run the local consent-flow simulator. - `npm run db:generate`: generate D1 migrations. - `npm run db:migrate`: apply remote D1 migrations. - `npm run db:migrate:local`: apply local D1 migrations. - `npm run db:seed-initial-user` / `npm run db:seed-initial-user:local`: seed the bootstrap invite. - `npm run auth:generate`: regenerate Better Auth schema. - `npm run format:check` / `npm run format`: check or apply Prettier formatting. ## Validation - Run `npm run typecheck` for most code changes. - Prefer focused `tests/worker/*.test.ts` for worker, auth, invite, OIDC, Turnstile, rate-limit, and service behavior changes. - Do not run the full `npm test` suite by default. It includes Argon2id coverage and is intentionally slow; run it only when broad cross-file worker regression coverage is clearly needed or explicitly requested. - Run `npm run build` when changing route wiring, bundling, static assets, `wrangler.jsonc`, or runtime registration. - Run `npm run test:e2e` or a focused Playwright spec for browser-visible auth, invite, account, admin, or consent flows. - Run `npm run test:client` after OIDC discovery, authorize/token, PKCE, client registration, or claim-shape changes. - Run `npm run test:consent` after consent-page or consent-route changes. - If an appropriate check cannot be run, state why. ## Primary References - Worker entry: `src/worker/index.ts` - Auth setup: `src/worker/auth/index.ts`, `src/worker/auth/cli-config.ts` - API handlers: `src/worker/api/` - Auth middleware: `src/worker/middleware/auth.ts` - D1 schema: `src/worker/db/schema/` - Issuer/base URL: `src/worker/config.ts` - Turnstile/rate limits: `src/worker/services/turnstile.ts`, `src/worker/middleware/rate-limit.ts` - Client app: `src/client/app.tsx`, `src/client/main.tsx` - Client helpers and UI: `src/client/lib/`, `src/client/components/ui/` - Tests: `tests/worker/`, `tests/client/`, `tests/e2e/`, `scripts/test-client/`, `scripts/test-consent/`