FE-GATE-020 proves a capability can be removed by rebuilding the whole project without it. The fixture it built could not get that far, and the failures all came from the fixture rather than from anything about removability. It was not a repository. The supply-chain inventory is defined as the tracked file set, so it asks `git ls-files` what the project contains; with no repository to ask, generation failed and took every provider suite down with it. It is now initialised on preparation and committed after the removal — not before, or the index would still list the files the removal deleted. It had no `.gitignore`, so once it did have a repository, every generated artifact and every linked module landed in the index and the inventory refused the fixture for tracked and generated paths colliding. It carries the ignore rules now, and therefore records the same tracked set as the repository it was copied from. Each removal script kept its own copy-target list and they had drifted: the reference-feature fixture omitted `playwright.capabilities.config.ts`, which the inventory requires. There is one list now. It also gained the install and workspace identity — `.npmrc`, the lockfile, the workspace file — without which the fixture is a different project, and the release evidence a candidate is assembled from, without which no candidate can be built at all. A tracked root the removal deletes is no longer required of the result: the optional-recipe fixture deletes `recipes/`, and the inventory policy demanded it back. Roots that are gone are pruned from the fixture's policy. Two smaller causes. A platform integration file asserted the reference feature's own route ids, so removing the feature left it importing a deleted module — typecheck, the test run, coverage and the residue scan all failed on that one misplaced assertion, which now lives in the feature's test tree. And the canonical exact-count authority was re-imposed on a contract the fixture deliberately reduces, failing the fixture for the reduction it exists to prove; `CI_CONTRACT_MODE` already marked those runs and is now honoured by default. The reference-feature fixture goes from failing before its first assertion to 1,612 passing with one failure, and that one is the live process-tree observation test already red on the main tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
424 lines
14 KiB
TypeScript
424 lines
14 KiB
TypeScript
import {
|
|
access,
|
|
mkdir,
|
|
mkdtemp,
|
|
readFile,
|
|
rm,
|
|
writeFile,
|
|
} from "node:fs/promises";
|
|
import path from "node:path";
|
|
|
|
import { isProductionModulePath } from "./lib/risk-coverage.ts";
|
|
import {
|
|
filesBelow,
|
|
prepareRemovalFixture,
|
|
pruneRemovalFixtureCiContract,
|
|
pruneScriptOrchestration,
|
|
regenerateRemovalFixtureWorkflow,
|
|
requireRemovalFixtureEnvironment,
|
|
runRemovalFixturePnpm,
|
|
} from "./lib/removal-fixture.ts";
|
|
|
|
const fixtureParent = path.resolve(".tmp");
|
|
await mkdir(fixtureParent, { recursive: true });
|
|
const fixtureRoot = await mkdtemp(
|
|
path.join(fixtureParent, "reference-feature-removal-"),
|
|
);
|
|
const pnpmCli = requireRemovalFixtureEnvironment("npm_execpath");
|
|
const featureSource = "src/features/reference-feature";
|
|
const featureTests = "tests/features/reference-feature";
|
|
/**
|
|
* Platform tests that must survive the sample feature's removal. Asserting they
|
|
* are still present is what stops the removal fixture from "passing" by having
|
|
* quietly deleted the platform's own coverage along with the feature.
|
|
*/
|
|
const commonTestPaths = [
|
|
"tests/unit/external-contract-runtime.test.ts",
|
|
"tests/unit/http-execution-v3.test.ts",
|
|
"tests/unit/runtime-adapters.test.ts",
|
|
"tests/integration/http-execution-v3-observability.test.ts",
|
|
];
|
|
const featureOwnedPaths = [
|
|
featureSource,
|
|
featureTests,
|
|
"tests/integration/http-scenario-catalog.test.ts",
|
|
"tests/e2e/reference-form.spec.ts",
|
|
"tests/e2e/reference-route.spec.ts",
|
|
"tests/mocks",
|
|
"tests/fixtures/typecheck/invalid-feature-input.ts",
|
|
"tests/fixtures/typecheck/invalid-reference-operation.ts",
|
|
];
|
|
|
|
const emptyContracts = `import { PLATFORM_ROUTE_RUNTIME_CONTRACT } from "../contracts/route-runtime-contract.ts";
|
|
import { PLATFORM_ROUTE_REGISTRY, type RouteDefinition } from "../contracts/routes.ts";
|
|
import { PLATFORM_SCHEMA_REGISTRY } from "../contracts/schema-registry.ts";
|
|
|
|
export const INSTALLED_FEATURE_CONTRACTS: readonly unknown[] = Object.freeze([]);
|
|
export const ROUTE_REGISTRY = PLATFORM_ROUTE_REGISTRY;
|
|
export const ROUTE_RUNTIME_CONTRACT = PLATFORM_ROUTE_RUNTIME_CONTRACT;
|
|
export const API_OPERATIONS = Object.freeze({});
|
|
export const INVALIDATION_REGISTRY = Object.freeze({
|
|
topics: Object.freeze([]),
|
|
namespaces: Object.freeze([]),
|
|
edges: Object.freeze([]),
|
|
});
|
|
export const INVALIDATION_TOPIC_VERSIONS = Object.freeze([]);
|
|
export const SCHEMA_REGISTRY = PLATFORM_SCHEMA_REGISTRY;
|
|
export const NAVIGATION_ROUTES = Object.freeze(
|
|
Object.values(ROUTE_REGISTRY)
|
|
.filter((definition) => definition.navigationOrder !== null)
|
|
.sort(
|
|
(left, right) =>
|
|
left.navigationOrder! - right.navigationOrder!,
|
|
),
|
|
);
|
|
export function getRoute(routeId: string): RouteDefinition {
|
|
const registry = ROUTE_REGISTRY as Readonly<Record<string, RouteDefinition>>;
|
|
const selected = registry[routeId];
|
|
if (!selected) throw new Error(\`Unregistered route: \${routeId}\`);
|
|
return selected;
|
|
}
|
|
export function routePath(routeId: string): string {
|
|
return getRoute(routeId).path;
|
|
}
|
|
`;
|
|
|
|
const emptyRuntimes = `import { PLATFORM_ROUTE_CODECS } from "../presentation/routes/platform-route-codecs.ts";
|
|
import { PLATFORM_ROUTE_RUNTIME } from "../presentation/routes/route-runtime.tsx";
|
|
|
|
export const ROUTE_CODECS = PLATFORM_ROUTE_CODECS;
|
|
export const ROUTE_RUNTIME = PLATFORM_ROUTE_RUNTIME;
|
|
`;
|
|
|
|
const emptyAdapters = `export function createInstalledFeatureInputs(_context: unknown) {
|
|
void _context;
|
|
return Object.freeze({});
|
|
}
|
|
`;
|
|
|
|
const emptyContractContributions = `import {
|
|
composeContractContributions,
|
|
type InstalledContractContribution,
|
|
type InstalledContractPackageIdentity,
|
|
} from "../contracts/external-contract-runtime.ts";
|
|
|
|
export const INSTALLED_CONTRACT_CONTRIBUTIONS: readonly InstalledContractContribution[] =
|
|
Object.freeze([]);
|
|
|
|
export const COMPOSED_CONTRACT_CONTRIBUTIONS = composeContractContributions(
|
|
INSTALLED_CONTRACT_CONTRIBUTIONS,
|
|
);
|
|
|
|
export const EXPECTED_CONTRACT_SET_PACKAGES: readonly InstalledContractPackageIdentity[] =
|
|
COMPOSED_CONTRACT_CONTRIBUTIONS.externalPackages;
|
|
`;
|
|
|
|
const emptyMessages = `export const INSTALLED_MESSAGE_CATALOGS = Object.freeze({
|
|
"ko-KR": Object.freeze({}),
|
|
"en-US": Object.freeze({}),
|
|
});
|
|
`;
|
|
|
|
type CoveragePolicy = {
|
|
repositoryBaseline: number;
|
|
generatedPaths: string[];
|
|
criticalModules: Array<{
|
|
path?: string;
|
|
minimum?: Record<string, number>;
|
|
}>;
|
|
highRiskPaths: string[];
|
|
waivers: Array<{ path?: string }>;
|
|
};
|
|
|
|
type EvidenceContribution = Readonly<{ owner?: string }>;
|
|
type EvidencePolicy = Record<
|
|
"scenarioCatalogs" | "sourceContracts",
|
|
unknown
|
|
>;
|
|
type GovernanceConsumer = Readonly<{ path?: string }>;
|
|
type GovernanceRegistry = Record<string, unknown> & {
|
|
consumers?: unknown;
|
|
consumerDirectories?: unknown;
|
|
};
|
|
type RemovalGovernance = { registries: GovernanceRegistry[] };
|
|
|
|
function runPnpm(script: string, extra: string[] = []): boolean {
|
|
return runRemovalFixturePnpm(fixtureRoot, pnpmCli, script, extra);
|
|
}
|
|
|
|
try {
|
|
await prepareRemovalFixture(fixtureRoot);
|
|
for (const excludedFixtureTest of [
|
|
"tests/unit/ci-workflow-generation.test.ts",
|
|
"tests/unit/__snapshots__/ci-workflow-generation.test.ts.snap",
|
|
"tests/unit/removal-fixture.test.ts",
|
|
"tests/unit/http-scenario-evidence.test.ts",
|
|
]) {
|
|
await rm(path.join(fixtureRoot, excludedFixtureTest), { force: true });
|
|
}
|
|
|
|
const coveragePolicyFile = path.join(
|
|
fixtureRoot,
|
|
"config/testing/risk-coverage.json",
|
|
);
|
|
const coveragePolicy = JSON.parse(
|
|
await readFile(coveragePolicyFile, "utf8"),
|
|
) as CoveragePolicy;
|
|
const generatedProductionModules = new Set(coveragePolicy.generatedPaths);
|
|
|
|
const removedProductionModuleCount = (
|
|
await filesBelow(path.join(fixtureRoot, featureSource))
|
|
)
|
|
.map((file) => path.relative(fixtureRoot, file).split(path.sep).join("/"))
|
|
.filter(
|
|
(file) =>
|
|
isProductionModulePath(file) && !generatedProductionModules.has(file),
|
|
).length;
|
|
|
|
for (const ownedPath of featureOwnedPaths) {
|
|
await rm(path.join(fixtureRoot, ownedPath), {
|
|
recursive: true,
|
|
force: true,
|
|
});
|
|
}
|
|
await writeFile(
|
|
path.join(fixtureRoot, "src/features/installed-feature-contracts.ts"),
|
|
emptyContracts,
|
|
);
|
|
await writeFile(
|
|
path.join(fixtureRoot, "src/features/installed-feature-runtimes.tsx"),
|
|
emptyRuntimes,
|
|
);
|
|
await writeFile(
|
|
path.join(fixtureRoot, "src/features/installed-feature-adapters.ts"),
|
|
emptyAdapters,
|
|
);
|
|
await writeFile(
|
|
path.join(fixtureRoot, "src/features/installed-feature-messages.ts"),
|
|
emptyMessages,
|
|
);
|
|
await writeFile(
|
|
path.join(fixtureRoot, "src/features/installed-contract-contributions.ts"),
|
|
emptyContractContributions,
|
|
);
|
|
|
|
const retainedCriticalModules = coveragePolicy.criticalModules.filter(
|
|
(modulePolicy) => !modulePolicy.path?.startsWith(`${featureSource}/`),
|
|
);
|
|
if (
|
|
retainedCriticalModules.length === coveragePolicy.criticalModules.length
|
|
) {
|
|
throw new Error("Reference feature coverage policy is not registered");
|
|
}
|
|
coveragePolicy.criticalModules = retainedCriticalModules;
|
|
coveragePolicy.highRiskPaths = coveragePolicy.highRiskPaths.filter(
|
|
(modulePath) => !modulePath.startsWith(`${featureSource}/`),
|
|
);
|
|
coveragePolicy.waivers = coveragePolicy.waivers.filter(
|
|
(waiver) => !waiver.path?.startsWith(`${featureSource}/`),
|
|
);
|
|
coveragePolicy.generatedPaths = coveragePolicy.generatedPaths.filter(
|
|
(modulePath) => !modulePath.startsWith(`${featureSource}/`),
|
|
);
|
|
coveragePolicy.repositoryBaseline -= removedProductionModuleCount;
|
|
if (coveragePolicy.repositoryBaseline <= 0) {
|
|
throw new Error("Reference feature removal produced an invalid coverage baseline");
|
|
}
|
|
await writeFile(
|
|
coveragePolicyFile,
|
|
`${JSON.stringify(coveragePolicy, null, 2)}\n`,
|
|
);
|
|
|
|
const evidencePolicyFile = path.join(
|
|
fixtureRoot,
|
|
"config/testing/test-evidence.json",
|
|
);
|
|
const evidencePolicy = JSON.parse(
|
|
await readFile(evidencePolicyFile, "utf8"),
|
|
) as EvidencePolicy;
|
|
let removedEvidenceContributions = 0;
|
|
for (const policyKey of ["scenarioCatalogs", "sourceContracts"] as const) {
|
|
const contributions = evidencePolicy[policyKey];
|
|
if (!Array.isArray(contributions)) {
|
|
throw new Error(`Test evidence policy is missing ${policyKey}`);
|
|
}
|
|
evidencePolicy[policyKey] = contributions.filter((candidate: unknown) => {
|
|
const contribution = candidate as EvidenceContribution;
|
|
const retained = contribution.owner !== "reference-feature";
|
|
if (!retained) removedEvidenceContributions += 1;
|
|
return retained;
|
|
});
|
|
}
|
|
if (removedEvidenceContributions === 0) {
|
|
throw new Error("Reference feature test evidence policy is not registered");
|
|
}
|
|
await writeFile(
|
|
evidencePolicyFile,
|
|
`${JSON.stringify(evidencePolicy, null, 2)}\n`,
|
|
);
|
|
|
|
const governanceFile = path.join(
|
|
fixtureRoot,
|
|
"config/contracts/registry-governance.json",
|
|
);
|
|
const removalGovernance = JSON.parse(
|
|
await readFile(governanceFile, "utf8"),
|
|
) as RemovalGovernance;
|
|
removalGovernance.registries = removalGovernance.registries.map(
|
|
(registry) => ({
|
|
...registry,
|
|
...(Array.isArray(registry.consumers)
|
|
? {
|
|
consumers: registry.consumers.filter(
|
|
(candidate: unknown) => {
|
|
const consumer = candidate as GovernanceConsumer;
|
|
return !consumer.path?.includes("features/reference-feature");
|
|
},
|
|
),
|
|
}
|
|
: {}),
|
|
...(Array.isArray(registry.consumerDirectories)
|
|
? {
|
|
consumerDirectories: registry.consumerDirectories.filter(
|
|
(directory: unknown) =>
|
|
typeof directory !== "string" ||
|
|
!directory.includes("features/reference-feature"),
|
|
),
|
|
}
|
|
: {}),
|
|
}),
|
|
);
|
|
await writeFile(
|
|
governanceFile,
|
|
`${JSON.stringify(removalGovernance, null, 2)}\n`,
|
|
);
|
|
|
|
const removedCiScripts = new Set([
|
|
"check:types:fixture:feature-input",
|
|
"check:types:fixture:reference-operation",
|
|
"test:http-scenario-evidence",
|
|
"test:reference-feature",
|
|
]);
|
|
const fixturePackagePath = path.join(fixtureRoot, "package.json");
|
|
const fixturePackage = JSON.parse(await readFile(fixturePackagePath, "utf8")) as {
|
|
scripts: Record<string, string>;
|
|
};
|
|
pruneScriptOrchestration(fixturePackage.scripts, "test:all", removedCiScripts);
|
|
fixturePackage.scripts["test:coverage"] = fixturePackage.scripts["test:coverage"]
|
|
.replace(" tests/features/reference-feature", "");
|
|
delete fixturePackage.scripts["check:http-scenario-evidence"];
|
|
delete fixturePackage.scripts["check:http-scenario-evidence:fixture"];
|
|
await writeFile(fixturePackagePath, `${JSON.stringify(fixturePackage, null, 2)}\n`);
|
|
await pruneRemovalFixtureCiContract({
|
|
root: fixtureRoot,
|
|
removedScripts: removedCiScripts,
|
|
removedEvidencePathFragments: [
|
|
"reference-feature.xml",
|
|
"http-scenario-executions",
|
|
"http-scenario-evidence",
|
|
],
|
|
});
|
|
await regenerateRemovalFixtureWorkflow(fixtureRoot);
|
|
|
|
const residue: string[] = [];
|
|
for (const root of ["src", "tests"]) {
|
|
for (const file of await filesBelow(path.join(fixtureRoot, root))) {
|
|
const relative = path.relative(fixtureRoot, file);
|
|
const content = await readFile(file, "utf8");
|
|
if (
|
|
/REFERENCE_RESOURCE|reference-feature|reference-resource/i.test(
|
|
`${relative}\n${content}`,
|
|
)
|
|
) {
|
|
residue.push(relative);
|
|
}
|
|
}
|
|
}
|
|
|
|
const checks: Array<[string, boolean]> = [
|
|
[
|
|
"common-test-evidence",
|
|
(
|
|
await Promise.all(
|
|
commonTestPaths.map(async (testPath) => {
|
|
try {
|
|
await access(path.join(fixtureRoot, testPath));
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}),
|
|
)
|
|
).every(Boolean),
|
|
],
|
|
["typecheck", runPnpm("check:types")],
|
|
["architecture", runPnpm("check:architecture")],
|
|
["registry-structure", runPnpm("check:registries:structure")],
|
|
["unit-integration", runPnpm("test:all")],
|
|
["coverage", runPnpm("test:coverage")],
|
|
["test-evidence-source", runPnpm("check:test-evidence:source")],
|
|
["ci-contract", runPnpm("check:ci")],
|
|
[
|
|
"home-smoke",
|
|
runPnpm("exec", [
|
|
"vitest",
|
|
"run",
|
|
"tests/component/router.test.tsx",
|
|
"--reporter=default",
|
|
]),
|
|
],
|
|
["build", runPnpm("build")],
|
|
];
|
|
const builtResidue: string[] = [];
|
|
for (const file of await filesBelow(path.join(fixtureRoot, "dist"))) {
|
|
if (!/\.(?:js|css|html|json)$/.test(file)) continue;
|
|
const content = await readFile(file, "utf8");
|
|
if (
|
|
/REFERENCE_RESOURCE|reference-feature|reference-resource/i.test(content)
|
|
) {
|
|
builtResidue.push(path.relative(fixtureRoot, file));
|
|
}
|
|
}
|
|
const routeCatalog = await import(
|
|
`${new URL(
|
|
"../src/features/installed-feature-contracts.ts",
|
|
`file://${fixtureRoot}/scripts/`,
|
|
).href}?removed=${Date.now()}`
|
|
) as { ROUTE_REGISTRY: Readonly<Record<string, unknown>> };
|
|
const routeIds = Object.keys(routeCatalog.ROUTE_REGISTRY);
|
|
const routeAbsent = routeIds.every((routeId) => !routeId.startsWith("REFERENCE_"));
|
|
checks.push(["route-absent", routeAbsent]);
|
|
checks.push(["fixture-id-residue", residue.length === 0]);
|
|
checks.push(["built-fixture-id-residue", builtResidue.length === 0]);
|
|
|
|
const passed = checks.every(([, result]) => result);
|
|
await mkdir("artifacts/tests", { recursive: true });
|
|
await writeFile(
|
|
"artifacts/tests/sample-removal.xml",
|
|
`<?xml version="1.0" encoding="UTF-8"?>\n` +
|
|
`<testsuite name="reference-feature-removal" tests="${checks.length}" failures="${passed ? 0 : 1}">` +
|
|
checks
|
|
.map(
|
|
([name, result]) =>
|
|
`<testcase name="${name}">${result ? "" : `<failure>${[...residue, ...builtResidue].join(", ")}</failure>`}</testcase>`,
|
|
)
|
|
.join("") +
|
|
`</testsuite>\n`,
|
|
);
|
|
if (!passed) {
|
|
const failures = checks
|
|
.filter(([, result]) => !result)
|
|
.map(([name]) => name);
|
|
process.stderr.write(
|
|
`Reference feature removal failed: ${failures.join(", ")}; residue: ${[...residue, ...builtResidue].join(", ")}\n`,
|
|
);
|
|
process.exitCode = 1;
|
|
} else {
|
|
process.stdout.write(
|
|
`Reference feature removal: PASS (${checks.length} checks, no fixture IDs)\n`,
|
|
);
|
|
}
|
|
} finally {
|
|
await rm(fixtureRoot, { recursive: true, force: true });
|
|
}
|