Files
DongHyeonkaandClaude Opus 5 bdee07a93b chore: sync the frontend template from a0fbafb to 5434760
Carries eight template commits: the provider sandbox actually running, release
admission to a named environment, the product feature manifest with its runtime
kill switch, architecture and documentation rules that match what is enforced,
the removability fixtures, and the browser, visual and performance evidence.

Product identity is unchanged. `package.json` keeps `tech-log-frontend` and the
catalog keeps the Tech Log naming; the home page was not in the delta. The
visual baselines are this product's own — the template's were excluded from the
transplant and these were regenerated here, where the only difference is the
platform overview's new product-feature section.

What this repository gains operationally: `config/runtime/{local,development,
staging,production}.json` with `FE-GATE-027` refusing an artifact whose runtime
document does not match the environment it is being admitted to, and
`FEATURE_OVERRIDES` for taking an installed feature out of service without a
rebuild.

Verified here: eight gates green, build green, visual 5/5, and 1,858 of 1,859
tests in the suites that do not need a sandbox — the one failure passes in
isolation and is a jsdom lazy-chunk timeout under parallel load. The provider
suites cannot run on this machine at all: `kernel.apparmor_restrict_unprivileged
_userns=1` makes `bwrap --unshare-net` fail, reproducible without any code from
either repository.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 21:34:19 +09:00

135 lines
5.9 KiB
TypeScript

import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { loadCiGateContract } from "../../scripts/contracts/ci-gates.ts";
import {
assertNoRuntimeImports,
pruneRemovalFixtureCiContract,
removeRuntimeDependentTests,
runtimeImportGraph,
} from "../../scripts/lib/removal-fixture.ts";
const temporaryRoots: string[] = [];
afterEach(async () => {
await Promise.all(
temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
);
});
it("rejects an empty scanned module universe", async () => {
const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-empty-modules-"));
temporaryRoots.push(root);
await expect(runtimeImportGraph(root, ["src/runtime"])).rejects.toThrow(
/scanned module universe is empty/i,
);
});
it("rejects a runtime removal scan that discovers no dependent tests", async () => {
const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-zero-dependent-tests-"));
temporaryRoots.push(root);
await mkdir(path.join(root, "src/runtime"), { recursive: true });
await mkdir(path.join(root, "tests"), { recursive: true });
await writeFile(path.join(root, "src/runtime/index.ts"), "export const runtime = true;\n");
await writeFile(path.join(root, "tests/unrelated.test.ts"), "export const unrelated = true;\n");
await expect(removeRuntimeDependentTests(root, ["src/runtime"])).rejects.toThrow(
/no runtime-dependent tests discovered/i,
);
});
it("allows zero runtime imports after the runtime has been removed", async () => {
const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-post-removal-"));
temporaryRoots.push(root);
await mkdir(path.join(root, "tests"), { recursive: true });
await writeFile(path.join(root, "tests/unrelated.test.ts"), "export const unrelated = true;\n");
await expect(
assertNoRuntimeImports(root, ["src/runtime"], "fixture runtime"),
).resolves.toBeUndefined();
});
it("prunes removed command and evidence references from a reduced CI contract", async () => {
const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-contract-"));
temporaryRoots.push(root);
await mkdir(path.join(root, "config/ci"), { recursive: true });
await writeFile(path.join(root, "config/ci/gates.json"), await readFile("config/ci/gates.json"));
await writeFile(path.join(root, "package.json"), await readFile("package.json"));
await pruneRemovalFixtureCiContract({
root,
removedScripts: new Set(["test:reference-feature"]),
removedEvidencePathFragments: ["reference-feature.xml"],
});
const contract = await loadCiGateContract(root, { mode: "removal-fixture" });
expect(contract.commands.some(({ script }) => script === "test:reference-feature")).toBe(false);
expect(contract.artifacts.some(({ path }) => path.includes("reference-feature.xml"))).toBe(false);
expect(contract.gates.some(({ commandIds }) => commandIds.includes("test-reference-feature"))).toBe(false);
expect(contract.gates.some(({ evidenceArtifactIds }) =>
evidenceArtifactIds.includes("artifact-artifacts-tests-reference-feature-xml")
)).toBe(false);
});
it("rejects pruning that leaves a reduced gate without commands", async () => {
const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-empty-gate-"));
temporaryRoots.push(root);
await mkdir(path.join(root, "config/ci"), { recursive: true });
await writeFile(path.join(root, "config/ci/gates.json"), await readFile("config/ci/gates.json"));
await writeFile(path.join(root, "package.json"), await readFile("package.json"));
await expect(pruneRemovalFixtureCiContract({
root,
removedScripts: new Set(["test:runtime-schema"]),
removedEvidencePathFragments: ["runtime-schema.xml"],
})).rejects.toThrow(/commandIds|too small|at least 1/i);
});
describe("release evidence fixture copy", () => {
it("keeps the release evidence and leaves the regenerated trees behind", async () => {
const { copyReleaseEvidenceTree, RELEASE_EVIDENCE_REGENERATED_TREES } =
await import("../../scripts/lib/removal-fixture.ts");
const { RELEASE_CANDIDATE_EVIDENCE_PATHS } = await import(
"../../scripts/lib/release-candidate.ts"
);
const { mkdtemp, access, readdir } = await import("node:fs/promises");
const { tmpdir } = await import("node:os");
const nodePath = (await import("node:path")).default;
const root = await mkdtemp(nodePath.join(tmpdir(), "release-evidence-"));
await copyReleaseEvidenceTree(process.cwd(), root);
// Every release artifact that exists here has to survive the copy; a
// fixture missing one cannot build a candidate at all, and every provider
// suite then fails while constructing its own fixture.
//
// Which ones exist depends on what this checkout has generated — a product
// repository that has not run the release chain has fewer than the template
// does — so the subject is preservation, not the presence of a full chain.
// Requiring at least one keeps that from quietly asserting nothing.
let preserved = 0;
for (const evidence of RELEASE_CANDIDATE_EVIDENCE_PATHS) {
if (!evidence.startsWith("artifacts/")) continue;
try {
await access(evidence);
} catch {
continue;
}
await expect(access(nodePath.join(root, evidence)), evidence).resolves.toBeUndefined();
preserved += 1;
}
expect(preserved).toBeGreaterThan(0);
// The regenerated trees are why this is a filter and not a plain copy: they
// are tens of megabytes of traces and coverage HTML. They still exist,
// because the repository inventory expects the directories.
for (const tree of RELEASE_EVIDENCE_REGENERATED_TREES) {
await expect(access(nodePath.join(root, tree)), tree).resolves.toBeUndefined();
await expect(readdir(nodePath.join(root, tree)), tree).resolves.toEqual([]);
}
});
});