# Conventions (for Claude Code)

Rules every ticket follows. If a ticket and this page disagree, the ticket wins for that ticket only.

## Folder layout

```text
vivra/
├── CLAUDE.md
├── apps/
│   ├── web/        Next.js 15: landing (/), dapp (/vote, /dossiers/[id], /war-room/[id], /ledger)
│   ├── admin/      Next.js 15: back-office (SIWE login), /projects, /cycles, /validations, /operations, /settings
│   ├── api/        Fastify 5: src/routes/<domain>.ts, src/plugins/*, src/ws.ts
│   └── workers/    BullMQ: src/queues/<queue>.ts, src/jobs/<job>.ts, src/scheduler.ts
├── packages/
│   ├── config/     env loading + validation (zod), shared constants
│   ├── db/         src/schema/*.ts (Drizzle), migrations/, src/seed.ts, src/client.ts
│   ├── core/       pure logic: status machine, dead/target/safety checks, recovery score, buyback planner, vote rules
│   ├── chain/      src/config.ts, src/abi/*.json, src/read/*.ts, src/write/*.ts, src/logs.ts
│   ├── agents/     src/llm.ts, src/runner.ts, src/tools/*.ts, src/specs/*.yaml, src/prompts/*.md, src/schemas/*.ts
│   ├── templates/  sites/, utilities/, brand/ (each template = folder + manifest.json)
│   └── ui/         React components + tokens (colors, type, radii)
├── contracts/      Foundry: src/BuybackBurner.sol, test/, script/
└── infra/          docker-compose.yml, Dockerfiles, deploy config
```

## TypeScript

- `strict: true`, `noUncheckedIndexedAccess: true`. No `any` (use `unknown` + zod).
- ESM everywhere. Path aliases: `@vivra/db`, `@vivra/core`, `@vivra/chain`, `@vivra/agents`, `@vivra/config`, `@vivra/ui`.
- Amounts: `bigint` for wei and raw token units. Convert with helpers in `@vivra/core/money` only: `toWei`, `fromWei`, `formatToken`, `usd(value: string)`.
- Dates: `Date` in UTC, ISO strings over the wire. Helpers in `@vivra/core/time`: `T(j0, hours)`, `J(j0, days)`.

## API

- One Fastify plugin per domain: `auth`, `cycles`, `votes`, `candidates`, `operations`, `ledger`, `admin`.
- Every route declares zod `body`/`querystring`/`params` and `response` schemas (fastify-type-provider-zod).
- Errors: throw `ApiError(code, httpStatus, message)`; a global handler maps to `{ error: { code, message } }`. Codes: annexe B.
- Rate limits with `@fastify/rate-limit`, values from annexe B.
- Sessions: cookie `vivra_session` (HttpOnly, Secure, SameSite=Lax). Admin: `vivra_admin` via SIWE.

## Database

- Drizzle schema mirrors annexe A exactly (names, types, enums). Any deviation = a note in the PR.
- Migrations are generated (`pnpm db:generate`), never hand-edited after merge.
- Append-only tables (`log_entries`, `ledger_entries`, `buyback_slices`, `admin_audit`): no UPDATE/DELETE in code, except `buyback_slices.status/tx_hash/...` transitions done by the executor.
- Settings: read through `getSetting(key)` with a typed registry in `@vivra/core/settings` (default values from the spec).

## Workers

- One queue per concern (annexe F list). Job names `<domain>.<action>`.
- Every job is idempotent: compute a deterministic `jobId`, and check DB state before side effects.
- Retries: 3 attempts, backoff 2 min / 10 min / 30 min, then mark failed + alert.
- Long-running chain reads chunk by block ranges (default 10,000 blocks, configurable).

## Chain

- All addresses come from `@vivra/chain/config` per `CHAIN_ENV` (testnet | fork | mainnet).
- ABIs are JSON copied from the explorer, never hand-written. Unknown ABI = stop and ask (⚠️ Pons).
- Writes go through `sendTx({ simulate, capCheck, send, waitFor:1, ledger })` in `@vivra/chain/write`.

## Agents

- An agent = a YAML spec (`specs/<name>.yaml`) + a prompt (`prompts/<name>.md`) + output zod schemas (`schemas/<step>.ts`) + a tool allowlist.
- The runner enforces: max 25 tool turns, budget cap in USD, 10 min timeout, schema validation with one retry.
- Every LLM call and tool call writes a `log_entries` row with a human summary generated by the runner.

## Tests

- `packages/core`: unit tests for every function, including the spec's worked examples (e.g. the recovery score example = 68).
- `packages/chain`: tests against recorded fixtures (JSON logs) + an optional testnet suite behind `TESTNET=1`.
- `apps/api`: route tests with an in-memory Fastify instance + a test Postgres (docker).
- `contracts/`: Foundry unit + fuzz + fork tests (annexe D table).
- `apps/web`: Playwright smoke test per screen.

## Pull requests

- Branch `t<id>-<slug>`. Title `T<id>: <title>`.
- Description: plan, files touched, decisions taken, the "Done when" checklist with the command used for each item, open questions.
- No PR mixes two tickets.
