Files
EpicNext-Cms/docs/superpowers/plans/2026-08-24-housekeeping-inventory-foundation.md
T

1402 lines
54 KiB
Markdown

# 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<I, O> {
id: string;
owner: HousekeepingDomainId;
capability: CapabilityRequirement;
run(context: HousekeepingCapabilityContext, input: I): Promise<HousekeepingResult<O>>;
}
export interface HousekeepingCommand<I, O> {
id: string;
owner: HousekeepingDomainId;
risk: "safe" | "sensitive";
capability: CapabilityRequirement;
requiresReason: boolean;
execute(context: HousekeepingCapabilityContext, input: I): Promise<HousekeepingResult<O>>;
}
```
- [ ] **Step 5: Define search, inbox, and widget contracts**
Use these exact public methods:
- `HousekeepingSearchProvider.search(context, { term, limit }): Promise<HousekeepingResult<readonly HousekeepingSearchResult[]>>`
- `HousekeepingInboxSource.getItems(context, signal): Promise<HousekeepingResult<HousekeepingInboxSourceResult>>`
- `HousekeepingWidgetDefinition.load(context): Promise<HousekeepingResult<unknown>>`
`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(
<HousekeepingShell
actor={{ id: 42, username: "operator", rank: 7 }}
activeDomainId="people"
domains={navigation}
labels={{
skipToContent: "Skip to content",
primaryNavigation: "Housekeeping domains",
contextualNavigation: "People navigation",
command: "Search and commands are enabled in a later subproject",
preview: "Foundation preview",
backToSite: "Back to site",
}}
>
<p>content</p>
</HousekeepingShell>,
);
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 `<aside>`, `<nav>`, `<header>`, and `<main>` elements. The rail is compact at desktop width; contextual navigation collapses below the desktop breakpoint. All colors use existing admin variables such as `var(--admin-canvas)`, `var(--admin-surface)`, `var(--admin-border)`, `var(--admin-text)`, `var(--admin-text-muted)`, and `var(--admin-accent)`.
The command trigger is a disabled `<button type="button" disabled>` with localized explanatory text. It must not import `cmdk`, register keyboard listeners, or expose an executable callback in phase 01.
- [ ] **Step 4: Write failing page-state tests**
```tsx
it.each([
["loading", "status"],
["empty", "status"],
["partial", "status"],
["error", "alert"],
] as const)("renders %s with role %s", (state, role) => {
const html = renderToStaticMarkup(
<HousekeepingPageState state={state} title="State title" description="State detail" />,
);
expect(html).toContain(`role="${role}"`);
});
```
- [ ] **Step 5: Implement page shell and states**
`HousekeepingPageShell` accepts `title`, `description`, optional `primaryAction`, optional `context`, and `children`. `HousekeepingPageState` accepts only the four locked states plus localized `title`, `description`, and optional retry action. Loading uses `aria-live="polite"`; partial uses a visible warning label; error uses `role="alert"`.
- [ ] **Step 6: Run component tests and commit**
Run:
```powershell
pnpm exec vitest run src/features/housekeeping/foundation/shell/housekeeping-shell.test.tsx src/features/housekeeping/foundation/page/housekeeping-page-state.test.tsx
pnpm typecheck
```
Expected: PASS; static markup contains the required landmarks and roles.
```powershell
git add src/features/housekeeping/foundation/shell src/features/housekeeping/foundation/page
git diff --cached --check
git commit -m "feat: build housekeeping command deck shell"
```
### Task 11: Non-production preview route
**Files:**
- Create: `src/features/housekeeping/foundation/preview-gate.ts`
- Test: `src/features/housekeeping/foundation/preview-gate.test.ts`
- Modify: `src/env.ts: schema`
- Modify: `.env.example: core runtime flags`
- Create: `src/app/admin-next/layout.tsx`
- Create: `src/app/admin-next/page.tsx`
- Create: `src/app/admin-next/[domain]/layout.tsx`
- Create: `src/app/admin-next/[domain]/page.tsx`
- Test: `src/features/housekeeping/foundation/preview-route-contract.test.ts`
**Interfaces:**
- Consumes: Task 8 server capability context, Task 9 registry/navigation, and Task 10 shell/page components.
- Produces: `isHousekeepingPreviewEnabled()` and the gated `/admin-next` preview.
- [ ] **Step 1: Write the failing gate matrix test**
```ts
it.each([
["development", true, true],
["test", true, true],
["development", false, false],
["production", true, false],
["production", false, false],
] as const)("NODE_ENV=%s flag=%s => %s", (nodeEnv, flag, expected) => {
expect(isHousekeepingPreviewEnabled({ nodeEnv, flag })).toBe(expected);
});
```
- [ ] **Step 2: Confirm the red state**
Run: `pnpm exec vitest run src/features/housekeeping/foundation/preview-gate.test.ts`
Expected: FAIL because `preview-gate.ts` is missing.
- [ ] **Step 3: Implement the fail-closed gate and env field**
```ts
export function isHousekeepingPreviewEnabled(input: {
nodeEnv: "development" | "test" | "production";
flag: boolean;
}): boolean {
return input.nodeEnv !== "production" && input.flag;
}
```
Add `HOUSEKEEPING_NEXT_PREVIEW_ENABLED` to `src/env.ts` as an optional string transformed from `"true"` or `"1"` to boolean. Add `HOUSEKEEPING_NEXT_PREVIEW_ENABLED=false` to `.env.example` with a comment that production always returns 404.
- [ ] **Step 4: Write the failing route source contract**
The contract reads all four preview route files and asserts:
```ts
expect(layoutSource).toContain("isHousekeepingPreviewEnabled");
expect(layoutSource).toContain("notFound");
expect(domainLayoutSource).toContain("getHousekeepingCapabilityContext");
expect(domainLayoutSource).toContain("buildHousekeepingNavigation");
expect(allPreviewSource).not.toMatch(/@\/lib\/db|@\/actions\//);
expect(allPreviewSource).not.toMatch(/AdminSidebarNav|AdminHubChrome/);
```
- [ ] **Step 5: Implement gated route composition**
- `admin-next/layout.tsx` calls `isHousekeepingPreviewEnabled({ nodeEnv: env.NODE_ENV, flag: env.HOUSEKEEPING_NEXT_PREVIEW_ENABLED })` and calls `notFound()` before rendering children when it returns false.
- `admin-next/page.tsx` loads the capability context, finds the first visible domain, and redirects to its `previewHref`; it calls `notFound()` if none is visible.
- `admin-next/[domain]/layout.tsx` resolves `params: Promise<{ domain: string }>`, rejects unknown or unauthorized domains with `notFound()`, builds localized navigation, and renders `HousekeepingShell`.
- `admin-next/[domain]/page.tsx` renders a localized `HousekeepingPageShell` plus the foundation empty state. It performs no domain query.
- [ ] **Step 6: Run preview and authorization checks**
Run:
```powershell
pnpm exec vitest run src/features/housekeeping/foundation/preview-gate.test.ts src/features/housekeeping/foundation/preview-route-contract.test.ts src/lib/admin/authorization-contract.test.ts
pnpm typecheck
```
Expected: PASS; production remains disabled and preview files have no database/action imports.
- [ ] **Step 7: Commit Task 11**
```powershell
git add src/features/housekeeping/foundation/preview-gate.ts src/features/housekeeping/foundation/preview-gate.test.ts src/features/housekeeping/foundation/preview-route-contract.test.ts src/app/admin-next src/env.ts .env.example
git diff --cached --check
git commit -m "feat: add gated housekeeping foundation preview"
```
### Task 12: Source contracts and final foundation verification
**Files:**
- Modify: `src/lib/admin-theme-source-audit.test.ts: ROOTS`
- Create: `src/features/housekeeping/foundation/foundation-source-contract.test.ts`
- Modify: `package.json: scripts`
**Interfaces:**
- Consumes: every Task 1-11 deliverable.
- Produces: source-boundary regression checks and `pnpm test:housekeeping`.
- [ ] **Step 1: Extend the theme audit and observe the initial failure if semantic tokens were missed**
```ts
const ROOTS = [
"src/app/admin",
"src/components/admin",
"src/app/admin-next",
"src/features/housekeeping",
];
```
Run: `pnpm exec vitest run src/lib/admin-theme-source-audit.test.ts`
Expected: PASS if Task 10 used only semantic admin tokens; otherwise FAIL with the exact offending file and class, which must be corrected before continuing.
- [ ] **Step 2: Write the foundation boundary tests**
```ts
it("keeps foundation independent from database and actions", () => {
for (const file of sourceFiles("src/features/housekeeping/foundation")) {
const source = readFileSync(file, "utf8");
expect(source, file).not.toMatch(/@\/lib\/db|@\/actions\//);
}
});
it("leaves the current administration route trees present", () => {
expect(existsSync("src/app/admin/layout.tsx")).toBe(true);
expect(existsSync("src/app/mod/layout.tsx")).toBe(true);
expect(existsSync("src/app/admin-next/layout.tsx")).toBe(true);
});
it("contains no executable phase-01 command provider", () => {
const source = readTree("src/features/housekeeping");
expect(source).not.toMatch(/registerCommand|executeCommand|CommandPaletteProvider/);
});
```
- [ ] **Step 3: Add the narrow project script**
```json
"test:housekeeping": "vitest run src/features/housekeeping src/lib/admin-theme-source-audit.test.ts src/lib/admin/authorization-contract.test.ts"
```
- [ ] **Step 4: Run the complete narrow verification**
Run:
```powershell
pnpm toolchain:check
pnpm hk:matrix:check
pnpm test:housekeeping
pnpm typecheck
pnpm lint
```
Expected:
- Node toolchain accepted at `>=26.7.0 <27`;
- matrix prints `137/137 valid`;
- all HK, theme, and authorization tests pass;
- TypeScript and Biome exit `0`.
- [ ] **Step 5: Run the production safety build**
Run:
```powershell
$env:HOUSEKEEPING_NEXT_PREVIEW_ENABLED = "true"
pnpm build
Remove-Item Env:HOUSEKEEPING_NEXT_PREVIEW_ENABLED
```
Expected: build exits `0`; the route compiles, while the runtime gate still evaluates production to disabled in the Task 11 unit test. This step does not start or deploy the application.
- [ ] **Step 6: Review scope and current-route diffs**
Run:
```powershell
git diff --name-only origin/main...HEAD
git diff --check
git status --short
```
Expected: no modification below `src/app/admin` or `src/app/mod`; only the approved design/plan documents, new preview tree, HK feature files, matrix verifier, two source locale files, env documentation/schema, package scripts, and the extended theme audit are present.
- [ ] **Step 7: Commit Task 12**
```powershell
git add src/lib/admin-theme-source-audit.test.ts src/features/housekeeping/foundation/foundation-source-contract.test.ts package.json
git diff --cached --check
git commit -m "test: verify housekeeping foundation boundaries"
```
- [ ] **Step 8: Record final evidence without claiming deployment**
Run:
```powershell
git log --oneline --max-count=12
git status --short --branch
```
Expected: every task commit is visible, the checkout is clean, and the branch is ahead of `origin/main`. Report local test/build evidence separately from any future push, CI, or deployment status.
## Plan Completion Criteria
The subproject is complete only when:
- the committed matrix contains 137 unique, structurally valid, reviewed rows;
- every `/mod` row is `MERGE` and targets People;
- `/admin/housekeeping` and `/admin/menu` are `REMOVE`, while live permissions have a System target;
- the six domain manifests validate against existing `PERMS` values;
- capability visibility uses `PermissionSet` methods and no fixed rank threshold;
- the Command Deck and four page states pass static accessibility smoke tests;
- `/admin-next` is disabled in production regardless of its flag;
- `/admin` and `/mod` behavior and source trees are unchanged;
- matrix, targeted tests, typecheck, lint, build, and `git diff --check` pass;
- no database migration, domain query, domain mutation, command execution, inbox aggregation, or preference persistence was added.