docs: surface fallow as 5th conformance gate in CLAUDE.md

This commit is contained in:
2026-05-13 08:52:09 +02:00
parent 749938e48b
commit 1655cd0879

View File

@@ -39,23 +39,24 @@ Turborepo + pnpm monorepo organized by vertical features. Each feature (`auth`,
- `docs/architecture/vertical-feature-spec.md` — Design spec with rationale and decision log
- `docs/guides/scaffolding-a-feature.md``turbo gen feature` reference (fast path; prefer this over the manual walkthrough)
- `docs/guides/adding-a-feature.md` — End-to-end new feature walkthrough (manual path; for cases the generator's Phase-1 scope doesn't cover)
- `docs/guides/events-and-jobs.md` — publish/consume/schedule cookbook (cross-feature events + background jobs; *requires `gen core-package events`*)
- `docs/guides/realtime.md` — Socket.IO channels, broadcasts, handlers (*requires `gen core-package realtime`*)
- `docs/guides/audit-and-compliance.md` — DPA-compliant audit logging cookbook (*requires `gen core-package audit`*)
- `docs/guides/events-and-jobs.md` — publish/consume/schedule cookbook (cross-feature events + background jobs; _requires `gen core-package events`_)
- `docs/guides/realtime.md` — Socket.IO channels, broadcasts, handlers (_requires `gen core-package realtime`_)
- `docs/guides/audit-and-compliance.md` — DPA-compliant audit logging cookbook (_requires `gen core-package audit`_)
- `docs/architecture/template-tiers.md` — must-have vs optional packages and how to scaffold the optionals
## Conformance system
Every feature has a `src/feature.manifest.ts` declaring its use cases, audits, publishes, consumes, and required cores. Drift is caught at four latencies:
Every feature has a `src/feature.manifest.ts` declaring its use cases, audits, publishes, consumes, and required cores. Drift is caught at five latencies:
| Layer | Latency | Catches |
|---|---|---|
| **TypeScript brands** | 0s | forgotten `withSpan` / `withCapture` / `withAudit` at bind time |
| **ESLint rules** | <1s | manifest code drift; undeclared `bus.publish` / `auditLog.record`; missing manifest; missing sibling test |
| **Boot assertion** (`pnpm dev`) | ~3s | binding without required brand at runtime; manifest edited without rebinder |
| **CI drift gate** (`pnpm conformance`) | ~120s | orphan event consumers across features |
| Layer | Latency | Catches |
| -------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------- |
| **TypeScript brands** | 0s | forgotten `withSpan` / `withCapture` / `withAudit` at bind time |
| **ESLint rules** | <1s | manifest code drift; undeclared `bus.publish` / `auditLog.record`; missing manifest; missing sibling test |
| **Boot assertion** (`pnpm dev`) | ~3s | binding without required brand at runtime; manifest edited without rebinder |
| **CI drift gate** (`pnpm conformance`) | ~120s | orphan event consumers across features |
| **Fallow** (`pnpm fallow`) | ~3060s | dead exports / unused files; duplicate code; circular deps; complexity hotspots; AI-change audit drift |
The five conformance ESLint rules: `feature-must-have-manifest` (error), `usecase-must-have-test-file` (error), `required-cores-installed` (error), `no-undeclared-event-publish` (warn), `no-undeclared-audit` (warn).
The five conformance ESLint rules: `feature-must-have-manifest` (error), `usecase-must-have-test-file` (error), `required-cores-installed` (error), `no-undeclared-event-publish` (warn), `no-undeclared-audit` (warn). Fallow runs as a fifth layer, post-ESLint, whole-codebase.
See `docs/architecture/agent-first-workflow-and-conformance.md` for the full design and `docs/guides/conformance-quickref.md` for the day-to-day reference.
@@ -83,11 +84,11 @@ See `docs/architecture/agent-first-workflow-and-conformance.md` for the full des
- **PII handling is non-negotiable** `sendDefaultPii: false` everywhere (R31, CI grep gate); replay default-masks all text/inputs/media (R34, R35, allowlist starts empty); `setUser({ id })` only no email/username (R36); server-side PII scrubbing happens at the OTel processor layer (`PiiScrubSpanProcessor` + `PiiScrubLogRecordProcessor`) before any exporter sees the data (R32, R33, ADR-017 §7)
- **Three apps, three Sentry projects** `WEB_NEXT_SENTRY_DSN`, `CMS_SENTRY_DSN`, `WEB_TANSTACK_SENTRY_DSN`. Browser DSNs use `NEXT_PUBLIC_` (web-next) and `VITE_` (web-tanstack) prefixes
- **Instrumentation binding is orthogonal to repo binding** `bindAll()`'s Rule 0 (DSN OTel+Sentry vs Noop) is independent of `USE_DEV_SEED` / `NODE_ENV`. Run `pnpm dev` with `WEB_NEXT_SENTRY_DSN` set to test the integration locally
- **Cross-feature events go through `IEventBus` (E0)** In-feature reactions are direct use-case calls, not bus publishes. The bus is for *crossing* feature boundaries (e.g. `auth` `marketing-pages` welcome email)
- **Cross-feature events go through `IEventBus` (E0)** In-feature reactions are direct use-case calls, not bus publishes. The bus is for _crossing_ feature boundaries (e.g. `auth` `marketing-pages` welcome email)
- **Event contracts are public; handlers are private (E1)** Publisher's `events/<x>.event.ts` is exported from the feature root barrel. Consumer's `events/handlers/on-<publisher>-<event>.handler.ts` is never re-exported (ESLint-enforced via `core-eslint/rules/no-handler-reexport`)
- **Jobs are for *deferred* work, not abstraction (J0)** Synchronous code stays synchronous. A job exists only when something must run off the request path (latency, retries, cron). Feature packages enqueue via `IJobQueue` only direct `payload.jobs.queue()` is ESLint-blocked outside `core-shared/jobs/`
- **Jobs are for _deferred_ work, not abstraction (J0)** Synchronous code stays synchronous. A job exists only when something must run off the request path (latency, retries, cron). Feature packages enqueue via `IJobQueue` only direct `payload.jobs.queue()` is ESLint-blocked outside `core-shared/jobs/`
- **Realtime is for state delivery, not for replacing tRPC (R0)** Persistent request/response operations belong on tRPC procedures. Use realtime when the server needs to push without a request or the data is too high-frequency for HTTP
- **Realtime channel descriptors are exported; handlers are private (R1)** A feature's `realtime/<name>.channel.ts` is re-exported from the root barrel; `realtime/handlers/*.handler.ts` is wired only in bind-* files and never re-exported (ESLint-enforced via `no-realtime-handler-reexport`)
- **Realtime channel descriptors are exported; handlers are private (R1)** A feature's `realtime/<name>.channel.ts` is re-exported from the root barrel; `realtime/handlers/*.handler.ts` is wired only in bind-\* files and never re-exported (ESLint-enforced via `no-realtime-handler-reexport`)
- **`socket.io` lives in `@repo/core-realtime` only (R2)** Feature packages MUST NOT import `socket.io` or `socket.io-client`. ESLint rule `no-direct-socket-io` enforces this; allowlist covers `core-realtime/src/socket-io-*.ts` and `apps/*/server.ts`
- **Manifest-first ordering** for any new use case, the workflow is **(1) manifest entry** **(2) contracts** (`xInputSchema`, `xOutputSchema`, `IXUseCase`) **(3) tests (red)** **(4) implementation (green)**. The generator emits the manifest + a self-asserting `bind-production.ts` so new features are conformance-compliant by default
- **Self-asserting `bindProductionX(ctx)`** every feature's bind-production calls `assertFeatureConformance(container, manifest, symbols, ctx)` at its tail. `pnpm dev` refuses to boot on drift
@@ -101,10 +102,10 @@ Storybook MCP available at `http://localhost:6006/mcp` — use `list-all-documen
## Key Ports
| Service | Port |
|---|---|
| Next.js | 3000 |
| Payload CMS | 3001 |
| Service | Port |
| -------------- | ---- |
| Next.js | 3000 |
| Payload CMS | 3001 |
| TanStack Start | 3002 |
| PostgreSQL | 5432 |
| Storybook | 6006 |
| PostgreSQL | 5432 |
| Storybook | 6006 |