# Housekeeping Inventory & Foundation Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Produce the complete 137-page HK migration inventory and a tested, non-production Command Deck foundation without changing the current `/admin` or `/mod` experience. **Architecture:** Add a modular `src/features/housekeeping` boundary containing migration evidence, capability-aware contracts, a validated domain registry, shell primitives, and a non-production preview. Existing authentication and ACL data remain authoritative through `getAdminContext()`; no business workflow is migrated in this subproject. **Tech Stack:** Node.js `>=26.7.0 <27`, pnpm `11.22.0`, Next.js `16.3.2`, React `19.2.8`, TypeScript `7.0.2`, Vitest `4.1.11`, Zod `4.4.3`, next-intl `4.13.7`, Tailwind CSS `4.3.3`, Biome `2.5.9`. **Spec:** `docs/superpowers/specs/2026-08-24-housekeeping-modernization-design.md` ## Global Constraints - Work directly in `E:\Users\simol\Desktop\EpicNext-cms`; do not create a Git worktree or another checkout. - Preserve all unrelated tracked, deleted, untracked, and committed local work. Stage only the files named by the current task. - Pull from the configured canonical upstream before execution. This repository has `origin/main` and no `origin/dev`. - Keep the current `/admin` and `/mod` route behavior unchanged throughout this subproject. - Do not expose the preview in production. `NODE_ENV=production` must disable it even if the preview flag is set. - Do not add operational inbox aggregation, entity search, command execution, domain mutations, preferences persistence, redirects, legacy deletion, or database migrations. - Reuse `getAdminContext()` and the existing `PermissionSet`; do not add rank thresholds or duplicate ACL queries. - Rank may influence presentation defaults only. Effective capability slugs determine visibility and access. - Use only semantic `--admin-*` theme tokens for HK chrome. Do not add hard-coded Tailwind palette colors to ordinary HK UI. - Put source copy under `pages.housekeeping` in `src/messages/en.json` and `src/messages/it.json`; all other locales use the repository's English fallback. - Keep files focused. Foundation modules must not import `@/lib/db`, `@/actions/*`, or domain page modules. - Use TDD: observe the requested failing assertion before writing its implementation, then run the narrow test before widening validation. - Use CRLF-compatible edits and run `git diff --check` before every commit. ## Locked File Structure ```text src/features/housekeeping/ migration/ types.ts migration row and evidence types discover-legacy-pages.ts filesystem-to-route discovery validate-matrix.ts structural and coverage validation operations.ts `/admin` dashboard row people.ts people, support, moderation, all `/mod` rows content.ts editorial and engagement rows economy.ts economy and catalog rows hotel.ts hotel, radio, rooms, studio rows system.ts configuration, ACL, logs, DevOps rows matrix.ts aggregate export only *.test.ts per-domain audit contracts foundation/ contracts/ capability.ts capability requirements result.ts typed success/error outcomes domain.ts domain and route manifests query.ts query contract command.ts command metadata/runtime contract search.ts search provider contract inbox.ts derived work-item/source contract widget.ts dashboard widget contract index.ts public contract barrel capability-context.ts pure capability helpers server-capability-context.ts `getAdminContext()` adapter registry.ts manifest validation and visibility navigation.ts registry-to-shell navigation model preview-gate.ts non-production flag policy shell/ housekeeping-shell.tsx Command Deck frame domain-rail.tsx six-domain primary navigation context-nav.tsx active-domain navigation command-trigger.tsx disabled phase-01 command affordance operator-summary.tsx authenticated actor summary page/ housekeeping-page-shell.tsx standard title/action/content frame housekeeping-page-state.tsx loading/empty/partial/error states domains/ operations/manifest.ts people/manifest.ts content/manifest.ts economy/manifest.ts hotel/manifest.ts system/manifest.ts manifests.ts aggregate of the six manifests src/app/admin-next/ layout.tsx global preview gate page.tsx first-visible-domain redirect [domain]/layout.tsx capability-filtered Command Deck [domain]/page.tsx foundation-only domain placeholder scripts/verify-housekeeping-matrix.ts ``` The feature folder contains no database access and no migrated business workflow. The only route added is `/admin-next`, which returns 404 outside an explicitly enabled non-production environment. --- ### Task 1: Legacy page discovery and migration schema **Files:** - Create: `src/features/housekeeping/migration/types.ts` - Create: `src/features/housekeeping/migration/discover-legacy-pages.ts` - Create: `src/features/housekeeping/migration/validate-matrix.ts` - Test: `src/features/housekeeping/migration/discover-legacy-pages.test.ts` - Test: `src/features/housekeeping/migration/validate-matrix.test.ts` **Interfaces:** - Consumes: filesystem roots `src/app/admin` and `src/app/mod`. - Produces: `discoverLegacyPages(rootDir?: string): LegacyPage[]`, `ownedLegacyPages(prefixes: readonly string[], pages?: readonly LegacyPage[]): LegacyPage[]`, `validateMigrationEntries(discovered: readonly LegacyPage[], entries: readonly MigrationEntry[]): string[]`, and the migration types used by Tasks 2-7. - [ ] **Step 1: Write the failing discovery tests** ```ts import { describe, expect, it } from "vitest"; import { discoverLegacyPages } from "./discover-legacy-pages"; describe("discoverLegacyPages", () => { it("discovers the exact legacy administration inventory", () => { const pages = discoverLegacyPages(); expect(pages).toHaveLength(137); expect(pages).toContainEqual({ surface: "admin", legacyPath: "/admin/users/:id/edit", sourceFile: "src/app/admin/users/[id]/edit/page.tsx", }); expect(pages).toContainEqual({ surface: "mod", legacyPath: "/mod/cfh/:id", sourceFile: "src/app/mod/cfh/[id]/page.tsx", }); }); it("sorts by surface, route, then source file", () => { const pages = discoverLegacyPages(); expect(pages).toEqual([...pages].sort((a, b) => `${a.surface}:${a.legacyPath}:${a.sourceFile}`.localeCompare( `${b.surface}:${b.legacyPath}:${b.sourceFile}`, ), )); }); }); ``` - [ ] **Step 2: Run the discovery test and confirm the red state** Run: `pnpm exec vitest run src/features/housekeeping/migration/discover-legacy-pages.test.ts` Expected: FAIL because `discover-legacy-pages.ts` does not exist. - [ ] **Step 3: Define the exact migration types** ```ts export const HOUSEKEEPING_DOMAIN_IDS = [ "operations", "people", "content", "economy", "hotel", "system", ] as const; export type HousekeepingDomainId = (typeof HOUSEKEEPING_DOMAIN_IDS)[number]; export type LegacySurface = "admin" | "mod"; export type MigrationDecision = "REHOST" | "REBUILD" | "MERGE" | "REMOVE"; export type AuditRequirement = "NONE" | "MUTATION" | "PRIVILEGED_MUTATION"; export type AuditState = "UNAUDITED" | "PARTIAL" | "COMPLETE"; export type MigrationStatus = "PLANNED" | "IN_PROGRESS" | "VERIFIED" | "REMOVED"; export interface LegacyPage { surface: LegacySurface; legacyPath: string; sourceFile: string; } export interface MigrationEntry extends LegacyPage { targetDomain: HousekeepingDomainId; targetPath: string | null; decision: MigrationDecision; capabilities: { read: readonly string[]; mutate: readonly string[] }; dependencies: { queries: readonly string[]; mutations: readonly string[] }; auditRequirement: AuditRequirement; localization: AuditState; accessibility: AuditState; requiredTests: readonly ("unit" | "integration" | "e2e" | "visual")[]; parityEvidence: readonly string[]; status: MigrationStatus; notes: readonly string[]; } ``` - [ ] **Step 4: Implement deterministic route discovery** Use `readdirSync(..., { withFileTypes: true })`, recurse only below the two locked roots, retain `page.tsx`, remove route-group segments such as `(group)`, and convert `[id]` to `:id` and `[...slug]` to `:slug*`. Normalize every file path with `/` separators before sorting. ```ts export function discoverLegacyPages(rootDir = process.cwd()): LegacyPage[] { return (["admin", "mod"] as const) .flatMap((surface) => discoverSurface(rootDir, surface)) .sort((a, b) => `${a.surface}:${a.legacyPath}:${a.sourceFile}`.localeCompare( `${b.surface}:${b.legacyPath}:${b.sourceFile}`, ), ); } export function ownedLegacyPages( prefixes: readonly string[], pages: readonly LegacyPage[] = discoverLegacyPages(), ): LegacyPage[] { return pages.filter((page) => prefixes.some((prefix) => page.legacyPath === prefix || page.legacyPath.startsWith(`${prefix}/`), )); } ``` - [ ] **Step 5: Write the failing validator tests** ```ts const page = (legacyPath: string): LegacyPage => ({ surface: legacyPath.startsWith("/mod") ? "mod" : "admin", legacyPath, sourceFile: `src/app${legacyPath}/page.tsx`, }); const entry = (legacyPath: string): MigrationEntry => ({ ...page(legacyPath), targetDomain: "system", targetPath: "/admin/system/example", decision: "REHOST", capabilities: { read: ["admin.dashboard"], mutate: [] }, dependencies: { queries: [], mutations: [] }, auditRequirement: "NONE", localization: "COMPLETE", accessibility: "COMPLETE", requiredTests: ["unit"], parityEvidence: [], status: "PLANNED", notes: [], }); it("reports missing, duplicate, and unknown legacy rows", () => { const discovered = [page("/admin"), page("/admin/users")]; const issues = validateMigrationEntries(discovered, [ entry("/admin"), entry("/admin"), entry("/admin/ghost"), ]); expect(issues).toEqual([ "duplicate legacyPath: /admin", "missing legacyPath: /admin/users", "unknown legacyPath: /admin/ghost", ]); }); it("rejects incomplete decisions", () => { const issues = validateMigrationEntries([page("/admin")], [ { ...entry("/admin"), targetPath: null, decision: "REBUILD" }, ]); expect(issues).toContain("REBUILD requires targetPath: /admin"); }); ``` - [ ] **Step 6: Implement structural validation** Validation must return sorted strings and enforce: - one entry for every discovered page and no unknown entry; - `targetPath === null` only for `REMOVE`; - non-`REMOVE` targets start with `/admin/`; - `MERGE` and `REBUILD` declare at least one required test; - mutation dependencies require mutation capabilities and non-`NONE` audit; - `VERIFIED` requires non-empty parity evidence; - no string field contains `TBD`, `TODO`, `FIXME`, or `UNCLASSIFIED`. - [ ] **Step 7: Run the narrow tests** Run: `pnpm exec vitest run src/features/housekeeping/migration/discover-legacy-pages.test.ts src/features/housekeeping/migration/validate-matrix.test.ts` Expected: PASS with 137 discovered pages. - [ ] **Step 8: Commit Task 1** ```powershell git add src/features/housekeeping/migration/types.ts src/features/housekeeping/migration/discover-legacy-pages.ts src/features/housekeeping/migration/validate-matrix.ts src/features/housekeeping/migration/discover-legacy-pages.test.ts src/features/housekeeping/migration/validate-matrix.test.ts git diff --cached --check git commit -m "test: define housekeeping migration inventory" ``` ### Task 2: Operations and People migration audit **Files:** - Create: `src/features/housekeeping/migration/operations.ts` - Create: `src/features/housekeeping/migration/people.ts` - Test: `src/features/housekeeping/migration/operations.test.ts` - Test: `src/features/housekeeping/migration/people.test.ts` **Interfaces:** - Consumes: `discoverLegacyPages()`, `MigrationEntry`, and `validateMigrationEntries()` from Task 1. - Produces: `operationsMigrationEntries` and `peopleMigrationEntries` with one reviewed row for every owned page. Owned prefixes are exact: ```ts const PEOPLE_PREFIXES = [ "/admin/users", "/admin/online", "/admin/guilds", "/admin/applications", "/admin/teams", "/admin/bans", "/admin/ip", "/admin/vpn", "/admin/wordfilter", "/admin/moderation", "/admin/tickets", "/admin/help-tickets", "/mod", ] as const; ``` Operations owns only `/admin` in this subproject. - [ ] **Step 1: Write failing ownership tests** ```ts it("covers the legacy dashboard once", () => { expect(operationsMigrationEntries).toHaveLength(1); expect(operationsMigrationEntries[0]).toMatchObject({ legacyPath: "/admin", targetDomain: "operations", targetPath: "/admin/work", decision: "REBUILD", status: "PLANNED", }); }); it("merges every mod page into a people workflow", () => { const modRows = peopleMigrationEntries.filter((row) => row.surface === "mod"); expect(modRows).toHaveLength(13); expect(modRows.every((row) => row.decision === "MERGE")).toBe(true); expect(modRows.every((row) => row.targetPath?.startsWith("/admin/people/"))) .toBe(true); }); ``` - [ ] **Step 2: Run the tests and confirm missing audit modules** Run: `pnpm exec vitest run src/features/housekeeping/migration/operations.test.ts src/features/housekeeping/migration/people.test.ts` Expected: FAIL because both entry arrays are missing. - [ ] **Step 3: Audit Operations** Read `src/app/admin/page.tsx` and record its real Drizzle query dependencies, current localization state, current accessibility state, and required parity checks. Use this locked decision: ```ts { surface: "admin", legacyPath: "/admin", sourceFile: "src/app/admin/page.tsx", targetDomain: "operations", targetPath: "/admin/work", decision: "REBUILD", capabilities: { read: [PERMS.ADMIN_DASHBOARD], mutate: [] }, dependencies: { queries: ["users", "active bans", "website articles", "staff activities"], mutations: [], }, auditRequirement: "NONE", localization: "COMPLETE", accessibility: "PARTIAL", requiredTests: ["integration", "e2e", "visual"], parityEvidence: [], status: "PLANNED", notes: ["Replace metric dashboard with capability-derived operational home"], } ``` - [ ] **Step 4: Audit People route groups in small batches** Review these batches separately so every row names actual imported actions/services: 1. users, multi-account aliases, online, guilds, applications; 2. teams, moderation, CFH, bans, IP, VPN, word filter; 3. tickets and help tickets; 4. all 13 `/mod` pages. Primary `/admin` workflows may be `REHOST` only when their current UI, ACL, localization, and service boundary are already sound. Duplicate show/edit aliases and every `/mod` route are `MERGE`. Targets use `/admin/people/users`, `/admin/people/community`, `/admin/people/staff`, `/admin/people/moderation`, or `/admin/people/support` plus stable `:id` suffixes. - [ ] **Step 5: Add per-domain validation** ```ts const expected = discoverLegacyPages().filter( (page) => page.legacyPath === "/admin" || PEOPLE_PREFIXES.some((prefix) => page.legacyPath === prefix || page.legacyPath.startsWith(`${prefix}/`), ), ); expect(validateMigrationEntries(expected, [ ...operationsMigrationEntries, ...peopleMigrationEntries, ])).toEqual([]); ``` - [ ] **Step 6: Run and commit the audit** Run: `pnpm exec vitest run src/features/housekeeping/migration/operations.test.ts src/features/housekeeping/migration/people.test.ts` Expected: PASS; exactly 13 `mod` rows are `MERGE`. ```powershell git add src/features/housekeeping/migration/operations.ts src/features/housekeeping/migration/people.ts src/features/housekeeping/migration/operations.test.ts src/features/housekeeping/migration/people.test.ts git diff --cached --check git commit -m "docs: audit housekeeping people workflows" ``` ### Task 3: Content and Engagement migration audit **Files:** - Create: `src/features/housekeeping/migration/content.ts` - Test: `src/features/housekeeping/migration/content.test.ts` **Interfaces:** - Consumes: Task 1 discovery and validation. - Produces: `contentMigrationEntries` for every path below the locked prefixes. ```ts const CONTENT_PREFIXES = [ "/admin/articles", "/admin/photos", "/admin/media", "/admin/banners", "/admin/ads", "/admin/events", "/admin/polls", "/admin/help-questions", "/admin/tags", "/admin/prefixes", "/admin/writeable-boxes", "/admin/email-templates", "/admin/theme", "/admin/favicon", "/admin/translations", ] as const; ``` - [ ] **Step 1: Write the failing coverage and target tests** ```ts it("covers every content page without legacy-domain targets", () => { const expected = ownedLegacyPages(CONTENT_PREFIXES); expect(validateMigrationEntries(expected, contentMigrationEntries)).toEqual([]); expect(contentMigrationEntries.every((row) => row.targetPath === null || row.targetPath.startsWith("/admin/content/"), )).toBe(true); }); ``` - [ ] **Step 2: Confirm the red state** Run: `pnpm exec vitest run src/features/housekeeping/migration/content.test.ts` Expected: FAIL because `contentMigrationEntries` is missing. - [ ] **Step 3: Audit editorial and media batches** Inspect collection, create, and detail pages for articles, photos, media, banners, and ads. Record actual action/service imports in `dependencies`; preserve `:id` in target paths. Classify duplicate create/detail variants as `MERGE` only when one target workflow owns the same state. - [ ] **Step 4: Audit engagement and help-content batches** Inspect events, polls, help questions, tags, prefixes, writable boxes, and email templates. Targets use `/admin/content/editorial`, `/admin/content/media`, `/admin/content/engagement`, or `/admin/content/help`. - [ ] **Step 5: Audit brand and localization batches** Inspect theme, favicon, and translations. Targets use `/admin/content/brand` and `/admin/content/localization`. Mark mutation pages `MUTATION` or `PRIVILEGED_MUTATION` based on whether they affect global runtime configuration. - [ ] **Step 6: Run and commit the audit** Run: `pnpm exec vitest run src/features/housekeeping/migration/content.test.ts` Expected: PASS with every content-owned legacy page represented once. ```powershell git add src/features/housekeeping/migration/content.ts src/features/housekeeping/migration/content.test.ts git diff --cached --check git commit -m "docs: audit housekeeping content workflows" ``` ### Task 4: Economy and Catalog migration audit **Files:** - Create: `src/features/housekeeping/migration/economy.ts` - Test: `src/features/housekeeping/migration/economy.test.ts` **Interfaces:** - Consumes: Task 1 discovery and validation. - Produces: `economyMigrationEntries` for the locked economy prefixes. ```ts const ECONOMY_PREFIXES = [ "/admin/catalog", "/admin/items", "/admin/shop", "/admin/marketplace", "/admin/transactions", "/admin/vouchers", "/admin/subscriptions", "/admin/rare-values", "/admin/badges", "/admin/achievements", "/admin/sounds", "/admin/calendar", ] as const; ``` - [ ] **Step 1: Write the failing economy audit test** ```ts it("requires privileged audit for economy mutations", () => { const mutationRows = economyMigrationEntries.filter( (row) => row.dependencies.mutations.length > 0, ); expect(mutationRows.length).toBeGreaterThan(0); expect(mutationRows.every( (row) => row.auditRequirement === "PRIVILEGED_MUTATION", )).toBe(true); }); ``` - [ ] **Step 2: Confirm the red state** Run: `pnpm exec vitest run src/features/housekeeping/migration/economy.test.ts` Expected: FAIL because the audit module is missing. - [ ] **Step 3: Audit catalog and item workflows** Inspect catalog collection/detail/builder-club/maintenance and item collection/detail pages. Record direct database access separately from service/action dependencies. Target `/admin/economy/catalog` and `/admin/economy/items`; mark overlapping editors `MERGE` when they mutate the same catalog entity. - [ ] **Step 4: Audit commerce and value workflows** Inspect shop, marketplace, transactions, vouchers, subscriptions, rare values, badges, achievements, sounds, and calendar. Use `/admin/economy/commerce`, `/admin/economy/history`, `/admin/economy/value`, and `/admin/economy/rewards` targets. - [ ] **Step 5: Validate the domain inventory** ```ts expect(validateMigrationEntries( ownedLegacyPages(ECONOMY_PREFIXES), economyMigrationEntries, )).toEqual([]); ``` - [ ] **Step 6: Run and commit the audit** Run: `pnpm exec vitest run src/features/housekeeping/migration/economy.test.ts` Expected: PASS and every economy mutation row requires privileged audit. ```powershell git add src/features/housekeeping/migration/economy.ts src/features/housekeeping/migration/economy.test.ts git diff --cached --check git commit -m "docs: audit housekeeping economy workflows" ``` ### Task 5: Hotel and World migration audit **Files:** - Create: `src/features/housekeeping/migration/hotel.ts` - Test: `src/features/housekeeping/migration/hotel.test.ts` **Interfaces:** - Consumes: Task 1 discovery and validation. - Produces: `hotelMigrationEntries` for rooms, navigator, radio, and studio. ```ts const HOTEL_PREFIXES = [ "/admin/rooms", "/admin/navigation", "/admin/radio", "/admin/studio", ] as const; ``` - [ ] **Step 1: Write the failing hotel coverage test** ```ts it("maps all hotel tools below the hotel target root", () => { expect(validateMigrationEntries( ownedLegacyPages(HOTEL_PREFIXES), hotelMigrationEntries, )).toEqual([]); expect(hotelMigrationEntries.every((row) => row.targetPath === null || row.targetPath.startsWith("/admin/hotel/"), )).toBe(true); }); ``` - [ ] **Step 2: Confirm the red state** Run: `pnpm exec vitest run src/features/housekeeping/migration/hotel.test.ts` Expected: FAIL because the hotel audit module is missing. - [ ] **Step 3: Audit rooms and navigator** Inspect room list/show/edit/furni paths and navigator management. Merge duplicate room detail aliases into `/admin/hotel/rooms/:id`; keep furni as `/admin/hotel/rooms/:id/furni`. - [ ] **Step 4: Audit radio and studio** Inspect all radio and studio pages, including API keys, monitoring, moderation, imports, sync, maintenance, and audit surfaces. Record external RCON/API/filesystem dependencies explicitly and mark credential or runtime mutation pages `PRIVILEGED_MUTATION`. - [ ] **Step 5: Run and commit the audit** Run: `pnpm exec vitest run src/features/housekeeping/migration/hotel.test.ts` Expected: PASS with all owned pages represented once. ```powershell git add src/features/housekeeping/migration/hotel.ts src/features/housekeeping/migration/hotel.test.ts git diff --cached --check git commit -m "docs: audit housekeeping hotel workflows" ``` ### Task 6: System audit and complete 137-row matrix **Files:** - Create: `src/features/housekeeping/migration/system.ts` - Create: `src/features/housekeeping/migration/matrix.ts` - Create: `scripts/verify-housekeeping-matrix.ts` - Modify: `package.json: scripts` - Test: `src/features/housekeeping/migration/system.test.ts` - Test: `src/features/housekeeping/migration/matrix.test.ts` **Interfaces:** - Consumes: all migration arrays from Tasks 2-5. - Produces: `HOUSEKEEPING_MIGRATION_MATRIX` and the `pnpm hk:matrix:check` CLI. ```ts const SYSTEM_PREFIXES = [ "/admin/alerts", "/admin/analytics", "/admin/commandocentrum", "/admin/devops", "/admin/emulator", "/admin/housekeeping", "/admin/logs", "/admin/maintenance", "/admin/menu", "/admin/permissions", "/admin/settings", ] as const; ``` - [ ] **Step 1: Write the failing system decisions** ```ts it("removes legacy foundation-owned pages", () => { expect(systemMigrationEntries).toEqual(expect.arrayContaining([ expect.objectContaining({ legacyPath: "/admin/housekeeping", decision: "REMOVE", targetPath: null }), expect.objectContaining({ legacyPath: "/admin/menu", decision: "REMOVE", targetPath: null }), ])); }); it("keeps live permissions as a system workflow", () => { expect(systemMigrationEntries).toContainEqual(expect.objectContaining({ legacyPath: "/admin/permissions", targetPath: "/admin/system/access/permissions", decision: expect.not.stringMatching("REMOVE"), })); }); ``` - [ ] **Step 2: Confirm the red state** Run: `pnpm exec vitest run src/features/housekeeping/migration/system.test.ts` Expected: FAIL because `systemMigrationEntries` is missing. - [ ] **Step 3: Audit system route groups** Inspect alerts, analytics, command center, DevOps, emulator, logs, maintenance, permissions, and settings. Targets use `/admin/system/access`, `/admin/system/configuration`, `/admin/system/observability`, or `/admin/system/operations`. Preserve the read-only value of the legacy HK export/comparison in the row notes even though its route decision is `REMOVE`. - [ ] **Step 4: Write the failing aggregate coverage test** ```ts it("covers all 137 legacy pages exactly once", () => { const discovered = discoverLegacyPages(); expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(137); expect(validateMigrationEntries( discovered, HOUSEKEEPING_MIGRATION_MATRIX, )).toEqual([]); }); it("contains no undecided evidence markers", () => { expect(JSON.stringify(HOUSEKEEPING_MIGRATION_MATRIX)).not.toMatch( /TBD|TODO|FIXME|UNCLASSIFIED/, ); }); ``` - [ ] **Step 5: Aggregate the six arrays without adding behavior** ```ts export const HOUSEKEEPING_MIGRATION_MATRIX = [ ...operationsMigrationEntries, ...peopleMigrationEntries, ...contentMigrationEntries, ...economyMigrationEntries, ...hotelMigrationEntries, ...systemMigrationEntries, ].sort((a, b) => a.legacyPath.localeCompare(b.legacyPath)); ``` - [ ] **Step 6: Add the CLI verifier** `scripts/verify-housekeeping-matrix.ts` calls `validateMigrationEntries(discoverLegacyPages(), HOUSEKEEPING_MIGRATION_MATRIX)`. It prints every returned issue to stderr and sets `process.exitCode = 1`; a valid matrix prints `Housekeeping migration matrix: 137/137 valid`. Add: ```json "hk:matrix:check": "tsx scripts/verify-housekeeping-matrix.ts" ``` - [ ] **Step 7: Run and commit the complete matrix** Run: ```powershell pnpm exec vitest run src/features/housekeeping/migration pnpm hk:matrix:check ``` Expected: all migration tests PASS and CLI prints `137/137 valid`. ```powershell git add src/features/housekeeping/migration/system.ts src/features/housekeeping/migration/matrix.ts src/features/housekeeping/migration/system.test.ts src/features/housekeeping/migration/matrix.test.ts scripts/verify-housekeeping-matrix.ts package.json git diff --cached --check git commit -m "docs: complete housekeeping migration matrix" ``` ### Task 7: Foundation contract types **Files:** - Create: `src/features/housekeeping/foundation/contracts/capability.ts` - Create: `src/features/housekeeping/foundation/contracts/result.ts` - Create: `src/features/housekeeping/foundation/contracts/domain.ts` - Create: `src/features/housekeeping/foundation/contracts/query.ts` - Create: `src/features/housekeeping/foundation/contracts/command.ts` - Create: `src/features/housekeeping/foundation/contracts/search.ts` - Create: `src/features/housekeeping/foundation/contracts/inbox.ts` - Create: `src/features/housekeeping/foundation/contracts/widget.ts` - Create: `src/features/housekeeping/foundation/contracts/index.ts` - Test: `src/features/housekeeping/foundation/contracts/contracts.test.ts` **Interfaces:** - Consumes: `HousekeepingDomainId` from migration types only. - Produces: every public foundation type used by Tasks 8-11. - [ ] **Step 1: Write failing constructor tests** ```ts it("creates typed success and error results", () => { expect(ok({ count: 2 }, "corr-1")).toEqual({ ok: true, data: { count: 2 }, correlationId: "corr-1", }); expect(fail("CAPABILITY_DENIED", "corr-2")).toEqual({ ok: false, error: { code: "CAPABILITY_DENIED" }, correlationId: "corr-2", }); }); it("rejects an empty capability requirement", () => { expect(() => anyCapability()).toThrow("capability requirement is empty"); }); ``` - [ ] **Step 2: Confirm the red state** Run: `pnpm exec vitest run src/features/housekeeping/foundation/contracts/contracts.test.ts` Expected: FAIL because the contract barrel does not exist. - [ ] **Step 3: Define capability and result contracts** ```ts export type CapabilityRequirement = | { mode: "all"; slugs: readonly string[] } | { mode: "any"; slugs: readonly string[] }; export interface HousekeepingActor { id: number; username: string; rank: number; } export interface HousekeepingCapabilityContext { actor: HousekeepingActor; isSuperAdmin: boolean; has(slug: string): boolean; hasAny(...slugs: string[]): boolean; hasAll(...slugs: string[]): boolean; } export type HousekeepingErrorCode = | "VALIDATION_FAILED" | "AUTHENTICATION_REQUIRED" | "CAPABILITY_DENIED" | "NOT_FOUND" | "CONFLICT" | "DEPENDENCY_UNAVAILABLE" | "PARTIAL_RESULT" | "INTERNAL_ERROR"; ``` - [ ] **Step 4: Define domain, query, and command contracts** ```ts export interface HousekeepingRouteDefinition { id: string; labelKey: string; href: string; capability: CapabilityRequirement; matchPrefixes?: readonly string[]; } export interface HousekeepingDomainManifest { id: HousekeepingDomainId; labelKey: string; descriptionKey: string; iconId: "inbox" | "users" | "file-text" | "gem" | "hotel" | "settings"; previewHref: `/admin-next/${HousekeepingDomainId}`; capability: CapabilityRequirement; routes: readonly HousekeepingRouteDefinition[]; } export interface HousekeepingQuery { id: string; owner: HousekeepingDomainId; capability: CapabilityRequirement; run(context: HousekeepingCapabilityContext, input: I): Promise>; } export interface HousekeepingCommand { id: string; owner: HousekeepingDomainId; risk: "safe" | "sensitive"; capability: CapabilityRequirement; requiresReason: boolean; execute(context: HousekeepingCapabilityContext, input: I): Promise>; } ``` - [ ] **Step 5: Define search, inbox, and widget contracts** Use these exact public methods: - `HousekeepingSearchProvider.search(context, { term, limit }): Promise>` - `HousekeepingInboxSource.getItems(context, signal): Promise>` - `HousekeepingWidgetDefinition.load(context): Promise>` `HousekeepingWorkItem` contains `sourceId`, `itemId`, `deduplicationKey`, `domain`, `capability`, `severity`, `occurredAt`, `titleKey`, `context`, `href`, and `freshness`. No assignment, dismissal, completion, or task-owner field is permitted. - [ ] **Step 6: Run tests and typecheck the public barrel** Run: ```powershell pnpm exec vitest run src/features/housekeeping/foundation/contracts/contracts.test.ts pnpm typecheck ``` Expected: PASS; no type or unused-export errors. - [ ] **Step 7: Commit Task 7** ```powershell git add src/features/housekeeping/foundation/contracts git diff --cached --check git commit -m "feat: define housekeeping foundation contracts" ``` ### Task 8: Request-scoped capability context **Files:** - Create: `src/features/housekeeping/foundation/capability-context.ts` - Create: `src/features/housekeeping/foundation/server-capability-context.ts` - Test: `src/features/housekeeping/foundation/capability-context.test.ts` - Test: `src/features/housekeeping/foundation/server-capability-context.test.ts` **Interfaces:** - Consumes: `PermissionSet`, `getAdminContext()`, and Task 7 capability contracts. - Produces: `createHousekeepingCapabilityContext()`, `satisfiesCapability()`, and cached `getHousekeepingCapabilityContext()`. - [ ] **Step 1: Write pure failing capability tests** ```ts const actor: HousekeepingActor = { id: 42, username: "operator", rank: 7 }; const permissionSet = ( slugs: readonly string[], isSuperAdmin = false, ): PermissionSet => { const granted = new Set(slugs); const has = (slug: string) => isSuperAdmin || granted.has(slug); return { isSuperAdmin, has, hasAny: (...requested) => requested.some(has), hasAll: (...requested) => requested.every(has), }; }; const superAdminPermissionSet = () => permissionSet([], true); it("evaluates any/all requirements from effective permission methods", () => { const context = createHousekeepingCapabilityContext(actor, permissionSet([ "admin.users.view", "admin.tickets.view", ])); expect(satisfiesCapability(context, anyCapability("admin.users.view", "admin.logs.view"))).toBe(true); expect(satisfiesCapability(context, allCapabilities("admin.users.view", "admin.logs.view"))).toBe(false); }); it("keeps the super administrator explicit", () => { const context = createHousekeepingCapabilityContext(actor, superAdminPermissionSet()); expect(context.isSuperAdmin).toBe(true); expect(context.has("unknown.future.slug")).toBe(true); }); ``` - [ ] **Step 2: Confirm the red state** Run: `pnpm exec vitest run src/features/housekeeping/foundation/capability-context.test.ts` Expected: FAIL because the helper module is missing. - [ ] **Step 3: Implement the pure adapter** ```ts export function createHousekeepingCapabilityContext( actor: HousekeepingActor, permissions: PermissionSet, ): HousekeepingCapabilityContext { return { actor, isSuperAdmin: permissions.isSuperAdmin, has: (slug) => permissions.has(slug), hasAny: (...slugs) => permissions.hasAny(...slugs), hasAll: (...slugs) => permissions.hasAll(...slugs), }; } ``` `satisfiesCapability()` returns true for super administrators, then delegates to `hasAny` or `hasAll`; an empty requirement is impossible because Task 7 constructors throw. - [ ] **Step 4: Write the failing server adapter test** Mock `@/lib/permissions` and assert that the adapter calls `getAdminContext()` once, uses the database-refreshed `session.user` actor, and forwards the returned `PermissionSet` without querying the database directly. ```ts expect(await getHousekeepingCapabilityContext()).toMatchObject({ actor: { id: 42, username: "operator", rank: 7 }, isSuperAdmin: false, }); expect(getAdminContext).toHaveBeenCalledTimes(1); ``` - [ ] **Step 5: Implement the cached server adapter** ```ts export const getHousekeepingCapabilityContext = cache(async () => { const { session, permissions } = await getAdminContext(); return createHousekeepingCapabilityContext( { id: session.user.id, username: session.user.username, rank: session.user.rank, }, permissions, ); }); ``` - [ ] **Step 6: Run authorization contracts and commit** Run: ```powershell pnpm exec vitest run src/features/housekeeping/foundation/capability-context.test.ts src/features/housekeeping/foundation/server-capability-context.test.ts src/lib/admin/authorization-contract.test.ts src/lib/admin/guard.test.ts pnpm typecheck ``` Expected: PASS; central authorization still contains no fixed rank threshold. ```powershell git add src/features/housekeeping/foundation/capability-context.ts src/features/housekeeping/foundation/server-capability-context.ts src/features/housekeeping/foundation/capability-context.test.ts src/features/housekeeping/foundation/server-capability-context.test.ts git diff --cached --check git commit -m "feat: add housekeeping capability context" ``` ### Task 9: Domain registry, manifests, and navigation model **Files:** - Create: `src/features/housekeeping/foundation/registry.ts` - Create: `src/features/housekeeping/foundation/navigation.ts` - Create: `src/features/housekeeping/domains/operations/manifest.ts` - Create: `src/features/housekeeping/domains/people/manifest.ts` - Create: `src/features/housekeeping/domains/content/manifest.ts` - Create: `src/features/housekeeping/domains/economy/manifest.ts` - Create: `src/features/housekeeping/domains/hotel/manifest.ts` - Create: `src/features/housekeeping/domains/system/manifest.ts` - Create: `src/features/housekeeping/manifests.ts` - Modify: `src/messages/en.json: pages` - Modify: `src/messages/it.json: pages` - Test: `src/features/housekeeping/foundation/registry.test.ts` - Test: `src/features/housekeeping/foundation/navigation.test.ts` - Test: `src/features/housekeeping/foundation/localization-contract.test.ts` **Interfaces:** - Consumes: Task 7 manifests and Task 8 `satisfiesCapability()`. - Produces: `HOUSEKEEPING_MANIFESTS`, `createHousekeepingRegistry()`, and `buildHousekeepingNavigation()`. - [ ] **Step 1: Write failing registry validation tests** ```ts const manifest = ( id: HousekeepingDomainId, capabilitySlug = PERMS.ADMIN_DASHBOARD, ): HousekeepingDomainManifest => ({ id, labelKey: `pages.housekeeping.domains.${id}.title`, descriptionKey: `pages.housekeeping.domains.${id}.description`, iconId: "settings", previewHref: `/admin-next/${id}`, capability: anyCapability(capabilitySlug), routes: [], }); const duplicateA = manifest("operations"); const duplicateB = manifest("operations"); const duplicateRouteManifest: HousekeepingDomainManifest = { ...manifest("people"), routes: [ { id: "one", labelKey: "one", href: "/admin-next/people/users", capability: anyCapability(PERMS.ADMIN_DASHBOARD) }, { id: "two", labelKey: "two", href: "/admin-next/people/users", capability: anyCapability(PERMS.ADMIN_DASHBOARD) }, ], }; const unknownPermissionManifest = manifest("system", "admin.ghost.view"); it("rejects duplicate domains, routes, and unknown capability slugs", () => { expect(() => createHousekeepingRegistry([duplicateA, duplicateB])) .toThrow(/duplicate domain id/); expect(() => createHousekeepingRegistry([duplicateRouteManifest])) .toThrow(/duplicate route href/); expect(() => createHousekeepingRegistry([unknownPermissionManifest])) .toThrow(/unknown capability slug: admin\.ghost\.view/); }); it("registers exactly the six approved domains", () => { const registry = createHousekeepingRegistry(HOUSEKEEPING_MANIFESTS); expect(registry.domains.map((domain) => domain.id)).toEqual([ "operations", "people", "content", "economy", "hotel", "system", ]); }); ``` - [ ] **Step 2: Confirm the red state** Run: `pnpm exec vitest run src/features/housekeeping/foundation/registry.test.ts` Expected: FAIL because registry and manifests are missing. - [ ] **Step 3: Implement registry validation** Build the known permission catalog from `Object.values(PERMS)`. Validate domain IDs, preview hrefs, route IDs, route hrefs, non-empty label keys, and every `CapabilityRequirement` slug. Freeze the returned domain array. - [ ] **Step 4: Define the six empty-workflow manifests** Each manifest has no migrated business route; only its preview href and domain capability are present. Use these capability groups: - operations: `admin.dashboard`; - people: any user, moderation, ticket, ban, or `mod.*` view capability; - content: any news, pages, banners, events, polls, or prefixes view capability; - economy: any catalog or shop view capability; - hotel: any room, radio, or asset-import capability; - system: any settings, logs, analytics, DevOps, notifications, permissions, or RCON capability. - [ ] **Step 5: Add English and Italian source copy** Under `pages.housekeeping`, add `preview`, `navigation`, `domains`, and `states`. Domain keys are `operations`, `people`, `content`, `economy`, `hotel`, and `system`, each with `title` and `description`. Add explicit strings for preview badge, disabled command trigger, back to site, empty foundation state, partial state, error state, and loading state. - [ ] **Step 6: Implement and test the navigation projection** ```ts export interface HousekeepingNavigationDomain { id: HousekeepingDomainId; href: string; iconId: HousekeepingDomainManifest["iconId"]; label: string; description: string; items: readonly { id: string; href: string; label: string }[]; } ``` `buildHousekeepingNavigation(registry, context, translate)` filters unauthorized domains and routes before translating. Tests must prove a moderator without `admin.dashboard` can see People when a `mod.*` capability is present, while Economy stays hidden. - [ ] **Step 7: Verify localization keys** The localization contract reads `en.json` and `it.json`, resolves every manifest key by dot path, and fails on missing or non-string values. - [ ] **Step 8: Run and commit Task 9** Run: ```powershell pnpm exec vitest run src/features/housekeeping/foundation/registry.test.ts src/features/housekeeping/foundation/navigation.test.ts src/features/housekeeping/foundation/localization-contract.test.ts pnpm typecheck ``` Expected: PASS; six domains in locked order and all manifest keys present in English and Italian. ```powershell git add src/features/housekeeping/foundation/registry.ts src/features/housekeeping/foundation/navigation.ts src/features/housekeeping/domains src/features/housekeeping/manifests.ts src/features/housekeeping/foundation/registry.test.ts src/features/housekeeping/foundation/navigation.test.ts src/features/housekeeping/foundation/localization-contract.test.ts src/messages/en.json src/messages/it.json git diff --cached --check git commit -m "feat: add housekeeping domain registry" ``` ### Task 10: Command Deck shell and standard page states **Files:** - Create: `src/features/housekeeping/foundation/shell/housekeeping-shell.tsx` - Create: `src/features/housekeeping/foundation/shell/domain-rail.tsx` - Create: `src/features/housekeeping/foundation/shell/context-nav.tsx` - Create: `src/features/housekeeping/foundation/shell/command-trigger.tsx` - Create: `src/features/housekeeping/foundation/shell/operator-summary.tsx` - Create: `src/features/housekeeping/foundation/page/housekeeping-page-shell.tsx` - Create: `src/features/housekeeping/foundation/page/housekeeping-page-state.tsx` - Test: `src/features/housekeeping/foundation/shell/housekeeping-shell.test.tsx` - Test: `src/features/housekeeping/foundation/page/housekeeping-page-state.test.tsx` **Interfaces:** - Consumes: `HousekeepingActor`, `HousekeepingDomainId`, and `HousekeepingNavigationDomain`. - Produces: pure server-renderable shell/page components with no search, mutation, persistence, or database behavior. - [ ] **Step 1: Write the failing shell accessibility test** ```tsx const navigation: readonly HousekeepingNavigationDomain[] = [{ id: "people", href: "/admin-next/people", iconId: "users", label: "People", description: "Users, moderation, and support", items: [{ id: "users", href: "/admin-next/people/users", label: "Users" }], }]; it("renders the Command Deck landmarks and active domain", () => { const html = renderToStaticMarkup(

content

, ); expect(html).toContain('href="#housekeeping-content"'); expect(html).toContain('aria-current="page"'); expect(html).toContain('id="housekeeping-content"'); expect(html).toContain("operator"); }); ``` - [ ] **Step 2: Confirm the red state** Run: `pnpm exec vitest run src/features/housekeeping/foundation/shell/housekeeping-shell.test.tsx` Expected: FAIL because the shell components are missing. - [ ] **Step 3: Implement the shell components** Use `Link`, semantic `