diff --git a/docs/work/conformance-system-v1/05-generator-updates/_story.md b/docs/work/conformance-system-v1/05-generator-updates/_story.md new file mode 100644 index 0000000..f34deb3 --- /dev/null +++ b/docs/work/conformance-system-v1/05-generator-updates/_story.md @@ -0,0 +1,52 @@ +--- +id: 05-generator-updates +epic: conformance-system-v1 +title: Generator updates — emit feature.manifest.ts + self-asserting bind-production +type: technical-story +status: done +feature: turbo-generators +depends-on: [04-ci-drift-gate] +blocks: [06-feature-migrations] +--- + +## Goal +`pnpm turbo gen feature ` produces a feature that's conformance- +compliant out of the box: ships a `feature.manifest.ts` declaring the +scaffolded use case, and the scaffolded `bind-production.ts` calls +`assertFeatureConformance` at its tail. + +## Why +Today, generating a new feature does NOT emit a manifest, so +`feature-must-have-manifest` would warn the moment the developer saves. +And `bindProductionX` doesn't self-assert. New features should be +conformance-ready by default. + +## Done when +- `feature.manifest.ts.hbs` template exists, declaring the scaffolded + `getX` use case with mutates: false / empty arrays +- `bind-production.ts.hbs` imports `assertFeatureConformance` + + `Manifest` and calls the assertion at the tail +- `config.ts` action emits the manifest file +- Running `pnpm turbo gen feature demo` into a tmp dir produces a + feature directory containing a valid `feature.manifest.ts` +- Existing snapshot tests pass (regenerate snapshots if needed) + +## In scope +- Manifest template +- bind-production template update +- config.ts action wiring +- Snapshot regen + +## Out of scope +- Multi-use-case feature scaffolding +- Generator changes to other generators (event/job/realtime) +- Backfilling manifests for existing features (milestone vi) + +## Tasks +- [x] Story scaffold +- [x] feature.manifest.ts.hbs template +- [x] bind-production.ts.hbs update +- [x] config.ts action wiring +- [x] Run generator into tmp; verify shape +- [x] Snapshot regen (if needed) + verify tests pass +- [x] Final verification + closeout diff --git a/docs/work/conformance-system-v1/_epic.md b/docs/work/conformance-system-v1/_epic.md index 4edaa4d..15e46d6 100644 --- a/docs/work/conformance-system-v1/_epic.md +++ b/docs/work/conformance-system-v1/_epic.md @@ -36,6 +36,6 @@ See `docs/architecture/feature-conformance-explainer.html` and - [x] [03.a — Structural rules](03-a-structural-eslint-rules/_story.md) - [x] [03.b — Manifest-aware AST rules](03-b-ast-eslint-rules/_story.md) - [x] [04 — CI drift gate](04-ci-drift-gate/_story.md) -- [ ] 05 — Generator emits manifest + contracts + test stubs (later plan) +- [x] [05 — Generator updates](05-generator-updates/_story.md) - [ ] 06 — Documentation rewrite (later plan) - [ ] 07 — Migrate auth feature reference (later plan) diff --git a/turbo/generators/config.ts b/turbo/generators/config.ts index c8f22e2..75b2bba 100644 --- a/turbo/generators/config.ts +++ b/turbo/generators/config.ts @@ -120,6 +120,11 @@ export default function generator(plop: PlopTypes.NodePlopAPI): void { path: "packages/{{kebabCase name}}/src/index.ts", templateFile: "templates/feature/src/index.ts.hbs", }, + { + type: "add", + path: "packages/{{kebabCase name}}/src/feature.manifest.ts", + templateFile: "templates/feature/src/feature.manifest.ts.hbs", + }, // Entities — models + errors { diff --git a/turbo/generators/templates/feature/src/di/bind-production.ts.hbs b/turbo/generators/templates/feature/src/di/bind-production.ts.hbs index 0e40e01..6832e06 100644 --- a/turbo/generators/templates/feature/src/di/bind-production.ts.hbs +++ b/turbo/generators/templates/feature/src/di/bind-production.ts.hbs @@ -11,6 +11,8 @@ import { {{constantCase name}}_SYMBOLS } from "./symbols"; import { {{pascalCase entity}}Repository } from "../infrastructure/repositories/{{kebabCase entity}}.repository"; import { get{{pascalCase entity}}UseCase } from "../application/use-cases/get-{{kebabCase entity}}.use-case"; import { get{{pascalCase entity}}Controller } from "../interface-adapters/controllers/get-{{kebabCase entity}}.controller"; +import { assertFeatureConformance } from "@repo/core-shared/conformance"; +import { {{camelCase name}}Manifest } from "../feature.manifest"; export function bindProduction{{pascalCase name}}(ctx: BindProductionContext): void { const { config, tracer, logger, bus, queue, realtime, realtimeRegistry } = ctx; @@ -73,4 +75,15 @@ export function bindProduction{{pascalCase name}}(ctx: BindProductionContext): v // // // + + // Boot-time conformance check: refuses to start if any use-case binding + // is missing a required brand (withSpan / withCapture / withAudit). + assertFeatureConformance( + {{camelCase name}}Container, + {{camelCase name}}Manifest, + { + get{{pascalCase entity}}: {{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}UseCase, + }, + ctx, + ); } diff --git a/turbo/generators/templates/feature/src/feature.manifest.ts.hbs b/turbo/generators/templates/feature/src/feature.manifest.ts.hbs new file mode 100644 index 0000000..7bb3900 --- /dev/null +++ b/turbo/generators/templates/feature/src/feature.manifest.ts.hbs @@ -0,0 +1,29 @@ +import { defineFeature } from "@repo/core-shared/conformance"; + +/** + * The {{camelCase name}} feature's conformance manifest. Drives binding-slot + * types in `di/bind-production.ts` and is read by ESLint, the boot + * assertion, and the CI drift gate. + * + * Conventions: + * - `mutates: true` for any use case that creates, updates, or deletes state + * - `audits` lists every audit event the use case emits (must match calls + * to `auditLog.record(...)` in the factory body — ESLint enforces this) + * - `publishes` / `consumes` cover cross-feature events through `IEventBus` + */ +export const {{camelCase name}}Manifest = defineFeature({ + name: "{{kebabCase name}}", + requiredCores: [], + useCases: { + get{{pascalCase entity}}: { + mutates: false, + audits: [], + publishes: [], + consumes: [], + }, + }, + realtimeChannels: [], + jobs: [], +} as const); + +export type {{pascalCase name}}Manifest = typeof {{camelCase name}}Manifest;