plan(conformance): milestone vi — feature migrations
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
# Conformance Milestone vi — Feature migrations
|
||||
|
||||
**Goal:** Backfill manifests + self-asserting `bind-production` for the remaining four features (`blog`, `media`, `navigation`, `marketing-pages`). After this milestone, every feature is conformance-compliant; `feature-must-have-manifest` flips from WARN to ERROR.
|
||||
|
||||
**Architecture:** For each feature: create `src/feature.manifest.ts` declaring its use cases (with `mutates: true` for state-changing ops like createArticle / deleteMedia; rest read-only; all with empty audits/publishes/consumes — none of the current use-case bodies have literal-string `bus.publish(...)` or `auditLog.record({type: "..."})` calls, so empty arrays are correct). Re-export from feature `index.ts`. Update each `bind-production.ts` to import `assertFeatureConformance` + `<name>Manifest` and call the assertion at the tail with the appropriate symbol map.
|
||||
|
||||
**Inventory:**
|
||||
- `blog` — useCases: `getArticles` (read), `createArticle` (mutate), `getArticleBySlug` (read)
|
||||
- `media` — useCases: `getMedia` (read), `listMedia` (read), `deleteMedia` (mutate)
|
||||
- `navigation` — useCases: `getHeader` (read)
|
||||
- `marketing-pages` — useCases: `getPageBySlug` (read), `getSiteSettings` (read)
|
||||
|
||||
---
|
||||
|
||||
## Tasks
|
||||
|
||||
### Task 1: Story 06 scaffold
|
||||
|
||||
Create `docs/work/conformance-system-v1/06-feature-migrations/_story.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: 06-feature-migrations
|
||||
epic: conformance-system-v1
|
||||
title: Migrate blog/media/navigation/marketing-pages to conformance pattern
|
||||
type: technical-story
|
||||
status: in-progress
|
||||
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 — the
|
||||
existing `auth.signUp` `bus.publish(userSignedUpEvent, ...)` uses a non-literal
|
||||
event name so the rule doesn't fire; other features have no publish/record calls)
|
||||
- Cross-feature event wiring (blog→marketing welcome flows, etc.)
|
||||
- Migrating realtime channels or jobs into manifests
|
||||
|
||||
## Tasks
|
||||
- [ ] Story scaffold
|
||||
- [ ] blog manifest + binding + re-export
|
||||
- [ ] media manifest + binding + re-export
|
||||
- [ ] navigation manifest + binding + re-export
|
||||
- [ ] marketing-pages manifest + binding + re-export
|
||||
- [ ] Flip feature-must-have-manifest to error
|
||||
- [ ] Final verification + closeout
|
||||
```
|
||||
|
||||
Commit: `docs(work): story 06 — feature migrations`.
|
||||
|
||||
### Task 2: Migrate blog
|
||||
|
||||
Create `packages/blog/src/feature.manifest.ts`:
|
||||
|
||||
```ts
|
||||
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;
|
||||
```
|
||||
|
||||
Re-export from `packages/blog/src/index.ts` — append at the bottom:
|
||||
|
||||
```ts
|
||||
export { blogManifest, type BlogManifest } from "./feature.manifest";
|
||||
```
|
||||
|
||||
Update `packages/blog/src/di/bind-production.ts`. Read the file. Add these two imports near the existing imports:
|
||||
|
||||
```ts
|
||||
import { assertFeatureConformance } from "@repo/core-shared/conformance";
|
||||
import { blogManifest } from "../feature.manifest";
|
||||
```
|
||||
|
||||
At the end of the `bindProductionBlog(ctx)` function body — after the existing `// <gen:*>` anchors, before the closing `}` — add:
|
||||
|
||||
```ts
|
||||
// Boot-time conformance check.
|
||||
assertFeatureConformance(
|
||||
blogContainer,
|
||||
blogManifest,
|
||||
{
|
||||
getArticles: BLOG_SYMBOLS.IGetArticlesUseCase,
|
||||
getArticleBySlug: BLOG_SYMBOLS.IGetArticleBySlugUseCase,
|
||||
createArticle: BLOG_SYMBOLS.ICreateArticleUseCase,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
```
|
||||
|
||||
(The container variable name might be `blogContainer` — verify by reading the existing file; if it's named differently, use the actual name.)
|
||||
|
||||
Verify:
|
||||
```
|
||||
pnpm --filter @repo/blog typecheck
|
||||
pnpm --filter @repo/blog test
|
||||
```
|
||||
|
||||
Commit:
|
||||
```bash
|
||||
git add packages/blog/src/feature.manifest.ts packages/blog/src/index.ts packages/blog/src/di/bind-production.ts
|
||||
git commit -m "feat(blog): conformance manifest + self-asserting bind-production"
|
||||
```
|
||||
|
||||
### Task 3: Migrate media
|
||||
|
||||
Same shape. Create `packages/media/src/feature.manifest.ts`:
|
||||
|
||||
```ts
|
||||
import { defineFeature } from "@repo/core-shared/conformance";
|
||||
|
||||
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;
|
||||
```
|
||||
|
||||
Append to `packages/media/src/index.ts`:
|
||||
```ts
|
||||
export { mediaManifest, type MediaManifest } from "./feature.manifest";
|
||||
```
|
||||
|
||||
Update `packages/media/src/di/bind-production.ts` — add imports and tail call:
|
||||
|
||||
```ts
|
||||
import { assertFeatureConformance } from "@repo/core-shared/conformance";
|
||||
import { mediaManifest } from "../feature.manifest";
|
||||
```
|
||||
|
||||
End of function body:
|
||||
```ts
|
||||
assertFeatureConformance(
|
||||
mediaContainer,
|
||||
mediaManifest,
|
||||
{
|
||||
getMedia: MEDIA_SYMBOLS.IGetMediaUseCase,
|
||||
listMedia: MEDIA_SYMBOLS.IListMediaUseCase,
|
||||
deleteMedia: MEDIA_SYMBOLS.IDeleteMediaUseCase,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
```
|
||||
|
||||
Verify + commit:
|
||||
```bash
|
||||
pnpm --filter @repo/media typecheck && pnpm --filter @repo/media test
|
||||
git add packages/media/src/feature.manifest.ts packages/media/src/index.ts packages/media/src/di/bind-production.ts
|
||||
git commit -m "feat(media): conformance manifest + self-asserting bind-production"
|
||||
```
|
||||
|
||||
### Task 4: Migrate navigation
|
||||
|
||||
`packages/navigation/src/feature.manifest.ts`:
|
||||
|
||||
```ts
|
||||
import { defineFeature } from "@repo/core-shared/conformance";
|
||||
|
||||
export const navigationManifest = defineFeature({
|
||||
name: "navigation",
|
||||
requiredCores: [],
|
||||
useCases: {
|
||||
getHeader: { mutates: false, audits: [], publishes: [], consumes: [] },
|
||||
},
|
||||
realtimeChannels: [],
|
||||
jobs: [],
|
||||
} as const);
|
||||
|
||||
export type NavigationManifest = typeof navigationManifest;
|
||||
```
|
||||
|
||||
Append to `packages/navigation/src/index.ts`:
|
||||
```ts
|
||||
export { navigationManifest, type NavigationManifest } from "./feature.manifest";
|
||||
```
|
||||
|
||||
Update `packages/navigation/src/di/bind-production.ts`. Imports:
|
||||
```ts
|
||||
import { assertFeatureConformance } from "@repo/core-shared/conformance";
|
||||
import { navigationManifest } from "../feature.manifest";
|
||||
```
|
||||
|
||||
Tail of function:
|
||||
```ts
|
||||
assertFeatureConformance(
|
||||
navigationContainer,
|
||||
navigationManifest,
|
||||
{ getHeader: NAVIGATION_SYMBOLS.IGetHeaderUseCase },
|
||||
ctx,
|
||||
);
|
||||
```
|
||||
|
||||
Verify + commit.
|
||||
|
||||
### Task 5: Migrate marketing-pages
|
||||
|
||||
`packages/marketing-pages/src/feature.manifest.ts`:
|
||||
|
||||
```ts
|
||||
import { defineFeature } from "@repo/core-shared/conformance";
|
||||
|
||||
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;
|
||||
```
|
||||
|
||||
Append to `packages/marketing-pages/src/index.ts`:
|
||||
```ts
|
||||
export { marketingPagesManifest, type MarketingPagesManifest } from "./feature.manifest";
|
||||
```
|
||||
|
||||
Update `packages/marketing-pages/src/di/bind-production.ts`:
|
||||
```ts
|
||||
import { assertFeatureConformance } from "@repo/core-shared/conformance";
|
||||
import { marketingPagesManifest } from "../feature.manifest";
|
||||
```
|
||||
|
||||
Tail:
|
||||
```ts
|
||||
assertFeatureConformance(
|
||||
marketingPagesContainer,
|
||||
marketingPagesManifest,
|
||||
{
|
||||
getPageBySlug: MARKETING_PAGES_SYMBOLS.IGetPageBySlugUseCase,
|
||||
getSiteSettings: MARKETING_PAGES_SYMBOLS.IGetSiteSettingsUseCase,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
```
|
||||
|
||||
Note: `MARKETING_PAGES_SYMBOLS` might be a different name in this package — verify by reading the existing `symbols.ts` first and use the actual exported constant name.
|
||||
|
||||
Verify + commit.
|
||||
|
||||
### Task 6: Flip `feature-must-have-manifest` from WARN to ERROR
|
||||
|
||||
Now that all features have manifests, the rule should hard-fail.
|
||||
|
||||
Modify `packages/core-eslint/base.js`. Find:
|
||||
|
||||
```js
|
||||
"conformance/feature-must-have-manifest": [
|
||||
"warn",
|
||||
{ repoRoot },
|
||||
],
|
||||
```
|
||||
|
||||
Change `"warn"` to `"error"`. Other rule severities stay as they are.
|
||||
|
||||
Run `pnpm lint` to verify still passes (now 0 warnings from feature-must-have-manifest because all features have manifests).
|
||||
|
||||
Commit:
|
||||
```bash
|
||||
git add packages/core-eslint/base.js
|
||||
git commit -m "feat(core-eslint): flip feature-must-have-manifest from warn to error"
|
||||
```
|
||||
|
||||
### Task 7: Final verification + closeout
|
||||
|
||||
```
|
||||
pnpm typecheck
|
||||
pnpm test
|
||||
pnpm lint
|
||||
pnpm conformance
|
||||
pnpm turbo boundaries
|
||||
```
|
||||
|
||||
All pass.
|
||||
|
||||
Update `docs/work/conformance-system-v1/06-feature-migrations/_story.md`:
|
||||
- frontmatter `status: in-progress` → `done`
|
||||
- All `- [ ]` → `- [x]`
|
||||
|
||||
Update `docs/work/conformance-system-v1/_epic.md`. Find:
|
||||
```markdown
|
||||
- [ ] 07 — Migrate auth feature reference (later plan)
|
||||
```
|
||||
|
||||
Replace with:
|
||||
```markdown
|
||||
- [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)
|
||||
```
|
||||
|
||||
Also: since this completes the conformance-system-v1 epic's planned stories, flip the epic frontmatter `status: in-progress` → `status: done`.
|
||||
|
||||
Commit: `docs(work): close story 06 + conformance-system-v1 epic`.
|
||||
Reference in New Issue
Block a user