138 lines
7.0 KiB
Markdown
138 lines
7.0 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 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` |
|
||
|
||
## 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.
|
||
|
||
## 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`.
|