fix: harden repository coverage evidence

This commit is contained in:
DongHyeonka
2026-08-02 08:20:25 +09:00
parent 5a73f7a1b5
commit 67cd37659d
6 changed files with 1178 additions and 509 deletions
+34 -46
View File
@@ -1,6 +1,9 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import {
readRiskCoverageInput,
writeRiskCoverageArtifactAtomic,
} from "./lib/risk-coverage-files.ts";
import {
buildProductionModuleInventory,
evaluateRiskCoverage,
@@ -18,71 +21,56 @@ function requiredArgument(name: string, fallback: string): string {
return value;
}
function parseChangedFiles(value: unknown): readonly string[] {
if (
!Array.isArray(value) ||
value.some((entry) => typeof entry !== "string") ||
new Set(value).size !== value.length
) {
throw new TypeError("changed files input must be an array of unique paths");
}
return Object.freeze([...value] as string[]);
}
const repositoryRoot = path.resolve(
requiredArgument("--repository-root", process.cwd()),
);
const policyPath = requiredArgument(
"--policy",
"config/testing/risk-coverage.json",
);
const summaryPath = requiredArgument(
"--summary",
"artifacts/tests/coverage/coverage-summary.json",
);
const policyInput = await readRiskCoverageInput({
repositoryRoot,
relativePath: requiredArgument(
"--policy",
"config/testing/risk-coverage.json",
),
label: "policy",
});
const summaryInput = await readRiskCoverageInput({
repositoryRoot,
relativePath: requiredArgument(
"--summary",
"artifacts/tests/coverage/coverage-summary.json",
),
label: "summary",
});
const artifactPath = requiredArgument(
"--artifact",
"artifacts/quality/risk-coverage.json",
);
const changedFilesPath = argumentValue("--changed-files");
const policy = parseRepositoryRiskCoveragePolicy(
JSON.parse(await readFile(path.resolve(repositoryRoot, policyPath), "utf8")) as unknown,
JSON.parse(policyInput.text) as unknown,
);
const inventory = await buildProductionModuleInventory({
repositoryRoot,
generatedPaths: policy.generatedPaths,
});
const changedFiles = changedFilesPath
? parseChangedFiles(
JSON.parse(
await readFile(path.resolve(repositoryRoot, changedFilesPath), "utf8"),
) as unknown,
)
: [];
const result = evaluateRiskCoverage({
repositoryRoot,
inventory,
policy,
summary: JSON.parse(
await readFile(path.resolve(repositoryRoot, summaryPath), "utf8"),
) as unknown,
changedFiles,
summary: JSON.parse(summaryInput.text) as unknown,
});
await writeRiskCoverageArtifactAtomic({
repositoryRoot,
relativePath: artifactPath,
inputPaths: [policyInput.relativePath, summaryInput.relativePath],
value: {
schemaVersion: 2,
policy: policyInput.relativePath,
summary: summaryInput.relativePath,
...result,
},
});
const artifact = {
schemaVersion: 2,
policy: policyPath,
summary: summaryPath,
changedFiles: changedFilesPath ?? null,
...result,
};
const resolvedArtifactPath = path.resolve(repositoryRoot, artifactPath);
await mkdir(path.dirname(resolvedArtifactPath), { recursive: true });
await writeFile(resolvedArtifactPath, `${JSON.stringify(artifact, null, 2)}\n`);
if (result.failures.length > 0) {
process.stderr.write(
`Risk coverage failed:\n- ${result.failures.join("\n- ")}\n`,
);
process.stderr.write(`Risk coverage failed:\n- ${result.failures.join("\n- ")}\n`);
process.exit(1);
}
process.stdout.write(
+249
View File
@@ -0,0 +1,249 @@
import { randomUUID } from "node:crypto";
import { constants, type Stats } from "node:fs";
import {
lstat,
mkdir,
open,
realpath,
rename,
rm,
} from "node:fs/promises";
import path from "node:path";
import { normalizeRepositoryRelativePath } from "./repository-file-inventory.ts";
type WritableHandle = Readonly<{
writeFile(data: string): Promise<unknown>;
sync(): Promise<unknown>;
close(): Promise<unknown>;
}>;
type DirectoryHandle = Readonly<{
sync(): Promise<unknown>;
close(): Promise<unknown>;
}>;
export type RiskCoverageArtifactFileSystem = Readonly<{
openFile(target: string, flags: number, mode: number): Promise<WritableHandle>;
openDirectory(target: string): Promise<DirectoryHandle>;
rename(source: string, destination: string): Promise<unknown>;
rm(target: string, options: Readonly<{ force: true }>): Promise<unknown>;
}>;
type WriterDependencies = Readonly<{
createNonce?: () => string;
fileSystem?: RiskCoverageArtifactFileSystem;
}>;
const defaultFileSystem: RiskCoverageArtifactFileSystem = Object.freeze({
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 () => handle.sync(),
close: async () => handle.close(),
};
},
rename,
rm,
});
function hasErrorCode(error: unknown, code: string): boolean {
return (
typeof error === "object" &&
error !== null &&
"code" in error &&
error.code === code
);
}
function isWithin(root: string, target: string): boolean {
const relative = path.relative(root, target);
return (
relative === "" ||
(relative !== ".." &&
!relative.startsWith(`..${path.sep}`) &&
!path.isAbsolute(relative))
);
}
function assertDirectory(metadata: Stats, relativePath: string): void {
if (metadata.isSymbolicLink()) {
throw new TypeError(`artifact ancestor is a symlink: ${relativePath}`);
}
if (!metadata.isDirectory()) {
throw new TypeError(`artifact ancestor is not a directory: ${relativePath}`);
}
}
export async function readRiskCoverageInput(input: Readonly<{
repositoryRoot: string;
relativePath: string;
label: string;
}>): Promise<Readonly<{ relativePath: string; absolutePath: string; text: string }>> {
const relativePath = normalizeRepositoryRelativePath(
input.relativePath,
`${input.label} path`,
);
const repositoryRoot = path.resolve(input.repositoryRoot);
const repositoryRealpath = await realpath(repositoryRoot);
const absolutePath = path.resolve(repositoryRoot, relativePath);
const metadata = await lstat(absolutePath);
if (metadata.isSymbolicLink()) {
throw new TypeError(`${input.label} path is a symlink: ${relativePath}`);
}
if (!metadata.isFile()) {
throw new TypeError(`${input.label} path is not a regular file: ${relativePath}`);
}
const resolvedPath = await realpath(absolutePath);
if (!isWithin(repositoryRealpath, resolvedPath)) {
throw new TypeError(`${input.label} path is outside repository: ${relativePath}`);
}
const handle = await open(
absolutePath,
constants.O_RDONLY | constants.O_NOFOLLOW,
);
try {
const openedMetadata = await handle.stat();
if (!openedMetadata.isFile()) {
throw new TypeError(`${input.label} path is not a regular file: ${relativePath}`);
}
const text = await handle.readFile("utf8");
return Object.freeze({ relativePath, absolutePath, text });
} finally {
await handle.close();
}
}
export async function resolveRiskCoverageArtifactPath(input: Readonly<{
repositoryRoot: string;
relativePath: string;
inputPaths: readonly string[];
}>): Promise<string> {
const relativePath = normalizeRepositoryRelativePath(
input.relativePath,
"artifact path",
);
if (!relativePath.startsWith("artifacts/quality/")) {
throw new TypeError("artifact path must be below artifacts/quality");
}
const normalizedInputs = input.inputPaths.map((inputPath) =>
normalizeRepositoryRelativePath(inputPath, "input path"),
);
if (normalizedInputs.includes(relativePath)) {
throw new TypeError(`artifact path must not overwrite an input: ${relativePath}`);
}
const repositoryRoot = path.resolve(input.repositoryRoot);
const repositoryRealpath = await realpath(repositoryRoot);
const relativeDirectory = path.posix.dirname(relativePath);
let currentDirectory = repositoryRoot;
let currentRelative = "";
for (const segment of relativeDirectory.split("/")) {
currentDirectory = path.join(currentDirectory, segment);
currentRelative = currentRelative ? `${currentRelative}/${segment}` : segment;
let metadata: Stats;
try {
metadata = await lstat(currentDirectory);
} catch (error) {
if (!hasErrorCode(error, "ENOENT")) throw error;
try {
await mkdir(currentDirectory);
} catch (mkdirError) {
if (!hasErrorCode(mkdirError, "EEXIST")) throw mkdirError;
}
metadata = await lstat(currentDirectory);
}
assertDirectory(metadata, currentRelative);
const resolvedDirectory = await realpath(currentDirectory);
if (!isWithin(repositoryRealpath, resolvedDirectory)) {
throw new TypeError(`artifact ancestor is outside repository: ${currentRelative}`);
}
}
const absolutePath = path.resolve(repositoryRoot, relativePath);
try {
const metadata = await lstat(absolutePath);
if (metadata.isSymbolicLink()) {
throw new TypeError(`artifact path is a symlink: ${relativePath}`);
}
if (!metadata.isFile()) {
throw new TypeError(`artifact path is not a regular file: ${relativePath}`);
}
} catch (error) {
if (!hasErrorCode(error, "ENOENT")) throw error;
}
return absolutePath;
}
export async function writeRiskCoverageArtifactAtomic(
input: Readonly<{
repositoryRoot: string;
relativePath: string;
inputPaths: readonly string[];
value: unknown;
}>,
dependencies: WriterDependencies = {},
): Promise<void> {
const serialized = JSON.stringify(input.value, null, 2);
if (serialized === undefined) {
throw new TypeError("risk coverage artifact is not JSON serializable");
}
const destination = await resolveRiskCoverageArtifactPath(input);
const temporaryPath = path.join(
path.dirname(destination),
`.${path.basename(destination)}.${(dependencies.createNonce ?? randomUUID)()}.tmp`,
);
const fileSystem = dependencies.fileSystem ?? defaultFileSystem;
let ownsTemporaryFile = false;
try {
const handle = await fileSystem.openFile(
temporaryPath,
constants.O_WRONLY |
constants.O_CREAT |
constants.O_EXCL |
constants.O_NOFOLLOW,
0o600,
);
ownsTemporaryFile = true;
let primaryFailure: unknown;
try {
await handle.writeFile(`${serialized}\n`);
await handle.sync();
} catch (error) {
primaryFailure = error;
}
try {
await handle.close();
} catch (error) {
primaryFailure ??= error;
}
if (primaryFailure !== undefined) throw primaryFailure;
await fileSystem.rename(temporaryPath, destination);
ownsTemporaryFile = false;
const directoryHandle = await fileSystem.openDirectory(path.dirname(destination));
try {
await directoryHandle.sync();
} finally {
await directoryHandle.close();
}
} catch (error) {
if (ownsTemporaryFile) {
try {
await fileSystem.rm(temporaryPath, { force: true });
} catch {
// Preserve the publication failure and clean only our nonce-owned path.
}
}
throw error;
}
}
+301 -201
View File
@@ -1,5 +1,11 @@
import { open, readdir, realpath, lstat } from "node:fs/promises";
import type { Dirent, Stats } from "node:fs";
import { constants, type Dirent, type Stats } from "node:fs";
import {
lstat,
open,
readdir,
realpath,
type FileHandle,
} from "node:fs/promises";
import path from "node:path";
import { normalizeRepositoryRelativePath } from "./repository-file-inventory.ts";
@@ -10,6 +16,8 @@ const coverageMetrics = [
"functions",
"branches",
] as const;
const teamIdPattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
const maximumWaiverDurationMs = 90 * 24 * 60 * 60 * 1_000;
export const REQUIRED_HIGH_RISK_PATHS = Object.freeze([
"src/adapters/http/http-execution-v3.ts",
@@ -23,10 +31,14 @@ export const REQUIRED_HIGH_RISK_PATHS = Object.freeze([
] as const);
type CoverageMetric = (typeof coverageMetrics)[number];
type Thresholds = Readonly<Partial<Record<CoverageMetric, number>>>;
type CoverageMetrics = Readonly<
Record<CoverageMetric, Readonly<{ pct: number }>>
>;
type Thresholds = Readonly<Record<CoverageMetric, number>>;
type CoverageCounter = Readonly<{
total: number;
covered: number;
skipped: number;
pct: number;
}>;
type CoverageMetrics = Readonly<Record<CoverageMetric, CoverageCounter>>;
export type RiskCoveragePolicy = Readonly<{
schemaVersion: 2;
@@ -47,12 +59,23 @@ export type RiskCoveragePolicy = Readonly<{
}>[];
}>;
export type ProductionModuleInventory = Readonly<{
files: readonly string[];
preExclusionTotal: number;
generatedExclusions: readonly string[];
}>;
export type RiskCoverageResult = Readonly<{
status: "PASS" | "FAIL";
selectedTotal: number;
repositoryTotal: number;
preExclusionTotal: number;
generatedExclusionCount: number;
generatedExclusions: readonly string[];
ownershipScope: "ALL_POLICY_HIGH_RISK";
ownedHighRiskPaths: readonly string[];
waivedHighRiskPaths: readonly string[];
uncoveredModules: readonly string[];
ignoredCoveragePaths: readonly string[];
results: readonly Readonly<{
scope: string;
metric: CoverageMetric;
@@ -63,13 +86,14 @@ export type RiskCoverageResult = Readonly<{
failures: readonly string[];
}>;
type ReadableFileHandle = Pick<FileHandle, "close" | "stat">;
type InventoryOptions = Readonly<{
repositoryRoot?: string;
generatedPaths?: readonly string[];
readDirectory?: (target: string) => Promise<Dirent[]>;
lstatPath?: (target: string) => Promise<Stats>;
realpathPath?: (target: string) => Promise<string>;
assertReadable?: (target: string) => Promise<void>;
openFile?: (target: string, flags: number) => Promise<ReadableFileHandle>;
}>;
function isRecord(value: unknown): value is Record<string, unknown> {
@@ -90,9 +114,7 @@ function assertExactKeys(
function exactSourcePath(value: unknown, label: string): string {
if (
typeof value !== "string" ||
["*", "?", "[", "]", "{", "}"].some((character) =>
value.includes(character),
)
["*", "?", "[", "]", "{", "}"].some((character) => value.includes(character))
) {
throw new TypeError(`${label} must be an exact repository-relative POSIX path`);
}
@@ -103,20 +125,15 @@ function exactSourcePath(value: unknown, label: string): string {
return normalized;
}
function nonBlank(value: unknown, label: string): string {
if (typeof value !== "string" || !value.trim()) {
throw new TypeError(`${label} must be a nonblank string`);
}
return value.trim();
}
function uniquePaths(
value: unknown,
label: string,
options: Readonly<{ allowEmpty: boolean }> = { allowEmpty: true },
): readonly string[] {
if (!Array.isArray(value) || (!options.allowEmpty && value.length === 0)) {
throw new TypeError(`${label} must be an array${options.allowEmpty ? "" : " with at least one path"}`);
throw new TypeError(
`${label} must be an array${options.allowEmpty ? "" : " with at least one path"}`,
);
}
const paths = value.map((entry) => exactSourcePath(entry, `${label} entry`));
if (new Set(paths).size !== paths.length) {
@@ -125,43 +142,60 @@ function uniquePaths(
return Object.freeze(paths);
}
function thresholds(
value: unknown,
label: string,
options: Readonly<{ requireAll: boolean }>,
): Thresholds {
if (!isRecord(value)) {
throw new TypeError(`${label} must be an object`);
function teamId(value: unknown, label: string): string {
if (typeof value !== "string" || !teamIdPattern.test(value)) {
throw new TypeError(`${label} must be a canonical team id`);
}
return value;
}
function thresholds(value: unknown, label: string): Thresholds {
if (!isRecord(value)) throw new TypeError(`${label} must be an object`);
assertExactKeys(value, coverageMetrics, label);
if (
Object.keys(value).length === 0 ||
(options.requireAll && coverageMetrics.some((metric) => !(metric in value)))
) {
throw new TypeError(`${label} must define ${options.requireAll ? "all " : "at least one "}coverage metric`);
if (coverageMetrics.some((metric) => !(metric in value))) {
throw new TypeError(`${label} must define all coverage metrics`);
}
const parsed: Partial<Record<CoverageMetric, number>> = {};
for (const [metric, threshold] of Object.entries(value)) {
const parsed = {} as Record<CoverageMetric, number>;
for (const metric of coverageMetrics) {
const threshold = value[metric];
if (
typeof threshold !== "number" ||
!Number.isFinite(threshold) ||
threshold < 0 ||
threshold <= 0 ||
threshold > 100
) {
throw new TypeError(`${label}.${metric} minimum must be a finite number from 0 to 100`);
throw new TypeError(
`${label}.${metric} minimum must be a finite number greater than 0 and at most 100`,
);
}
parsed[metric as CoverageMetric] = threshold;
parsed[metric] = threshold;
}
return Object.freeze(parsed);
}
function waiverReason(value: unknown, label: string): string {
if (typeof value !== "string" || value.length < 12 || value.length > 240) {
throw new TypeError(`${label} must contain 12 to 240 characters`);
}
if (value !== value.trim()) {
throw new TypeError(`${label} must not contain surrounding whitespace`);
}
if (
[...value].some((character) => {
const codePoint = character.codePointAt(0) ?? 0;
return codePoint <= 31 || codePoint === 127;
})
) {
throw new TypeError(`${label} must not contain control characters`);
}
return value;
}
export function parseRiskCoveragePolicy(
value: unknown,
options: Readonly<{ now?: number }> = {},
): RiskCoveragePolicy {
if (!isRecord(value)) {
throw new TypeError("risk coverage policy must be an object");
}
if (!isRecord(value)) throw new TypeError("risk coverage policy must be an object");
assertExactKeys(
value,
[
@@ -180,11 +214,12 @@ export function parseRiskCoveragePolicy(
}
if (
typeof value.repositoryBaseline !== "number" ||
!Number.isInteger(value.repositoryBaseline) ||
!Number.isSafeInteger(value.repositoryBaseline) ||
value.repositoryBaseline <= 0
) {
throw new TypeError("repositoryBaseline must be a positive integer");
throw new TypeError("repositoryBaseline must be a positive safe integer");
}
const generatedPaths = uniquePaths(value.generatedPaths, "generatedPaths");
const highRiskPaths = uniquePaths(value.highRiskPaths, "highRiskPaths", {
allowEmpty: false,
@@ -199,48 +234,65 @@ export function parseRiskCoveragePolicy(
assertExactKeys(candidate, ["path", "owner", "minimum"], `criticalModules[${index}]`);
return Object.freeze({
path: exactSourcePath(candidate.path, `criticalModules[${index}].path`),
owner: nonBlank(candidate.owner, `criticalModules[${index}].owner`),
minimum: thresholds(candidate.minimum, `criticalModules[${index}].minimum`, {
requireAll: false,
}),
owner: teamId(candidate.owner, `criticalModules[${index}].owner`),
minimum: thresholds(candidate.minimum, `criticalModules[${index}].minimum`),
});
});
if (new Set(criticalModules.map((entry) => entry.path)).size !== criticalModules.length) {
throw new TypeError("criticalModules contains a duplicate path");
}
if (!Array.isArray(value.waivers)) {
throw new TypeError("waivers must be an array");
}
if (!Array.isArray(value.waivers)) throw new TypeError("waivers must be an array");
const currentTime = options.now ?? Date.now();
if (!Number.isFinite(currentTime)) throw new TypeError("policy time must be finite");
const waivers = value.waivers.map((candidate, index) => {
if (!isRecord(candidate)) {
throw new TypeError(`waivers[${index}] must be an object`);
}
if (!isRecord(candidate)) throw new TypeError(`waivers[${index}] must be an object`);
assertExactKeys(candidate, ["path", "owner", "reason", "expiresAt"], `waivers[${index}]`);
const waiverPath = exactSourcePath(candidate.path, `waivers[${index}].path`);
const expiresAt = nonBlank(candidate.expiresAt, `waivers[${index}].expiresAt`);
const expiresAt = candidate.expiresAt;
if (typeof expiresAt !== "string") {
throw new TypeError(`waivers[${index}].expiresAt must be a canonical UTC ISO timestamp`);
}
const expiry = Date.parse(expiresAt);
if (!Number.isFinite(expiry) || expiry <= currentTime) {
throw new TypeError(`waivers[${index}] is expired or has an invalid expiry`);
if (!Number.isFinite(expiry) || new Date(expiry).toISOString() !== expiresAt) {
throw new TypeError(`waivers[${index}].expiresAt must be a canonical UTC ISO timestamp`);
}
if (expiry <= currentTime) throw new TypeError(`waivers[${index}] is expired`);
if (expiry - currentTime > maximumWaiverDurationMs) {
throw new TypeError(`waivers[${index}] expiry must be within 90 days`);
}
if (!highRiskPaths.includes(waiverPath)) {
throw new TypeError(`waivers[${index}] is stale because ${waiverPath} is not high-risk`);
}
return Object.freeze({
path: waiverPath,
owner: nonBlank(candidate.owner, `waivers[${index}].owner`),
reason: nonBlank(candidate.reason, `waivers[${index}].reason`),
owner: teamId(candidate.owner, `waivers[${index}].owner`),
reason: waiverReason(candidate.reason, `waivers[${index}].reason`),
expiresAt,
});
});
if (new Set(waivers.map((entry) => entry.path)).size !== waivers.length) {
throw new TypeError("waivers contains a duplicate path");
}
const criticalPaths = new Set(criticalModules.map((entry) => entry.path));
const waiverPaths = new Set(waivers.map((entry) => entry.path));
for (const highRiskPath of highRiskPaths) {
if (criticalPaths.has(highRiskPath) && waiverPaths.has(highRiskPath)) {
throw new TypeError(
`high-risk module cannot have both a critical owner and waiver: ${highRiskPath}`,
);
}
if (!criticalPaths.has(highRiskPath) && !waiverPaths.has(highRiskPath)) {
throw new TypeError(`high-risk module has no owner or waiver: ${highRiskPath}`);
}
}
return Object.freeze({
schemaVersion: 2,
repositoryBaseline: value.repositoryBaseline,
generatedPaths,
summary: thresholds(value.summary, "summary", { requireAll: true }),
summary: thresholds(value.summary, "summary"),
criticalModules: Object.freeze(criticalModules),
highRiskPaths,
waivers: Object.freeze(waivers),
@@ -256,15 +308,15 @@ export function parseRepositoryRiskCoveragePolicy(
if (!policy.highRiskPaths.includes(requiredPath)) {
throw new TypeError(`required high-risk path is missing: ${requiredPath}`);
}
if (policy.generatedPaths.includes(requiredPath)) {
throw new TypeError(
`required high-risk path cannot be generated-excluded: ${requiredPath}`,
);
}
}
return policy;
}
async function defaultAssertReadable(target: string): Promise<void> {
const handle = await open(target, "r");
await handle.close();
}
function isWithin(root: string, target: string): boolean {
const relative = path.relative(root, target);
return (
@@ -275,7 +327,7 @@ function isWithin(root: string, target: string): boolean {
);
}
function isProductionModule(relativePath: string): boolean {
export function isProductionModulePath(relativePath: string): boolean {
return (
/\.tsx?$/u.test(relativePath) &&
!/\.d\.ts$/u.test(relativePath) &&
@@ -285,13 +337,15 @@ function isProductionModule(relativePath: string): boolean {
export async function buildProductionModuleInventory(
options: InventoryOptions = {},
): Promise<readonly string[]> {
): Promise<ProductionModuleInventory> {
const repositoryRoot = path.resolve(options.repositoryRoot ?? process.cwd());
const sourceRoot = path.join(repositoryRoot, "src");
const readDirectory = options.readDirectory ?? ((target) => readdir(target, { withFileTypes: true }));
const readDirectory = options.readDirectory ??
((target: string) => readdir(target, { withFileTypes: true }));
const lstatPath = options.lstatPath ?? lstat;
const realpathPath = options.realpathPath ?? realpath;
const assertReadable = options.assertReadable ?? defaultAssertReadable;
const openFile = options.openFile ??
((target: string, flags: number) => open(target, flags));
const generatedPaths = uniquePaths(options.generatedPaths ?? [], "generatedPaths");
const generated = new Set(generatedPaths);
const repositoryRealpath = await realpathPath(repositoryRoot);
@@ -322,7 +376,7 @@ export async function buildProductionModuleInventory(
await visit(relativeTarget);
continue;
}
if (!isProductionModule(relativeTarget)) continue;
if (!isProductionModulePath(relativeTarget)) continue;
if (!metadata.isFile()) {
throw new TypeError(`production inventory path is not a regular file: ${relativeTarget}`);
}
@@ -330,12 +384,22 @@ export async function buildProductionModuleInventory(
if (!isWithin(repositoryRealpath, resolvedTarget)) {
throw new TypeError(`production inventory path is outside repository: ${relativeTarget}`);
}
let handle: ReadableFileHandle | undefined;
try {
await assertReadable(absoluteTarget);
handle = await openFile(
absoluteTarget,
constants.O_RDONLY | constants.O_NOFOLLOW,
);
const openedMetadata = await handle.stat();
if (!openedMetadata.isFile()) {
throw new TypeError("opened target is not a regular file");
}
} catch (error) {
throw new Error(`production inventory file is unreadable: ${relativeTarget}`, {
cause: error,
});
} finally {
await handle?.close();
}
allModules.push(relativeTarget);
}
@@ -347,35 +411,89 @@ export async function buildProductionModuleInventory(
}
}
const inventory = allModules.filter((file) => !generated.has(file)).sort();
if (inventory.length === 0) {
throw new Error("production module inventory is empty");
}
if (inventory.length === 0) throw new Error("production module inventory is empty");
if (new Set(inventory).size !== inventory.length) {
throw new TypeError("production module inventory contains a duplicate path");
}
return Object.freeze(inventory);
return Object.freeze({
files: Object.freeze(inventory),
preExclusionTotal: allModules.length,
generatedExclusions: Object.freeze([...generatedPaths].sort()),
});
}
function coveragePath(repositoryRoot: string, rawPath: string): string {
if (rawPath.includes("\\") || rawPath.includes("\0")) {
export function normalizeCoverageProducerPath(input: Readonly<{
repositoryRoot: string;
rawPath: string;
platform?: "posix" | "win32";
}>): string {
const platform = input.platform ?? (process.platform === "win32" ? "win32" : "posix");
const pathApi = platform === "win32" ? path.win32 : path.posix;
const { rawPath } = input;
if (rawPath.includes("\0")) throw new TypeError("coverage path contains NUL");
if (platform === "posix" && rawPath.includes("\\")) {
throw new TypeError("coverage path must use POSIX separators");
}
if (path.isAbsolute(rawPath)) {
const relative = path.relative(repositoryRoot, rawPath).split(path.sep).join("/");
if (!relative || relative === ".." || relative.startsWith("../")) {
if (platform === "win32" && rawPath.includes("\\") && !pathApi.isAbsolute(rawPath)) {
throw new TypeError("relative coverage path must use POSIX separators");
}
if (pathApi.isAbsolute(rawPath)) {
const root = pathApi.resolve(input.repositoryRoot);
const relative = pathApi.relative(root, pathApi.resolve(rawPath));
if (
!relative ||
relative === ".." ||
relative.startsWith(`..${pathApi.sep}`) ||
pathApi.isAbsolute(relative)
) {
throw new TypeError(`coverage path is outside repository: ${rawPath}`);
}
return normalizeRepositoryRelativePath(relative, "coverage path");
return normalizeRepositoryRelativePath(
relative.split(pathApi.sep).join("/"),
"coverage path",
);
}
return normalizeRepositoryRelativePath(rawPath, "coverage path");
}
function expectedPct(total: number, covered: number): number {
return total === 0 ? 100 : Math.floor((covered / total) * 10_000) / 100;
}
function parseCoverageCounter(value: unknown, label: string): CoverageCounter {
if (!isRecord(value)) throw new TypeError(`${label} must be an object`);
const fields = ["total", "covered", "skipped", "pct"] as const;
assertExactKeys(value, fields, label);
if (fields.some((field) => !(field in value))) {
throw new TypeError(`${label} must define total, covered, skipped, and pct`);
}
for (const count of ["total", "covered", "skipped"] as const) {
if (
typeof value[count] !== "number" ||
!Number.isSafeInteger(value[count]) ||
value[count] < 0
) {
throw new TypeError(`${label}.${count} must be a nonnegative safe integer`);
}
}
const total = value.total as number;
const covered = value.covered as number;
const skipped = value.skipped as number;
if (covered + skipped > total) {
throw new TypeError(`${label} covered plus skipped must not exceed total`);
}
const pct = value.pct;
const calculatedPct = expectedPct(total, covered);
if (typeof pct !== "number" || !Number.isFinite(pct) || pct !== calculatedPct) {
throw new TypeError(`${label}.pct must equal ${calculatedPct}`);
}
return Object.freeze({ total, covered, skipped, pct });
}
function parseCoverageMetrics(value: unknown, label: string): CoverageMetrics {
if (!isRecord(value)) throw new TypeError(`${label} must be an object`);
const unknownMetrics = Object.keys(value).filter(
(metric) =>
metric !== "branchesTrue" &&
!coverageMetrics.includes(metric as CoverageMetric),
(metric) => metric !== "branchesTrue" && !coverageMetrics.includes(metric as CoverageMetric),
);
if (unknownMetrics.length > 0) {
throw new TypeError(
@@ -383,104 +501,85 @@ function parseCoverageMetrics(value: unknown, label: string): CoverageMetrics {
);
}
if (value.branchesTrue !== undefined) {
if (!isRecord(value.branchesTrue)) {
throw new TypeError(`${label}.branchesTrue must be an object`);
}
assertExactKeys(
value.branchesTrue,
["total", "covered", "skipped", "pct"],
`${label}.branchesTrue`,
);
for (const count of ["total", "covered", "skipped"] as const) {
const received = value.branchesTrue[count];
if (
typeof received !== "number" ||
!Number.isSafeInteger(received) ||
received < 0
) {
throw new TypeError(
`${label}.branchesTrue.${count} must be a nonnegative safe integer`,
);
}
}
const pct = value.branchesTrue.pct;
if (typeof pct !== "number" || !Number.isFinite(pct) || pct < 0 || pct > 100) {
throw new TypeError(
`${label}.branchesTrue.pct must be a finite number from 0 to 100`,
);
}
if (
(value.branchesTrue.covered as number) >
(value.branchesTrue.total as number) ||
(value.branchesTrue.skipped as number) >
(value.branchesTrue.total as number)
) {
throw new TypeError(`${label}.branchesTrue counts exceed total`);
}
parseCoverageCounter(value.branchesTrue, `${label}.branchesTrue`);
}
const parsed = {} as Record<CoverageMetric, { pct: number }>;
const parsed = {} as Record<CoverageMetric, CoverageCounter>;
for (const metric of coverageMetrics) {
const rawMetric = value[metric];
if (!isRecord(rawMetric)) {
throw new TypeError(`${label}.${metric} must be an object`);
}
assertExactKeys(
rawMetric,
["total", "covered", "skipped", "pct"],
`${label}.${metric}`,
);
const suppliedCounts = ["total", "covered", "skipped"].filter(
(count) => rawMetric[count] !== undefined,
);
if (suppliedCounts.length !== 0 && suppliedCounts.length !== 3) {
throw new TypeError(
`${label}.${metric} must define total, covered, and skipped together`,
);
}
for (const count of suppliedCounts) {
const received = rawMetric[count];
if (
typeof received !== "number" ||
!Number.isSafeInteger(received) ||
received < 0
) {
throw new TypeError(
`${label}.${metric}.${count} must be a nonnegative safe integer`,
);
}
}
if (
suppliedCounts.length === 3 &&
((rawMetric.covered as number) > (rawMetric.total as number) ||
(rawMetric.skipped as number) > (rawMetric.total as number))
) {
throw new TypeError(`${label}.${metric} counts exceed total`);
}
const pct = rawMetric.pct;
if (typeof pct !== "number" || !Number.isFinite(pct) || pct < 0 || pct > 100) {
throw new TypeError(`${label}.${metric}.pct must be a finite number from 0 to 100`);
}
parsed[metric] = { pct };
parsed[metric] = parseCoverageCounter(value[metric], `${label}.${metric}`);
}
return Object.freeze(parsed);
}
function aggregateCoverage(selected: readonly CoverageMetrics[]): CoverageMetrics {
const aggregate = {} as Record<CoverageMetric, CoverageCounter>;
for (const metric of coverageMetrics) {
let total = 0;
let covered = 0;
let skipped = 0;
for (const metrics of selected) {
total += metrics[metric].total;
covered += metrics[metric].covered;
skipped += metrics[metric].skipped;
if (![total, covered, skipped].every(Number.isSafeInteger)) {
throw new TypeError(`recomputed coverage ${metric} count exceeds safe integer range`);
}
}
aggregate[metric] = Object.freeze({
total,
covered,
skipped,
pct: expectedPct(total, covered),
});
}
return Object.freeze(aggregate);
}
function assertMatchingTotal(
producer: CoverageMetrics,
recomputed: CoverageMetrics,
): void {
for (const metric of coverageMetrics) {
const actual = producer[metric];
const expected = recomputed[metric];
if (
actual.total !== expected.total ||
actual.covered !== expected.covered ||
actual.skipped !== expected.skipped ||
actual.pct !== expected.pct
) {
throw new TypeError(
`coverage total.${metric} does not match recomputed inventory total`,
);
}
}
}
export function evaluateRiskCoverage(input: Readonly<{
repositoryRoot?: string;
inventory: readonly string[];
inventory: ProductionModuleInventory;
policy: RiskCoveragePolicy;
summary: unknown;
changedFiles?: readonly string[];
now?: number;
}>): RiskCoverageResult {
const repositoryRoot = path.resolve(input.repositoryRoot ?? process.cwd());
if (input.inventory.length === 0) {
throw new TypeError("production module inventory is empty");
}
const inventory = input.inventory.map((file) => exactSourcePath(file, "inventory path"));
const inventory = input.inventory.files.map((file) => exactSourcePath(file, "inventory path"));
if (inventory.length === 0) throw new TypeError("production module inventory is empty");
if (new Set(inventory).size !== inventory.length) {
throw new TypeError("production module inventory contains a duplicate path");
}
const generatedExclusions = input.inventory.generatedExclusions.map((file) =>
exactSourcePath(file, "generated exclusion"),
);
if (
new Set(generatedExclusions).size !== generatedExclusions.length ||
input.inventory.preExclusionTotal !== inventory.length + generatedExclusions.length ||
!Number.isSafeInteger(input.inventory.preExclusionTotal)
) {
throw new TypeError("production inventory provenance is inconsistent");
}
const policyGenerated = [...input.policy.generatedPaths].sort();
if (generatedExclusions.join("\n") !== policyGenerated.join("\n")) {
throw new TypeError("production inventory generated exclusions do not match policy");
}
if (!isRecord(input.summary) || !("total" in input.summary)) {
throw new TypeError("coverage summary must contain total metrics");
}
@@ -488,13 +587,25 @@ export function evaluateRiskCoverage(input: Readonly<{
const selected = new Map<string, CoverageMetrics>();
for (const [rawPath, rawMetrics] of Object.entries(input.summary)) {
if (rawPath === "total") continue;
const normalized = coveragePath(repositoryRoot, rawPath);
if (selected.has(normalized)) {
throw new TypeError(`duplicate coverage path: ${normalized}`);
}
const normalized = normalizeCoverageProducerPath({ repositoryRoot, rawPath });
if (selected.has(normalized)) throw new TypeError(`duplicate coverage path: ${normalized}`);
selected.set(normalized, parseCoverageMetrics(rawMetrics, `coverage ${normalized}`));
}
const inventorySet = new Set(inventory);
const generatedSet = new Set(generatedExclusions);
for (const selectedPath of selected.keys()) {
if (!inventorySet.has(selectedPath) && !generatedSet.has(selectedPath)) {
throw new TypeError(`unexpected coverage path outside inventory: ${selectedPath}`);
}
}
const inventoryMetrics = inventory.flatMap((file) => {
const metrics = selected.get(file);
return metrics ? [metrics] : [];
});
assertMatchingTotal(totalMetrics, aggregateCoverage([...selected.values()]));
const recomputedInventoryMetrics = aggregateCoverage(inventoryMetrics);
const failures: string[] = [];
const results: Array<{
scope: string;
@@ -506,30 +617,21 @@ export function evaluateRiskCoverage(input: Readonly<{
function evaluate(scope: string, actual: CoverageMetrics, minimum: Thresholds): void {
for (const metric of coverageMetrics) {
const threshold = minimum[metric];
if (threshold === undefined) continue;
const received = actual[metric].pct;
const passed = received >= threshold;
results.push({ scope, metric, threshold, received, passed });
if (!passed) {
failures.push(`${scope}.${metric} expected >= ${threshold}, received ${received}`);
}
if (!passed) failures.push(`${scope}.${metric} expected >= ${threshold}, received ${received}`);
}
}
evaluate("total", totalMetrics, input.policy.summary);
const inventorySet = new Set(inventory);
evaluate("total", recomputedInventoryMetrics, input.policy.summary);
if (inventory.length < input.policy.repositoryBaseline) {
failures.push(
`repository module baseline expected >= ${input.policy.repositoryBaseline}, received ${inventory.length}`,
);
}
const uncoveredModules = inventory.filter((file) => !selected.has(file)).sort();
const selectedModules = inventory.filter((file) => selected.has(file));
const ignoredCoveragePaths = [...selected.keys()]
.filter((file) => !inventorySet.has(file))
.sort();
failures.push(
...uncoveredModules.map((file) => `production module missing from coverage: ${file}`),
);
failures.push(...uncoveredModules.map((file) => `production module missing from coverage: ${file}`));
for (const modulePolicy of input.policy.criticalModules) {
if (!inventorySet.has(modulePolicy.path)) {
failures.push(`critical module is outside production inventory: ${modulePolicy.path}`);
@@ -543,36 +645,34 @@ export function evaluateRiskCoverage(input: Readonly<{
evaluate(modulePolicy.path, actual, modulePolicy.minimum);
}
const owners = new Set(input.policy.criticalModules.map((entry) => entry.path));
const waivers = new Set(input.policy.waivers.map((entry) => entry.path));
const highRisk = new Set(input.policy.highRiskPaths);
const criticalPaths = new Set(input.policy.criticalModules.map((entry) => entry.path));
const waiverPaths = new Set(input.policy.waivers.map((entry) => entry.path));
for (const highRiskPath of input.policy.highRiskPaths) {
if (!owners.has(highRiskPath) && !waivers.has(highRiskPath)) {
failures.push(`high-risk module has no owner or waiver: ${highRiskPath}`);
}
}
for (const changedFile of input.changedFiles ?? []) {
const normalized = exactSourcePath(changedFile, "changed file");
if (highRisk.has(normalized) && !owners.has(normalized) && !waivers.has(normalized)) {
failures.push(`changed high-risk module has no owner or waiver: ${normalized}`);
if (!inventorySet.has(highRiskPath)) {
failures.push(`high-risk module is outside production inventory: ${highRiskPath}`);
}
}
for (const waiver of input.policy.waivers) {
if (!inventorySet.has(waiver.path) || !highRisk.has(waiver.path)) {
failures.push(`coverage waiver is stale: ${waiver.path}`);
}
const expiry = Date.parse(waiver.expiresAt);
if (!Number.isFinite(expiry) || expiry <= (input.now ?? Date.now())) {
failures.push(`coverage waiver is expired: ${waiver.path}`);
}
if (!inventorySet.has(waiver.path)) failures.push(`coverage waiver is stale: ${waiver.path}`);
}
const ownedHighRiskPaths = input.policy.highRiskPaths
.filter((modulePath) => criticalPaths.has(modulePath))
.sort();
const waivedHighRiskPaths = input.policy.highRiskPaths
.filter((modulePath) => waiverPaths.has(modulePath))
.sort();
return Object.freeze({
status: failures.length === 0 ? "PASS" : "FAIL",
selectedTotal: selectedModules.length,
selectedTotal: inventory.length - uncoveredModules.length,
repositoryTotal: inventory.length,
preExclusionTotal: input.inventory.preExclusionTotal,
generatedExclusionCount: generatedExclusions.length,
generatedExclusions: Object.freeze([...generatedExclusions].sort()),
ownershipScope: "ALL_POLICY_HIGH_RISK",
ownedHighRiskPaths: Object.freeze(ownedHighRiskPaths),
waivedHighRiskPaths: Object.freeze(waivedHighRiskPaths),
uncoveredModules: Object.freeze(uncoveredModules),
ignoredCoveragePaths: Object.freeze(ignoredCoveragePaths),
results: Object.freeze(results),
failures: Object.freeze(failures),
});