Merge branch 'worktree-conformance-milestone-vi': conformance milestone vi — feature migrations
This commit is contained in:
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
33
packages/blog/src/feature.manifest.ts
Normal file
33
packages/blog/src/feature.manifest.ts
Normal 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;
|
||||
@@ -33,3 +33,4 @@ export type { IGetArticleBySlugController } from "./interface-adapters/controlle
|
||||
|
||||
// <gen:events>
|
||||
// <gen:realtime-channels>
|
||||
export { blogManifest, type BlogManifest } from "./feature.manifest";
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
17
packages/marketing-pages/src/feature.manifest.ts
Normal file
17
packages/marketing-pages/src/feature.manifest.ts
Normal 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;
|
||||
@@ -26,3 +26,4 @@ export type { IGetSiteSettingsController } from "./interface-adapters/controller
|
||||
|
||||
// <gen:events>
|
||||
// <gen:realtime-channels>
|
||||
export { marketingPagesManifest, type MarketingPagesManifest } from "./feature.manifest";
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
18
packages/media/src/feature.manifest.ts
Normal file
18
packages/media/src/feature.manifest.ts
Normal 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;
|
||||
@@ -31,3 +31,4 @@ export type { IDeleteMediaController } from "./interface-adapters/controllers/de
|
||||
|
||||
// <gen:events>
|
||||
// <gen:realtime-channels>
|
||||
export { mediaManifest, type MediaManifest } from "./feature.manifest";
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
16
packages/navigation/src/feature.manifest.ts
Normal file
16
packages/navigation/src/feature.manifest.ts
Normal 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;
|
||||
@@ -17,3 +17,4 @@ export type { IGetHeaderController } from "./interface-adapters/controllers/get-
|
||||
|
||||
// <gen:events>
|
||||
// <gen:realtime-channels>
|
||||
export { navigationManifest, type NavigationManifest } from "./feature.manifest";
|
||||
|
||||
Reference in New Issue
Block a user