test(housekeeping): prove 137 route parity
CI / check (pull_request) Successful in 1m9s
CI / release (pull_request) Skipped
CI / deploy (pull_request) Skipped

This commit is contained in:
Simo committed 2026-08-30 19:11:14 +02:00
1 parent b235ce08ff
commit 8a31556d51
36 files changed
+430 -76

No files matched your search

+1
View File
@@ -23,6 +23,7 @@
"db:migrate:status": "tsx scripts/apply-migrations.ts --status",
"db:studio": "drizzle-kit studio",
"hk:matrix:check": "tsx scripts/verify-housekeeping-matrix.ts",
"hk:parity:test": "vitest run --coverage.enabled=false src/features/housekeeping/cutover/parity.test.ts",
"test:housekeeping": "vitest run --coverage.enabled=false src/features/housekeeping src/lib/admin-theme-source-audit.test.ts src/lib/admin/authorization-contract.test.ts"
},
"lint-staged": {
+25 -1
View File
@@ -1,3 +1,5 @@
import { spawnSync } from "node:child_process";
import { resolve } from "node:path";
import { discoverLegacyPages } from "../src/features/housekeeping/migration/discover-legacy-pages";
import { HOUSEKEEPING_MIGRATION_MATRIX } from "../src/features/housekeeping/migration/matrix";
import { validateMigrationEntries } from "../src/features/housekeeping/migration/validate-matrix";
@@ -8,11 +10,33 @@ const issues = validateMigrationEntries(
HOUSEKEEPING_MIGRATION_MATRIX,
);
if (issues.length > 0) {
const parityTest = spawnSync(
process.execPath,
[
resolve(process.cwd(), "node_modules/vitest/vitest.mjs"),
"run",
"--coverage.enabled=false",
"src/features/housekeeping/cutover/parity.test.ts",
],
{ cwd: process.cwd(), encoding: "utf8" },
);
const parityPassed = parityTest.status === 0;
if (issues.length > 0 || !parityPassed) {
for (const issue of issues) console.error(issue);
if (!parityPassed) {
process.stderr.write(parityTest.stdout);
process.stderr.write(parityTest.stderr);
}
process.exitCode = 1;
} else {
console.log(
`Housekeeping migration matrix: ${HOUSEKEEPING_MIGRATION_MATRIX.length}/${discovered.length} valid`,
);
const removed = HOUSEKEEPING_MIGRATION_MATRIX.filter(
(row) => row.status === "REMOVED",
).length;
console.log(
`Housekeeping runtime parity: discovered=137 mapped=137 verified=137 removed=${removed}`,
);
}
+1 -1
View File
@@ -12,7 +12,7 @@ vi.mock(
}),
);
vi.mock("@/features/housekeeping/foundation/commands/bootstrap", () => ({
vi.mock("@/features/housekeeping/commands", () => ({
housekeepingCommandRegistryReady: true,
}));
+1 -1
View File
@@ -1,6 +1,6 @@
"use server";
import "@/features/housekeeping/foundation/commands/bootstrap";
import "@/features/housekeeping/commands";
import { dispatchHousekeepingCommand } from "@/features/housekeeping/foundation/commands/dispatcher";
import {
type HousekeepingResult,
+4
View File
@@ -7,6 +7,10 @@ const { getContextMock, searchMock } = vi.hoisted(() => ({
searchMock: vi.fn(),
}));
vi.mock("@/features/housekeeping/commands", () => ({
housekeepingCommandRegistryReady: true,
}));
vi.mock("@/features/housekeeping/foundation/server-capability-context", () => ({
getHousekeepingCapabilityContext: getContextMock,
}));
+1
View File
@@ -23,6 +23,7 @@ export async function executeHousekeepingSearch(
): Promise<HousekeepingSearchResponse> {
if (typeof term !== "string") return failedSearch("VALIDATION");
try {
await import("@/features/housekeeping/commands");
const context = await getHousekeepingCapabilityContext();
return await searchHousekeeping(term, context);
} catch {
+1 -1
View File
@@ -5,8 +5,8 @@ import { satisfiesCapability } from "@/features/housekeeping/foundation/capabili
import { buildHousekeepingNavigation } from "@/features/housekeeping/foundation/navigation";
import { createHousekeepingRegistry } from "@/features/housekeeping/foundation/registry";
import { getHousekeepingCapabilityContext } from "@/features/housekeeping/foundation/server-capability-context";
import { HousekeepingShell } from "@/features/housekeeping/foundation/shell/housekeeping-shell";
import { HOUSEKEEPING_MANIFESTS } from "@/features/housekeeping/manifests";
import { HousekeepingShell } from "@/features/housekeeping/shell";
const MESSAGE_PREFIX = "pages.housekeeping.";
+1 -1
View File
@@ -6,9 +6,9 @@ import { createHousekeepingRegistry } from "@/features/housekeeping/foundation/r
import { toHousekeepingHref } from "@/features/housekeeping/foundation/routing/href";
import { matchHousekeepingRoute } from "@/features/housekeeping/foundation/routing/match-route";
import { getHousekeepingCapabilityContext } from "@/features/housekeeping/foundation/server-capability-context";
import { HousekeepingShell } from "@/features/housekeeping/foundation/shell/housekeeping-shell";
import { HOUSEKEEPING_MANIFESTS } from "@/features/housekeeping/manifests";
import { HOUSEKEEPING_ROUTE_HANDLERS } from "@/features/housekeeping/route-handlers";
import { HousekeepingShell } from "@/features/housekeeping/shell";
const MESSAGE_PREFIX = "pages.housekeeping.";
+33
View File
@@ -0,0 +1,33 @@
import "server-only";
import { CONTENT_COMMANDS } from "./domains/content/commands/content-commands";
import { ECONOMY_COMMANDS } from "./domains/economy/commands/economy-commands";
import { HOTEL_COMMANDS } from "./domains/hotel/commands/hotel-commands";
import { STUDIO_COMMANDS } from "./domains/hotel/commands/studio-commands";
import { COMMUNITY_COMMANDS } from "./domains/people/commands/community-commands";
import { MODERATION_COMMANDS } from "./domains/people/commands/moderation-commands";
import { SUPPORT_COMMANDS } from "./domains/people/commands/support-commands";
import { USER_COMMANDS } from "./domains/people/commands/user-commands";
import { SYSTEM_COMMANDS } from "./domains/system/commands/system-commands";
import {
defineHousekeepingCommands,
registerHousekeepingCommands,
} from "./foundation/commands/bootstrap";
import { sealHousekeepingCommandRegistry } from "./foundation/commands/registry";
const currentHousekeepingCommands = defineHousekeepingCommands(
...CONTENT_COMMANDS,
...ECONOMY_COMMANDS,
...HOTEL_COMMANDS,
...STUDIO_COMMANDS,
...USER_COMMANDS,
...COMMUNITY_COMMANDS,
...SUPPORT_COMMANDS,
...MODERATION_COMMANDS,
...SYSTEM_COMMANDS,
);
registerHousekeepingCommands(currentHousekeepingCommands);
sealHousekeepingCommandRegistry();
export const housekeepingCommandRegistryReady = true;
@@ -0,0 +1,47 @@
import { describe, expect, it } from "vitest";
import { createHousekeepingRegistry } from "../foundation/registry";
import { HOUSEKEEPING_MANIFESTS } from "../manifests";
import { discoverLegacyPages } from "../migration/discover-legacy-pages";
import { HOUSEKEEPING_MIGRATION_MATRIX } from "../migration/matrix";
import { HOUSEKEEPING_ROUTE_HANDLERS } from "../route-handlers";
import { verifyHousekeepingRuntimeParity } from "./parity";
describe("Housekeeping runtime parity", () => {
const report = verifyHousekeepingRuntimeParity(
HOUSEKEEPING_MIGRATION_MATRIX,
createHousekeepingRegistry(HOUSEKEEPING_MANIFESTS),
HOUSEKEEPING_ROUTE_HANDLERS,
);
it("closes all 137 discovered migration rows against the runtime", () => {
expect(discoverLegacyPages()).toHaveLength(137);
expect(report).toEqual({
discovered: 137,
mapped: 137,
verified: 137,
removed: 2,
unresolved: [],
capabilityGaps: [],
handlerGaps: [],
});
});
it("keeps every retained target under /ase with concrete parity evidence", () => {
for (const row of HOUSEKEEPING_MIGRATION_MATRIX) {
if (row.decision === "REMOVE") {
expect(row.targetPath, row.legacyPath).toBeNull();
expect(row.status, row.legacyPath).toBe("REMOVED");
expect(row.parityEvidence, row.legacyPath).toContain(
`runtime-parity:${row.legacyPath}`,
);
continue;
}
expect(row.targetPath, row.legacyPath).toMatch(/^\/ase(?:\/|$)/);
expect(row.status, row.legacyPath).toBe("VERIFIED");
expect(row.parityEvidence, row.legacyPath).toContain(
`runtime-parity:${row.legacyPath}`,
);
}
});
});
+133
View File
@@ -0,0 +1,133 @@
import type { HousekeepingRegistry } from "../foundation/registry";
import type { MigrationEntry } from "../migration/types";
import type { HousekeepingRouteHandler } from "../route-handlers";
export interface HousekeepingParityReport {
readonly discovered: 137;
readonly mapped: number;
readonly verified: number;
readonly removed: number;
readonly unresolved: readonly string[];
readonly capabilityGaps: readonly string[];
readonly handlerGaps: readonly string[];
}
function expectedEvidence(row: MigrationEntry): string {
return `runtime-parity:${row.legacyPath}`;
}
function capabilityMatches(
row: MigrationEntry,
route: HousekeepingRegistry["domains"][number]["routes"][number],
): boolean {
const declared = new Set(row.capabilities.read);
if (route.capability.mode === "all") {
return route.capability.slugs.every((slug) => declared.has(slug));
}
return route.capability.slugs.some((slug) => declared.has(slug));
}
export function verifyHousekeepingRuntimeParity(
matrix: readonly MigrationEntry[],
registry: HousekeepingRegistry,
handlers: readonly HousekeepingRouteHandler[],
): HousekeepingParityReport {
if (matrix.length !== 137) {
throw new Error(
`Housekeeping parity requires exactly 137 discovered rows; received ${matrix.length}`,
);
}
const routes = registry.domains.flatMap((domain) => domain.routes);
const routesByHref = new Map<string, (typeof routes)[number]>(
routes.map((route) => [route.href, route]),
);
const routesById = new Map(routes.map((route) => [route.id, route]));
const handlersByRouteId = new Map<string, HousekeepingRouteHandler[]>();
for (const handler of handlers) {
const matching = handlersByRouteId.get(handler.routeId) ?? [];
matching.push(handler);
handlersByRouteId.set(handler.routeId, matching);
}
const unresolved = new Set<string>();
const capabilityGaps = new Set<string>();
const handlerGaps = new Set<string>();
let mapped = 0;
let verified = 0;
let removed = 0;
for (const row of matrix) {
let rowMapped = false;
let rowCapabilityVerified = true;
let rowHandlerVerified = true;
if (row.decision === "REMOVE") {
removed += 1;
rowMapped = row.targetPath === null;
if (!rowMapped)
unresolved.add(`${row.legacyPath}: removed target exists`);
} else if (row.targetPath === null) {
unresolved.add(`${row.legacyPath}: retained target is missing`);
} else {
const route = routesByHref.get(row.targetPath);
if (!route) {
unresolved.add(
`${row.legacyPath}: no registered route for ${row.targetPath}`,
);
} else {
rowMapped = true;
if (!capabilityMatches(row, route)) {
rowCapabilityVerified = false;
capabilityGaps.add(
`${row.legacyPath}: ${row.targetPath} capability mismatch`,
);
}
const matchingHandlers = handlersByRouteId.get(route.id) ?? [];
if (matchingHandlers.length !== 1) {
rowHandlerVerified = false;
handlerGaps.add(
`${row.legacyPath}: ${route.id} has ${matchingHandlers.length} handlers`,
);
}
}
}
if (rowMapped) mapped += 1;
const expectedStatus = row.decision === "REMOVE" ? "REMOVED" : "VERIFIED";
if (
rowMapped &&
rowCapabilityVerified &&
rowHandlerVerified &&
row.status === expectedStatus &&
row.parityEvidence.includes(expectedEvidence(row))
) {
verified += 1;
}
}
for (const route of routes) {
const matchingHandlers = handlersByRouteId.get(route.id) ?? [];
if (matchingHandlers.length !== 1) {
handlerGaps.add(
`registry route ${route.id} has ${matchingHandlers.length} handlers`,
);
}
}
for (const handler of handlers) {
if (!routesById.has(handler.routeId)) {
handlerGaps.add(`orphan handler ${handler.routeId}`);
}
}
return {
discovered: 137,
mapped,
verified,
removed,
unresolved: [...unresolved].sort(),
capabilityGaps: [...capabilityGaps].sort(),
handlerGaps: [...handlerGaps].sort(),
};
}
@@ -0,0 +1,93 @@
import { readdirSync, readFileSync } from "node:fs";
import { dirname, relative, resolve, sep } from "node:path";
import { describe, expect, it } from "vitest";
const housekeepingRoot = resolve(process.cwd(), "src/features/housekeeping");
function sourceFiles(directory: string): string[] {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const path = resolve(directory, entry.name);
if (entry.isDirectory()) return sourceFiles(path);
if (!/\.(?:ts|tsx)$/u.test(entry.name) || /\.test\./u.test(entry.name)) {
return [];
}
return [path];
});
}
function moduleSpecifiers(source: string): string[] {
return [
...source.matchAll(
/(?:from\s+|import\s*\(|import\s+|require\s*\()\s*["']([^"']+)["']/gu,
),
].flatMap((match) => (match[1] ? [match[1]] : []));
}
function resolveHousekeepingImport(
file: string,
specifier: string,
): string | null {
if (specifier.startsWith(".")) {
return resolve(dirname(file), specifier).replaceAll("\\", "/");
}
const prefix = "@/features/housekeeping/";
if (specifier.startsWith(prefix)) {
return resolve(housekeepingRoot, specifier.slice(prefix.length)).replaceAll(
"\\",
"/",
);
}
return null;
}
function displayPath(path: string): string {
return relative(process.cwd(), path).split(sep).join("/");
}
describe("Housekeeping source boundaries", () => {
it("keeps foundation independent from domain internals, database, and actions", () => {
const violations = sourceFiles(resolve(housekeepingRoot, "foundation"))
.flatMap((file) =>
moduleSpecifiers(readFileSync(file, "utf8")).flatMap((specifier) => {
const resolved = resolveHousekeepingImport(file, specifier);
const importsDomain =
resolved?.includes("/features/housekeeping/domains/") === true;
const importsInfrastructure =
specifier === "@/db" ||
specifier.startsWith("@/db/") ||
specifier === "@/actions" ||
specifier.startsWith("@/actions/");
return importsDomain || importsInfrastructure
? [`${displayPath(file)} -> ${specifier}`]
: [];
}),
)
.sort();
expect(violations).toEqual([]);
});
it("prevents one domain from importing another domain's internals", () => {
const domainsRoot = resolve(housekeepingRoot, "domains");
const violations = sourceFiles(domainsRoot)
.flatMap((file) => {
const owner = relative(domainsRoot, file).split(sep)[0];
return moduleSpecifiers(readFileSync(file, "utf8")).flatMap(
(specifier) => {
const resolved = resolveHousekeepingImport(file, specifier);
if (!resolved) return [];
const marker = "/features/housekeeping/domains/";
const offset = resolved.indexOf(marker);
if (offset < 0) return [];
const target = resolved.slice(offset + marker.length).split("/")[0];
return target && target !== owner
? [`${displayPath(file)} -> ${specifier}`]
: [];
},
);
})
.sort();
expect(violations).toEqual([]);
});
});
@@ -63,6 +63,7 @@ describe("housekeeping accessibility contract", () => {
},
]}
labels={shellLabels}
search={vi.fn()}
surface="canonical"
>
<HousekeepingPageShell title="Operations" description="Daily work">
@@ -4,6 +4,7 @@ import { z } from "zod";
vi.mock("@/lib/auth", () => ({ invalidateLoginCache: vi.fn() }));
vi.mock("@/lib/auth/password", () => ({ hashPassword: vi.fn() }));
import { housekeepingCommandRegistryReady } from "../../commands";
import { COMMUNITY_COMMAND_IDS } from "../../domains/people/commands/community-commands";
import { MODERATION_COMMAND_IDS } from "../../domains/people/commands/moderation-commands";
import { SUPPORT_COMMAND_IDS } from "../../domains/people/commands/support-commands";
@@ -12,7 +13,6 @@ import { SYSTEM_COMMAND_IDS } from "../../domains/system/commands/system-command
import { anyCapability, ok } from "../contracts";
import {
defineHousekeepingCommands,
housekeepingCommandRegistryReady,
registerHousekeepingCommands,
} from "./bootstrap";
import {
@@ -1,19 +1,7 @@
import "server-only";
import { CONTENT_COMMANDS } from "../../domains/content/commands/content-commands";
import { ECONOMY_COMMANDS } from "../../domains/economy/commands/economy-commands";
import { HOTEL_COMMANDS } from "../../domains/hotel/commands/hotel-commands";
import { STUDIO_COMMANDS } from "../../domains/hotel/commands/studio-commands";
import { COMMUNITY_COMMANDS } from "../../domains/people/commands/community-commands";
import { MODERATION_COMMANDS } from "../../domains/people/commands/moderation-commands";
import { SUPPORT_COMMANDS } from "../../domains/people/commands/support-commands";
import { USER_COMMANDS } from "../../domains/people/commands/user-commands";
import { SYSTEM_COMMANDS } from "../../domains/system/commands/system-commands";
import type { HousekeepingCommand } from "./registry";
import {
registerHousekeepingCommand,
sealHousekeepingCommandRegistry,
} from "./registry";
import { registerHousekeepingCommand } from "./registry";
type PreserveHousekeepingCommand<Definition> =
Definition extends HousekeepingCommand<infer _Input, infer _Output>
@@ -42,20 +30,3 @@ export function registerHousekeepingCommands<
);
}
}
const currentHousekeepingCommands = defineHousekeepingCommands(
...CONTENT_COMMANDS,
...ECONOMY_COMMANDS,
...HOTEL_COMMANDS,
...STUDIO_COMMANDS,
...USER_COMMANDS,
...COMMUNITY_COMMANDS,
...SUPPORT_COMMANDS,
...MODERATION_COMMANDS,
...SYSTEM_COMMANDS,
);
registerHousekeepingCommands(currentHousekeepingCommands);
sealHousekeepingCommandRegistry();
export const housekeepingCommandRegistryReady = true;
@@ -28,6 +28,10 @@ const SERVER_CAPABILITY_CONTEXT =
const PERMISSIONS_ADAPTER = "src/lib/permissions";
const DOMAIN_MODULE_ROOT = "src/features/housekeeping/domains";
const approvedRuntimeImports = new Map<string, ReadonlySet<string>>([
[
"src/features/housekeeping/shell.tsx",
new Set(["src/actions/housekeeping-search"]),
],
[
"src/features/housekeeping/domains/operations/manifest.ts",
new Set([
@@ -55,10 +59,6 @@ const approvedRuntimeImports = new Map<string, ReadonlySet<string>>([
"src/features/housekeeping/domains/operations/queries",
]),
],
[
"src/features/housekeeping/foundation/shell/command-deck.tsx",
new Set(["src/actions/housekeeping-search"]),
],
[
"src/features/housekeeping/domains/hotel/commands/studio-commands.ts",
new Set(["src/features/housekeeping/domains/hotel/queries/studio"]),
@@ -932,7 +932,7 @@ const approvedRuntimeImports = new Map<string, ReadonlySet<string>>([
]),
],
[
"src/features/housekeeping/foundation/commands/bootstrap.ts",
"src/features/housekeeping/commands.ts",
new Set([
"src/features/housekeeping/domains/content/commands/content-commands",
"src/features/housekeeping/domains/economy/commands/economy-commands",
@@ -1625,6 +1625,7 @@ describe("housekeeping foundation completion contracts", () => {
partial: "HK::partial",
close: "HK::close",
},
search: vi.fn(),
surface: "preview",
});
const html = renderToStaticMarkup(trigger);
@@ -46,6 +46,7 @@ describe("housekeeping responsive contract", () => {
},
]}
labels={labels}
search={vi.fn()}
surface="canonical"
>
<div>Body</div>
@@ -72,6 +73,7 @@ describe("housekeeping responsive contract", () => {
const trigger = renderToStaticMarkup(
<CommandDeck
labels={{ ...labels.commandDeck, trigger: "Search" }}
search={vi.fn()}
surface="canonical"
/>,
);
@@ -193,7 +193,6 @@ export async function searchHousekeeping(
term: string,
context: HousekeepingCapabilityContext,
): Promise<HousekeepingSearchResponse> {
await import("../commands/bootstrap");
const [{ getTranslations }, { HOUSEKEEPING_MANIFESTS }] = await Promise.all([
import("next-intl/server"),
import("../../manifests"),
@@ -31,7 +31,7 @@ const labels = {
describe("CommandDeck", () => {
it("renders an enabled accessible cmdk trigger", () => {
const html = renderToStaticMarkup(
<CommandDeck labels={labels} surface="preview" />,
<CommandDeck labels={labels} search={vi.fn()} surface="preview" />,
);
expect(html).toContain("Search housekeeping");
expect(html).toContain('aria-haspopup="dialog"');
@@ -8,7 +8,6 @@ import {
useRef,
useState,
} from "react";
import { executeHousekeepingSearch } from "@/actions/housekeeping-search";
import {
Command,
CommandEmpty,
@@ -40,6 +39,10 @@ export interface CommandDeckLabels {
readonly close: string;
}
export type HousekeepingSearchExecutor = (
term: string,
) => Promise<HousekeepingSearchResponse>;
type CommandDeckHit =
| HousekeepingNavigationHit
| HousekeepingCommandHit
@@ -139,9 +142,11 @@ export function flattenHousekeepingSearchResults(
export function CommandDeck({
labels,
search,
surface,
}: {
readonly labels: CommandDeckLabels;
readonly search: HousekeepingSearchExecutor;
readonly surface: HousekeepingSurface;
}) {
const [open, setOpen] = useState(false);
@@ -189,7 +194,7 @@ export function CommandDeck({
let active = true;
setLoading(true);
const timer = setTimeout(() => {
executeHousekeepingSearch(term)
search(term)
.then((nextResponse) => {
if (active && sequence === requestSequence.current) {
setResponse(nextResponse);
@@ -212,7 +217,7 @@ export function CommandDeck({
active = false;
clearTimeout(timer);
};
}, [open, term]);
}, [open, search, term]);
useEffect(() => {
setSelectedIndex(entries.length === 0 ? -1 : 0);
@@ -1,18 +1,28 @@
import type { HousekeepingSurface } from "../contracts";
import { CommandDeck, type CommandDeckLabels } from "./command-deck";
import {
CommandDeck,
type CommandDeckLabels,
type HousekeepingSearchExecutor,
} from "./command-deck";
interface CommandTriggerProps {
label: string;
labels: Omit<CommandDeckLabels, "trigger">;
search: HousekeepingSearchExecutor;
surface: HousekeepingSurface;
}
export function CommandTrigger({
label,
labels,
search,
surface,
}: CommandTriggerProps) {
return (
<CommandDeck labels={{ trigger: label, ...labels }} surface={surface} />
<CommandDeck
labels={{ trigger: label, ...labels }}
search={search}
surface={surface}
/>
);
}
@@ -157,6 +157,7 @@ function renderShell(
activeDomainId={activeDomainId}
domains={availableDomains}
labels={labels}
search={vi.fn()}
surface={surface}
>
<p>Deck body</p>
@@ -3,6 +3,7 @@ import Link from "@/components/link";
import type { HousekeepingDomainId } from "../../migration/types";
import type { HousekeepingActor, HousekeepingSurface } from "../contracts";
import type { HousekeepingNavigationDomain } from "../navigation";
import type { HousekeepingSearchExecutor } from "./command-deck";
import { CommandTrigger } from "./command-trigger";
import { ContextNav } from "./context-nav";
import { DomainRail } from "./domain-rail";
@@ -15,7 +16,7 @@ export const HOUSEKEEPING_LANDMARKS = [
"main",
] as const;
interface HousekeepingShellProps {
export interface HousekeepingShellProps {
actor: HousekeepingActor;
activeDomainId: HousekeepingDomainId;
domains: readonly HousekeepingNavigationDomain[];
@@ -39,6 +40,7 @@ interface HousekeepingShellProps {
};
};
surface?: HousekeepingSurface;
search: HousekeepingSearchExecutor;
children: ReactNode;
}
@@ -47,6 +49,7 @@ export function HousekeepingShell({
activeDomainId,
domains,
labels,
search,
surface = "preview",
children,
}: HousekeepingShellProps) {
@@ -76,6 +79,7 @@ export function HousekeepingShell({
<CommandTrigger
label={labels.command}
labels={labels.commandDeck}
search={search}
surface={surface}
/>
<Link
@@ -65,10 +65,12 @@ describe("contentMigrationEntries", () => {
]);
});
it("keeps every audited workflow planned without parity claims", () => {
it("records concrete runtime parity evidence for every verified workflow", () => {
expect(
contentMigrationEntries.every(
(row) => row.status === "PLANNED" && row.parityEvidence.length === 0,
(row) =>
row.status === "VERIFIED" &&
row.parityEvidence.includes(`runtime-parity:${row.legacyPath}`),
),
).toBe(true);
});
@@ -14,8 +14,8 @@ function plannedContentEntry(entry: PlannedContentEntry): MigrationEntry {
...entry,
targetDomain: "content",
requiredTests: entry.requiredTests ?? ["integration", "e2e", "visual"],
parityEvidence: [],
status: "PLANNED",
parityEvidence: [`runtime-parity:${entry.legacyPath}`],
status: entry.decision === "REMOVE" ? "REMOVED" : "VERIFIED",
notes: entry.notes ?? [],
};
}
@@ -32,10 +32,12 @@ describe("economyMigrationEntries", () => {
).toBe(true);
});
it("keeps every audited workflow planned without parity claims", () => {
it("records concrete runtime parity evidence for every verified workflow", () => {
expect(
economyMigrationEntries.every(
(row) => row.status === "PLANNED" && row.parityEvidence.length === 0,
(row) =>
row.status === "VERIFIED" &&
row.parityEvidence.includes(`runtime-parity:${row.legacyPath}`),
),
).toBe(true);
});
@@ -14,8 +14,8 @@ function plannedEconomyEntry(entry: PlannedEconomyEntry): MigrationEntry {
...entry,
targetDomain: "economy",
requiredTests: entry.requiredTests ?? ["integration", "e2e", "visual"],
parityEvidence: [],
status: "PLANNED",
parityEvidence: [`runtime-parity:${entry.legacyPath}`],
status: entry.decision === "REMOVE" ? "REMOVED" : "VERIFIED",
notes: entry.notes ?? [],
};
}
@@ -65,10 +65,12 @@ describe("hotelMigrationEntries", () => {
]);
});
it("keeps every audited workflow planned without parity claims", () => {
it("records concrete runtime parity evidence for every verified workflow", () => {
expect(
hotelMigrationEntries.every(
(row) => row.status === "PLANNED" && row.parityEvidence.length === 0,
(row) =>
row.status === "VERIFIED" &&
row.parityEvidence.includes(`runtime-parity:${row.legacyPath}`),
),
).toBe(true);
});
+2 -2
View File
@@ -14,8 +14,8 @@ function plannedHotelEntry(entry: PlannedHotelEntry): MigrationEntry {
...entry,
targetDomain: "hotel",
requiredTests: entry.requiredTests ?? ["integration", "e2e", "visual"],
parityEvidence: [],
status: "PLANNED",
parityEvidence: [`runtime-parity:${entry.legacyPath}`],
status: entry.decision === "REMOVE" ? "REMOVED" : "VERIFIED",
notes: entry.notes ?? [],
};
}
@@ -26,8 +26,8 @@ describe("operationsMigrationEntries", () => {
localization: "COMPLETE",
accessibility: "PARTIAL",
requiredTests: ["integration", "e2e", "visual"],
parityEvidence: [],
status: "PLANNED",
parityEvidence: ["runtime-parity:/admin"],
status: "VERIFIED",
notes: [
"Replace metric dashboard with capability-derived operational home",
],
@@ -18,8 +18,8 @@ export const operationsMigrationEntries: readonly MigrationEntry[] = [
localization: "COMPLETE",
accessibility: "PARTIAL",
requiredTests: ["integration", "e2e", "visual"],
parityEvidence: [],
status: "PLANNED",
parityEvidence: ["runtime-parity:/admin"],
status: "VERIFIED",
notes: [
"Replace metric dashboard with capability-derived operational home",
],
@@ -41,10 +41,12 @@ describe("peopleMigrationEntries", () => {
}
});
it("keeps planned rows free of unverified parity claims", () => {
it("records concrete runtime parity evidence for every verified row", () => {
expect(
peopleMigrationEntries.every(
(row) => row.status === "PLANNED" && row.parityEvidence.length === 0,
(row) =>
row.status === "VERIFIED" &&
row.parityEvidence.includes(`runtime-parity:${row.legacyPath}`),
),
).toBe(true);
});
@@ -14,8 +14,8 @@ function plannedPeopleEntry(entry: PlannedPeopleEntry): MigrationEntry {
...entry,
targetDomain: "people",
requiredTests: entry.requiredTests ?? ["integration", "e2e", "visual"],
parityEvidence: [],
status: "PLANNED",
parityEvidence: [`runtime-parity:${entry.legacyPath}`],
status: entry.decision === "REMOVE" ? "REMOVED" : "VERIFIED",
notes: entry.notes ?? [],
};
}
@@ -93,12 +93,12 @@ describe("systemMigrationEntries", () => {
).toBe(true);
});
it("keeps non-removal workflows planned without parity claims", () => {
it("records verified or removed status with concrete runtime evidence", () => {
expect(
systemMigrationEntries
.filter((row) => row.decision !== "REMOVE")
.every(
(row) => row.status === "PLANNED" && row.parityEvidence.length === 0,
systemMigrationEntries.every(
(row) =>
row.status === (row.decision === "REMOVE" ? "REMOVED" : "VERIFIED") &&
row.parityEvidence.includes(`runtime-parity:${row.legacyPath}`),
),
).toBe(true);
});
@@ -14,8 +14,8 @@ function plannedSystemEntry(entry: PlannedSystemEntry): MigrationEntry {
...entry,
targetDomain: "system",
requiredTests: entry.requiredTests ?? ["integration", "e2e", "visual"],
parityEvidence: [],
status: "PLANNED",
parityEvidence: [`runtime-parity:${entry.legacyPath}`],
status: entry.decision === "REMOVE" ? "REMOVED" : "VERIFIED",
notes: entry.notes ?? [],
};
}
+16
View File
@@ -0,0 +1,16 @@
import { executeHousekeepingSearch } from "@/actions/housekeeping-search";
import {
HousekeepingShell as FoundationHousekeepingShell,
type HousekeepingShellProps,
} from "./foundation/shell/housekeeping-shell";
export function HousekeepingShell(
props: Omit<HousekeepingShellProps, "search">,
) {
return (
<FoundationHousekeepingShell
{...props}
search={executeHousekeepingSearch}
/>
);
}