Merge branch 'worktree-conformance-milestone-vi': conformance milestone vi — feature migrations

This commit is contained in:
2026-05-13 00:11:51 +02:00
15 changed files with 183 additions and 6 deletions

View File

@@ -0,0 +1,39 @@
---
id: 06-feature-migrations
epic: conformance-system-v1
title: Migrate blog/media/navigation/marketing-pages to conformance pattern
type: technical-story
status: done
feature: <cross-feature>
depends-on: [05-generator-updates]
blocks: []
---
## Goal
Every feature in the repo has a manifest + a self-asserting bindProduction.
After this milestone, `feature-must-have-manifest` flips from WARN to ERROR.
## Why
Three of the four enforcement layers already exist; the only thing keeping
them from being fully effective is that 4 of 5 features still don't have
manifests. This story closes that gap.
## In scope
- `feature.manifest.ts` for blog / media / navigation / marketing-pages
- Manifest re-export from each feature's `src/index.ts`
- `bind-production.ts` update for each: imports + tail `assertFeatureConformance` call
- Flip `feature-must-have-manifest` from `warn` to `error` in base.js
## Out of scope
- Adding publishes/audits to existing use cases (declared empty for now)
- Cross-feature event wiring (blog→marketing welcome flows, etc.)
- Migrating realtime channels or jobs into manifests
## Tasks
- [x] Story scaffold
- [x] blog manifest + binding + re-export
- [x] media manifest + binding + re-export
- [x] navigation manifest + binding + re-export
- [x] marketing-pages manifest + binding + re-export
- [x] Flip feature-must-have-manifest to error
- [x] Final verification + closeout

View File

@@ -3,7 +3,7 @@ id: conformance-system-v1
prd: null
title: Conformance system v1
type: epic
status: in-progress
status: done
features: [cross-cutting]
created: 2026-05-12
---
@@ -37,5 +37,5 @@ See `docs/architecture/feature-conformance-explainer.html` and
- [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)
- [x] [05 — Generator updates](05-generator-updates/_story.md)
- [ ] 06 — Documentation rewrite (later plan)
- [ ] 07 — Migrate auth feature reference (later plan)
- [x] [06 — Migrate blog / media / navigation / marketing-pages](06-feature-migrations/_story.md)
- [x] 07 — Migrate auth feature reference (completed inline in milestone i; signIn through ProductionUseCase slot)

View File

@@ -6,8 +6,10 @@ import {
type ILogger,
} from "@repo/core-shared/instrumentation";
import type { BindProductionContext } from "@repo/core-shared/di";
import { assertFeatureConformance } from "@repo/core-shared/conformance";
import { blogContainer } from "./container";
import { BLOG_SYMBOLS } from "./symbols";
import { blogManifest } from "../feature.manifest";
import { ArticlesRepository } from "../infrastructure/repositories/articles.repository";
import { getArticlesUseCase } from "../application/use-cases/get-articles.use-case";
import { getArticleBySlugUseCase } from "../application/use-cases/get-article-by-slug.use-case";
@@ -138,4 +140,16 @@ export function bindProductionBlog(ctx: BindProductionContext): void {
// <gen:event-handlers>
// <gen:jobs>
// <gen:realtime-handlers>
// Boot-time conformance check.
assertFeatureConformance(
blogContainer,
blogManifest,
{
getArticles: BLOG_SYMBOLS.IGetArticlesUseCase,
getArticleBySlug: BLOG_SYMBOLS.IGetArticleBySlugUseCase,
createArticle: BLOG_SYMBOLS.ICreateArticleUseCase,
},
ctx,
);
}

View File

@@ -0,0 +1,33 @@
import { defineFeature } from "@repo/core-shared/conformance";
/**
* The blog feature's conformance manifest.
*/
export const blogManifest = defineFeature({
name: "blog",
requiredCores: [],
useCases: {
getArticles: {
mutates: false,
audits: [],
publishes: [],
consumes: [],
},
getArticleBySlug: {
mutates: false,
audits: [],
publishes: [],
consumes: [],
},
createArticle: {
mutates: true,
audits: [],
publishes: [],
consumes: [],
},
},
realtimeChannels: [],
jobs: [],
} as const);
export type BlogManifest = typeof blogManifest;

View File

@@ -33,3 +33,4 @@ export type { IGetArticleBySlugController } from "./interface-adapters/controlle
// <gen:events>
// <gen:realtime-channels>
export { blogManifest, type BlogManifest } from "./feature.manifest";

View File

@@ -33,10 +33,9 @@ export default [
plugins: { conformance: conformancePlugin },
rules: {
// Structural conformance rules (milestone iii.a).
// `feature-must-have-manifest` is WARN today because only auth has a manifest;
// flip to ERROR after blog/media/navigation/marketing-pages migrate.
// All 5 features now have manifests; promoted to ERROR.
"conformance/feature-must-have-manifest": [
"warn",
"error",
{ repoRoot },
],
"conformance/usecase-must-have-test-file": "error",

View File

@@ -6,8 +6,10 @@ import {
type ILogger,
} from "@repo/core-shared/instrumentation";
import type { BindProductionContext } from "@repo/core-shared/di";
import { assertFeatureConformance } from "@repo/core-shared/conformance";
import { marketingPagesContainer } from "./container";
import { MARKETING_PAGES_SYMBOLS } from "./symbols";
import { marketingPagesManifest } from "../feature.manifest";
import { PagesRepository } from "../infrastructure/repositories/pages.repository";
import { SiteSettingsRepository } from "../infrastructure/repositories/site-settings.repository";
import { getSiteSettingsUseCase } from "../application/use-cases/get-site-settings.use-case";
@@ -172,4 +174,15 @@ export function bindProductionMarketingPages(ctx: BindProductionContext): void {
void realtime;
void realtimeRegistry;
// <gen:realtime-handlers>
// Boot-time conformance check.
assertFeatureConformance(
marketingPagesContainer,
marketingPagesManifest,
{
getPageBySlug: MARKETING_PAGES_SYMBOLS.IGetPageBySlugUseCase,
getSiteSettings: MARKETING_PAGES_SYMBOLS.IGetSiteSettingsUseCase,
},
ctx,
);
}

View File

@@ -0,0 +1,17 @@
import { defineFeature } from "@repo/core-shared/conformance";
/**
* The marketing-pages feature's conformance manifest.
*/
export const marketingPagesManifest = defineFeature({
name: "marketing-pages",
requiredCores: [],
useCases: {
getPageBySlug: { mutates: false, audits: [], publishes: [], consumes: [] },
getSiteSettings: { mutates: false, audits: [], publishes: [], consumes: [] },
},
realtimeChannels: [],
jobs: [],
} as const);
export type MarketingPagesManifest = typeof marketingPagesManifest;

View File

@@ -26,3 +26,4 @@ export type { IGetSiteSettingsController } from "./interface-adapters/controller
// <gen:events>
// <gen:realtime-channels>
export { marketingPagesManifest, type MarketingPagesManifest } from "./feature.manifest";

View File

@@ -6,8 +6,10 @@ import {
type ILogger,
} from "@repo/core-shared/instrumentation";
import type { BindProductionContext } from "@repo/core-shared/di";
import { assertFeatureConformance } from "@repo/core-shared/conformance";
import { mediaContainer } from "./container";
import { MEDIA_SYMBOLS } from "./symbols";
import { mediaManifest } from "../feature.manifest";
import { MediaRepository } from "../infrastructure/repositories/media.repository";
import { getMediaUseCase } from "../application/use-cases/get-media.use-case";
import { listMediaUseCase } from "../application/use-cases/list-media.use-case";
@@ -134,4 +136,16 @@ export function bindProductionMedia(ctx: BindProductionContext): void {
// <gen:event-handlers>
// <gen:jobs>
// <gen:realtime-handlers>
// Boot-time conformance check.
assertFeatureConformance(
mediaContainer,
mediaManifest,
{
getMedia: MEDIA_SYMBOLS.IGetMediaUseCase,
listMedia: MEDIA_SYMBOLS.IListMediaUseCase,
deleteMedia: MEDIA_SYMBOLS.IDeleteMediaUseCase,
},
ctx,
);
}

View File

@@ -0,0 +1,18 @@
import { defineFeature } from "@repo/core-shared/conformance";
/**
* The media feature's conformance manifest.
*/
export const mediaManifest = defineFeature({
name: "media",
requiredCores: [],
useCases: {
getMedia: { mutates: false, audits: [], publishes: [], consumes: [] },
listMedia: { mutates: false, audits: [], publishes: [], consumes: [] },
deleteMedia: { mutates: true, audits: [], publishes: [], consumes: [] },
},
realtimeChannels: [],
jobs: [],
} as const);
export type MediaManifest = typeof mediaManifest;

View File

@@ -31,3 +31,4 @@ export type { IDeleteMediaController } from "./interface-adapters/controllers/de
// <gen:events>
// <gen:realtime-channels>
export { mediaManifest, type MediaManifest } from "./feature.manifest";

View File

@@ -6,8 +6,10 @@ import {
type ILogger,
} from "@repo/core-shared/instrumentation";
import type { BindProductionContext } from "@repo/core-shared/di";
import { assertFeatureConformance } from "@repo/core-shared/conformance";
import { navigationContainer } from "./container";
import { NAVIGATION_SYMBOLS } from "./symbols";
import { navigationManifest } from "../feature.manifest";
import { HeaderRepository } from "../infrastructure/repositories/header.repository";
import { getHeaderUseCase } from "../application/use-cases/get-header.use-case";
import { getHeaderController } from "../interface-adapters/controllers/get-header.controller";
@@ -78,4 +80,12 @@ export function bindProductionNavigation(ctx: BindProductionContext): void {
// <gen:event-handlers>
// <gen:jobs>
// <gen:realtime-handlers>
// Boot-time conformance check.
assertFeatureConformance(
navigationContainer,
navigationManifest,
{ getHeader: NAVIGATION_SYMBOLS.IGetHeaderUseCase },
ctx,
);
}

View File

@@ -0,0 +1,16 @@
import { defineFeature } from "@repo/core-shared/conformance";
/**
* The navigation feature's conformance manifest.
*/
export const navigationManifest = defineFeature({
name: "navigation",
requiredCores: [],
useCases: {
getHeader: { mutates: false, audits: [], publishes: [], consumes: [] },
},
realtimeChannels: [],
jobs: [],
} as const);
export type NavigationManifest = typeof navigationManifest;

View File

@@ -17,3 +17,4 @@ export type { IGetHeaderController } from "./interface-adapters/controllers/get-
// <gen:events>
// <gen:realtime-channels>
export { navigationManifest, type NavigationManifest } from "./feature.manifest";