Files
agentic-dev/docs/guides/conformance-quickref.md
Danijel Martinek 1cab88916a feat(core-eslint): add no-undeclared-consent-check rule (conformance gate 12)
Extends the conformance ESLint layer with the consent-check rule:

- `no-undeclared-consent-check` (warn): `consent.isGranted("X")` in a
  use-case file must match a category declared in `manifest.requiresConsent`;
  also warns when requiresConsent is declared but no isGranted call is found.
- `_manifest-ast.js`: adds `parseManifestFully` which extracts top-level
  `name`, `requiredCores`, `requiresConsent`, and per-use-case maps from the
  manifest AST; `requiresConsent` extraction tested in `_manifest-ast.test.js`.
- `_rule-context.js` / `_rule-schema.js`: shared helpers extracted from the
  existing per-rule files so the new rule can resolve use-case name + feature
  root without duplication.
- Existing rules (`no-undeclared-audit`, `no-undeclared-event-publish`,
  `no-undeclared-analytics-event`) updated to use the shared helpers.
- `plugin.js` + `base.js` register the rule at warn severity.
- CLAUDE.md + conformance-quickref.md: rule count advanced from 11 → 12.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 11:51:30 +00:00

8.8 KiB
Raw Blame History

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:

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:

export { fooManifest, type FooManifest } from "./feature.manifest";

bindProductionX self-assertion

Every feature's bind-production.ts calls the assertion at the tail:

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 ~3060s 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/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

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 binderusecase-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 itconformance/no-undeclared-event-publish (warn)
  • Feature has use cases but no manifestconformance/feature-must-have-manifest (error)
  • Manifest references requiredCores: ["X"] but no core-X package existsconformance/required-cores-installed (error)
  • One feature consumes Y but no feature publishes Ypnpm conformance orphan check (CI gate)
  • Factory calls analytics.track("X") but manifest doesn't declare it in analyticsEventsconformance/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.