# Conventions

## Language

- [ ] **Everything in English**: identifiers, file names, comments, commit messages, PR
      descriptions, docs, `.ai/`, test names, log messages, message *keys*.
- [ ] Only end-user UI copy is translated, in `messages/fr.json` and `messages/en.json`.
      Never hard-code a user-facing string in a component.
- [ ] Use the words in [`glossary.md`](glossary.md); avoid the listed synonyms.

## Security (the Brief's rules, restated — the Brief wins on conflict)

Enforced by ESLint:

- [ ] No `EZYFLOW_*` / `process.env` read outside `src/server/**`, `src/routes/api/**`, `scripts/**`.
- [ ] Every module under `src/server/**` (and `src/server.ts`) starts with
      `import "@tanstack/react-start/server-only";` — except modules exporting `createServerFn`.
- [ ] No `node:crypto` / hand-rolled HMAC — delivery verification is the Runtime's.

Enforced by review (no tool catches these — check them yourself):

- [ ] Every server function re-derives the role: `requireAppUser` to read, `requireAdmin` to
      push / replay / change settings. Route guards are navigation only.
- [ ] Handlers are idempotent: a retry after failure runs them again.
- [ ] Never commit `.env.local`; never log or print a secret (logs, fixtures, UI, chat).
- [ ] MCP writes (real push, replay, test send) hit the live activity: rehearse first, say what
      you are about to do, and prefer `recette`.
- [ ] Do not weaken `minimumReleaseAge` in `pnpm-workspace.yaml`.
- [ ] Never invent `EZYFLOW_*` values or point the App at a mock to "get it working" — stop and
      ask a human which portal value is missing.

## Code

- [ ] Server functions: `createServerFn({ method }).inputValidator(zodSchema).handler(...)`,
      wrap API calls in `asAnswer(...)` so Problems reach the UI intact, use `throwOnError: true`.
- [ ] One Zod schema per form, exported from the server module, reused by the client form.
- [ ] Optional text: blank means absent (`blankIsAbsent` pattern) → `null` in the body, never `""`.
- [ ] Body builders are pure functions (`orderCreatedBody(input)`) and unit-tested.
- [ ] Never pass `activity` anywhere; the client adds it.
- [ ] Types come from the SDK (`Extract<…, { type: "procurement.created" }>`), never re-declared.
- [ ] No business logic in routes; routes call server functions and render.
- [ ] Do not edit `docs/ezyflow/**`, the Brief block, `src/paraglide/**`, `routeTree.gen.ts`.

## Tests

- [ ] Unit: Vitest + MSW handlers generated from the pinned specs — no network.
- [ ] Each push path: body builder test + server function test for viewer refusal (403) and
      admin success, and for a Problem answer.
- [ ] e2e: Playwright against the compose stack (`pnpm e2e`), one spec per screen.
- [ ] `pnpm test` includes `ezyflow:agents --check`: keep generated material fresh.

## Gates (run before every commit / PR)

```sh
pnpm format:check && pnpm typecheck && pnpm lint && pnpm test
```

## Git

- [ ] Conventional Commits in English (`feat(push): add receipt notice form`).
- [ ] Small PRs, one capability each; the PR body states which events are pushed or handled.
