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
|
prd: null
|
||||||
title: Conformance system v1
|
title: Conformance system v1
|
||||||
type: epic
|
type: epic
|
||||||
status: in-progress
|
status: done
|
||||||
features: [cross-cutting]
|
features: [cross-cutting]
|
||||||
created: 2026-05-12
|
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] [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] [04 — CI drift gate](04-ci-drift-gate/_story.md)
|
||||||
- [x] [05 — Generator updates](05-generator-updates/_story.md)
|
- [x] [05 — Generator updates](05-generator-updates/_story.md)
|
||||||
- [ ] 06 — Documentation rewrite (later plan)
|
- [x] [06 — Migrate blog / media / navigation / marketing-pages](06-feature-migrations/_story.md)
|
||||||
- [ ] 07 — Migrate auth feature reference (later plan)
|
- [x] 07 — Migrate auth feature reference (completed inline in milestone i; signIn through ProductionUseCase slot)
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import {
|
|||||||
type ILogger,
|
type ILogger,
|
||||||
} from "@repo/core-shared/instrumentation";
|
} from "@repo/core-shared/instrumentation";
|
||||||
import type { BindProductionContext } from "@repo/core-shared/di";
|
import type { BindProductionContext } from "@repo/core-shared/di";
|
||||||
|
import { assertFeatureConformance } from "@repo/core-shared/conformance";
|
||||||
import { blogContainer } from "./container";
|
import { blogContainer } from "./container";
|
||||||
import { BLOG_SYMBOLS } from "./symbols";
|
import { BLOG_SYMBOLS } from "./symbols";
|
||||||
|
import { blogManifest } from "../feature.manifest";
|
||||||
import { ArticlesRepository } from "../infrastructure/repositories/articles.repository";
|
import { ArticlesRepository } from "../infrastructure/repositories/articles.repository";
|
||||||
import { getArticlesUseCase } from "../application/use-cases/get-articles.use-case";
|
import { getArticlesUseCase } from "../application/use-cases/get-articles.use-case";
|
||||||
import { getArticleBySlugUseCase } from "../application/use-cases/get-article-by-slug.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:event-handlers>
|
||||||
// <gen:jobs>
|
// <gen:jobs>
|
||||||
// <gen:realtime-handlers>
|
// <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:events>
|
||||||
// <gen:realtime-channels>
|
// <gen:realtime-channels>
|
||||||
|
export { blogManifest, type BlogManifest } from "./feature.manifest";
|
||||||
|
|||||||
@@ -33,10 +33,9 @@ export default [
|
|||||||
plugins: { conformance: conformancePlugin },
|
plugins: { conformance: conformancePlugin },
|
||||||
rules: {
|
rules: {
|
||||||
// Structural conformance rules (milestone iii.a).
|
// Structural conformance rules (milestone iii.a).
|
||||||
// `feature-must-have-manifest` is WARN today because only auth has a manifest;
|
// All 5 features now have manifests; promoted to ERROR.
|
||||||
// flip to ERROR after blog/media/navigation/marketing-pages migrate.
|
|
||||||
"conformance/feature-must-have-manifest": [
|
"conformance/feature-must-have-manifest": [
|
||||||
"warn",
|
"error",
|
||||||
{ repoRoot },
|
{ repoRoot },
|
||||||
],
|
],
|
||||||
"conformance/usecase-must-have-test-file": "error",
|
"conformance/usecase-must-have-test-file": "error",
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import {
|
|||||||
type ILogger,
|
type ILogger,
|
||||||
} from "@repo/core-shared/instrumentation";
|
} from "@repo/core-shared/instrumentation";
|
||||||
import type { BindProductionContext } from "@repo/core-shared/di";
|
import type { BindProductionContext } from "@repo/core-shared/di";
|
||||||
|
import { assertFeatureConformance } from "@repo/core-shared/conformance";
|
||||||
import { marketingPagesContainer } from "./container";
|
import { marketingPagesContainer } from "./container";
|
||||||
import { MARKETING_PAGES_SYMBOLS } from "./symbols";
|
import { MARKETING_PAGES_SYMBOLS } from "./symbols";
|
||||||
|
import { marketingPagesManifest } from "../feature.manifest";
|
||||||
import { PagesRepository } from "../infrastructure/repositories/pages.repository";
|
import { PagesRepository } from "../infrastructure/repositories/pages.repository";
|
||||||
import { SiteSettingsRepository } from "../infrastructure/repositories/site-settings.repository";
|
import { SiteSettingsRepository } from "../infrastructure/repositories/site-settings.repository";
|
||||||
import { getSiteSettingsUseCase } from "../application/use-cases/get-site-settings.use-case";
|
import { getSiteSettingsUseCase } from "../application/use-cases/get-site-settings.use-case";
|
||||||
@@ -172,4 +174,15 @@ export function bindProductionMarketingPages(ctx: BindProductionContext): void {
|
|||||||
void realtime;
|
void realtime;
|
||||||
void realtimeRegistry;
|
void realtimeRegistry;
|
||||||
// <gen:realtime-handlers>
|
// <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:events>
|
||||||
// <gen:realtime-channels>
|
// <gen:realtime-channels>
|
||||||
|
export { marketingPagesManifest, type MarketingPagesManifest } from "./feature.manifest";
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import {
|
|||||||
type ILogger,
|
type ILogger,
|
||||||
} from "@repo/core-shared/instrumentation";
|
} from "@repo/core-shared/instrumentation";
|
||||||
import type { BindProductionContext } from "@repo/core-shared/di";
|
import type { BindProductionContext } from "@repo/core-shared/di";
|
||||||
|
import { assertFeatureConformance } from "@repo/core-shared/conformance";
|
||||||
import { mediaContainer } from "./container";
|
import { mediaContainer } from "./container";
|
||||||
import { MEDIA_SYMBOLS } from "./symbols";
|
import { MEDIA_SYMBOLS } from "./symbols";
|
||||||
|
import { mediaManifest } from "../feature.manifest";
|
||||||
import { MediaRepository } from "../infrastructure/repositories/media.repository";
|
import { MediaRepository } from "../infrastructure/repositories/media.repository";
|
||||||
import { getMediaUseCase } from "../application/use-cases/get-media.use-case";
|
import { getMediaUseCase } from "../application/use-cases/get-media.use-case";
|
||||||
import { listMediaUseCase } from "../application/use-cases/list-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:event-handlers>
|
||||||
// <gen:jobs>
|
// <gen:jobs>
|
||||||
// <gen:realtime-handlers>
|
// <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:events>
|
||||||
// <gen:realtime-channels>
|
// <gen:realtime-channels>
|
||||||
|
export { mediaManifest, type MediaManifest } from "./feature.manifest";
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import {
|
|||||||
type ILogger,
|
type ILogger,
|
||||||
} from "@repo/core-shared/instrumentation";
|
} from "@repo/core-shared/instrumentation";
|
||||||
import type { BindProductionContext } from "@repo/core-shared/di";
|
import type { BindProductionContext } from "@repo/core-shared/di";
|
||||||
|
import { assertFeatureConformance } from "@repo/core-shared/conformance";
|
||||||
import { navigationContainer } from "./container";
|
import { navigationContainer } from "./container";
|
||||||
import { NAVIGATION_SYMBOLS } from "./symbols";
|
import { NAVIGATION_SYMBOLS } from "./symbols";
|
||||||
|
import { navigationManifest } from "../feature.manifest";
|
||||||
import { HeaderRepository } from "../infrastructure/repositories/header.repository";
|
import { HeaderRepository } from "../infrastructure/repositories/header.repository";
|
||||||
import { getHeaderUseCase } from "../application/use-cases/get-header.use-case";
|
import { getHeaderUseCase } from "../application/use-cases/get-header.use-case";
|
||||||
import { getHeaderController } from "../interface-adapters/controllers/get-header.controller";
|
import { getHeaderController } from "../interface-adapters/controllers/get-header.controller";
|
||||||
@@ -78,4 +80,12 @@ export function bindProductionNavigation(ctx: BindProductionContext): void {
|
|||||||
// <gen:event-handlers>
|
// <gen:event-handlers>
|
||||||
// <gen:jobs>
|
// <gen:jobs>
|
||||||
// <gen:realtime-handlers>
|
// <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:events>
|
||||||
// <gen:realtime-channels>
|
// <gen:realtime-channels>
|
||||||
|
export { navigationManifest, type NavigationManifest } from "./feature.manifest";
|
||||||
|
|||||||
Reference in New Issue
Block a user