fix: complete TechLog migration evidence

This commit is contained in:
DongHyeonka
2026-08-16 04:55:52 +09:00
parent 6c2780b7a7
commit 3a7c5deca0
81 changed files with 1492 additions and 345 deletions
+23 -2
View File
@@ -192,14 +192,35 @@ describe("CI gate contract", () => {
expect(contract.commands).toHaveLength(81);
expect(contract.gates.reduce((total, gate) => total + gate.commandIds.length, 0)).toBe(93);
expect(contract.commands.filter(({ expect }) => expect === "fail")).toHaveLength(23);
expect(contract.gates.reduce((total, gate) => total + gate.evidenceArtifactIds.length, 0)).toBe(85);
expect(contract.artifacts).toHaveLength(105);
expect(contract.gates.reduce((total, gate) => total + gate.evidenceArtifactIds.length, 0)).toBe(106);
expect(contract.artifacts).toHaveLength(126);
expect(contract.stages).toHaveLength(5);
expect(contract.retention.classes).toHaveLength(5);
expect(index.gates.get("FE-GATE-015")?.commandIds).toHaveLength(2);
expect(index.gates.get("FE-GATE-020")?.name).toBe("removability");
});
it("rejects accessibility evidence registrations that drift from the installed route scope", async () => {
const candidate = JSON.parse(
JSON.stringify(await loadCiGateContract(process.cwd())),
) as Record<string, any>;
const gate = candidate.gates.find(
(entry: Record<string, any>) => entry.id === "FE-GATE-009",
);
const artifactId =
"artifact-artifacts-tests-a11y-manual-TECH-LOG-HOME-md";
candidate.artifacts = candidate.artifacts.filter(
(entry: Record<string, any>) => entry.id !== artifactId,
);
gate.evidenceArtifactIds = gate.evidenceArtifactIds.filter(
(id: string) => id !== artifactId,
);
expect(() =>
parseCiGateContract(candidate, { mode: "removal-fixture" }),
).toThrow(/FE-GATE-009 manual accessibility evidence.*installed route scope/i);
});
it("keeps the action registry recursively immutable and resolves only known actions", () => {
expect(Object.isFrozen(CI_ACTION_REGISTRY)).toBe(true);
expect(Object.values(CI_ACTION_REGISTRY).every((action) => Object.isFrozen(action))).toBe(true);
+1 -1
View File
@@ -7,7 +7,7 @@ import {
import { ROUTE_REGISTRY } from "../../src/features/installed-feature-contracts.ts";
const reviewed = `Status: reviewed
Route ID: APP_HOME
Route ID: TECH_LOG_HOME
Release ID: release-1
Reviewer: reviewer@example.test
Reviewed at: 2026-07-25T12:00:00Z
@@ -148,7 +148,7 @@ describe("selective Task 3 contract closure", () => {
expect(canonical.gates).toHaveLength(26);
expect(canonical.commands).toHaveLength(81);
expect(canonical.gates.reduce((sum, gate) => sum + gate.commandIds.length, 0)).toBe(93);
expect(canonical.artifacts).toHaveLength(105);
expect(canonical.artifacts).toHaveLength(126);
expect(canonical.stages).toHaveLength(5);
expect(canonical.retention.classes).toHaveLength(5);
@@ -0,0 +1,26 @@
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
describe("TechLog source parity evidence harness", () => {
it("is a checked Node runner with fixture, response, recursive tree, and provenance coverage", async () => {
const [packageJson, source] = await Promise.all([
readFile("package.json", "utf8").then(JSON.parse),
readFile("tests/support/browser/verify-tech-log-source-parity.ts", "utf8"),
]);
expect(packageJson.scripts["verify:tech-log-source-parity"]).toBe(
"node tests/support/browser/verify-tech-log-source-parity.ts",
);
expect(source).toContain("TECH_LOG_PUBLIC_FIXTURE_PATHS.map");
expect(source).toContain("element.childNodes");
expect(source).toContain("responseContractEqual");
expect(source).toContain("sourceChecksums(sourceRoot)");
expect(source).toContain("caseInventorySha256");
expect(source).toContain("evidenceSha256");
expect(source).toContain(
'"docs/operations/evidence/tech-log-source-parity.json"',
);
expect(source).not.toContain("tsx");
});
});
@@ -0,0 +1,44 @@
import { pathToFileURL } from "node:url";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { writeTechLogServingArtifact } from "../../scripts/lib/tech-log-serving-artifact.ts";
const roots: string[] = [];
afterEach(async () => {
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true })));
});
describe("TechLog serving artifact", () => {
it("emits a self-contained deployment server and its route contract", async () => {
const root = await mkdtemp(path.join(tmpdir(), "tech-log-serving-artifact-"));
roots.push(root);
const contract = {
schemaVersion: 1 as const,
publicSpaPaths: ["/", "/projects/auth-lab"],
studioPathPrefix: "/studio" as const,
studioSpaPathPatterns: ["^/studio$"],
notFound: {
status: 404 as const,
contentType: "text/plain;charset=UTF-8" as const,
body: "Not Found" as const,
},
};
await writeTechLogServingArtifact({ distRoot: root, contract });
expect(
JSON.parse(
await readFile(path.join(root, "tech-log-serving-contract.json"), "utf8"),
),
).toEqual(contract);
const serverModule = await import(
`${pathToFileURL(path.join(root, "server.mjs")).href}?test=${Date.now()}`
);
expect(serverModule.createTechLogProductionServer).toEqual(expect.any(Function));
});
});
@@ -0,0 +1,62 @@
import { describe, expect, it } from "vitest";
import { createTechLogServingContract } from "../../scripts/lib/tech-log-serving-contract.ts";
import {
projects,
publicRecords,
releases,
} from "../../src/features/tech-log/adapters/static/public-content.ts";
describe("TechLog production serving contract", () => {
it("derives every known Public path from the installed static content", () => {
const contract = createTechLogServingContract({
projects,
publicRecords,
releases,
});
expect(contract.publicSpaPaths).toEqual([
"/",
"/cases/collection-fetch-join-pagination",
"/cases/redis-adapter-ttl-boundary",
"/explore",
"/explore/cases",
"/explore/questions",
"/explore/references",
"/profile",
"/projects",
"/projects/auth-lab",
"/projects/auth-lab/activity",
"/projects/auth-lab/decisions",
"/projects/auth-lab/records",
"/projects/backend-skeleton",
"/projects/backend-skeleton/activity",
"/projects/backend-skeleton/decisions",
"/projects/backend-skeleton/records",
"/questions/collection-fetch-join-with-pagination",
"/questions/validate-edge-token-again",
"/references/jpa-list-fetch-strategy",
"/references/state-and-nonce-boundary",
"/releases",
"/releases/0.1.0",
"/search",
"/topics/authentication",
"/topics/jpa",
"/topics/redis",
]);
expect(contract.studioPathPrefix).toBe("/studio");
expect(contract.studioSpaPathPatterns).toEqual([
"^/studio$",
"^/studio/documents$",
"^/studio/documents/new$",
"^/studio/documents/[^/]+/(edit|validation|preview|publish)$",
"^/studio/publications$",
"^/studio/publications/[^/]+/preview$",
]);
expect(contract.notFound).toEqual({
status: 404,
contentType: "text/plain;charset=UTF-8",
body: "Not Found",
});
});
});