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

54 KiB

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

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

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
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.

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
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
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:

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
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:

{
  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
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.

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.
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
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.

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.
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
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
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.

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.
const HOTEL_PREFIXES = [
  "/admin/rooms", "/admin/navigation", "/admin/radio", "/admin/studio",
] as const;
  • Step 1: Write the failing hotel coverage test
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.

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.
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
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
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
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:

"hk:matrix:check": "tsx scripts/verify-housekeeping-matrix.ts"
  • Step 7: Run and commit the complete matrix

Run:

pnpm exec vitest run src/features/housekeeping/migration
pnpm hk:matrix:check

Expected: all migration tests PASS and CLI prints 137/137 valid.

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

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
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
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:

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
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

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
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.

expect(await getHousekeepingCapabilityContext()).toMatchObject({
  actor: { id: 42, username: "operator", rank: 7 },
  isSuperAdmin: false,
});
expect(getAdminContext).toHaveBeenCalledTimes(1);
  • Step 5: Implement the cached server adapter
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:

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.

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

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
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:

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.

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

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
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:

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.

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

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
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:

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:

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
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

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
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
"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:

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:

$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:

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
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:

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.