899 lines
32 KiB
TypeScript
899 lines
32 KiB
TypeScript
import { constants, type Dirent, type Stats } from "node:fs";
|
|
import {
|
|
lstat,
|
|
open,
|
|
readdir,
|
|
realpath,
|
|
} from "node:fs/promises";
|
|
import path from "node:path";
|
|
import babelParser from "@babel/eslint-parser";
|
|
|
|
import { normalizeRepositoryRelativePath } from "./repository-file-inventory.ts";
|
|
|
|
const coverageMetrics = [
|
|
"lines",
|
|
"statements",
|
|
"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",
|
|
"src/adapters/http/request-builder.ts",
|
|
"src/adapters/http/bounded-body-reader.ts",
|
|
"src/adapters/http/bounded-json.ts",
|
|
"src/bootstrap/read-bounded-boot-json.ts",
|
|
"src/adapters/service-worker/service-worker-lifecycle.ts",
|
|
"src/adapters/query-cache/server-state-scope-runtime.ts",
|
|
"src/bootstrap/load-release-manifest.ts",
|
|
] as const);
|
|
|
|
type CoverageMetric = (typeof coverageMetrics)[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;
|
|
repositoryBaseline: number;
|
|
generatedPaths: readonly string[];
|
|
summary: Thresholds;
|
|
criticalModules: readonly Readonly<{
|
|
path: string;
|
|
owner: string;
|
|
minimum: Thresholds;
|
|
}>[];
|
|
highRiskPaths: readonly string[];
|
|
waivers: readonly Readonly<{
|
|
path: string;
|
|
owner: string;
|
|
reason: string;
|
|
expiresAt: string;
|
|
}>[];
|
|
}>;
|
|
|
|
export type ProductionModuleInventory = Readonly<{
|
|
files: readonly string[];
|
|
preExclusionTotal: number;
|
|
generatedExclusions: readonly string[];
|
|
executableModules: readonly string[];
|
|
nonExecutableModules: readonly string[];
|
|
}>;
|
|
|
|
export type RiskCoverageResult = Readonly<{
|
|
status: "PASS" | "FAIL";
|
|
selectedTotal: number;
|
|
repositoryTotal: number;
|
|
executableTotal: number;
|
|
instrumentedExecutableTotal: number;
|
|
nonExecutableTotal: number;
|
|
nonExecutableModules: readonly string[];
|
|
preExclusionTotal: number;
|
|
generatedExclusionCount: number;
|
|
generatedExclusions: readonly string[];
|
|
ownershipScope: "ALL_POLICY_HIGH_RISK";
|
|
ownedHighRiskPaths: readonly string[];
|
|
waivedHighRiskPaths: readonly string[];
|
|
uncoveredModules: readonly string[];
|
|
results: readonly Readonly<{
|
|
scope: string;
|
|
metric: CoverageMetric;
|
|
threshold: number;
|
|
received: number;
|
|
passed: boolean;
|
|
}>[];
|
|
failures: readonly string[];
|
|
}>;
|
|
|
|
type ReadableFileHandle = Readonly<{
|
|
stat(): Promise<Stats>;
|
|
readFile(encoding: "utf8"): Promise<string>;
|
|
close(): Promise<unknown>;
|
|
}>;
|
|
type InventoryOptions = Readonly<{
|
|
repositoryRoot?: string;
|
|
generatedPaths?: readonly string[];
|
|
readDirectory?: (target: string) => Promise<Dirent[]>;
|
|
lstatPath?: (target: string) => Promise<Stats>;
|
|
realpathPath?: (target: string) => Promise<string>;
|
|
openFile?: (target: string, flags: number) => Promise<ReadableFileHandle>;
|
|
}>;
|
|
|
|
class FileIdentityChangedError extends Error {}
|
|
class StableFileIdentityUnavailableError extends Error {}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
}
|
|
|
|
function assertExactKeys(
|
|
value: Record<string, unknown>,
|
|
allowed: readonly string[],
|
|
label: string,
|
|
): void {
|
|
const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
if (unknown.length > 0) {
|
|
throw new TypeError(`${label} has unknown fields: ${unknown.sort().join(", ")}`);
|
|
}
|
|
}
|
|
|
|
function exactSourcePath(value: unknown, label: string): string {
|
|
if (
|
|
typeof value !== "string" ||
|
|
["*", "?", "[", "]", "{", "}"].some((character) => value.includes(character))
|
|
) {
|
|
throw new TypeError(`${label} must be an exact repository-relative POSIX path`);
|
|
}
|
|
const normalized = normalizeRepositoryRelativePath(value, label);
|
|
if (!normalized.startsWith("src/") || !/\.tsx?$/u.test(normalized)) {
|
|
throw new TypeError(`${label} must identify a TypeScript module below src`);
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
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"}`,
|
|
);
|
|
}
|
|
const paths = value.map((entry) => exactSourcePath(entry, `${label} entry`));
|
|
if (new Set(paths).size !== paths.length) {
|
|
throw new TypeError(`${label} contains a duplicate path`);
|
|
}
|
|
return Object.freeze(paths);
|
|
}
|
|
|
|
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 (coverageMetrics.some((metric) => !(metric in value))) {
|
|
throw new TypeError(`${label} must define all coverage metrics`);
|
|
}
|
|
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 > 100
|
|
) {
|
|
throw new TypeError(
|
|
`${label}.${metric} minimum must be a finite number greater than 0 and at most 100`,
|
|
);
|
|
}
|
|
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");
|
|
assertExactKeys(
|
|
value,
|
|
[
|
|
"schemaVersion",
|
|
"repositoryBaseline",
|
|
"generatedPaths",
|
|
"summary",
|
|
"criticalModules",
|
|
"highRiskPaths",
|
|
"waivers",
|
|
],
|
|
"risk coverage policy",
|
|
);
|
|
if (value.schemaVersion !== 2) {
|
|
throw new TypeError("risk coverage policy schemaVersion must be 2");
|
|
}
|
|
if (
|
|
typeof value.repositoryBaseline !== "number" ||
|
|
!Number.isSafeInteger(value.repositoryBaseline) ||
|
|
value.repositoryBaseline <= 0
|
|
) {
|
|
throw new TypeError("repositoryBaseline must be a positive safe integer");
|
|
}
|
|
|
|
const generatedPaths = uniquePaths(value.generatedPaths, "generatedPaths");
|
|
const highRiskPaths = uniquePaths(value.highRiskPaths, "highRiskPaths", {
|
|
allowEmpty: false,
|
|
});
|
|
if (!Array.isArray(value.criticalModules) || value.criticalModules.length === 0) {
|
|
throw new TypeError("criticalModules must be a non-empty array");
|
|
}
|
|
const criticalModules = value.criticalModules.map((candidate, index) => {
|
|
if (!isRecord(candidate)) {
|
|
throw new TypeError(`criticalModules[${index}] must be an object`);
|
|
}
|
|
assertExactKeys(candidate, ["path", "owner", "minimum"], `criticalModules[${index}]`);
|
|
return Object.freeze({
|
|
path: exactSourcePath(candidate.path, `criticalModules[${index}].path`),
|
|
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");
|
|
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`);
|
|
assertExactKeys(candidate, ["path", "owner", "reason", "expiresAt"], `waivers[${index}]`);
|
|
const waiverPath = exactSourcePath(candidate.path, `waivers[${index}].path`);
|
|
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) || 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: 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"),
|
|
criticalModules: Object.freeze(criticalModules),
|
|
highRiskPaths,
|
|
waivers: Object.freeze(waivers),
|
|
});
|
|
}
|
|
|
|
export function parseRepositoryRiskCoveragePolicy(
|
|
value: unknown,
|
|
options: Readonly<{ now?: number }> = {},
|
|
): RiskCoveragePolicy {
|
|
const policy = parseRiskCoveragePolicy(value, options);
|
|
for (const requiredPath of REQUIRED_HIGH_RISK_PATHS) {
|
|
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;
|
|
}
|
|
|
|
function isWithin(root: string, target: string): boolean {
|
|
const relative = path.relative(root, target);
|
|
return (
|
|
relative === "" ||
|
|
(relative !== ".." &&
|
|
!relative.startsWith(`..${path.sep}`) &&
|
|
!path.isAbsolute(relative))
|
|
);
|
|
}
|
|
|
|
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 {
|
|
if (!hasStableIdentity(before) || !hasStableIdentity(after)) {
|
|
throw new StableFileIdentityUnavailableError(
|
|
"stable file identity unavailable",
|
|
);
|
|
}
|
|
return before.dev === after.dev && before.ino === after.ino;
|
|
}
|
|
|
|
export function isProductionModulePath(relativePath: string): boolean {
|
|
return (
|
|
/\.tsx?$/u.test(relativePath) &&
|
|
!/\.d\.ts$/u.test(relativePath) &&
|
|
!/\.stories\.tsx?$/u.test(relativePath)
|
|
);
|
|
}
|
|
|
|
type TypeScriptAstNode = Readonly<{
|
|
type?: unknown;
|
|
body?: unknown;
|
|
declaration?: unknown;
|
|
specifiers?: unknown;
|
|
declare?: unknown;
|
|
const?: unknown;
|
|
importKind?: unknown;
|
|
}>;
|
|
|
|
function statementIsExecutable(value: unknown): boolean {
|
|
if (!isRecord(value) || typeof value.type !== "string") {
|
|
throw new TypeError("TypeScript parser returned an invalid statement");
|
|
}
|
|
const statement = value as TypeScriptAstNode;
|
|
if (
|
|
[
|
|
"EmptyStatement",
|
|
"TSDeclareFunction",
|
|
"TSInterfaceDeclaration",
|
|
"TSNamespaceExportDeclaration",
|
|
"TSTypeAliasDeclaration",
|
|
].includes(statement.type as string)
|
|
) {
|
|
return false;
|
|
}
|
|
if (statement.type === "ImportDeclaration") {
|
|
return Array.isArray(statement.specifiers) && statement.specifiers.length === 0;
|
|
}
|
|
if (
|
|
statement.type === "ExportAllDeclaration" ||
|
|
statement.type === "TSExportAssignment"
|
|
) {
|
|
return statement.type === "TSExportAssignment";
|
|
}
|
|
if (
|
|
statement.type === "ExportNamedDeclaration" ||
|
|
statement.type === "ExportDefaultDeclaration"
|
|
) {
|
|
return statement.declaration !== null && statement.declaration !== undefined
|
|
? statementIsExecutable(statement.declaration)
|
|
: false;
|
|
}
|
|
if (
|
|
statement.type === "FunctionDeclaration" ||
|
|
statement.type === "VariableDeclaration" ||
|
|
statement.type === "ClassDeclaration" ||
|
|
statement.type === "TSModuleDeclaration"
|
|
) {
|
|
return statement.declare !== true &&
|
|
(statement.type !== "FunctionDeclaration" || statement.body !== null);
|
|
}
|
|
if (statement.type === "TSEnumDeclaration") {
|
|
return statement.declare !== true && statement.const !== true;
|
|
}
|
|
if (statement.type === "TSImportEqualsDeclaration") {
|
|
return statement.importKind !== "type" && statement.declare !== true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function hasExecutableTypeScriptStatements(
|
|
source: string,
|
|
relativePath: string,
|
|
): boolean {
|
|
let parsed: unknown;
|
|
try {
|
|
parsed = babelParser.parse(source, {
|
|
sourceType: "module",
|
|
requireConfigFile: false,
|
|
filePath: relativePath,
|
|
babelOptions: {
|
|
parserOpts: {
|
|
plugins: [
|
|
"typescript",
|
|
...(relativePath.endsWith(".tsx") ? ["jsx"] : []),
|
|
],
|
|
},
|
|
},
|
|
});
|
|
} catch (error) {
|
|
throw new TypeError(`production module has invalid TypeScript syntax: ${relativePath}`, {
|
|
cause: error,
|
|
});
|
|
}
|
|
if (!isRecord(parsed) || !Array.isArray(parsed.body)) {
|
|
throw new TypeError("TypeScript parser returned an invalid program");
|
|
}
|
|
return parsed.body.some(statementIsExecutable);
|
|
}
|
|
|
|
export async function buildProductionModuleInventory(
|
|
options: InventoryOptions = {},
|
|
): Promise<ProductionModuleInventory> {
|
|
const repositoryRoot = path.resolve(options.repositoryRoot ?? process.cwd());
|
|
const sourceRoot = path.join(repositoryRoot, "src");
|
|
const readDirectory = options.readDirectory ??
|
|
((target: string) => readdir(target, { withFileTypes: true }));
|
|
const lstatPath = options.lstatPath ?? lstat;
|
|
const realpathPath = options.realpathPath ?? realpath;
|
|
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);
|
|
const sourceMetadata = await lstatPath(sourceRoot);
|
|
if (!sourceMetadata.isDirectory() || sourceMetadata.isSymbolicLink()) {
|
|
throw new TypeError("production source root is not a regular directory: src");
|
|
}
|
|
const sourceRealpath = await realpathPath(sourceRoot);
|
|
if (!isWithin(repositoryRealpath, sourceRealpath)) {
|
|
throw new TypeError("production source root is outside repository");
|
|
}
|
|
|
|
const allModules: string[] = [];
|
|
const executableModules: string[] = [];
|
|
const nonExecutableModules: string[] = [];
|
|
async function visit(relativeDirectory: string): Promise<void> {
|
|
const absoluteDirectory = path.join(repositoryRoot, relativeDirectory);
|
|
const entries = await readDirectory(absoluteDirectory);
|
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
const relativeTarget = normalizeRepositoryRelativePath(
|
|
`${relativeDirectory}/${entry.name}`,
|
|
"production inventory path",
|
|
);
|
|
const absoluteTarget = path.join(repositoryRoot, relativeTarget);
|
|
const metadata = await lstatPath(absoluteTarget);
|
|
if (metadata.isSymbolicLink() || entry.isSymbolicLink()) {
|
|
throw new TypeError(`production inventory path is a symlink: ${relativeTarget}`);
|
|
}
|
|
if (metadata.isDirectory()) {
|
|
await visit(relativeTarget);
|
|
continue;
|
|
}
|
|
if (!isProductionModulePath(relativeTarget)) continue;
|
|
if (!metadata.isFile()) {
|
|
throw new TypeError(`production inventory path is not a regular file: ${relativeTarget}`);
|
|
}
|
|
const resolvedTarget = await realpathPath(absoluteTarget);
|
|
if (!isWithin(repositoryRealpath, resolvedTarget)) {
|
|
throw new TypeError(`production inventory path is outside repository: ${relativeTarget}`);
|
|
}
|
|
let handle: ReadableFileHandle | undefined;
|
|
let source: string;
|
|
try {
|
|
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");
|
|
}
|
|
if (!sameFileIdentity(metadata, openedMetadata)) {
|
|
throw new FileIdentityChangedError("opened file identity changed");
|
|
}
|
|
source = await handle.readFile("utf8");
|
|
} catch (error) {
|
|
if (error instanceof StableFileIdentityUnavailableError) {
|
|
throw new Error(
|
|
`production inventory stable file identity unavailable: ${relativeTarget}`,
|
|
{ cause: 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}`, {
|
|
cause: error,
|
|
});
|
|
} finally {
|
|
await handle?.close();
|
|
}
|
|
allModules.push(relativeTarget);
|
|
if (hasExecutableTypeScriptStatements(source, relativeTarget)) {
|
|
executableModules.push(relativeTarget);
|
|
} else {
|
|
nonExecutableModules.push(relativeTarget);
|
|
}
|
|
}
|
|
}
|
|
await visit("src");
|
|
for (const generatedPath of generatedPaths) {
|
|
if (!allModules.includes(generatedPath)) {
|
|
throw new TypeError(`generated path is stale or not a production module: ${generatedPath}`);
|
|
}
|
|
}
|
|
const inventory = allModules.filter((file) => !generated.has(file)).sort();
|
|
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({
|
|
files: Object.freeze(inventory),
|
|
preExclusionTotal: allModules.length,
|
|
generatedExclusions: Object.freeze([...generatedPaths].sort()),
|
|
executableModules: Object.freeze(
|
|
executableModules.filter((file) => !generated.has(file)).sort(),
|
|
),
|
|
nonExecutableModules: Object.freeze(
|
|
nonExecutableModules.filter((file) => !generated.has(file)).sort(),
|
|
),
|
|
});
|
|
}
|
|
|
|
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 (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.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),
|
|
);
|
|
if (unknownMetrics.length > 0) {
|
|
throw new TypeError(
|
|
`${label} has unknown coverage metric keys: ${unknownMetrics.sort().join(", ")}`,
|
|
);
|
|
}
|
|
if (value.branchesTrue !== undefined) {
|
|
parseCoverageCounter(value.branchesTrue, `${label}.branchesTrue`);
|
|
}
|
|
const parsed = {} as Record<CoverageMetric, CoverageCounter>;
|
|
for (const metric of coverageMetrics) {
|
|
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: ProductionModuleInventory;
|
|
policy: RiskCoveragePolicy;
|
|
summary: unknown;
|
|
}>): RiskCoverageResult {
|
|
const repositoryRoot = path.resolve(input.repositoryRoot ?? process.cwd());
|
|
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 executableModules = input.inventory.executableModules.map((file) =>
|
|
exactSourcePath(file, "executable inventory path"),
|
|
);
|
|
const nonExecutableModules = input.inventory.nonExecutableModules.map((file) =>
|
|
exactSourcePath(file, "non-executable inventory path"),
|
|
);
|
|
const executableSet = new Set(executableModules);
|
|
const nonExecutableSet = new Set(nonExecutableModules);
|
|
const partition = [...executableModules, ...nonExecutableModules].sort();
|
|
if (
|
|
executableSet.size !== executableModules.length ||
|
|
nonExecutableSet.size !== nonExecutableModules.length ||
|
|
executableModules.some((file) => nonExecutableSet.has(file)) ||
|
|
partition.join("\n") !== [...inventory].sort().join("\n")
|
|
) {
|
|
throw new TypeError("production inventory executable provenance is inconsistent");
|
|
}
|
|
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");
|
|
}
|
|
const totalMetrics = parseCoverageMetrics(input.summary.total, "coverage total");
|
|
const selected = new Map<string, CoverageMetrics>();
|
|
for (const [rawPath, rawMetrics] of Object.entries(input.summary)) {
|
|
if (rawPath === "total") continue;
|
|
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] : [];
|
|
});
|
|
const zeroCoverageModules = inventory.filter((file) => {
|
|
const metrics = selected.get(file);
|
|
return (
|
|
metrics !== undefined &&
|
|
coverageMetrics.every((metric) => metrics[metric].total === 0)
|
|
);
|
|
});
|
|
const zeroCoverageSet = new Set(zeroCoverageModules);
|
|
const zeroExecutableModules = zeroCoverageModules.filter((file) =>
|
|
executableSet.has(file),
|
|
);
|
|
const nonExecutableWithCounters = nonExecutableModules.filter((file) => {
|
|
const metrics = selected.get(file);
|
|
return metrics !== undefined && !zeroCoverageSet.has(file);
|
|
});
|
|
const instrumentedExecutableTotal = executableModules.filter((file) => {
|
|
const metrics = selected.get(file);
|
|
return metrics !== undefined && !zeroCoverageSet.has(file);
|
|
}).length;
|
|
assertMatchingTotal(totalMetrics, aggregateCoverage([...selected.values()]));
|
|
const recomputedInventoryMetrics = aggregateCoverage(inventoryMetrics);
|
|
|
|
const failures: string[] = [];
|
|
const results: Array<{
|
|
scope: string;
|
|
metric: CoverageMetric;
|
|
threshold: number;
|
|
received: number;
|
|
passed: boolean;
|
|
}> = [];
|
|
function evaluate(scope: string, actual: CoverageMetrics, minimum: Thresholds): void {
|
|
for (const metric of coverageMetrics) {
|
|
const threshold = minimum[metric];
|
|
const received = actual[metric].pct;
|
|
const hasCoverageTotal = actual[metric].total > 0;
|
|
const passed = hasCoverageTotal && received >= threshold;
|
|
results.push({ scope, metric, threshold, received, passed });
|
|
if (!hasCoverageTotal) {
|
|
failures.push(`${scope}.${metric} coverage total must be greater than 0`);
|
|
} else if (!passed) {
|
|
failures.push(`${scope}.${metric} expected >= ${threshold}, received ${received}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
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) ||
|
|
(executableSet.has(file) && zeroCoverageSet.has(file)),
|
|
)
|
|
.sort();
|
|
failures.push(
|
|
...inventory
|
|
.filter((file) => !selected.has(file))
|
|
.map((file) => `production module missing from coverage: ${file}`),
|
|
...zeroExecutableModules.map(
|
|
(file) => `production module has zero coverage totals: ${file}`,
|
|
),
|
|
...nonExecutableWithCounters.map(
|
|
(file) => `non-executable module has coverage counters: ${file}`,
|
|
),
|
|
);
|
|
for (const modulePolicy of input.policy.criticalModules) {
|
|
if (!inventorySet.has(modulePolicy.path)) {
|
|
failures.push(`critical module is outside production inventory: ${modulePolicy.path}`);
|
|
continue;
|
|
}
|
|
if (nonExecutableSet.has(modulePolicy.path)) {
|
|
failures.push(`critical module cannot be non-executable: ${modulePolicy.path}`);
|
|
}
|
|
const actual = selected.get(modulePolicy.path);
|
|
if (!actual) {
|
|
failures.push(`critical module missing from coverage: ${modulePolicy.path}`);
|
|
continue;
|
|
}
|
|
evaluate(modulePolicy.path, actual, modulePolicy.minimum);
|
|
}
|
|
|
|
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 (!inventorySet.has(highRiskPath)) {
|
|
failures.push(`high-risk module is outside production inventory: ${highRiskPath}`);
|
|
}
|
|
if (nonExecutableSet.has(highRiskPath)) {
|
|
failures.push(`high-risk module cannot be non-executable: ${highRiskPath}`);
|
|
}
|
|
}
|
|
for (const waiver of input.policy.waivers) {
|
|
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: inventory.length - uncoveredModules.length,
|
|
repositoryTotal: inventory.length,
|
|
executableTotal: executableModules.length,
|
|
instrumentedExecutableTotal,
|
|
nonExecutableTotal: nonExecutableModules.length,
|
|
nonExecutableModules: Object.freeze([...nonExecutableModules].sort()),
|
|
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),
|
|
results: Object.freeze(results),
|
|
failures: Object.freeze(failures),
|
|
});
|
|
}
|