Merge branch 'worktree-agent-workflow-docs': agent-workflow-docs v1 — surface conformance system

This commit is contained in:
2026-05-13 07:29:50 +02:00
8 changed files with 275 additions and 0 deletions

View File

@@ -366,6 +366,22 @@ Each feature binder signature is `(ctx: BindProductionContext): void` for produc
---
### Conformance contract (every feature)
Every feature package MUST declare a `src/feature.manifest.ts` using `defineFeature` from `@repo/core-shared/conformance`. The manifest declares the use cases, what they audit/publish/consume, and which optional cores they require.
The feature's `src/di/bind-production.ts` MUST call `assertFeatureConformance(container, manifest, symbols, ctx)` at the tail of `bindProduction<Name>` so `pnpm dev` refuses to boot if a binding loses its brand.
Re-export the manifest from `src/index.ts`:
```ts
export { fooManifest, type FooManifest } from "./feature.manifest";
```
See `docs/guides/conformance-quickref.md` for the canonical pattern; the generator (`pnpm turbo gen feature <name>`) emits all of this correctly by default.
---
### Cross-feature events and background jobs (Plan 10, ADR-015)
Three rules:

View File

@@ -44,6 +44,21 @@ Turborepo + pnpm monorepo organized by vertical features. Each feature (`auth`,
- `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:
| 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 |
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).
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.
## Key Conventions
- **Relative imports in `src/`** Source files use relative paths (`../repositories/...`), not `@/` alias
@@ -74,6 +89,9 @@ Turborepo + pnpm monorepo organized by vertical features. Each feature (`auth`,
- **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`)
- **`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
- **`pnpm conformance`** cross-feature event-closure check; fails CI on orphan consumers
## MCP Servers

View File

@@ -25,6 +25,21 @@ per-use-case patterns below.
---
## Workflow ordering
For any new use case, follow these four steps in order:
1. **Manifest entry** — declare the use case in `src/feature.manifest.ts` with its `mutates` flag and (initially empty) `audits` / `publishes` / `consumes` arrays.
2. **Contracts** — export `xInputSchema`, `xOutputSchema`, and the `IXUseCase` type alias from the use-case file. Factory body starts as `throw new Error("not implemented")`.
3. **Tests (red)** — write the failing test that exercises the contract via the factory + a mock repository.
4. **Implementation (green)** — fill the factory body until the tests pass.
The `feature-must-have-manifest` ESLint rule will catch step 1 omissions; `usecase-must-have-test-file` catches step 3. The boot assertion (`assertFeatureConformance` at the tail of `bindProductionX`) catches forgotten wrappers at startup.
For the fast path, run `pnpm turbo gen feature <name>` — the generator emits the manifest + contracts + bind-production with the assertion already wired in.
---
## 1. Overview
Every feature package owns:

View File

@@ -0,0 +1,124 @@
# 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`.

View File

@@ -32,6 +32,17 @@ pnpm install # link the new workspace package
pnpm --filter @repo/widgets lint typecheck test
```
## Conformance-ready by default
Since milestone v of the conformance system, `pnpm turbo gen feature <name>` emits two conformance artefacts:
- **`src/feature.manifest.ts`** declaring the scaffolded `getX` use case
- **`src/di/bind-production.ts`** with `assertFeatureConformance(...)` called at the tail
Run `pnpm conformance` after generating a feature — it should pass cleanly. If you add `bus.publish("X")` calls in a factory body, you'll also need to add `"X"` to the manifest's `publishes[]` array for that use case, or the `no-undeclared-event-publish` ESLint rule will warn.
See `docs/guides/conformance-quickref.md` for the manifest field reference.
## What it generates
- Package files: `package.json`, `tsconfig.json`, `vitest.config.ts`,

View File

@@ -704,3 +704,16 @@ expect(logger.captures[0]).toMatchObject({
For an end-to-end example (controller → use case → repo, all wrapped, asserting no double-capture across layers), see `packages/blog/tests/r44-no-double-capture.test.ts`.
**Default mocks** (when you don't need assertions): construct `new MockArticlesRepository()` with no args — the constructor defaults bind `NoopTracer` + `NoopLogger`.
---
## Conformance gates (post-TDD)
After your tests are green and the impl is committed, four gates check that the new code stays consistent with the feature's manifest:
1. **TypeScript brands** — the `ProductionUseCase<I, O, M>` slot in `bind-production.ts` only accepts factories wrapped through `withSpan` + `withCapture` + (if mutating with audits) `withAudit`.
2. **ESLint rules** — five `conformance/*` rules check manifest ↔ code drift; see `docs/guides/conformance-quickref.md`.
3. **Boot assertion**`assertFeatureConformance` runs at the tail of every `bindProductionX(ctx)`; `pnpm dev` refuses to start on drift.
4. **CI drift gate**`pnpm conformance` runs after `pnpm lint` in CI; fails on orphan event consumers across features.
The TDD red-green cycle covers behavioural correctness; the conformance gates cover architectural correctness.

View File

@@ -0,0 +1,42 @@
---
id: 01-docs-rewrite
epic: agent-workflow-docs-v1
title: Surface conformance system across top-level docs
type: technical-story
status: done
feature: docs
depends-on: []
blocks: []
---
## Goal
Update CLAUDE.md, AGENTS.md, and four guides so agents picking up this
repo discover and follow the manifest-first workflow by default.
## Done when
- CLAUDE.md has a "Conformance system" section + manifest-first ordering
added to Key Conventions
- AGENTS.md's per-feature conventions mention the manifest contract
- adding-a-feature.md updated to the 4-step ordering
- scaffolding-a-feature.md notes the manifest emission + boot assertion
- tdd-workflow.md links to the conformance enforcement
- New conformance-quickref.md exists as a single-page agent reference
## In scope
Mechanical doc updates pointing at existing artifacts (the design doc, the
feature-conformance-explainer.html, the actual rules / wrappers / script
already merged).
## Out of scope
- Rewriting the existing architecture docs (already in place)
- Updating per-package AGENTS.md inside packages/*
## Tasks
- [x] Epic + story scaffold
- [x] CLAUDE.md update
- [x] AGENTS.md update
- [x] adding-a-feature.md update
- [x] scaffolding-a-feature.md update
- [x] tdd-workflow.md update
- [x] conformance-quickref.md (new)
- [x] Final verification + closeout

View File

@@ -0,0 +1,36 @@
---
id: agent-workflow-docs-v1
prd: null
title: Agent-workflow docs rollout
type: epic
status: done
features: [docs]
created: 2026-05-13
---
## Goal
Surface the conformance-system-v1 mechanics across CLAUDE.md, AGENTS.md,
and the feature-development guides so agents picking up this codebase
follow the manifest-first workflow by default.
## Why
The conformance system is the substrate, but agents read the top-level
docs first. Until the docs say "feature.manifest.ts exists, here's what
it does, here's how the gates fire," the system stays invisible to
agents iterating on this repo for the first time.
## In scope
- CLAUDE.md: add conformance system section + manifest-first ordering
- AGENTS.md: add manifest convention to per-package conventions
- docs/guides/adding-a-feature.md: update workflow to manifest → contracts → tests → code
- docs/guides/scaffolding-a-feature.md: note that generator now emits manifest + boot assertion
- docs/guides/tdd-workflow.md: link conformance system as the enforcement layer
- New docs/guides/conformance-quickref.md: ~80-line agent-facing reference
## Out of scope
- Frontend-conformance docs (next epic)
- Work-system-v1 docs (those land with the work-system epic)
- Editing the original conformance design docs (already in place)
## Stories
- [x] [01 — Surface conformance system across CLAUDE.md / AGENTS.md / guides](01-docs-rewrite/_story.md)