Architecture record for the agent-first coverage initiative seeded by the 2026-05-13 PRD. Captures the durable decisions: - 4-layer architecture (L0 vitest, L1 diff, L2 aggregate, L3 mutation) - Manifest-driven coverage band as single source of truth (vitest + assertFeatureConformance + pnpm coverage:diff all read from it) - Cover-the-diff (changed lines), not cover-the-new-code - Committed coverage/summary.json (no SaaS), trend via git log - Mutation testing scoped to entities + use-cases, on-demand only - Machine-first output format (JSON stdout, human stderr) Glossary gets a new "Coverage" section with 7 entries (coverage band, L0-L3 layers, diff coverage, mutation testing, mutation score, coverage/summary.json), plus two relationship rows and a flagged ambiguity for "coverage" qualifiers. prompt-context.sh hook gets a 9th keyword group — when a prompt mentions coverage / uncovered / lcov / mutation / stryker, the relevant ADR + guide path are injected as additional context for the turn. This is the documentation layer of the coverage epic. Implementation (manifest schema, vitest auto-derive, scripts, boot assertion, mutation tooling) lands in subsequent stories. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
18 KiB
Glossary
Canonical vocabulary for template-vertical. Shared by humans and AI agents — every term means the same thing here. Terms specific to this repo's domain (monorepo / Clean Architecture / agent-first workflow / conformance). General programming concepts (DI, retries, errors, etc.) don't belong unless they have a project-specific meaning.
Rules:
- One sentence per definition. Define what it is, not what it does.
- Pick one canonical term; list aliases under
_Avoid:_. - Relationships expressed with bold term names + cardinality.
- Flag ambiguities under "Flagged ambiguities" with a resolution.
- Reference ADRs by ID (
ADR-NNN); reference paths sparingly (they rot fast).
Packages
Package:
A workspace member under packages/ or apps/ with its own package.json and tag. The atomic unit of dependency, build, and boundary enforcement.
Avoid: module, library, app (unless specifically apps/).
Tag:
The boundary classification on a package (app, core, core-composition, feature, tooling). Enforced by ESLint (eslint-plugin-boundaries) and Turborepo boundaries. See AGENTS.md → Boundary Rules.
Feature (a.k.a. feature package):
A vertical slice owning its Clean Architecture layers + integrations under packages/<name>/. Currently: auth, blog, media, marketing-pages, navigation.
Avoid: domain, module, vertical (use "feature" or "vertical feature").
Must-have core:
A core package the template can't run without — core-shared, core-cms, core-api.
Avoid: baseline core, base core.
Optional core:
A core package scaffolded on demand via pnpm turbo gen core-package <name> — core-realtime, core-events, core-trpc, core-ui, core-audit. See docs/architecture/template-tiers.md.
Core-composition:
A tag for packages that compose feature exports — core-api, core-cms, core-trpc. The only packages allowed to import from @repo/<feature>/api or @repo/<feature>/cms subpaths.
Tooling package:
A package providing build/lint/test infrastructure — core-eslint, core-typescript, core-testing. May only depend on other tooling.
App:
A runtime entry point under apps/ — web-next, web-tanstack, cms, storybook.
Clean Architecture layers (per feature)
Entities layer (packages/<feature>/src/entities/):
Domain models (models/<x>.ts) and domain errors (errors/<domain>.ts + errors/common.ts). No I/O.
Application layer (packages/<feature>/src/application/):
Use cases. Pure orchestration over repository + service ports.
Infrastructure layer (packages/<feature>/src/infrastructure/):
Repository and service implementations (real Payload-backed + mock).
Interface-adapters layer (packages/<feature>/src/interface-adapters/):
Controllers — one per use case. Translate unknown input → use-case input via Zod safeParse; thread the output through a presenter.
Integrations layer (packages/<feature>/src/integrations/):
Vendor-specific glue — integrations/api/ (tRPC routers), integrations/cms/ (Payload collections + tasks).
DI folder (packages/<feature>/src/di/):
Container, symbols, and the two binders. The wiring layer.
Feature building blocks
Use case:
A single business action expressed as a factory: (deps) => async (input) => output. Always paired with a Zod xInputSchema + (non-void) xOutputSchema co-located in the same file. Exports the type alias I*UseCase = ReturnType<typeof xUseCase>.
Avoid: command, action, service method.
Controller:
The thin adapter from unknown input to a use case. One per use case. Co-located top-level function presenter for non-void outputs.
Avoid: multi-method controller (forbidden — one verb-noun per file).
Repository:
A port for collection-style entity access. Lives in infrastructure/<x>.repository.{ts,mock.ts,interface.ts}. Real impls drop the payload- prefix; mocks use the .mock.ts suffix.
Service:
A port for non-collection capabilities (auth, mailer, etc.). Same .ts / .mock.ts / .interface.ts triad as repositories.
Manifest (feature.manifest.ts):
The per-feature contract declaring useCases, audits, publishes, consumes, and requiredCores. Source of truth for the conformance system. Always edited before code (manifest-first ordering).
Symbol:
A DI token in packages/<feature>/src/di/symbols.ts. Used by Inversify to identify a binding.
Binder:
A function that registers DI bindings — bindProductionX(ctx) (real Payload) or bindDevSeedX(ctx) (populated mock). Lives at src/di/bind-production.ts and src/di/bind-dev-seed.ts. Self-asserts conformance at its tail via assertFeatureConformance(...).
bindAll():
The app-level DI dispatcher (e.g. apps/web-next/src/server/bind-production.ts). Picks each feature's binder by env (USE_DEV_SEED, NODE_ENV) and threads a single shared ctx through them. Idempotent.
ctx (a.k.a. bind context):
The object passed to every binder. Required fields: tracer, logger, plus config for production. Optional fields: bus, queue, realtime, realtimeRegistry (only when the corresponding optional cores are scaffolded).
Dev-seed:
The populated in-memory mock mode. Lives in src/__seeds__/dev.ts per feature, exported as a lazy buildDev<Entities>() function. Selected by USE_DEV_SEED=true or by default when not in production.
Public surface:
A feature's allowed import surface — . (contracts: types, errors, schemas, I*UseCase aliases, router type), ./ui (queries, components), ./cms, ./api, ./di/bind-production, ./di/bind-dev-seed. No deep source paths exist in exports maps.
Anchor:
A // <gen:*> comment marking where a generator injects code (e.g. <gen:events>, <gen:job-symbols>, <gen:realtime-handlers>). A CI guard at packages/core-eslint/anchors.test.js keeps them present.
Conformance system
Conformance: The 5-gate enforcement chain that keeps a feature's manifest and code in sync. Layers:
- TypeScript brands (0s) —
Instrumented,Captured,Auditedon every wired use case / controller. - ESLint conformance rules (<1s) — five custom rules in
core-eslint. - Boot assertion (~3s) —
assertFeatureConformance(...)at the tail of every binder. - CI drift gate (
pnpm conformance, ~120s) — cross-feature event closure. - Fallow (
pnpm fallow, ~30–60s) — whole-codebase dead exports / dupes / complexity / AI-change audit.
Brand:
A TypeScript phantom type attached to a wired use case / controller — Instrumented (after withSpan), Captured (after withCapture), Audited (after withAudit). Enforces the wrapper composition at bind time.
Manifest-first ordering:
The non-negotiable authoring order for any new use case: (1) manifest entry → (2) contracts (xInputSchema, xOutputSchema, IXUseCase) → (3) tests (red) → (4) implementation (green). The generator scaffolds (1)+(2)+test stubs so new features are conformance-compliant by default.
Fallow:
The whole-codebase auditor (pnpm fallow) — dead exports, unused files, duplicate code, circular deps, complexity hotspots, AI-change audit drift. The fifth conformance gate. Run pnpm fallow:audit before commits.
Drift: Any disagreement between a feature's manifest and its code. The conformance gates are designed to catch drift at the earliest possible latency.
Coverage
Coverage band:
The per-path threshold declared in feature.manifest.ts under coverage.bands — one entry per Clean Architecture layer (entities, use-cases, controllers) plus a baseline for everything else. Single source of truth read by vitest, assertFeatureConformance, and pnpm coverage:diff. See ADR-020.
L0 / L1 / L2 / L3:
The four coverage layers (ADR-020). L0 = vitest per-layer thresholds (test-time). L1 = pnpm coverage:diff cover-the-diff gate (post-test, CI + dispatch loop). L2 = pnpm coverage:aggregate → committed coverage/summary.json (observability). L3 = pnpm mutate Stryker mutation testing on entities + use-cases (on-demand, not default).
Diff coverage:
The gate that asserts every changed executable line has execution count > 0 in the merged lcov. Cover-the-diff (modified + new lines both count), not cover-the-new-code. Run via pnpm coverage:diff [<base-ref>].
Avoid: patch coverage, delta coverage (use "diff coverage" canonically).
Mutation testing:
A test-quality signal that mutates source code and re-runs tests; surviving mutants mean the test exists + executes the code but doesn't actually assert behavior. Scoped to entities/ + application/use-cases/ per feature. Run via pnpm mutate [--filter @repo/<feature>].
Mutation score:
The percentage of mutants killed (i.e., caught by tests) out of all mutants generated. Per-feature threshold defaults to 80% (overridable in feature.manifest.ts via coverage.mutationThreshold).
coverage/summary.json:
The aggregated per-package + repo-level coverage snapshot, committed on merge to main. Grep-able from git history via git log -- coverage/summary.json. Includes timestamp + commit SHA for correlation with deploys.
Cross-feature mechanisms
Event bus (IEventBus):
The vendor-neutral cross-feature pub/sub interface in @repo/core-events. Two implementations: InMemoryEventBus (dev/test, synchronous) and PayloadJobsEventBus (production, durable via Payload tasks). Selected by bindAll(). See ADR-015.
Use when: feature A's success must trigger feature B's reaction. Don't use for in-feature flow control — that's a direct use-case call (rule E0).
Event descriptor:
A defineEvent(...) declaration with a name (wire format) + Zod payload schema. Lives at packages/<feature>/src/events/<event>.event.ts. Publishers re-export from the feature root; consumers do not.
Event handler:
A consumer's reaction to a published event. Lives at packages/<feature>/src/events/handlers/on-<publisher>-<event>.handler.ts. Always private — never re-exported (rule E1, enforced by no-handler-reexport).
Job queue (IJobQueue):
The vendor-neutral deferred-work interface in @repo/core-shared/jobs/. Two implementations: InMemoryJobQueue (dev/test, setImmediate) and PayloadJobQueue (production). Feature packages enqueue via IJobQueue only — direct payload.jobs.queue() is ESLint-blocked (rule J0).
Realtime channel descriptor:
A defineRealtimeChannel(...) declaration in packages/<feature>/src/realtime/<channel>.channel.ts. Re-exported from the feature root. Carries a Zod payload schema + a scope.
Channel scope:
A channel's subscription rule — public | authenticated | role:<name> | user-scoped. See ADR-016.
Broadcaster (IRealtimeBroadcaster):
The server-side push interface in @repo/core-realtime. Use cases call broadcaster.broadcast(channel, payload) after the success path.
Realtime handler:
A consumer's reaction to an inbound client message on a channel. Lives at packages/<feature>/src/realtime/handlers/*.handler.ts. Always private — never re-exported (rule R1, enforced by no-realtime-handler-reexport).
Audit log:
A DPA-compliant record of a use case's side effects. Emitted via auditLog.record(...); declared in the manifest's audits: array. See ADR-018.
Instrumentation
Tracer (ITracer):
The span interface in @repo/core-shared/instrumentation/. Three impls: NoopTracer, OtelTracer, RecordingTracer (test-only, from core-testing).
Logger (ILogger):
The capture interface. Same three-impl shape as ITracer. Used for exception reporting and structured logs.
Metrics (IMetrics):
The counter/gauge/histogram interface. Same three-impl shape.
withSpan / withCapture / withAudit:
The three wrapper composers applied at DI bind time. Composition: withSpan(tracer, opts, withCapture(logger, tags, withAudit(auditLog, schema, factory(deps)))) — withSpan outermost. Together they attach the Instrumented / Captured / Audited brands.
Span:
A traced unit of work. Emitted by tracer.startSpan(...) (inline in repos) or withSpan(...) (composed for use cases + controllers).
Capture:
The act of recording an exception once. Guarded by the non-enumerable __sentryReported flag so a bubbled error surfaces exactly once.
PII scrub:
The server-side scrubbing of email/passwords/tokens/cookies/auth/IPs at the OTel processor layer (PiiScrubSpanProcessor + PiiScrubLogRecordProcessor) before any exporter sees the data. sendDefaultPii: false everywhere, CI grep-enforced. See ADR-017 §7.
Rule 0:
The bindAll() decision that selects OTel+Sentry instrumentation when a DSN env var is present, otherwise the Noop chain. Orthogonal to USE_DEV_SEED / NODE_ENV (those control repo bindings, not instrumentation).
Workflow
PRD:
The top-level requirements doc at docs/work/prds/<date>-<slug>.prd.md. Status flows draft → in-review → approved → superseded. pnpm work decompose refuses to run on draft.
Avoid: spec (use "spec" only for docs/superpowers/specs/ design docs).
Epic:
A large body of work, one folder under docs/work/<epic-slug>/ with _epic.md. Decomposed from a PRD.
Story:
One use case or one technical capability, one folder under <epic>/<story-slug>/ with _story.md. Type metadata: user-story or technical-story.
Task:
One vertical slice = one PR = one commit. File <slug>.task.md under the story folder.
Slice (a.k.a. vertical slice): A change that exercises every Clean Architecture layer end-to-end for one capability — manifest entry + contracts + tests + impl + DI wiring + integration. The unit of a task.
Sandcastle:
The agent dispatch orchestrator (https://github.com/mattpocock/sandcastle). Invoked via pnpm work dispatch --execute. See ADR-019.
_state.json:
The orchestrator-managed derived index at docs/work/_state.json. Regenerated from markdown via pnpm work rebuild-state. Committed.
Generator:
A pnpm turbo gen <kind> invocation. Generator-first is non-negotiable — agents always prefer the generator over hand-rolled scaffolding. Available kinds: feature, event, job, realtime, core-package, core-ui-component.
Slice = task = PR = commit: The shipping rhythm. One vertical slice closes one task, becomes one PR, lands as one commit.
Modes
Production mode:
NODE_ENV=production (and USE_DEV_SEED unset) → bindAll() wires Payload-backed repositories + PayloadJobsEventBus + PayloadJobQueue.
Dev-seed mode:
USE_DEV_SEED=true (or default fallback when not in production) → bindAll() wires populated mocks + InMemoryEventBus + InMemoryJobQueue. Developer default so pnpm dev boots without Payload running.
Relationships
- A PRD decomposes into one or more Epics.
- An Epic contains one or more Stories.
- A Story is implemented by one or more Tasks, each a Slice.
- A Use case is declared in a Manifest before it has code (manifest-first ordering).
- A Use case has exactly one Controller.
- A Controller has at most one
presenter(omitted for void outputs). - A Feature owns its Repositories, Services, Use cases, Controllers, Events, Jobs, Channels, and DI Container.
- Cross-feature reactions travel through the Event bus; in-feature reactions are direct use-case calls.
- An Event descriptor is public; its Event handler is always private.
- A Channel descriptor is public; its Realtime handler is always private.
- Brands are attached only at DI bind time, by
withSpan/withCapture/withAudit. - Conformance asserts the Manifest and code agree, at five latency tiers.
- A Coverage band in the Manifest is read by Vitest (L0),
assertFeatureConformance(boot), andpnpm coverage:diff(L1) — one decision, three enforcers. - Mutation testing is the third dimension of test quality, after "test file exists" (ESLint structural) and "test executes the code" (L0/L1 coverage).
Flagged ambiguities
- "feature" — always a vertical feature package; never a CMS-collection field or a generic capability.
- "service" — a DI-injected port (e.g.
IAuthenticationService); not a Kubernetes service, Payload collection, or generic "service object". - "handler" — qualify by context: event handler (cross-feature) | realtime handler (inbound socket message) | task handler (Payload job).
- "schema" — qualify: Zod schema (input/output contracts) | Payload collection schema (CMS field definitions).
- "config" — qualify: Payload config | Next config | Vitest config | TS config.
- "bind" — DI binding (
bind<I>(SYMBOL).toDynamicValue(...)); not aFunction.prototype.bind. - "spec" —
docs/superpowers/specs/design doc; never a PRD or a Jest spec file. - "controller" — one verb-noun pair per file; never a multi-method MVC controller.
- "module" — avoid for packages (use "package"); reserve for Node ESM/CJS module semantics if needed.
- "coverage" — qualify when ambiguous: L0 coverage (per-layer thresholds, test-time) | diff coverage / L1 (cover-the-diff gate) | aggregate coverage / L2 (committed summary.json) | mutation coverage / L3 (Stryker mutation score, not line coverage).
Cross-references
- Architecture:
docs/architecture/overview.md,docs/architecture/vertical-feature-spec.md,docs/architecture/agent-first-workflow-and-conformance.md - ADRs:
docs/decisions/adr-NNN-<slug>.md - Workflow:
docs/work/README.md,AGENTS.md§ "Agent-driven development" - Guides:
docs/guides/runbook.md,docs/guides/conformance-quickref.md,docs/guides/tdd-workflow.md