fix: close coverage evidence races

This commit is contained in:
DongHyeonka
2026-08-02 08:43:44 +09:00
parent 67cd37659d
commit 8d6fbb97e9
7 changed files with 733 additions and 260 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
{ {
"path": "src/adapters/http/bounded-body-reader.ts", "path": "src/adapters/http/bounded-body-reader.ts",
"owner": "http-runtime", "owner": "http-runtime",
"minimum": { "lines": 68, "statements": 66, "functions": 55, "branches": 55 } "minimum": { "lines": 95, "statements": 95, "functions": 95, "branches": 90 }
}, },
{ {
"path": "src/adapters/http/bounded-json.ts", "path": "src/adapters/http/bounded-json.ts",
+102 -9
View File
@@ -35,6 +35,18 @@ type WriterDependencies = Readonly<{
fileSystem?: RiskCoverageArtifactFileSystem; fileSystem?: RiskCoverageArtifactFileSystem;
}>; }>;
type ReadableHandle = Readonly<{
stat(): Promise<Stats>;
readFile(encoding: "utf8"): Promise<string>;
close(): Promise<unknown>;
}>;
type InputDependencies = Readonly<{
lstatPath?: (target: string) => Promise<Stats>;
realpathPath?: (target: string) => Promise<string>;
openFile?: (target: string, flags: number) => Promise<ReadableHandle>;
}>;
const defaultFileSystem: RiskCoverageArtifactFileSystem = Object.freeze({ const defaultFileSystem: RiskCoverageArtifactFileSystem = Object.freeze({
openFile: async (target, flags, mode) => { openFile: async (target, flags, mode) => {
const handle = await open(target, flags, mode); const handle = await open(target, flags, mode);
@@ -74,6 +86,45 @@ function isWithin(root: string, target: string): boolean {
); );
} }
function hasStableIdentity(metadata: Stats): boolean {
return (
Number.isSafeInteger(metadata.dev) &&
Number.isSafeInteger(metadata.ino) &&
(metadata.dev !== 0 || metadata.ino !== 0)
);
}
function sameFileIdentity(before: Stats, after: Stats): boolean {
return (
!hasStableIdentity(before) ||
!hasStableIdentity(after) ||
(before.dev === after.dev && before.ino === after.ino)
);
}
async function rejectSymlinkAncestors(
repositoryRoot: string,
relativePath: string,
label: string,
lstatPath: (target: string) => Promise<Stats>,
): Promise<void> {
let current = repositoryRoot;
let currentRelative = "";
const directory = path.posix.dirname(relativePath);
if (directory === ".") return;
for (const segment of directory.split("/")) {
current = path.join(current, segment);
currentRelative = currentRelative ? `${currentRelative}/${segment}` : segment;
const metadata = await lstatPath(current);
if (metadata.isSymbolicLink()) {
throw new TypeError(`${label} ancestor is a symlink: ${currentRelative}`);
}
if (!metadata.isDirectory()) {
throw new TypeError(`${label} ancestor is not a directory: ${currentRelative}`);
}
}
}
function assertDirectory(metadata: Stats, relativePath: string): void { function assertDirectory(metadata: Stats, relativePath: string): void {
if (metadata.isSymbolicLink()) { if (metadata.isSymbolicLink()) {
throw new TypeError(`artifact ancestor is a symlink: ${relativePath}`); throw new TypeError(`artifact ancestor is a symlink: ${relativePath}`);
@@ -87,27 +138,41 @@ export async function readRiskCoverageInput(input: Readonly<{
repositoryRoot: string; repositoryRoot: string;
relativePath: string; relativePath: string;
label: string; label: string;
}>): Promise<Readonly<{ relativePath: string; absolutePath: string; text: string }>> { }>, dependencies: InputDependencies = {}): Promise<Readonly<{
relativePath: string;
absolutePath: string;
text: string;
}>> {
const lstatPath = dependencies.lstatPath ?? lstat;
const realpathPath = dependencies.realpathPath ?? realpath;
const openFile = dependencies.openFile ??
((target: string, flags: number) => open(target, flags));
const relativePath = normalizeRepositoryRelativePath( const relativePath = normalizeRepositoryRelativePath(
input.relativePath, input.relativePath,
`${input.label} path`, `${input.label} path`,
); );
const repositoryRoot = path.resolve(input.repositoryRoot); const repositoryRoot = path.resolve(input.repositoryRoot);
const repositoryRealpath = await realpath(repositoryRoot); const repositoryRealpath = await realpathPath(repositoryRoot);
const absolutePath = path.resolve(repositoryRoot, relativePath); const absolutePath = path.resolve(repositoryRoot, relativePath);
const metadata = await lstat(absolutePath); await rejectSymlinkAncestors(
repositoryRoot,
relativePath,
input.label,
lstatPath,
);
const metadata = await lstatPath(absolutePath);
if (metadata.isSymbolicLink()) { if (metadata.isSymbolicLink()) {
throw new TypeError(`${input.label} path is a symlink: ${relativePath}`); throw new TypeError(`${input.label} path is a symlink: ${relativePath}`);
} }
if (!metadata.isFile()) { if (!metadata.isFile()) {
throw new TypeError(`${input.label} path is not a regular file: ${relativePath}`); throw new TypeError(`${input.label} path is not a regular file: ${relativePath}`);
} }
const resolvedPath = await realpath(absolutePath); const resolvedPath = await realpathPath(absolutePath);
if (!isWithin(repositoryRealpath, resolvedPath)) { if (!isWithin(repositoryRealpath, resolvedPath)) {
throw new TypeError(`${input.label} path is outside repository: ${relativePath}`); throw new TypeError(`${input.label} path is outside repository: ${relativePath}`);
} }
const handle = await open( const handle = await openFile(
absolutePath, absolutePath,
constants.O_RDONLY | constants.O_NOFOLLOW, constants.O_RDONLY | constants.O_NOFOLLOW,
); );
@@ -116,6 +181,9 @@ export async function readRiskCoverageInput(input: Readonly<{
if (!openedMetadata.isFile()) { if (!openedMetadata.isFile()) {
throw new TypeError(`${input.label} path is not a regular file: ${relativePath}`); throw new TypeError(`${input.label} path is not a regular file: ${relativePath}`);
} }
if (!sameFileIdentity(metadata, openedMetadata)) {
throw new TypeError(`${input.label} path changed during validation: ${relativePath}`);
}
const text = await handle.readFile("utf8"); const text = await handle.readFile("utf8");
return Object.freeze({ relativePath, absolutePath, text }); return Object.freeze({ relativePath, absolutePath, text });
} finally { } finally {
@@ -170,17 +238,35 @@ export async function resolveRiskCoverageArtifactPath(input: Readonly<{
} }
const absolutePath = path.resolve(repositoryRoot, relativePath); const absolutePath = path.resolve(repositoryRoot, relativePath);
let destinationMetadata: Stats | undefined;
try { try {
const metadata = await lstat(absolutePath); destinationMetadata = await lstat(absolutePath);
if (metadata.isSymbolicLink()) { if (destinationMetadata.isSymbolicLink()) {
throw new TypeError(`artifact path is a symlink: ${relativePath}`); throw new TypeError(`artifact path is a symlink: ${relativePath}`);
} }
if (!metadata.isFile()) { if (!destinationMetadata.isFile()) {
throw new TypeError(`artifact path is not a regular file: ${relativePath}`); throw new TypeError(`artifact path is not a regular file: ${relativePath}`);
} }
} catch (error) { } catch (error) {
if (!hasErrorCode(error, "ENOENT")) throw error; if (!hasErrorCode(error, "ENOENT")) throw error;
} }
if (destinationMetadata) {
const destinationRealpath = await realpath(absolutePath);
for (const inputPath of normalizedInputs) {
const inputAbsolutePath = path.resolve(repositoryRoot, inputPath);
const inputRealpath = await realpath(inputAbsolutePath);
const inputMetadata = await lstat(inputAbsolutePath);
if (
inputRealpath === destinationRealpath ||
(hasStableIdentity(inputMetadata) &&
hasStableIdentity(destinationMetadata) &&
inputMetadata.dev === destinationMetadata.dev &&
inputMetadata.ino === destinationMetadata.ino)
) {
throw new TypeError(`artifact path is the same file as an input: ${inputPath}`);
}
}
}
return absolutePath; return absolutePath;
} }
@@ -232,7 +318,14 @@ export async function writeRiskCoverageArtifactAtomic(
ownsTemporaryFile = false; ownsTemporaryFile = false;
const directoryHandle = await fileSystem.openDirectory(path.dirname(destination)); const directoryHandle = await fileSystem.openDirectory(path.dirname(destination));
try { try {
await directoryHandle.sync(); try {
await directoryHandle.sync();
} catch (error) {
// Windows and some filesystems do not support fsync on directory handles.
if (!hasErrorCode(error, "EINVAL") && !hasErrorCode(error, "ENOTSUP")) {
throw error;
}
}
} finally { } finally {
await directoryHandle.close(); await directoryHandle.close();
} }
+27
View File
@@ -96,6 +96,8 @@ type InventoryOptions = Readonly<{
openFile?: (target: string, flags: number) => Promise<ReadableFileHandle>; openFile?: (target: string, flags: number) => Promise<ReadableFileHandle>;
}>; }>;
class FileIdentityChangedError extends Error {}
function isRecord(value: unknown): value is Record<string, unknown> { function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value); return Boolean(value) && typeof value === "object" && !Array.isArray(value);
} }
@@ -327,6 +329,22 @@ function isWithin(root: string, target: string): boolean {
); );
} }
function hasStableIdentity(metadata: Stats): boolean {
return (
Number.isSafeInteger(metadata.dev) &&
Number.isSafeInteger(metadata.ino) &&
(metadata.dev !== 0 || metadata.ino !== 0)
);
}
function sameFileIdentity(before: Stats, after: Stats): boolean {
return (
!hasStableIdentity(before) ||
!hasStableIdentity(after) ||
(before.dev === after.dev && before.ino === after.ino)
);
}
export function isProductionModulePath(relativePath: string): boolean { export function isProductionModulePath(relativePath: string): boolean {
return ( return (
/\.tsx?$/u.test(relativePath) && /\.tsx?$/u.test(relativePath) &&
@@ -394,7 +412,16 @@ export async function buildProductionModuleInventory(
if (!openedMetadata.isFile()) { if (!openedMetadata.isFile()) {
throw new TypeError("opened target is not a regular file"); throw new TypeError("opened target is not a regular file");
} }
if (!sameFileIdentity(metadata, openedMetadata)) {
throw new FileIdentityChangedError("opened file identity changed");
}
} catch (error) { } catch (error) {
if (error instanceof FileIdentityChangedError) {
throw new Error(
`production inventory file changed during validation: ${relativeTarget}`,
{ cause: error },
);
}
throw new Error(`production inventory file is unreadable: ${relativeTarget}`, { throw new Error(`production inventory file is unreadable: ${relativeTarget}`, {
cause: error, cause: error,
}); });
+232 -250
View File
@@ -12,6 +12,8 @@ import {
} from "node:fs/promises"; } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { isProductionModulePath } from "./lib/risk-coverage.ts";
const fixtureParent = path.resolve(".tmp"); const fixtureParent = path.resolve(".tmp");
await mkdir(fixtureParent, { recursive: true }); await mkdir(fixtureParent, { recursive: true });
const fixtureRoot = await mkdtemp( const fixtureRoot = await mkdtemp(
@@ -182,265 +184,245 @@ function runPnpm(script: string, extra: string[] = []): boolean {
return result.status === 0; return result.status === 0;
} }
for (const target of copyTargets) { try {
await cp(target, path.join(fixtureRoot, target), { recursive: true }); for (const target of copyTargets) {
} await cp(target, path.join(fixtureRoot, target), { recursive: true });
await symlink(path.resolve("node_modules"), path.join(fixtureRoot, "node_modules"), "dir");
const removedProductionModuleCount = (
await filesBelow(path.join(fixtureRoot, featureSource))
).filter(
(file) =>
/\.tsx?$/u.test(file) &&
!/\.d\.ts$/u.test(file) &&
!/\.stories\.tsx?$/u.test(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 coveragePolicyFile = path.join(
fixtureRoot,
"config/testing/risk-coverage.json",
);
const coveragePolicy = JSON.parse(
await readFile(coveragePolicyFile, "utf8"),
) as CoveragePolicy;
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;
// The removable reference feature exercises shared request-body failure branches.
// Keep the production policy unchanged while preserving an audited floor for the
// intentionally smaller executable universe in this isolated removal proof.
const removalSpecificBoundedBodyFloor = {
lines: 65,
statements: 63,
functions: 55,
branches: 45,
};
const boundedBodyPolicy = coveragePolicy.criticalModules.find(
(modulePolicy) =>
modulePolicy.path === "src/adapters/http/bounded-body-reader.ts",
);
if (!boundedBodyPolicy?.minimum) {
throw new Error("Shared bounded-body coverage policy is not registered");
}
for (const [metric, floor] of Object.entries(removalSpecificBoundedBodyFloor)) {
const productionFloor = boundedBodyPolicy.minimum[metric];
if (typeof productionFloor !== "number" || productionFloor < floor) {
throw new Error(
`Production bounded-body ${metric} floor must remain at least ${floor}`,
);
} }
} await symlink(path.resolve("node_modules"), path.join(fixtureRoot, "node_modules"), "dir");
boundedBodyPolicy.minimum = removalSpecificBoundedBodyFloor;
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( const coveragePolicyFile = path.join(
fixtureRoot, fixtureRoot,
"config/testing/test-evidence.json", "config/testing/risk-coverage.json",
); );
const evidencePolicy = JSON.parse( const coveragePolicy = JSON.parse(
await readFile(evidencePolicyFile, "utf8"), await readFile(coveragePolicyFile, "utf8"),
) as EvidencePolicy; ) as CoveragePolicy;
let removedEvidenceContributions = 0; const generatedProductionModules = new Set(coveragePolicy.generatedPaths);
for (const policyKey of ["scenarioCatalogs", "sourceContracts"] as const) {
const contributions = evidencePolicy[policyKey]; const removedProductionModuleCount = (
if (!Array.isArray(contributions)) { await filesBelow(path.join(fixtureRoot, featureSource))
throw new Error(`Test evidence policy is missing ${policyKey}`); )
.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,
});
} }
evidencePolicy[policyKey] = contributions.filter((candidate: unknown) => { await writeFile(
const contribution = candidate as EvidenceContribution; path.join(fixtureRoot, "src/features/installed-feature-contracts.ts"),
const retained = contribution.owner !== "reference-feature"; emptyContracts,
if (!retained) removedEvidenceContributions += 1; );
return retained; await writeFile(
}); path.join(fixtureRoot, "src/features/installed-feature-runtimes.tsx"),
} emptyRuntimes,
if (removedEvidenceContributions === 0) { );
throw new Error("Reference feature test evidence policy is not registered"); await writeFile(
} path.join(fixtureRoot, "src/features/installed-feature-adapters.ts"),
await writeFile( emptyAdapters,
evidencePolicyFile, );
`${JSON.stringify(evidencePolicy, null, 2)}\n`, 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 governanceFile = path.join( const retainedCriticalModules = coveragePolicy.criticalModules.filter(
fixtureRoot, (modulePolicy) => !modulePolicy.path?.startsWith(`${featureSource}/`),
"config/contracts/registry-governance.json", );
); if (
const removalGovernance = JSON.parse( retainedCriticalModules.length === coveragePolicy.criticalModules.length
await readFile(governanceFile, "utf8"), ) {
) as RemovalGovernance; throw new Error("Reference feature coverage policy is not registered");
removalGovernance.registries = removalGovernance.registries.map( }
(registry) => ({ coveragePolicy.criticalModules = retainedCriticalModules;
...registry, coveragePolicy.highRiskPaths = coveragePolicy.highRiskPaths.filter(
...(Array.isArray(registry.consumers) (modulePath) => !modulePath.startsWith(`${featureSource}/`),
? { );
consumers: registry.consumers.filter( coveragePolicy.waivers = coveragePolicy.waivers.filter(
(candidate: unknown) => { (waiver) => !waiver.path?.startsWith(`${featureSource}/`),
const consumer = candidate as GovernanceConsumer; );
return !consumer.path?.includes("features/reference-feature"); coveragePolicy.generatedPaths = coveragePolicy.generatedPaths.filter(
}, (modulePath) => !modulePath.startsWith(`${featureSource}/`),
), );
} coveragePolicy.repositoryBaseline -= removedProductionModuleCount;
: {}), if (coveragePolicy.repositoryBaseline <= 0) {
...(Array.isArray(registry.consumerDirectories) throw new Error("Reference feature removal produced an invalid coverage baseline");
? { }
consumerDirectories: registry.consumerDirectories.filter( await writeFile(
(directory: unknown) => coveragePolicyFile,
typeof directory !== "string" || `${JSON.stringify(coveragePolicy, null, 2)}\n`,
!directory.includes("features/reference-feature"), );
),
}
: {}),
}),
);
await writeFile(
governanceFile,
`${JSON.stringify(removalGovernance, null, 2)}\n`,
);
const residue: string[] = []; const evidencePolicyFile = path.join(
for (const root of ["src", "tests"]) { fixtureRoot,
for (const file of await filesBelow(path.join(fixtureRoot, root))) { "config/testing/test-evidence.json",
const relative = path.relative(fixtureRoot, file); );
const content = await readFile(file, "utf8"); const evidencePolicy = JSON.parse(
if ( await readFile(evidencePolicyFile, "utf8"),
/REFERENCE_RESOURCE|reference-feature|reference-resource/i.test( ) as EvidencePolicy;
`${relative}\n${content}`, let removedEvidenceContributions = 0;
) for (const policyKey of ["scenarioCatalogs", "sourceContracts"] as const) {
) { const contributions = evidencePolicy[policyKey];
residue.push(relative); 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 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]> = [ const checks: Array<[string, boolean]> = [
[ [
"common-test-evidence", "common-test-evidence",
( (
await Promise.all( await Promise.all(
commonTestPaths.map(async (testPath) => { commonTestPaths.map(async (testPath) => {
try { try {
await access(path.join(fixtureRoot, testPath)); await access(path.join(fixtureRoot, testPath));
return true; return true;
} catch { } catch {
return false; return false;
} }
}), }),
) )
).every(Boolean), ).every(Boolean),
], ],
["typecheck", runPnpm("check:types")], ["typecheck", runPnpm("check:types")],
["architecture", runPnpm("check:architecture")], ["architecture", runPnpm("check:architecture")],
["registry-structure", runPnpm("check:registries:structure")], ["registry-structure", runPnpm("check:registries:structure")],
["unit-integration", runPnpm("test:all")], ["unit-integration", runPnpm("test:all")],
["coverage", runPnpm("test:coverage")], ["coverage", runPnpm("test:coverage")],
["test-evidence-source", runPnpm("check:test-evidence:source")], ["test-evidence-source", runPnpm("check:test-evidence:source")],
[ [
"home-smoke", "home-smoke",
runPnpm("exec", [ runPnpm("exec", [
"vitest", "vitest",
"run", "run",
"tests/component/router.test.tsx", "tests/component/router.test.tsx",
"--reporter=default", "--reporter=default",
]), ]),
], ],
["build", runPnpm("build")], ["build", runPnpm("build")],
]; ];
const builtResidue: string[] = []; const builtResidue: string[] = [];
for (const file of await filesBelow(path.join(fixtureRoot, "dist"))) { for (const file of await filesBelow(path.join(fixtureRoot, "dist"))) {
if (!/\.(?:js|css|html|json)$/.test(file)) continue; if (!/\.(?:js|css|html|json)$/.test(file)) continue;
const content = await readFile(file, "utf8"); const content = await readFile(file, "utf8");
if ( if (
/REFERENCE_RESOURCE|reference-feature|reference-resource/i.test(content) /REFERENCE_RESOURCE|reference-feature|reference-resource/i.test(content)
) { ) {
builtResidue.push(path.relative(fixtureRoot, file)); builtResidue.push(path.relative(fixtureRoot, file));
}
} }
} const routeCatalog = await import(
const routeCatalog = await import( `${new URL(
`${new URL( "../src/features/installed-feature-contracts.ts",
"../src/features/installed-feature-contracts.ts", `file://${fixtureRoot}/scripts/`,
`file://${fixtureRoot}/scripts/`, ).href}?removed=${Date.now()}`
).href}?removed=${Date.now()}` ) as { ROUTE_REGISTRY: Readonly<Record<string, unknown>> };
) as { ROUTE_REGISTRY: Readonly<Record<string, unknown>> }; const routeIds = Object.keys(routeCatalog.ROUTE_REGISTRY);
const routeIds = Object.keys(routeCatalog.ROUTE_REGISTRY); const routeAbsent = routeIds.every((routeId) => !routeId.startsWith("REFERENCE_"));
const routeAbsent = routeIds.every((routeId) => !routeId.startsWith("REFERENCE_")); checks.push(["route-absent", routeAbsent]);
checks.push(["route-absent", routeAbsent]); checks.push(["fixture-id-residue", residue.length === 0]);
checks.push(["fixture-id-residue", residue.length === 0]); checks.push(["built-fixture-id-residue", builtResidue.length === 0]);
checks.push(["built-fixture-id-residue", builtResidue.length === 0]);
const passed = checks.every(([, result]) => result); const passed = checks.every(([, result]) => result);
await mkdir("artifacts/tests", { recursive: true }); await mkdir("artifacts/tests", { recursive: true });
await writeFile( await writeFile(
"artifacts/tests/sample-removal.xml", "artifacts/tests/sample-removal.xml",
`<?xml version="1.0" encoding="UTF-8"?>\n` + `<?xml version="1.0" encoding="UTF-8"?>\n` +
`<testsuite name="reference-feature-removal" tests="${checks.length}" failures="${passed ? 0 : 1}">` + `<testsuite name="reference-feature-removal" tests="${checks.length}" failures="${passed ? 0 : 1}">` +
checks checks
.map( .map(
([name, result]) => ([name, result]) =>
`<testcase name="${name}">${result ? "" : `<failure>${[...residue, ...builtResidue].join(", ")}</failure>`}</testcase>`, `<testcase name="${name}">${result ? "" : `<failure>${[...residue, ...builtResidue].join(", ")}</failure>`}</testcase>`,
) )
.join("") + .join("") +
`</testsuite>\n`, `</testsuite>\n`,
);
await rm(fixtureRoot, { recursive: true, force: true });
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.exit(1); 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 });
} }
process.stdout.write(
`Reference feature removal: PASS (${checks.length} checks, no fixture IDs)\n`,
);
+225
View File
@@ -0,0 +1,225 @@
import { describe, expect, it, vi } from "vitest";
import {
declaredContentLength,
decodeJsonBytes,
isEffectivelyEmpty,
isJsonMediaType,
probeForbiddenBody,
readBoundedBytes,
} from "../../src/adapters/http/bounded-body-reader.ts";
function responseWithBody(
body: ReadableStream<Uint8Array> | null,
contentLength?: string,
): Response {
return new Response(body, {
headers:
contentLength === undefined ? undefined : { "content-length": contentLength },
});
}
describe("bounded body reader", () => {
it.each([
[null, false],
["", false],
[" application/json ; charset=utf-8 ", true],
["APPLICATION/PROBLEM+JSON", true],
["text/json", false],
["application/jsonp", false],
] as const)("classifies JSON media type %j", (header, expected) => {
expect(isJsonMediaType(header)).toBe(expected);
});
it.each([
[undefined, null],
["0", 0],
["12", 12],
["-1", null],
["NaN", null],
["Infinity", null],
] as const)("parses declared content length %s", (header, expected) => {
expect(declaredContentLength(responseWithBody(null, header))).toBe(expected);
});
it("rejects an oversized declared body and tolerates cancellation failure", async () => {
const cancel = vi.fn(async () => {
throw new Error("already settled");
});
const response = {
headers: new Headers({ "content-length": "9" }),
body: { cancel },
} as unknown as Response;
await expect(readBoundedBytes(response, 8)).resolves.toEqual({
ok: false,
code: "RESPONSE_TOO_LARGE",
});
expect(cancel).toHaveBeenCalledOnce();
});
it("returns empty bytes when a successful response has no body", async () => {
const result = await readBoundedBytes(responseWithBody(null), 8);
expect(result).toEqual({ ok: true, bytes: new Uint8Array(0) });
});
it("joins chunks without retaining empty chunks", async () => {
const body = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new Uint8Array(0));
controller.enqueue(Uint8Array.of(1, 2));
controller.enqueue(Uint8Array.of(3));
controller.close();
},
});
await expect(readBoundedBytes(responseWithBody(body), 3)).resolves.toEqual({
ok: true,
bytes: Uint8Array.of(1, 2, 3),
});
});
it("cancels streaming input as soon as the accumulated limit is exceeded", async () => {
const cancel = vi.fn().mockRejectedValue(new Error("cancel failed"));
const reader = {
read: vi
.fn()
.mockResolvedValueOnce({ done: false, value: Uint8Array.of(1, 2) })
.mockResolvedValueOnce({ done: false, value: Uint8Array.of(3, 4) }),
cancel,
releaseLock: vi.fn(),
};
const response = {
headers: new Headers(),
body: { getReader: () => reader },
} as unknown as Response;
await expect(readBoundedBytes(response, 3)).resolves.toEqual({
ok: false,
code: "RESPONSE_TOO_LARGE",
});
expect(cancel).toHaveBeenCalledOnce();
expect(reader.releaseLock).toHaveBeenCalledOnce();
});
it("maps reader failure and cancellation failure to a stream failure", async () => {
const reader = {
read: vi.fn().mockRejectedValue(new Error("stream failed")),
cancel: vi.fn().mockRejectedValue(new Error("cancel failed")),
releaseLock: vi.fn(() => {
throw new Error("already released");
}),
};
const response = {
headers: new Headers(),
body: { getReader: () => reader },
} as unknown as Response;
await expect(readBoundedBytes(response, 3)).resolves.toEqual({
ok: false,
code: "RESPONSE_STREAM_FAILURE",
});
});
it("detects a forbidden body from declared length without reading it", async () => {
const cancel = vi.fn();
const response = {
headers: new Headers({ "content-length": "1" }),
body: { cancel },
} as unknown as Response;
await expect(probeForbiddenBody(response)).resolves.toEqual({
ok: true,
present: true,
});
expect(cancel).toHaveBeenCalledOnce();
});
it("accepts an absent, completed, or zero-byte forbidden body", async () => {
await expect(probeForbiddenBody(responseWithBody(null))).resolves.toEqual({
ok: true,
present: false,
});
for (const next of [
{ done: true, value: undefined },
{ done: false, value: new Uint8Array(0) },
]) {
const reader = {
read: vi.fn().mockResolvedValue(next),
cancel: vi.fn(),
releaseLock: vi.fn(),
};
const response = {
headers: new Headers(),
body: { getReader: () => reader },
} as unknown as Response;
await expect(probeForbiddenBody(response)).resolves.toEqual({
ok: true,
present: false,
});
expect(reader.cancel).not.toHaveBeenCalled();
}
});
it("probes only one present byte and cancels the remaining body", async () => {
const reader = {
read: vi.fn().mockResolvedValue({ done: false, value: Uint8Array.of(1) }),
cancel: vi.fn().mockRejectedValue(new Error("cancel failed")),
releaseLock: vi.fn(),
};
const response = {
headers: new Headers(),
body: { getReader: () => reader },
} as unknown as Response;
await expect(probeForbiddenBody(response)).resolves.toEqual({
ok: true,
present: true,
});
expect(reader.read).toHaveBeenCalledOnce();
expect(reader.cancel).toHaveBeenCalledOnce();
});
it("maps a forbidden-body probe error even when cleanup also fails", async () => {
const reader = {
read: vi.fn().mockRejectedValue(new Error("probe failed")),
cancel: vi.fn().mockRejectedValue(new Error("cancel failed")),
releaseLock: vi.fn(() => {
throw new Error("released");
}),
};
const response = {
headers: new Headers(),
body: { getReader: () => reader },
} as unknown as Response;
await expect(probeForbiddenBody(response)).resolves.toEqual({
ok: false,
code: "RESPONSE_STREAM_FAILURE",
});
});
it("decodes valid JSON and distinguishes UTF-8 from JSON failures", () => {
expect(decodeJsonBytes(new TextEncoder().encode('{"ok":true}'))).toEqual({
ok: true,
value: { ok: true },
});
expect(decodeJsonBytes(Uint8Array.of(0xc3, 0x28))).toEqual({
ok: false,
code: "UTF8_INVALID",
});
expect(decodeJsonBytes(new TextEncoder().encode("{"))).toEqual({
ok: false,
code: "JSON_INVALID",
});
});
it.each([
[new Uint8Array(0), true],
[Uint8Array.of(0x20, 0x09, 0x0a, 0x0d), true],
[Uint8Array.of(0x20, 0x00), false],
])("classifies effective emptiness %#", (bytes, expected) => {
expect(isEffectivelyEmpty(bytes)).toBe(expected);
});
});
+102
View File
@@ -1,6 +1,7 @@
import { constants } from "node:fs"; import { constants } from "node:fs";
import { import {
mkdir, mkdir,
link,
mkdtemp, mkdtemp,
open, open,
readFile, readFile,
@@ -78,6 +79,13 @@ describe("risk coverage CLI files", () => {
path.join(repositoryRoot, "config/testing/link.json"), path.join(repositoryRoot, "config/testing/link.json"),
); );
await symlink(outside, path.join(repositoryRoot, "linked-config"), "dir"); await symlink(outside, path.join(repositoryRoot, "linked-config"), "dir");
await mkdir(path.join(repositoryRoot, "real-config"));
await writeFile(path.join(repositoryRoot, "real-config/inside.json"), "{}\n");
await symlink(
path.join(repositoryRoot, "real-config"),
path.join(repositoryRoot, "inside-alias"),
"dir",
);
await expect( await expect(
readRiskCoverageInput({ readRiskCoverageInput({
@@ -93,6 +101,34 @@ describe("risk coverage CLI files", () => {
label: "policy", label: "policy",
}), }),
).rejects.toThrow(/outside repository|symlink/u); ).rejects.toThrow(/outside repository|symlink/u);
await expect(
readRiskCoverageInput({
repositoryRoot,
relativePath: "inside-alias/inside.json",
label: "policy",
}),
).rejects.toThrow(/ancestor is a symlink/u);
});
it("rejects an input identity swap between lstat and open", async () => {
const repositoryRoot = await fixture();
const outside = await mkdtemp(path.join(tmpdir(), "risk-coverage-input-race-"));
roots.push(outside);
const replacement = path.join(outside, "replacement.json");
await writeFile(replacement, "{\"replacement\":true}\n");
await expect(
readRiskCoverageInput(
{
repositoryRoot,
relativePath: "config/testing/policy.json",
label: "policy",
},
{
openFile: async (_target, flags) => open(replacement, flags),
},
),
).rejects.toThrow(/changed during validation/u);
}); });
it("confines artifact output and rejects input overwrite or symlink ancestors", async () => { it("confines artifact output and rejects input overwrite or symlink ancestors", async () => {
@@ -132,6 +168,24 @@ describe("risk coverage CLI files", () => {
).rejects.toThrow(/symlink/u); ).rejects.toThrow(/symlink/u);
}); });
it("rejects input overwrite through a realpath or hard-link alias", async () => {
const repositoryRoot = await fixture();
const outputDirectory = path.join(repositoryRoot, "artifacts/quality");
await mkdir(outputDirectory, { recursive: true });
const destination = path.join(outputDirectory, "risk-coverage.json");
await writeFile(destination, "{}\n");
const hardLinkInput = path.join(repositoryRoot, "config/testing/output-alias.json");
await link(destination, hardLinkInput);
await expect(
resolveRiskCoverageArtifactPath({
repositoryRoot,
relativePath: "artifacts/quality/risk-coverage.json",
inputPaths: ["config/testing/output-alias.json"],
}),
).rejects.toThrow(/same file as an input/u);
});
it("syncs an exclusive sibling temp before atomic rename", async () => { it("syncs an exclusive sibling temp before atomic rename", async () => {
const repositoryRoot = await fixture(); const repositoryRoot = await fixture();
const observed: string[] = []; const observed: string[] = [];
@@ -233,6 +287,54 @@ describe("risk coverage CLI files", () => {
expect(await readdir(outputDirectory)).toEqual(["risk-coverage.json"]); expect(await readdir(outputDirectory)).toEqual(["risk-coverage.json"]);
}); });
it.each([
["EINVAL", true],
["ENOTSUP", true],
["EIO", false],
] as const)(
"handles directory sync error %s with an explicit portability fallback",
async (code, accepted) => {
const repositoryRoot = await fixture();
const operation = writeRiskCoverageArtifactAtomic(
{
repositoryRoot,
relativePath: `artifacts/quality/sync-${code}.json`,
inputPaths: [],
value: { schemaVersion: 2 },
},
{
createNonce: () => code,
fileSystem: {
openFile: async (target, flags, mode) => {
const handle = await open(target, flags, mode);
return {
writeFile: async (data) => handle.writeFile(data, "utf8"),
sync: async () => handle.sync(),
close: async () => handle.close(),
};
},
openDirectory: async (target) => {
const handle = await open(target, constants.O_RDONLY);
return {
sync: async () => {
throw Object.assign(new Error(`sync ${code}`), { code });
},
close: async () => handle.close(),
};
},
rename,
rm,
},
},
);
if (accepted) {
await expect(operation).resolves.toBeUndefined();
} else {
await expect(operation).rejects.toMatchObject({ code });
}
},
);
it("has no changed-files gate in the executable", async () => { it("has no changed-files gate in the executable", async () => {
const source = await readFile("scripts/check-risk-coverage.ts", "utf8"); const source = await readFile("scripts/check-risk-coverage.ts", "utf8");
expect(source).not.toMatch(/changedFiles|changed-files/u); expect(source).not.toMatch(/changedFiles|changed-files/u);
+44
View File
@@ -186,6 +186,34 @@ describe("repository-aware risk coverage", () => {
).toThrow(/unexpected coverage path.*tests\/unit\/a\.test\.ts/u); ).toThrow(/unexpected coverage path.*tests\/unit\/a\.test\.ts/u);
}); });
it("rejects outside and duplicate normalized producer paths", () => {
const parsedPolicy = parseRiskCoveragePolicy(
policy({ repositoryBaseline: 1, generatedPaths: [] }),
{ now },
);
const base = {
repositoryRoot: "/repository",
inventory: inventory(["src/a.ts"]),
policy: parsedPolicy,
};
expect(() =>
evaluateRiskCoverage({
...base,
summary: { total: fullMetrics, "/outside/src/a.ts": fullMetrics },
}),
).toThrow(/outside repository/u);
expect(() =>
evaluateRiskCoverage({
...base,
summary: {
total: metrics(2),
"src/a.ts": fullMetrics,
"/repository/src/a.ts": fullMetrics,
},
}),
).toThrow(/duplicate coverage path/u);
});
it("accepts only explicitly configured generated coverage paths", () => { it("accepts only explicitly configured generated coverage paths", () => {
const parsedPolicy = parseRiskCoveragePolicy( const parsedPolicy = parseRiskCoveragePolicy(
policy({ repositoryBaseline: 1 }), policy({ repositoryBaseline: 1 }),
@@ -341,6 +369,22 @@ describe("repository-aware risk coverage", () => {
expect(observedFlags & constants.O_NOFOLLOW).toBe(constants.O_NOFOLLOW); expect(observedFlags & constants.O_NOFOLLOW).toBe(constants.O_NOFOLLOW);
}); });
it("rejects a post-lstat file identity swap even without relying on O_NOFOLLOW", async () => {
const repositoryRoot = await repositoryFixture();
const outside = await mkdtemp(path.join(tmpdir(), "risk-coverage-race-"));
roots.push(outside);
const outsideFile = path.join(outside, "replacement.ts");
await writeFile(outsideFile, "export const replacement = true;\n");
await expect(
buildProductionModuleInventory({
repositoryRoot,
openFile: async (target, flags) =>
open(target.endsWith("src/a.ts") ? outsideFile : target, flags),
}),
).rejects.toThrow(/changed during validation/u);
});
it("fails closed on empty, traversing, symlinked, or stale generated inventory", async () => { it("fails closed on empty, traversing, symlinked, or stale generated inventory", async () => {
const repositoryRoot = await repositoryFixture(); const repositoryRoot = await repositoryFixture();
const emptyRoot = await mkdtemp(path.join(tmpdir(), "risk-coverage-empty-")); const emptyRoot = await mkdtemp(path.join(tmpdir(), "risk-coverage-empty-"));