Files
agentic-dev/docs/guides/conformance-quickref.md

125 lines
5.2 KiB
Markdown

# 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 |
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 four 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 |
## 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` |
## 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 + boot assertion
- **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)
## 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.
---
For the deeper design rationale see `docs/architecture/agent-first-workflow-and-conformance.md` and the interactive `feature-conformance-explainer.html`.