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/mainand noorigin/dev. - Keep the current
/adminand/modroute behavior unchanged throughout this subproject. - Do not expose the preview in production.
NODE_ENV=productionmust 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 existingPermissionSet; 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.housekeepinginsrc/messages/en.jsonandsrc/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 --checkbefore 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/adminandsrc/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 === nullonly forREMOVE; -
non-
REMOVEtargets start with/admin/; -
MERGEandREBUILDdeclare at least one required test; -
mutation dependencies require mutation capabilities and non-
NONEaudit; -
VERIFIEDrequires non-empty parity evidence; -
no string field contains
TBD,TODO,FIXME, orUNCLASSIFIED. -
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, andvalidateMigrationEntries()from Task 1. - Produces:
operationsMigrationEntriesandpeopleMigrationEntrieswith 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:
- users, multi-account aliases, online, guilds, applications;
- teams, moderation, CFH, bans, IP, VPN, word filter;
- tickets and help tickets;
- all 13
/modpages.
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:
contentMigrationEntriesfor 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:
economyMigrationEntriesfor 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:
hotelMigrationEntriesfor 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_MATRIXand thepnpm hk:matrix:checkCLI.
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:
HousekeepingDomainIdfrom 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 cachedgetHousekeepingCapabilityContext(). -
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(), andbuildHousekeepingNavigation(). -
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, andHousekeepingNavigationDomain. -
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-nextpreview. -
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.tsxcallsisHousekeepingPreviewEnabled({ nodeEnv: env.NODE_ENV, flag: env.HOUSEKEEPING_NEXT_PREVIEW_ENABLED })and callsnotFound()before rendering children when it returns false. -
admin-next/page.tsxloads the capability context, finds the first visible domain, and redirects to itspreviewHref; it callsnotFound()if none is visible. -
admin-next/[domain]/layout.tsxresolvesparams: Promise<{ domain: string }>, rejects unknown or unauthorized domains withnotFound(), builds localized navigation, and rendersHousekeepingShell. -
admin-next/[domain]/page.tsxrenders a localizedHousekeepingPageShellplus 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
/modrow isMERGEand targets People; /admin/housekeepingand/admin/menuareREMOVE, while live permissions have a System target;- the six domain manifests validate against existing
PERMSvalues; - capability visibility uses
PermissionSetmethods and no fixed rank threshold; - the Command Deck and four page states pass static accessibility smoke tests;
/admin-nextis disabled in production regardless of its flag;/adminand/modbehavior and source trees are unchanged;- matrix, targeted tests, typecheck, lint, build, and
git diff --checkpass; - no database migration, domain query, domain mutation, command execution, inbox aggregation, or preference persistence was added.