feat(housekeeping): unify complete rebuild in gated preview
CI / check (pull_request) Failing after 10s
CI / deploy (pull_request) Skipped
CI / release (pull_request) Skipped

This commit is contained in:
Simo committed 2026-08-31 20:37:51 +02:00
1 parent 4ad007d241
commit 43470ebf82
402 files changed
+78313 -1663

No files matched your search

@@ -0,0 +1,52 @@
import { describe, expect, it } from "vitest";
import { createHousekeepingRegistry } from "../foundation/registry";
import { HOUSEKEEPING_MANIFESTS } from "../manifests";
import {
discoverLegacyPages,
recordedLegacyPages,
} 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 138 recorded migration rows while stable routes coexist", () => {
expect(discoverLegacyPages()).toEqual(
recordedLegacyPages(HOUSEKEEPING_MIGRATION_MATRIX),
);
expect(report).toEqual({
discovered: 138,
mapped: 138,
verified: 138,
removed: 2,
unresolved: [],
capabilityGaps: [],
handlerGaps: [],
});
});
it("keeps every retained target under /ase-next 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-next(?:\/|$)/);
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: 138;
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 !== 138) {
throw new Error(
`Housekeeping parity requires exactly 138 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: 138,
mapped,
verified,
removed,
unresolved: [...unresolved].sort(),
capabilityGaps: [...capabilityGaps].sort(),
handlerGaps: [...handlerGaps].sort(),
};
}
@@ -0,0 +1,78 @@
import { existsSync, readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
const PREVIEW_ENTRYPOINTS = [
"src/app/ase-next/layout.tsx",
"src/app/ase-next/page.tsx",
"src/app/ase-next/[domain]/layout.tsx",
"src/app/ase-next/[domain]/[[...segments]]/page.tsx",
"src/features/housekeeping/route-handlers.ts",
] as const;
const STABLE_UI_ROOTS = ["src/app/admin", "src/app/mod"] as const;
const PREMATURE_UI_ROOTS = [["src/app/admin", "-next"].join(""), ["src/app", "ase"].join("/")] as const;
const STABLE_GLOBAL_SOURCES = [
"src/lib/admin/guard.ts",
"src/lib/proxy-access.ts",
"src/components/navigation.tsx",
"src/components/top-header.tsx",
] as const;
describe("gated Housekeeping preview coexistence", () => {
it("publishes the /ase-next entrypoints and dispatcher", () => {
for (const path of PREVIEW_ENTRYPOINTS) {
expect(existsSync(path), path).toBe(true);
}
});
it("preserves the stable /admin and /mod surfaces during preview", () => {
for (const path of STABLE_UI_ROOTS) {
expect(existsSync(path), path).toBe(true);
}
expect(existsSync("src/app/api/admin/csrf/route.ts")).toBe(true);
});
it("does not expose either superseded preview tree", () => {
for (const path of PREMATURE_UI_ROOTS) {
expect(existsSync(path), path).toBe(false);
}
});
it("keeps the preview environment-gated outside production", () => {
expect(
existsSync("src/features/housekeeping/foundation/preview-gate.ts"),
).toBe(true);
for (const path of ["src/env.ts", ".env.example"] as const) {
expect(readFileSync(path, "utf8"), path).toContain(
"HOUSEKEEPING_NEXT_PREVIEW_ENABLED",
);
}
const gate = readFileSync(
"src/features/housekeeping/foundation/preview-gate.ts",
"utf8",
);
expect(gate).toContain('input.nodeEnv !== "production"');
expect(gate).toContain("input.flag");
});
it("leaves stable global links and authorization fallbacks on /admin and /mod", () => {
for (const path of STABLE_GLOBAL_SOURCES) {
expect(readFileSync(path, "utf8"), path).not.toContain("/ase-next");
}
expect(readFileSync("src/components/top-header.tsx", "utf8")).toContain(
'href="/admin"',
);
expect(readFileSync("src/components/top-header.tsx", "utf8")).toContain(
'href="/mod"',
);
});
it("does not add global redirects into or out of the preview namespace", () => {
for (const path of ["next.config.ts", "src/proxy.ts"] as const) {
const source = readFileSync(path, "utf8");
expect(source, path).not.toMatch(
/(?:source|destination|redirect)\s*[:(][^\n]*\/ase-next/,
);
}
});
});
@@ -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([]);
});
});