Files
agentic-dev/docs/guides/conformance-quickref.md
Danijel Martinek f77e6ea881 chore(template): clean-slate template snapshot from bb4a0c7
Curated, product-agnostic snapshot of the post-story-04 tree: demo
content deleted, auth-only reference feature, web-next shell, all gates
green. Product-specific docs, ADRs 027-029, PRDs/epics/archive, editor
library traces, and product naming are curated out; generic template
repairs (coverage provider devDeps, root test:coverage script, live
lint fixes, root-only release-please) are kept. See TEMPLATE.md for
provenance, curation list, and usage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
2026-07-12 20:40:54 +02:00

152 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Conformance system — quick reference
Day-to-day reference for the manifest-first workflow. For design rationale see `docs/architecture/agent-first-workflow-and-conformance.md` and the interactive `feature-conformance-explainer.html`.
---
## The manifest
Every feature has one at `src/feature.manifest.ts`:
```ts
import { defineFeature } from "@repo/core-shared/conformance";
export const fooManifest = defineFeature({
name: "foo",
requiredCores: [],
useCases: {
getThing: { mutates: false, audits: [], publishes: [], consumes: [] },
createThing: {
mutates: true,
audits: ["thing.created"],
publishes: ["foo.thing-created"],
consumes: [],
reads: ["auth"], // cross-feature reader dependency
},
},
realtimeChannels: [],
jobs: [],
} as const);
export type FooManifest = typeof fooManifest;
```
Field reference:
| Field | Type | Meaning |
| --------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------- |
| `name` | string literal | Feature name (kebab-case, matches package name) |
| `requiredCores` | string[] | Optional cores this feature requires (e.g. `["audit", "events"]`) |
| `useCases.<name>.mutates` | boolean | True for create/update/delete; drives whether `__audited` brand is required |
| `useCases.<name>.audits` | string[] | Audit event types this use case emits via `auditLog.record({ type: "X" })` |
| `useCases.<name>.publishes` | string[] | Cross-feature events this use case publishes via `bus.publish("X")` |
| `useCases.<name>.consumes` | string[] | Cross-feature events this use case consumes (via an event handler) |
| `useCases.<name>.reads` | string[] | Other features whose readers this use case queries (e.g. `["auth"]`) |
| `realtimeChannels` | string[] | Realtime channels this feature owns |
| `jobs` | string[] | Job slugs this feature enqueues |
| `requiresConsent` | ConsentCategory[] | Consent categories feature use cases require; drives `withConsent` wrapping + `no-undeclared-consent-check` |
| `rateLimit` | RateLimitBudget[] | Rate-limit budgets this feature's use cases enforce; drives `withRateLimit` wrapping + `no-undeclared-rate-limit` |
Re-export from `src/index.ts`:
```ts
export { fooManifest, type FooManifest } from "./feature.manifest";
```
## bindProductionX self-assertion
Every feature's `bind-production.ts` calls the assertion at the tail:
```ts
import { assertFeatureConformance } from "@repo/core-shared/conformance";
import { fooManifest } from "../feature.manifest";
export function bindProductionFoo(ctx: BindProductionContext): void {
// ... bind use cases, wrapped with withSpan + withCapture + (if mutating + audits) withAudit ...
assertFeatureConformance(
fooContainer,
fooManifest,
{
getThing: FOO_SYMBOLS.IGetThingUseCase,
createThing: FOO_SYMBOLS.ICreateThingUseCase,
},
ctx,
);
}
```
The symbol map declares which container symbol each manifest use-case key resolves to.
## The five gates
| Gate | When it fires | What it catches | Severity |
| ------------------ | --------------------- | ------------------------------------------------------------------------ | -------------------- |
| `tsc` | on save | forgotten wrappers; manifest-derived slot type rejects unwrapped factory | error |
| `eslint` | on save / `pnpm lint` | manifest ↔ code drift; missing sibling test; missing manifest | error or warn |
| `pnpm dev` | at boot | binding lost its runtime brand; manifest declares more than wired | throws synchronously |
| `pnpm conformance` | CI | orphan event consumers across features | exits non-zero |
| `pnpm fallow` | ~3060s | unused exports/files, dupes, circular deps, complexity, AI-change audit | warn (currently) |
## ESLint rules
| Rule | Severity | What it does |
| ---------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `conformance/feature-must-have-manifest` | error | Use-case files require a sibling manifest |
| `conformance/usecase-must-have-test-file` | error | Every `*.use-case.ts` has a sibling `*.use-case.test.ts` |
| `conformance/required-cores-installed` | error | Manifest's `requiredCores` must exist as `core-<name>` packages in pnpm-workspace.yaml |
| `conformance/no-undeclared-event-publish` | warn | `bus.publish("X")` literal must match the manifest's `publishes` for the use case |
| `conformance/no-undeclared-audit` | warn | `auditLog.record({ type: "X" })` literal must match the manifest's `audits` |
| `conformance/usecase-must-be-wired` | error | Every manifest use case must be bound via `wireUseCase({ name: "<key>" })` in `bind-production.ts` / `bind-dev-seed.ts` |
| `conformance/no-undeclared-analytics-event` | warn | `analytics.track("X")` literal must match the manifest's `analyticsEvents` for the use case |
| `conformance/pii-declaration-must-be-complete` | warn | `custom.pii` blocks in Payload config files must declare all required fields: `category`, `purpose`, `exportable`, `restrictable` |
| `conformance/component-must-have-story` | warn | Every component file under `src/` must have a sibling `.stories.tsx` file |
| `conformance/component-must-have-test` | warn | Every component file under `src/` must have a sibling `.test.tsx` file |
| `conformance/atomic-tier-import-direction` | warn | Atomic-design import direction must flow downward (atoms ← molecules ← organisms ← templates ← pages); no upward imports |
| `conformance/no-undeclared-consent-check` | warn | `consent.isGranted("X")` literal in a use-case file must match a category declared in `manifest.requiresConsent`; warns if declared categories are never checked |
| `conformance/no-undeclared-rate-limit` | warn | `rateLimit.consume(budgetName, ...)` call in a use-case file must match a budget name declared in `manifest.rateLimit`; warns if declared budgets are never consumed |
## Workflow ordering for new use cases
1. **Manifest** — add the use case to `feature.manifest.ts` with empty `audits` / `publishes` / `consumes`
2. **Contracts** — export `xInputSchema`, `xOutputSchema`, `IXUseCase` from the use-case file (factory body throws "not implemented")
3. **Tests (red)** — write the test importing the contracts; verify it fails
4. **Implementation (green)** — fill the factory body until tests pass
For the fast path: `pnpm turbo gen feature <name>` scaffolds steps 1 + 2 in a single command.
## Common drift patterns and the gate that catches them
- **Forgot `withSpan` at bind time** → tsc TS2322 + `usecase-must-be-wired` ESLint error + boot assertion + bind-production smoke test
- **Manifest entry has no `wireUseCase` call in either binder** → `usecase-must-be-wired` ESLint error + boot assertion + bind-production smoke test
- **Manifest declares `audits: ["X"]` but factory doesn't call `auditLog.record({type:"X"})`** → no automatic catch yet; future story
- **Factory calls `bus.publish("Y")` but manifest doesn't declare it** → `conformance/no-undeclared-event-publish` (warn)
- **Feature has use cases but no manifest** → `conformance/feature-must-have-manifest` (error)
- **Manifest references `requiredCores: ["X"]` but no `core-X` package exists** → `conformance/required-cores-installed` (error)
- **One feature consumes `Y` but no feature publishes `Y`** → `pnpm conformance` orphan check (CI gate)
- **Factory calls `analytics.track("X")` but manifest doesn't declare it in `analyticsEvents`** → `conformance/no-undeclared-analytics-event` (warn); add the event slug to the manifest or remove the call
## Pinning down a drift
When a gate fires, the error message tells you what to run. For example:
> `Feature blog has use cases but no feature.manifest.ts. Run 'pnpm turbo gen feature blog' or scaffold the manifest manually at packages/blog/src/feature.manifest.ts.`
That's the "fix" line — follow it.
## Fallow audit for AI changes
When you (the agent) finish a task and are about to commit, run:
```
pnpm fallow:audit
```
This runs `fallow audit --base main`, comparing your branch's diff against main. If your change adds dead exports, dupes, or complexity hotspots, fallow tells you exactly what and where. Fix or accept (with --gate flag to ignore inherited findings).
This is the catch-all for whole-codebase drift the per-file gates can't see.
---
For the deeper design rationale see `docs/architecture/agent-first-workflow-and-conformance.md` and the interactive `feature-conformance-explainer.html`.