# 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: [],
    },
  },
  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)                                                |
| `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`      | ~30–60s               | 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`.
