fix: harden repository coverage evidence
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
|
import {
|
||||||
|
readRiskCoverageInput,
|
||||||
|
writeRiskCoverageArtifactAtomic,
|
||||||
|
} from "./lib/risk-coverage-files.ts";
|
||||||
import {
|
import {
|
||||||
buildProductionModuleInventory,
|
buildProductionModuleInventory,
|
||||||
evaluateRiskCoverage,
|
evaluateRiskCoverage,
|
||||||
@@ -18,71 +21,56 @@ function requiredArgument(name: string, fallback: string): string {
|
|||||||
return value;
|
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(
|
const repositoryRoot = path.resolve(
|
||||||
requiredArgument("--repository-root", process.cwd()),
|
requiredArgument("--repository-root", process.cwd()),
|
||||||
);
|
);
|
||||||
const policyPath = requiredArgument(
|
const policyInput = await readRiskCoverageInput({
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: requiredArgument(
|
||||||
"--policy",
|
"--policy",
|
||||||
"config/testing/risk-coverage.json",
|
"config/testing/risk-coverage.json",
|
||||||
);
|
),
|
||||||
const summaryPath = requiredArgument(
|
label: "policy",
|
||||||
|
});
|
||||||
|
const summaryInput = await readRiskCoverageInput({
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: requiredArgument(
|
||||||
"--summary",
|
"--summary",
|
||||||
"artifacts/tests/coverage/coverage-summary.json",
|
"artifacts/tests/coverage/coverage-summary.json",
|
||||||
);
|
),
|
||||||
|
label: "summary",
|
||||||
|
});
|
||||||
const artifactPath = requiredArgument(
|
const artifactPath = requiredArgument(
|
||||||
"--artifact",
|
"--artifact",
|
||||||
"artifacts/quality/risk-coverage.json",
|
"artifacts/quality/risk-coverage.json",
|
||||||
);
|
);
|
||||||
const changedFilesPath = argumentValue("--changed-files");
|
|
||||||
const policy = parseRepositoryRiskCoveragePolicy(
|
const policy = parseRepositoryRiskCoveragePolicy(
|
||||||
JSON.parse(await readFile(path.resolve(repositoryRoot, policyPath), "utf8")) as unknown,
|
JSON.parse(policyInput.text) as unknown,
|
||||||
);
|
);
|
||||||
const inventory = await buildProductionModuleInventory({
|
const inventory = await buildProductionModuleInventory({
|
||||||
repositoryRoot,
|
repositoryRoot,
|
||||||
generatedPaths: policy.generatedPaths,
|
generatedPaths: policy.generatedPaths,
|
||||||
});
|
});
|
||||||
const changedFiles = changedFilesPath
|
|
||||||
? parseChangedFiles(
|
|
||||||
JSON.parse(
|
|
||||||
await readFile(path.resolve(repositoryRoot, changedFilesPath), "utf8"),
|
|
||||||
) as unknown,
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
const result = evaluateRiskCoverage({
|
const result = evaluateRiskCoverage({
|
||||||
repositoryRoot,
|
repositoryRoot,
|
||||||
inventory,
|
inventory,
|
||||||
policy,
|
policy,
|
||||||
summary: JSON.parse(
|
summary: JSON.parse(summaryInput.text) as unknown,
|
||||||
await readFile(path.resolve(repositoryRoot, summaryPath), "utf8"),
|
|
||||||
) as unknown,
|
|
||||||
changedFiles,
|
|
||||||
});
|
});
|
||||||
const artifact = {
|
await writeRiskCoverageArtifactAtomic({
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: artifactPath,
|
||||||
|
inputPaths: [policyInput.relativePath, summaryInput.relativePath],
|
||||||
|
value: {
|
||||||
schemaVersion: 2,
|
schemaVersion: 2,
|
||||||
policy: policyPath,
|
policy: policyInput.relativePath,
|
||||||
summary: summaryPath,
|
summary: summaryInput.relativePath,
|
||||||
changedFiles: changedFilesPath ?? null,
|
|
||||||
...result,
|
...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) {
|
if (result.failures.length > 0) {
|
||||||
process.stderr.write(
|
process.stderr.write(`Risk coverage failed:\n- ${result.failures.join("\n- ")}\n`);
|
||||||
`Risk coverage failed:\n- ${result.failures.join("\n- ")}\n`,
|
|
||||||
);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
process.stdout.write(
|
process.stdout.write(
|
||||||
|
|||||||
@@ -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
@@ -1,5 +1,11 @@
|
|||||||
import { open, readdir, realpath, lstat } from "node:fs/promises";
|
import { constants, type Dirent, type Stats } from "node:fs";
|
||||||
import type { Dirent, Stats } from "node:fs";
|
import {
|
||||||
|
lstat,
|
||||||
|
open,
|
||||||
|
readdir,
|
||||||
|
realpath,
|
||||||
|
type FileHandle,
|
||||||
|
} from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { normalizeRepositoryRelativePath } from "./repository-file-inventory.ts";
|
import { normalizeRepositoryRelativePath } from "./repository-file-inventory.ts";
|
||||||
@@ -10,6 +16,8 @@ const coverageMetrics = [
|
|||||||
"functions",
|
"functions",
|
||||||
"branches",
|
"branches",
|
||||||
] as const;
|
] 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([
|
export const REQUIRED_HIGH_RISK_PATHS = Object.freeze([
|
||||||
"src/adapters/http/http-execution-v3.ts",
|
"src/adapters/http/http-execution-v3.ts",
|
||||||
@@ -23,10 +31,14 @@ export const REQUIRED_HIGH_RISK_PATHS = Object.freeze([
|
|||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
type CoverageMetric = (typeof coverageMetrics)[number];
|
type CoverageMetric = (typeof coverageMetrics)[number];
|
||||||
type Thresholds = Readonly<Partial<Record<CoverageMetric, number>>>;
|
type Thresholds = Readonly<Record<CoverageMetric, number>>;
|
||||||
type CoverageMetrics = Readonly<
|
type CoverageCounter = Readonly<{
|
||||||
Record<CoverageMetric, Readonly<{ pct: number }>>
|
total: number;
|
||||||
>;
|
covered: number;
|
||||||
|
skipped: number;
|
||||||
|
pct: number;
|
||||||
|
}>;
|
||||||
|
type CoverageMetrics = Readonly<Record<CoverageMetric, CoverageCounter>>;
|
||||||
|
|
||||||
export type RiskCoveragePolicy = Readonly<{
|
export type RiskCoveragePolicy = Readonly<{
|
||||||
schemaVersion: 2;
|
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<{
|
export type RiskCoverageResult = Readonly<{
|
||||||
status: "PASS" | "FAIL";
|
status: "PASS" | "FAIL";
|
||||||
selectedTotal: number;
|
selectedTotal: number;
|
||||||
repositoryTotal: number;
|
repositoryTotal: number;
|
||||||
|
preExclusionTotal: number;
|
||||||
|
generatedExclusionCount: number;
|
||||||
|
generatedExclusions: readonly string[];
|
||||||
|
ownershipScope: "ALL_POLICY_HIGH_RISK";
|
||||||
|
ownedHighRiskPaths: readonly string[];
|
||||||
|
waivedHighRiskPaths: readonly string[];
|
||||||
uncoveredModules: readonly string[];
|
uncoveredModules: readonly string[];
|
||||||
ignoredCoveragePaths: readonly string[];
|
|
||||||
results: readonly Readonly<{
|
results: readonly Readonly<{
|
||||||
scope: string;
|
scope: string;
|
||||||
metric: CoverageMetric;
|
metric: CoverageMetric;
|
||||||
@@ -63,13 +86,14 @@ export type RiskCoverageResult = Readonly<{
|
|||||||
failures: readonly string[];
|
failures: readonly string[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
type ReadableFileHandle = Pick<FileHandle, "close" | "stat">;
|
||||||
type InventoryOptions = Readonly<{
|
type InventoryOptions = Readonly<{
|
||||||
repositoryRoot?: string;
|
repositoryRoot?: string;
|
||||||
generatedPaths?: readonly string[];
|
generatedPaths?: readonly string[];
|
||||||
readDirectory?: (target: string) => Promise<Dirent[]>;
|
readDirectory?: (target: string) => Promise<Dirent[]>;
|
||||||
lstatPath?: (target: string) => Promise<Stats>;
|
lstatPath?: (target: string) => Promise<Stats>;
|
||||||
realpathPath?: (target: string) => Promise<string>;
|
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> {
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
@@ -90,9 +114,7 @@ function assertExactKeys(
|
|||||||
function exactSourcePath(value: unknown, label: string): string {
|
function exactSourcePath(value: unknown, label: string): string {
|
||||||
if (
|
if (
|
||||||
typeof value !== "string" ||
|
typeof value !== "string" ||
|
||||||
["*", "?", "[", "]", "{", "}"].some((character) =>
|
["*", "?", "[", "]", "{", "}"].some((character) => value.includes(character))
|
||||||
value.includes(character),
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
throw new TypeError(`${label} must be an exact repository-relative POSIX path`);
|
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;
|
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(
|
function uniquePaths(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
label: string,
|
label: string,
|
||||||
options: Readonly<{ allowEmpty: boolean }> = { allowEmpty: true },
|
options: Readonly<{ allowEmpty: boolean }> = { allowEmpty: true },
|
||||||
): readonly string[] {
|
): readonly string[] {
|
||||||
if (!Array.isArray(value) || (!options.allowEmpty && value.length === 0)) {
|
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`));
|
const paths = value.map((entry) => exactSourcePath(entry, `${label} entry`));
|
||||||
if (new Set(paths).size !== paths.length) {
|
if (new Set(paths).size !== paths.length) {
|
||||||
@@ -125,43 +142,60 @@ function uniquePaths(
|
|||||||
return Object.freeze(paths);
|
return Object.freeze(paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
function thresholds(
|
function teamId(value: unknown, label: string): string {
|
||||||
value: unknown,
|
if (typeof value !== "string" || !teamIdPattern.test(value)) {
|
||||||
label: string,
|
throw new TypeError(`${label} must be a canonical team id`);
|
||||||
options: Readonly<{ requireAll: boolean }>,
|
|
||||||
): Thresholds {
|
|
||||||
if (!isRecord(value)) {
|
|
||||||
throw new TypeError(`${label} must be an object`);
|
|
||||||
}
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function thresholds(value: unknown, label: string): Thresholds {
|
||||||
|
if (!isRecord(value)) throw new TypeError(`${label} must be an object`);
|
||||||
assertExactKeys(value, coverageMetrics, label);
|
assertExactKeys(value, coverageMetrics, label);
|
||||||
if (
|
if (coverageMetrics.some((metric) => !(metric in value))) {
|
||||||
Object.keys(value).length === 0 ||
|
throw new TypeError(`${label} must define all coverage metrics`);
|
||||||
(options.requireAll && coverageMetrics.some((metric) => !(metric in value)))
|
|
||||||
) {
|
|
||||||
throw new TypeError(`${label} must define ${options.requireAll ? "all " : "at least one "}coverage metric`);
|
|
||||||
}
|
}
|
||||||
const parsed: Partial<Record<CoverageMetric, number>> = {};
|
const parsed = {} as Record<CoverageMetric, number>;
|
||||||
for (const [metric, threshold] of Object.entries(value)) {
|
for (const metric of coverageMetrics) {
|
||||||
|
const threshold = value[metric];
|
||||||
if (
|
if (
|
||||||
typeof threshold !== "number" ||
|
typeof threshold !== "number" ||
|
||||||
!Number.isFinite(threshold) ||
|
!Number.isFinite(threshold) ||
|
||||||
threshold < 0 ||
|
threshold <= 0 ||
|
||||||
threshold > 100
|
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);
|
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(
|
export function parseRiskCoveragePolicy(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
options: Readonly<{ now?: number }> = {},
|
options: Readonly<{ now?: number }> = {},
|
||||||
): RiskCoveragePolicy {
|
): RiskCoveragePolicy {
|
||||||
if (!isRecord(value)) {
|
if (!isRecord(value)) throw new TypeError("risk coverage policy must be an object");
|
||||||
throw new TypeError("risk coverage policy must be an object");
|
|
||||||
}
|
|
||||||
assertExactKeys(
|
assertExactKeys(
|
||||||
value,
|
value,
|
||||||
[
|
[
|
||||||
@@ -180,11 +214,12 @@ export function parseRiskCoveragePolicy(
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
typeof value.repositoryBaseline !== "number" ||
|
typeof value.repositoryBaseline !== "number" ||
|
||||||
!Number.isInteger(value.repositoryBaseline) ||
|
!Number.isSafeInteger(value.repositoryBaseline) ||
|
||||||
value.repositoryBaseline <= 0
|
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 generatedPaths = uniquePaths(value.generatedPaths, "generatedPaths");
|
||||||
const highRiskPaths = uniquePaths(value.highRiskPaths, "highRiskPaths", {
|
const highRiskPaths = uniquePaths(value.highRiskPaths, "highRiskPaths", {
|
||||||
allowEmpty: false,
|
allowEmpty: false,
|
||||||
@@ -199,48 +234,65 @@ export function parseRiskCoveragePolicy(
|
|||||||
assertExactKeys(candidate, ["path", "owner", "minimum"], `criticalModules[${index}]`);
|
assertExactKeys(candidate, ["path", "owner", "minimum"], `criticalModules[${index}]`);
|
||||||
return Object.freeze({
|
return Object.freeze({
|
||||||
path: exactSourcePath(candidate.path, `criticalModules[${index}].path`),
|
path: exactSourcePath(candidate.path, `criticalModules[${index}].path`),
|
||||||
owner: nonBlank(candidate.owner, `criticalModules[${index}].owner`),
|
owner: teamId(candidate.owner, `criticalModules[${index}].owner`),
|
||||||
minimum: thresholds(candidate.minimum, `criticalModules[${index}].minimum`, {
|
minimum: thresholds(candidate.minimum, `criticalModules[${index}].minimum`),
|
||||||
requireAll: false,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (new Set(criticalModules.map((entry) => entry.path)).size !== criticalModules.length) {
|
if (new Set(criticalModules.map((entry) => entry.path)).size !== criticalModules.length) {
|
||||||
throw new TypeError("criticalModules contains a duplicate path");
|
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();
|
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) => {
|
const waivers = value.waivers.map((candidate, index) => {
|
||||||
if (!isRecord(candidate)) {
|
if (!isRecord(candidate)) throw new TypeError(`waivers[${index}] must be an object`);
|
||||||
throw new TypeError(`waivers[${index}] must be an object`);
|
|
||||||
}
|
|
||||||
assertExactKeys(candidate, ["path", "owner", "reason", "expiresAt"], `waivers[${index}]`);
|
assertExactKeys(candidate, ["path", "owner", "reason", "expiresAt"], `waivers[${index}]`);
|
||||||
const waiverPath = exactSourcePath(candidate.path, `waivers[${index}].path`);
|
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);
|
const expiry = Date.parse(expiresAt);
|
||||||
if (!Number.isFinite(expiry) || expiry <= currentTime) {
|
if (!Number.isFinite(expiry) || new Date(expiry).toISOString() !== expiresAt) {
|
||||||
throw new TypeError(`waivers[${index}] is expired or has an invalid expiry`);
|
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)) {
|
if (!highRiskPaths.includes(waiverPath)) {
|
||||||
throw new TypeError(`waivers[${index}] is stale because ${waiverPath} is not high-risk`);
|
throw new TypeError(`waivers[${index}] is stale because ${waiverPath} is not high-risk`);
|
||||||
}
|
}
|
||||||
return Object.freeze({
|
return Object.freeze({
|
||||||
path: waiverPath,
|
path: waiverPath,
|
||||||
owner: nonBlank(candidate.owner, `waivers[${index}].owner`),
|
owner: teamId(candidate.owner, `waivers[${index}].owner`),
|
||||||
reason: nonBlank(candidate.reason, `waivers[${index}].reason`),
|
reason: waiverReason(candidate.reason, `waivers[${index}].reason`),
|
||||||
expiresAt,
|
expiresAt,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (new Set(waivers.map((entry) => entry.path)).size !== waivers.length) {
|
if (new Set(waivers.map((entry) => entry.path)).size !== waivers.length) {
|
||||||
throw new TypeError("waivers contains a duplicate path");
|
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({
|
return Object.freeze({
|
||||||
schemaVersion: 2,
|
schemaVersion: 2,
|
||||||
repositoryBaseline: value.repositoryBaseline,
|
repositoryBaseline: value.repositoryBaseline,
|
||||||
generatedPaths,
|
generatedPaths,
|
||||||
summary: thresholds(value.summary, "summary", { requireAll: true }),
|
summary: thresholds(value.summary, "summary"),
|
||||||
criticalModules: Object.freeze(criticalModules),
|
criticalModules: Object.freeze(criticalModules),
|
||||||
highRiskPaths,
|
highRiskPaths,
|
||||||
waivers: Object.freeze(waivers),
|
waivers: Object.freeze(waivers),
|
||||||
@@ -256,15 +308,15 @@ export function parseRepositoryRiskCoveragePolicy(
|
|||||||
if (!policy.highRiskPaths.includes(requiredPath)) {
|
if (!policy.highRiskPaths.includes(requiredPath)) {
|
||||||
throw new TypeError(`required high-risk path is missing: ${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;
|
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 {
|
function isWithin(root: string, target: string): boolean {
|
||||||
const relative = path.relative(root, target);
|
const relative = path.relative(root, target);
|
||||||
return (
|
return (
|
||||||
@@ -275,7 +327,7 @@ function isWithin(root: string, target: string): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isProductionModule(relativePath: string): boolean {
|
export function isProductionModulePath(relativePath: string): boolean {
|
||||||
return (
|
return (
|
||||||
/\.tsx?$/u.test(relativePath) &&
|
/\.tsx?$/u.test(relativePath) &&
|
||||||
!/\.d\.ts$/u.test(relativePath) &&
|
!/\.d\.ts$/u.test(relativePath) &&
|
||||||
@@ -285,13 +337,15 @@ function isProductionModule(relativePath: string): boolean {
|
|||||||
|
|
||||||
export async function buildProductionModuleInventory(
|
export async function buildProductionModuleInventory(
|
||||||
options: InventoryOptions = {},
|
options: InventoryOptions = {},
|
||||||
): Promise<readonly string[]> {
|
): Promise<ProductionModuleInventory> {
|
||||||
const repositoryRoot = path.resolve(options.repositoryRoot ?? process.cwd());
|
const repositoryRoot = path.resolve(options.repositoryRoot ?? process.cwd());
|
||||||
const sourceRoot = path.join(repositoryRoot, "src");
|
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 lstatPath = options.lstatPath ?? lstat;
|
||||||
const realpathPath = options.realpathPath ?? realpath;
|
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 generatedPaths = uniquePaths(options.generatedPaths ?? [], "generatedPaths");
|
||||||
const generated = new Set(generatedPaths);
|
const generated = new Set(generatedPaths);
|
||||||
const repositoryRealpath = await realpathPath(repositoryRoot);
|
const repositoryRealpath = await realpathPath(repositoryRoot);
|
||||||
@@ -322,7 +376,7 @@ export async function buildProductionModuleInventory(
|
|||||||
await visit(relativeTarget);
|
await visit(relativeTarget);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!isProductionModule(relativeTarget)) continue;
|
if (!isProductionModulePath(relativeTarget)) continue;
|
||||||
if (!metadata.isFile()) {
|
if (!metadata.isFile()) {
|
||||||
throw new TypeError(`production inventory path is not a regular file: ${relativeTarget}`);
|
throw new TypeError(`production inventory path is not a regular file: ${relativeTarget}`);
|
||||||
}
|
}
|
||||||
@@ -330,12 +384,22 @@ export async function buildProductionModuleInventory(
|
|||||||
if (!isWithin(repositoryRealpath, resolvedTarget)) {
|
if (!isWithin(repositoryRealpath, resolvedTarget)) {
|
||||||
throw new TypeError(`production inventory path is outside repository: ${relativeTarget}`);
|
throw new TypeError(`production inventory path is outside repository: ${relativeTarget}`);
|
||||||
}
|
}
|
||||||
|
let handle: ReadableFileHandle | undefined;
|
||||||
try {
|
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) {
|
} catch (error) {
|
||||||
throw new Error(`production inventory file is unreadable: ${relativeTarget}`, {
|
throw new Error(`production inventory file is unreadable: ${relativeTarget}`, {
|
||||||
cause: error,
|
cause: error,
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
await handle?.close();
|
||||||
}
|
}
|
||||||
allModules.push(relativeTarget);
|
allModules.push(relativeTarget);
|
||||||
}
|
}
|
||||||
@@ -347,35 +411,89 @@ export async function buildProductionModuleInventory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const inventory = allModules.filter((file) => !generated.has(file)).sort();
|
const inventory = allModules.filter((file) => !generated.has(file)).sort();
|
||||||
if (inventory.length === 0) {
|
if (inventory.length === 0) throw new Error("production module inventory is empty");
|
||||||
throw new Error("production module inventory is empty");
|
|
||||||
}
|
|
||||||
if (new Set(inventory).size !== inventory.length) {
|
if (new Set(inventory).size !== inventory.length) {
|
||||||
throw new TypeError("production module inventory contains a duplicate path");
|
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 {
|
export function normalizeCoverageProducerPath(input: Readonly<{
|
||||||
if (rawPath.includes("\\") || rawPath.includes("\0")) {
|
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");
|
throw new TypeError("coverage path must use POSIX separators");
|
||||||
}
|
}
|
||||||
if (path.isAbsolute(rawPath)) {
|
if (platform === "win32" && rawPath.includes("\\") && !pathApi.isAbsolute(rawPath)) {
|
||||||
const relative = path.relative(repositoryRoot, rawPath).split(path.sep).join("/");
|
throw new TypeError("relative coverage path must use POSIX separators");
|
||||||
if (!relative || relative === ".." || relative.startsWith("../")) {
|
}
|
||||||
|
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}`);
|
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");
|
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 {
|
function parseCoverageMetrics(value: unknown, label: string): CoverageMetrics {
|
||||||
if (!isRecord(value)) throw new TypeError(`${label} must be an object`);
|
if (!isRecord(value)) throw new TypeError(`${label} must be an object`);
|
||||||
const unknownMetrics = Object.keys(value).filter(
|
const unknownMetrics = Object.keys(value).filter(
|
||||||
(metric) =>
|
(metric) => metric !== "branchesTrue" && !coverageMetrics.includes(metric as CoverageMetric),
|
||||||
metric !== "branchesTrue" &&
|
|
||||||
!coverageMetrics.includes(metric as CoverageMetric),
|
|
||||||
);
|
);
|
||||||
if (unknownMetrics.length > 0) {
|
if (unknownMetrics.length > 0) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
@@ -383,104 +501,85 @@ function parseCoverageMetrics(value: unknown, label: string): CoverageMetrics {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (value.branchesTrue !== undefined) {
|
if (value.branchesTrue !== undefined) {
|
||||||
if (!isRecord(value.branchesTrue)) {
|
parseCoverageCounter(value.branchesTrue, `${label}.branchesTrue`);
|
||||||
throw new TypeError(`${label}.branchesTrue must be an object`);
|
|
||||||
}
|
}
|
||||||
assertExactKeys(
|
const parsed = {} as Record<CoverageMetric, CoverageCounter>;
|
||||||
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`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const parsed = {} as Record<CoverageMetric, { pct: number }>;
|
|
||||||
for (const metric of coverageMetrics) {
|
for (const metric of coverageMetrics) {
|
||||||
const rawMetric = value[metric];
|
parsed[metric] = parseCoverageCounter(value[metric], `${label}.${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 };
|
|
||||||
}
|
}
|
||||||
return Object.freeze(parsed);
|
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<{
|
export function evaluateRiskCoverage(input: Readonly<{
|
||||||
repositoryRoot?: string;
|
repositoryRoot?: string;
|
||||||
inventory: readonly string[];
|
inventory: ProductionModuleInventory;
|
||||||
policy: RiskCoveragePolicy;
|
policy: RiskCoveragePolicy;
|
||||||
summary: unknown;
|
summary: unknown;
|
||||||
changedFiles?: readonly string[];
|
|
||||||
now?: number;
|
|
||||||
}>): RiskCoverageResult {
|
}>): RiskCoverageResult {
|
||||||
const repositoryRoot = path.resolve(input.repositoryRoot ?? process.cwd());
|
const repositoryRoot = path.resolve(input.repositoryRoot ?? process.cwd());
|
||||||
if (input.inventory.length === 0) {
|
const inventory = input.inventory.files.map((file) => exactSourcePath(file, "inventory path"));
|
||||||
throw new TypeError("production module inventory is empty");
|
if (inventory.length === 0) throw new TypeError("production module inventory is empty");
|
||||||
}
|
|
||||||
const inventory = input.inventory.map((file) => exactSourcePath(file, "inventory path"));
|
|
||||||
if (new Set(inventory).size !== inventory.length) {
|
if (new Set(inventory).size !== inventory.length) {
|
||||||
throw new TypeError("production module inventory contains a duplicate path");
|
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)) {
|
if (!isRecord(input.summary) || !("total" in input.summary)) {
|
||||||
throw new TypeError("coverage summary must contain total metrics");
|
throw new TypeError("coverage summary must contain total metrics");
|
||||||
}
|
}
|
||||||
@@ -488,13 +587,25 @@ export function evaluateRiskCoverage(input: Readonly<{
|
|||||||
const selected = new Map<string, CoverageMetrics>();
|
const selected = new Map<string, CoverageMetrics>();
|
||||||
for (const [rawPath, rawMetrics] of Object.entries(input.summary)) {
|
for (const [rawPath, rawMetrics] of Object.entries(input.summary)) {
|
||||||
if (rawPath === "total") continue;
|
if (rawPath === "total") continue;
|
||||||
const normalized = coveragePath(repositoryRoot, rawPath);
|
const normalized = normalizeCoverageProducerPath({ repositoryRoot, rawPath });
|
||||||
if (selected.has(normalized)) {
|
if (selected.has(normalized)) throw new TypeError(`duplicate coverage path: ${normalized}`);
|
||||||
throw new TypeError(`duplicate coverage path: ${normalized}`);
|
|
||||||
}
|
|
||||||
selected.set(normalized, parseCoverageMetrics(rawMetrics, `coverage ${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 failures: string[] = [];
|
||||||
const results: Array<{
|
const results: Array<{
|
||||||
scope: string;
|
scope: string;
|
||||||
@@ -506,30 +617,21 @@ export function evaluateRiskCoverage(input: Readonly<{
|
|||||||
function evaluate(scope: string, actual: CoverageMetrics, minimum: Thresholds): void {
|
function evaluate(scope: string, actual: CoverageMetrics, minimum: Thresholds): void {
|
||||||
for (const metric of coverageMetrics) {
|
for (const metric of coverageMetrics) {
|
||||||
const threshold = minimum[metric];
|
const threshold = minimum[metric];
|
||||||
if (threshold === undefined) continue;
|
|
||||||
const received = actual[metric].pct;
|
const received = actual[metric].pct;
|
||||||
const passed = received >= threshold;
|
const passed = received >= threshold;
|
||||||
results.push({ scope, metric, threshold, received, passed });
|
results.push({ scope, metric, threshold, received, passed });
|
||||||
if (!passed) {
|
if (!passed) failures.push(`${scope}.${metric} expected >= ${threshold}, received ${received}`);
|
||||||
failures.push(`${scope}.${metric} expected >= ${threshold}, received ${received}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
evaluate("total", totalMetrics, input.policy.summary);
|
evaluate("total", recomputedInventoryMetrics, input.policy.summary);
|
||||||
const inventorySet = new Set(inventory);
|
|
||||||
if (inventory.length < input.policy.repositoryBaseline) {
|
if (inventory.length < input.policy.repositoryBaseline) {
|
||||||
failures.push(
|
failures.push(
|
||||||
`repository module baseline expected >= ${input.policy.repositoryBaseline}, received ${inventory.length}`,
|
`repository module baseline expected >= ${input.policy.repositoryBaseline}, received ${inventory.length}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const uncoveredModules = inventory.filter((file) => !selected.has(file)).sort();
|
const uncoveredModules = inventory.filter((file) => !selected.has(file)).sort();
|
||||||
const selectedModules = inventory.filter((file) => selected.has(file));
|
failures.push(...uncoveredModules.map((file) => `production module missing from coverage: ${file}`));
|
||||||
const ignoredCoveragePaths = [...selected.keys()]
|
|
||||||
.filter((file) => !inventorySet.has(file))
|
|
||||||
.sort();
|
|
||||||
failures.push(
|
|
||||||
...uncoveredModules.map((file) => `production module missing from coverage: ${file}`),
|
|
||||||
);
|
|
||||||
for (const modulePolicy of input.policy.criticalModules) {
|
for (const modulePolicy of input.policy.criticalModules) {
|
||||||
if (!inventorySet.has(modulePolicy.path)) {
|
if (!inventorySet.has(modulePolicy.path)) {
|
||||||
failures.push(`critical module is outside production inventory: ${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);
|
evaluate(modulePolicy.path, actual, modulePolicy.minimum);
|
||||||
}
|
}
|
||||||
|
|
||||||
const owners = new Set(input.policy.criticalModules.map((entry) => entry.path));
|
const criticalPaths = new Set(input.policy.criticalModules.map((entry) => entry.path));
|
||||||
const waivers = new Set(input.policy.waivers.map((entry) => entry.path));
|
const waiverPaths = new Set(input.policy.waivers.map((entry) => entry.path));
|
||||||
const highRisk = new Set(input.policy.highRiskPaths);
|
|
||||||
for (const highRiskPath of input.policy.highRiskPaths) {
|
for (const highRiskPath of input.policy.highRiskPaths) {
|
||||||
if (!owners.has(highRiskPath) && !waivers.has(highRiskPath)) {
|
if (!inventorySet.has(highRiskPath)) {
|
||||||
failures.push(`high-risk module has no owner or waiver: ${highRiskPath}`);
|
failures.push(`high-risk module is outside production inventory: ${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}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const waiver of input.policy.waivers) {
|
for (const waiver of input.policy.waivers) {
|
||||||
if (!inventorySet.has(waiver.path) || !highRisk.has(waiver.path)) {
|
if (!inventorySet.has(waiver.path)) failures.push(`coverage waiver is stale: ${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}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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({
|
return Object.freeze({
|
||||||
status: failures.length === 0 ? "PASS" : "FAIL",
|
status: failures.length === 0 ? "PASS" : "FAIL",
|
||||||
selectedTotal: selectedModules.length,
|
selectedTotal: inventory.length - uncoveredModules.length,
|
||||||
repositoryTotal: inventory.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),
|
uncoveredModules: Object.freeze(uncoveredModules),
|
||||||
ignoredCoveragePaths: Object.freeze(ignoredCoveragePaths),
|
|
||||||
results: Object.freeze(results),
|
results: Object.freeze(results),
|
||||||
failures: Object.freeze(failures),
|
failures: Object.freeze(failures),
|
||||||
});
|
});
|
||||||
|
|||||||
+18
-18
@@ -1,50 +1,50 @@
|
|||||||
{
|
{
|
||||||
"total": {
|
"total": {
|
||||||
"lines": { "pct": 100 },
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 },
|
||||||
"statements": { "pct": 100 },
|
"statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 },
|
||||||
"functions": { "pct": 100 },
|
"functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 },
|
||||||
"branches": { "pct": 100 }
|
"branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/adapters/http/bounded-body-reader.ts": {
|
"src/adapters/http/bounded-body-reader.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/adapters/http/bounded-json.ts": {
|
"src/adapters/http/bounded-json.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/adapters/http/http-execution-v3.ts": {
|
"src/adapters/http/http-execution-v3.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/adapters/http/request-builder.ts": {
|
"src/adapters/http/request-builder.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/adapters/http/retry-policy.ts": {
|
"src/adapters/http/retry-policy.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/adapters/query-cache/server-state-scope-runtime.ts": {
|
"src/adapters/query-cache/server-state-scope-runtime.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/adapters/service-worker/service-worker-lifecycle.ts": {
|
"src/adapters/service-worker/service-worker-lifecycle.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/adapters/storage/browser-storage-adapter.ts": {
|
"src/adapters/storage/browser-storage-adapter.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/adapters/telemetry/best-effort-telemetry.ts": {
|
"src/adapters/telemetry/best-effort-telemetry.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/application/create-application.ts": {
|
"src/application/create-application.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/application/policies/compatibility.ts": {
|
"src/application/policies/compatibility.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/application/policies/performance-budgets.ts": {
|
"src/application/policies/performance-budgets.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/application/policies/promotion-readiness.ts": {
|
"src/application/policies/promotion-readiness.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
},
|
},
|
||||||
"src/application/use-cases/decide-chunk-recovery.ts": {
|
"src/application/use-cases/decide-chunk-recovery.ts": {
|
||||||
"lines": { "pct": 100 }, "statements": { "pct": 100 }, "functions": { "pct": 100 }, "branches": { "pct": 100 }
|
"lines": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "statements": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
import { constants } from "node:fs";
|
||||||
|
import {
|
||||||
|
mkdir,
|
||||||
|
mkdtemp,
|
||||||
|
open,
|
||||||
|
readFile,
|
||||||
|
readdir,
|
||||||
|
rename,
|
||||||
|
rm,
|
||||||
|
symlink,
|
||||||
|
writeFile,
|
||||||
|
} from "node:fs/promises";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import {
|
||||||
|
readRiskCoverageInput,
|
||||||
|
resolveRiskCoverageArtifactPath,
|
||||||
|
writeRiskCoverageArtifactAtomic,
|
||||||
|
} from "../../scripts/lib/risk-coverage-files.ts";
|
||||||
|
|
||||||
|
const roots: string[] = [];
|
||||||
|
|
||||||
|
async function fixture(): Promise<string> {
|
||||||
|
const root = await mkdtemp(path.join(tmpdir(), "risk-coverage-files-"));
|
||||||
|
roots.push(root);
|
||||||
|
await mkdir(path.join(root, "config/testing"), { recursive: true });
|
||||||
|
await mkdir(path.join(root, "artifacts/tests/coverage"), { recursive: true });
|
||||||
|
await writeFile(path.join(root, "config/testing/policy.json"), "{\"policy\":true}\n");
|
||||||
|
await writeFile(path.join(root, "artifacts/tests/coverage/summary.json"), "{\"total\":{}}\n");
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await Promise.all(
|
||||||
|
roots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("risk coverage CLI files", () => {
|
||||||
|
it("reads only exact contained regular input files", async () => {
|
||||||
|
const repositoryRoot = await fixture();
|
||||||
|
await expect(
|
||||||
|
readRiskCoverageInput({
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: "config/testing/policy.json",
|
||||||
|
label: "policy",
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
relativePath: "config/testing/policy.json",
|
||||||
|
text: "{\"policy\":true}\n",
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
readRiskCoverageInput({
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: path.join(repositoryRoot, "config/testing/policy.json"),
|
||||||
|
label: "policy",
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(/repository-relative POSIX/u);
|
||||||
|
await expect(
|
||||||
|
readRiskCoverageInput({
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: "config\\testing\\policy.json",
|
||||||
|
label: "policy",
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(/repository-relative POSIX/u);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects final and ancestor input symlinks", async () => {
|
||||||
|
const repositoryRoot = await fixture();
|
||||||
|
const outside = await mkdtemp(path.join(tmpdir(), "risk-coverage-input-outside-"));
|
||||||
|
roots.push(outside);
|
||||||
|
await writeFile(path.join(outside, "outside.json"), "{}\n");
|
||||||
|
await symlink(
|
||||||
|
path.join(outside, "outside.json"),
|
||||||
|
path.join(repositoryRoot, "config/testing/link.json"),
|
||||||
|
);
|
||||||
|
await symlink(outside, path.join(repositoryRoot, "linked-config"), "dir");
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
readRiskCoverageInput({
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: "config/testing/link.json",
|
||||||
|
label: "policy",
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(/symlink/u);
|
||||||
|
await expect(
|
||||||
|
readRiskCoverageInput({
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: "linked-config/outside.json",
|
||||||
|
label: "policy",
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(/outside repository|symlink/u);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("confines artifact output and rejects input overwrite or symlink ancestors", async () => {
|
||||||
|
const repositoryRoot = await fixture();
|
||||||
|
await expect(
|
||||||
|
resolveRiskCoverageArtifactPath({
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: "artifacts/quality/risk-coverage.json",
|
||||||
|
inputPaths: ["config/testing/policy.json", "artifacts/tests/coverage/summary.json"],
|
||||||
|
}),
|
||||||
|
).resolves.toBe(path.join(repositoryRoot, "artifacts/quality/risk-coverage.json"));
|
||||||
|
await expect(
|
||||||
|
resolveRiskCoverageArtifactPath({
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: "config/testing/result.json",
|
||||||
|
inputPaths: [],
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(/artifacts\/quality/u);
|
||||||
|
await expect(
|
||||||
|
resolveRiskCoverageArtifactPath({
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: "artifacts/quality/risk-coverage.json",
|
||||||
|
inputPaths: ["artifacts/quality/risk-coverage.json"],
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(/must not overwrite an input/u);
|
||||||
|
|
||||||
|
const outside = await mkdtemp(path.join(tmpdir(), "risk-coverage-output-outside-"));
|
||||||
|
roots.push(outside);
|
||||||
|
await rm(path.join(repositoryRoot, "artifacts/quality"), { recursive: true, force: true });
|
||||||
|
await symlink(outside, path.join(repositoryRoot, "artifacts/quality"), "dir");
|
||||||
|
await expect(
|
||||||
|
resolveRiskCoverageArtifactPath({
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: "artifacts/quality/risk-coverage.json",
|
||||||
|
inputPaths: [],
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(/symlink/u);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("syncs an exclusive sibling temp before atomic rename", async () => {
|
||||||
|
const repositoryRoot = await fixture();
|
||||||
|
const observed: string[] = [];
|
||||||
|
let observedFlags = 0;
|
||||||
|
await writeRiskCoverageArtifactAtomic(
|
||||||
|
{
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: "artifacts/quality/risk-coverage.json",
|
||||||
|
inputPaths: ["config/testing/policy.json", "artifacts/tests/coverage/summary.json"],
|
||||||
|
value: { schemaVersion: 2, status: "PASS" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
createNonce: () => "owned",
|
||||||
|
fileSystem: {
|
||||||
|
openFile: async (target, flags, mode) => {
|
||||||
|
observedFlags = flags;
|
||||||
|
const handle = await open(target, flags, mode);
|
||||||
|
return {
|
||||||
|
writeFile: async (data) => handle.writeFile(data, "utf8"),
|
||||||
|
sync: async () => {
|
||||||
|
observed.push("file-sync");
|
||||||
|
await handle.sync();
|
||||||
|
},
|
||||||
|
close: async () => handle.close(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
openDirectory: async (target) => {
|
||||||
|
const handle = await open(target, constants.O_RDONLY);
|
||||||
|
return {
|
||||||
|
sync: async () => {
|
||||||
|
observed.push("directory-sync");
|
||||||
|
await handle.sync();
|
||||||
|
},
|
||||||
|
close: async () => handle.close(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
rename: async (source, destination) => {
|
||||||
|
observed.push("rename");
|
||||||
|
await rename(source, destination);
|
||||||
|
},
|
||||||
|
rm,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(observedFlags & constants.O_EXCL).toBe(constants.O_EXCL);
|
||||||
|
expect(observedFlags & constants.O_NOFOLLOW).toBe(constants.O_NOFOLLOW);
|
||||||
|
expect(observed).toEqual(["file-sync", "rename", "directory-sync"]);
|
||||||
|
expect(
|
||||||
|
JSON.parse(
|
||||||
|
await readFile(
|
||||||
|
path.join(repositoryRoot, "artifacts/quality/risk-coverage.json"),
|
||||||
|
"utf8",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).toEqual({ schemaVersion: 2, status: "PASS" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("cleans its owned temp and preserves destination when publication fails", 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, "previous\n");
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
writeRiskCoverageArtifactAtomic(
|
||||||
|
{
|
||||||
|
repositoryRoot,
|
||||||
|
relativePath: "artifacts/quality/risk-coverage.json",
|
||||||
|
inputPaths: [],
|
||||||
|
value: { schemaVersion: 2 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
createNonce: () => "owned",
|
||||||
|
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 () => handle.sync(), close: async () => handle.close() };
|
||||||
|
},
|
||||||
|
rename: async () => {
|
||||||
|
throw new Error("injected rename failure");
|
||||||
|
},
|
||||||
|
rm,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).rejects.toThrow(/injected rename failure/u);
|
||||||
|
|
||||||
|
await expect(readFile(destination, "utf8")).resolves.toBe("previous\n");
|
||||||
|
expect(await readdir(outputDirectory)).toEqual(["risk-coverage.json"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has no changed-files gate in the executable", async () => {
|
||||||
|
const source = await readFile("scripts/check-risk-coverage.ts", "utf8");
|
||||||
|
expect(source).not.toMatch(/changedFiles|changed-files/u);
|
||||||
|
});
|
||||||
|
});
|
||||||
+322
-230
@@ -1,6 +1,8 @@
|
|||||||
|
import { constants } from "node:fs";
|
||||||
import {
|
import {
|
||||||
mkdir,
|
mkdir,
|
||||||
mkdtemp,
|
mkdtemp,
|
||||||
|
open,
|
||||||
readFile,
|
readFile,
|
||||||
rm,
|
rm,
|
||||||
symlink,
|
symlink,
|
||||||
@@ -14,18 +16,45 @@ import { afterEach, describe, expect, it } from "vitest";
|
|||||||
import {
|
import {
|
||||||
buildProductionModuleInventory,
|
buildProductionModuleInventory,
|
||||||
evaluateRiskCoverage,
|
evaluateRiskCoverage,
|
||||||
|
normalizeCoverageProducerPath,
|
||||||
parseRepositoryRiskCoveragePolicy,
|
parseRepositoryRiskCoveragePolicy,
|
||||||
parseRiskCoveragePolicy,
|
parseRiskCoveragePolicy,
|
||||||
|
type ProductionModuleInventory,
|
||||||
} from "../../scripts/lib/risk-coverage.ts";
|
} from "../../scripts/lib/risk-coverage.ts";
|
||||||
|
|
||||||
const roots: string[] = [];
|
const roots: string[] = [];
|
||||||
const now = Date.parse("2026-08-02T00:00:00.000Z");
|
const now = Date.parse("2026-08-02T00:00:00.000Z");
|
||||||
const fullMetrics = {
|
|
||||||
lines: { pct: 100 },
|
function counter(total = 1, covered = total, skipped = 0) {
|
||||||
statements: { pct: 100 },
|
return {
|
||||||
functions: { pct: 100 },
|
total,
|
||||||
branches: { pct: 100 },
|
covered,
|
||||||
|
skipped,
|
||||||
|
pct: total === 0 ? 100 : Math.floor((covered / total) * 10_000) / 100,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function metrics(total = 1, covered = total) {
|
||||||
|
return {
|
||||||
|
lines: counter(total, covered),
|
||||||
|
statements: counter(total, covered),
|
||||||
|
functions: counter(total, covered),
|
||||||
|
branches: counter(total, covered),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullMetrics = metrics();
|
||||||
|
|
||||||
|
function inventory(
|
||||||
|
files: readonly string[],
|
||||||
|
generatedExclusions: readonly string[] = [],
|
||||||
|
): ProductionModuleInventory {
|
||||||
|
return {
|
||||||
|
files,
|
||||||
|
preExclusionTotal: files.length + generatedExclusions.length,
|
||||||
|
generatedExclusions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function repositoryFixture(): Promise<string> {
|
async function repositoryFixture(): Promise<string> {
|
||||||
const root = await mkdtemp(path.join(tmpdir(), "risk-coverage-"));
|
const root = await mkdtemp(path.join(tmpdir(), "risk-coverage-"));
|
||||||
@@ -87,17 +116,15 @@ describe("repository-aware risk coverage", () => {
|
|||||||
await readFile("tests/fixtures/coverage/repository-omission.json", "utf8"),
|
await readFile("tests/fixtures/coverage/repository-omission.json", "utf8"),
|
||||||
) as unknown;
|
) as unknown;
|
||||||
const parsedPolicy = parseRepositoryRiskCoveragePolicy(rawPolicy, { now });
|
const parsedPolicy = parseRepositoryRiskCoveragePolicy(rawPolicy, { now });
|
||||||
const inventory = await buildProductionModuleInventory({
|
const productionInventory = await buildProductionModuleInventory({
|
||||||
repositoryRoot,
|
repositoryRoot,
|
||||||
generatedPaths: parsedPolicy.generatedPaths,
|
generatedPaths: parsedPolicy.generatedPaths,
|
||||||
});
|
});
|
||||||
const result = evaluateRiskCoverage({
|
const result = evaluateRiskCoverage({
|
||||||
repositoryRoot,
|
repositoryRoot,
|
||||||
inventory,
|
inventory: productionInventory,
|
||||||
policy: parsedPolicy,
|
policy: parsedPolicy,
|
||||||
summary,
|
summary,
|
||||||
changedFiles: [],
|
|
||||||
now,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.selectedTotal).toBe(14);
|
expect(result.selectedTotal).toBe(14);
|
||||||
@@ -108,342 +135,407 @@ describe("repository-aware risk coverage", () => {
|
|||||||
expect(result.status).toBe("FAIL");
|
expect(result.status).toBe("FAIL");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reports the exact selected and repository totals plus sorted omissions", async () => {
|
it("reports exact inventory and generated-exclusion provenance", async () => {
|
||||||
const repositoryRoot = await repositoryFixture();
|
const repositoryRoot = await repositoryFixture();
|
||||||
const inventory = await buildProductionModuleInventory({
|
const productionInventory = await buildProductionModuleInventory({
|
||||||
repositoryRoot,
|
repositoryRoot,
|
||||||
generatedPaths: ["src/generated.ts"],
|
generatedPaths: ["src/generated.ts"],
|
||||||
});
|
});
|
||||||
const result = evaluateRiskCoverage({
|
const result = evaluateRiskCoverage({
|
||||||
repositoryRoot,
|
repositoryRoot,
|
||||||
inventory,
|
inventory: productionInventory,
|
||||||
policy: parseRiskCoveragePolicy(policy(), { now }),
|
policy: parseRiskCoveragePolicy(policy(), { now }),
|
||||||
summary: {
|
summary: {
|
||||||
total: fullMetrics,
|
total: fullMetrics,
|
||||||
[path.join(repositoryRoot, "src/a.ts")]: fullMetrics,
|
[path.join(repositoryRoot, "src/a.ts")]: fullMetrics,
|
||||||
},
|
},
|
||||||
changedFiles: [],
|
|
||||||
now,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(inventory).toEqual(["src/a.ts", "src/nested/b.tsx"]);
|
expect(productionInventory).toEqual({
|
||||||
|
files: ["src/a.ts", "src/nested/b.tsx"],
|
||||||
|
preExclusionTotal: 3,
|
||||||
|
generatedExclusions: ["src/generated.ts"],
|
||||||
|
});
|
||||||
expect(result).toMatchObject({
|
expect(result).toMatchObject({
|
||||||
status: "FAIL",
|
status: "FAIL",
|
||||||
selectedTotal: 1,
|
selectedTotal: 1,
|
||||||
repositoryTotal: 2,
|
repositoryTotal: 2,
|
||||||
|
preExclusionTotal: 3,
|
||||||
|
generatedExclusionCount: 1,
|
||||||
|
generatedExclusions: ["src/generated.ts"],
|
||||||
uncoveredModules: ["src/nested/b.tsx"],
|
uncoveredModules: ["src/nested/b.tsx"],
|
||||||
});
|
});
|
||||||
expect(result.failures).toContain(
|
});
|
||||||
"production module missing from coverage: src/nested/b.tsx",
|
|
||||||
|
it("rejects arbitrary coverage paths instead of silently allowing inflation", () => {
|
||||||
|
const parsedPolicy = parseRiskCoveragePolicy(
|
||||||
|
policy({ repositoryBaseline: 1, generatedPaths: [] }),
|
||||||
|
{ now },
|
||||||
);
|
);
|
||||||
});
|
expect(() =>
|
||||||
|
|
||||||
it("maps only exact repository-relative POSIX coverage paths", async () => {
|
|
||||||
const repositoryRoot = await repositoryFixture();
|
|
||||||
const inventory = await buildProductionModuleInventory({
|
|
||||||
repositoryRoot,
|
|
||||||
generatedPaths: ["src/generated.ts"],
|
|
||||||
});
|
|
||||||
const parsedPolicy = parseRiskCoveragePolicy(policy(), { now });
|
|
||||||
|
|
||||||
expect(
|
|
||||||
evaluateRiskCoverage({
|
evaluateRiskCoverage({
|
||||||
repositoryRoot,
|
repositoryRoot: "/repository",
|
||||||
inventory,
|
inventory: inventory(["src/a.ts"]),
|
||||||
policy: parsedPolicy,
|
policy: parsedPolicy,
|
||||||
summary: {
|
summary: {
|
||||||
total: fullMetrics,
|
total: metrics(2),
|
||||||
"src/a.ts": fullMetrics,
|
"src/a.ts": fullMetrics,
|
||||||
"src/nested/b.tsx": fullMetrics,
|
|
||||||
"tests/unit/a.test.ts": fullMetrics,
|
"tests/unit/a.test.ts": fullMetrics,
|
||||||
"src/generated.ts": fullMetrics,
|
|
||||||
},
|
},
|
||||||
changedFiles: [],
|
|
||||||
now,
|
|
||||||
}),
|
}),
|
||||||
).toMatchObject({
|
).toThrow(/unexpected coverage path.*tests\/unit\/a\.test\.ts/u);
|
||||||
status: "PASS",
|
|
||||||
selectedTotal: 2,
|
|
||||||
ignoredCoveragePaths: ["src/generated.ts", "tests/unit/a.test.ts"],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accepts only explicitly configured generated coverage paths", () => {
|
||||||
|
const parsedPolicy = parseRiskCoveragePolicy(
|
||||||
|
policy({ repositoryBaseline: 1 }),
|
||||||
|
{ now },
|
||||||
|
);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
evaluateRiskCoverage({
|
evaluateRiskCoverage({
|
||||||
repositoryRoot,
|
repositoryRoot: "/repository",
|
||||||
inventory,
|
inventory: inventory(["src/a.ts"], ["src/generated.ts"]),
|
||||||
policy: parsedPolicy,
|
policy: parsedPolicy,
|
||||||
summary: {
|
summary: {
|
||||||
total: fullMetrics,
|
total: metrics(2, 1),
|
||||||
[path.join(tmpdir(), "other/src/a.ts")]: fullMetrics,
|
|
||||||
},
|
|
||||||
changedFiles: [],
|
|
||||||
now,
|
|
||||||
}),
|
|
||||||
).toThrow(/outside repository/u);
|
|
||||||
expect(() =>
|
|
||||||
evaluateRiskCoverage({
|
|
||||||
repositoryRoot,
|
|
||||||
inventory,
|
|
||||||
policy: parsedPolicy,
|
|
||||||
summary: {
|
|
||||||
total: fullMetrics,
|
|
||||||
"src/a.ts": fullMetrics,
|
"src/a.ts": fullMetrics,
|
||||||
[path.join(repositoryRoot, "src/a.ts")]: fullMetrics,
|
"src/generated.ts": metrics(1, 0),
|
||||||
},
|
},
|
||||||
changedFiles: [],
|
|
||||||
now,
|
|
||||||
}),
|
}),
|
||||||
).toThrow(/duplicate coverage path/u);
|
).not.toThrow();
|
||||||
|
const result = evaluateRiskCoverage({
|
||||||
|
repositoryRoot: "/repository",
|
||||||
|
inventory: inventory(["src/a.ts"], ["src/generated.ts"]),
|
||||||
|
policy: parsedPolicy,
|
||||||
|
summary: {
|
||||||
|
total: metrics(2, 1),
|
||||||
|
"src/a.ts": fullMetrics,
|
||||||
|
"src/generated.ts": metrics(1, 0),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
result.results.find(
|
||||||
|
({ scope, metric }) => scope === "total" && metric === "lines",
|
||||||
|
),
|
||||||
|
).toMatchObject({ received: 100, passed: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("accepts and validates Vitest's branchesTrue total without evaluating it", () => {
|
it("requires complete internally consistent Istanbul counters", () => {
|
||||||
const parsedPolicy = parseRiskCoveragePolicy(policy(), { now });
|
const parsedPolicy = parseRiskCoveragePolicy(
|
||||||
const branchesTrue = { total: 0, covered: 0, skipped: 0, pct: 100 };
|
policy({ repositoryBaseline: 1, generatedPaths: [] }),
|
||||||
|
{ now },
|
||||||
|
);
|
||||||
|
const base = {
|
||||||
|
repositoryRoot: "/repository",
|
||||||
|
inventory: inventory(["src/a.ts"]),
|
||||||
|
policy: parsedPolicy,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
evaluateRiskCoverage({
|
||||||
|
...base,
|
||||||
|
summary: { total: fullMetrics, "src/a.ts": { ...fullMetrics, lines: { pct: 100 } } },
|
||||||
|
}),
|
||||||
|
).toThrow(/lines.*total.*covered.*skipped/u);
|
||||||
|
expect(() =>
|
||||||
|
evaluateRiskCoverage({
|
||||||
|
...base,
|
||||||
|
summary: {
|
||||||
|
total: fullMetrics,
|
||||||
|
"src/a.ts": {
|
||||||
|
...fullMetrics,
|
||||||
|
lines: { total: 2, covered: 1, skipped: 2, pct: 50 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrow(/covered plus skipped.*total/u);
|
||||||
|
expect(() =>
|
||||||
|
evaluateRiskCoverage({
|
||||||
|
...base,
|
||||||
|
summary: {
|
||||||
|
total: fullMetrics,
|
||||||
|
"src/a.ts": {
|
||||||
|
...fullMetrics,
|
||||||
|
lines: { total: 3, covered: 2, skipped: 0, pct: 66.67 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrow(/pct.*66\.66/u);
|
||||||
|
expect(() =>
|
||||||
|
evaluateRiskCoverage({
|
||||||
|
...base,
|
||||||
|
summary: {
|
||||||
|
total: metrics(0),
|
||||||
|
"src/a.ts": { ...fullMetrics, lines: counter(0, 0, 0) },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrow(/coverage total.*does not match/u);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("recomputes all global counters and requires an exact producer total", () => {
|
||||||
|
const parsedPolicy = parseRiskCoveragePolicy(
|
||||||
|
policy({ repositoryBaseline: 1, generatedPaths: [] }),
|
||||||
|
{ now },
|
||||||
|
);
|
||||||
|
expect(() =>
|
||||||
|
evaluateRiskCoverage({
|
||||||
|
repositoryRoot: "/repository",
|
||||||
|
inventory: inventory(["src/a.ts"]),
|
||||||
|
policy: parsedPolicy,
|
||||||
|
summary: {
|
||||||
|
total: metrics(2),
|
||||||
|
"src/a.ts": fullMetrics,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrow(/coverage total\.lines does not match recomputed inventory total/u);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts and validates Vitest branchesTrue without evaluating it", () => {
|
||||||
|
const parsedPolicy = parseRiskCoveragePolicy(
|
||||||
|
policy({ repositoryBaseline: 1, generatedPaths: [] }),
|
||||||
|
{ now },
|
||||||
|
);
|
||||||
|
const branchesTrue = counter(0);
|
||||||
expect(
|
expect(
|
||||||
evaluateRiskCoverage({
|
evaluateRiskCoverage({
|
||||||
repositoryRoot: "/repository",
|
repositoryRoot: "/repository",
|
||||||
inventory: ["src/a.ts"],
|
inventory: inventory(["src/a.ts"]),
|
||||||
policy: { ...parsedPolicy, repositoryBaseline: 1 },
|
policy: parsedPolicy,
|
||||||
summary: {
|
summary: {
|
||||||
total: { ...fullMetrics, branchesTrue },
|
total: { ...fullMetrics, branchesTrue },
|
||||||
"src/a.ts": fullMetrics,
|
"src/a.ts": fullMetrics,
|
||||||
},
|
},
|
||||||
changedFiles: [],
|
|
||||||
now,
|
|
||||||
}),
|
}),
|
||||||
).toMatchObject({ status: "PASS", selectedTotal: 1 });
|
).toMatchObject({ status: "PASS" });
|
||||||
expect(() =>
|
expect(() =>
|
||||||
evaluateRiskCoverage({
|
evaluateRiskCoverage({
|
||||||
repositoryRoot: "/repository",
|
repositoryRoot: "/repository",
|
||||||
inventory: ["src/a.ts"],
|
inventory: inventory(["src/a.ts"]),
|
||||||
policy: { ...parsedPolicy, repositoryBaseline: 1 },
|
policy: parsedPolicy,
|
||||||
summary: {
|
summary: {
|
||||||
total: {
|
total: {
|
||||||
...fullMetrics,
|
...fullMetrics,
|
||||||
branchesTrue: { ...branchesTrue, pct: Number.NaN },
|
branchesTrue: { ...branchesTrue, pct: 0 },
|
||||||
},
|
},
|
||||||
"src/a.ts": fullMetrics,
|
"src/a.ts": fullMetrics,
|
||||||
},
|
},
|
||||||
changedFiles: [],
|
|
||||||
now,
|
|
||||||
}),
|
}),
|
||||||
).toThrow(/branchesTrue.*finite/u);
|
).toThrow(/branchesTrue\.pct.*100/u);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("fails closed on an empty, unreadable, traversing, or symlinked inventory", async () => {
|
it("opens regular files with O_NOFOLLOW and fails a deterministic symlink swap", async () => {
|
||||||
|
const repositoryRoot = await repositoryFixture();
|
||||||
|
let observedFlags = 0;
|
||||||
|
await expect(
|
||||||
|
buildProductionModuleInventory({
|
||||||
|
repositoryRoot,
|
||||||
|
openFile: async (target, flags) => {
|
||||||
|
observedFlags = flags;
|
||||||
|
if (target.endsWith("src/a.ts")) {
|
||||||
|
throw Object.assign(new Error("injected link swap"), { code: "ELOOP" });
|
||||||
|
}
|
||||||
|
return open(target, flags);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(/unreadable.*src\/a\.ts/u);
|
||||||
|
expect(observedFlags & constants.O_NOFOLLOW).toBe(constants.O_NOFOLLOW);
|
||||||
|
});
|
||||||
|
|
||||||
|
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-"));
|
||||||
roots.push(emptyRoot);
|
roots.push(emptyRoot);
|
||||||
await mkdir(path.join(emptyRoot, "src"));
|
await mkdir(path.join(emptyRoot, "src"));
|
||||||
|
|
||||||
|
await expect(buildProductionModuleInventory({ repositoryRoot: emptyRoot })).rejects.toThrow(
|
||||||
|
/inventory is empty/u,
|
||||||
|
);
|
||||||
await expect(
|
await expect(
|
||||||
buildProductionModuleInventory({ repositoryRoot: emptyRoot }),
|
buildProductionModuleInventory({ repositoryRoot, generatedPaths: ["../escape.ts"] }),
|
||||||
).rejects.toThrow(/inventory is empty/u);
|
|
||||||
await expect(
|
|
||||||
buildProductionModuleInventory({
|
|
||||||
repositoryRoot,
|
|
||||||
assertReadable: async (target) => {
|
|
||||||
if (target.endsWith("src/a.ts")) throw new Error("denied");
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).rejects.toThrow(/unreadable.*src\/a\.ts/u);
|
|
||||||
await expect(
|
|
||||||
buildProductionModuleInventory({
|
|
||||||
repositoryRoot,
|
|
||||||
generatedPaths: ["../escape.ts"],
|
|
||||||
}),
|
|
||||||
).rejects.toThrow(/repository-relative POSIX/u);
|
).rejects.toThrow(/repository-relative POSIX/u);
|
||||||
|
await expect(
|
||||||
|
buildProductionModuleInventory({ repositoryRoot, generatedPaths: ["src/missing.ts"] }),
|
||||||
|
).rejects.toThrow(/stale or not a production module/u);
|
||||||
|
|
||||||
const outside = await mkdtemp(path.join(tmpdir(), "risk-coverage-outside-"));
|
const outside = await mkdtemp(path.join(tmpdir(), "risk-coverage-outside-"));
|
||||||
roots.push(outside);
|
roots.push(outside);
|
||||||
await writeFile(path.join(outside, "linked.ts"), "export {};\n");
|
await writeFile(path.join(outside, "linked.ts"), "export {};\n");
|
||||||
await symlink(path.join(outside, "linked.ts"), path.join(repositoryRoot, "src/link.ts"));
|
await symlink(path.join(outside, "linked.ts"), path.join(repositoryRoot, "src/link.ts"));
|
||||||
await expect(
|
await expect(buildProductionModuleInventory({ repositoryRoot })).rejects.toThrow(
|
||||||
buildProductionModuleInventory({ repositoryRoot }),
|
/symlink.*src\/link\.ts/u,
|
||||||
).rejects.toThrow(/symlink.*src\/link\.ts/u);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects malformed totals, duplicate paths, invalid minimums, and weak ownership", () => {
|
it("normalizes native Windows absolute producer paths but rejects POSIX backslashes", () => {
|
||||||
expect(() => parseRiskCoveragePolicy(null, { now })).toThrow(/policy/u);
|
expect(
|
||||||
|
normalizeCoverageProducerPath({
|
||||||
|
repositoryRoot: "C:\\repo",
|
||||||
|
rawPath: "C:\\repo\\src\\nested\\a.ts",
|
||||||
|
platform: "win32",
|
||||||
|
}),
|
||||||
|
).toBe("src/nested/a.ts");
|
||||||
expect(() =>
|
expect(() =>
|
||||||
parseRiskCoveragePolicy(policy({ repositoryBaseline: 0 }), { now }),
|
normalizeCoverageProducerPath({
|
||||||
).toThrow(/repositoryBaseline/u);
|
repositoryRoot: "/repository",
|
||||||
|
rawPath: "/repository/src\\a.ts",
|
||||||
|
platform: "posix",
|
||||||
|
}),
|
||||||
|
).toThrow(/POSIX separators/u);
|
||||||
|
expect(() =>
|
||||||
|
normalizeCoverageProducerPath({
|
||||||
|
repositoryRoot: "C:\\repo",
|
||||||
|
rawPath: "D:\\outside\\a.ts",
|
||||||
|
platform: "win32",
|
||||||
|
}),
|
||||||
|
).toThrow(/outside repository/u);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requires four positive metrics and canonical team ownership", () => {
|
||||||
|
expect(() =>
|
||||||
|
parseRiskCoveragePolicy(policy({ summary: { lines: 80 } }), { now }),
|
||||||
|
).toThrow(/summary must define all/u);
|
||||||
|
expect(() =>
|
||||||
|
parseRiskCoveragePolicy(policy({ summary: { lines: 80, statements: 78, functions: 85, branches: 0 } }), { now }),
|
||||||
|
).toThrow(/greater than 0/u);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
parseRiskCoveragePolicy(
|
parseRiskCoveragePolicy(
|
||||||
policy({
|
policy({
|
||||||
criticalModules: [
|
criticalModules: [
|
||||||
{ path: "src/a.ts", owner: "team", minimum: { lines: 101 } },
|
{ path: "src/a.ts", owner: "Platform Runtime", minimum: policy().summary },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
{ now },
|
{ now },
|
||||||
),
|
),
|
||||||
).toThrow(/minimum/u);
|
).toThrow(/canonical team id/u);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
parseRiskCoveragePolicy(
|
parseRiskCoveragePolicy(
|
||||||
policy({
|
policy({
|
||||||
criticalModules: [
|
criticalModules: [
|
||||||
{ path: "src/a.ts", owner: " ", minimum: { lines: 80 } },
|
{ path: "src/a.ts", owner: "platform-runtime", minimum: { lines: 80 } },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
{ now },
|
{ now },
|
||||||
),
|
),
|
||||||
).toThrow(/owner/u);
|
).toThrow(/minimum must define all/u);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("enforces ALL_POLICY_HIGH_RISK ownership without changed-file input", () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
parseRiskCoveragePolicy(
|
parseRiskCoveragePolicy(
|
||||||
policy({ highRiskPaths: ["src/a.ts", "src/a.ts"] }),
|
policy({ highRiskPaths: ["src/a.ts", "src/nested/b.tsx"] }),
|
||||||
{ now },
|
{ now },
|
||||||
),
|
),
|
||||||
).toThrow(/duplicate/u);
|
).toThrow(/high-risk module has no owner or waiver.*nested\/b/u);
|
||||||
|
|
||||||
const parsedPolicy = parseRiskCoveragePolicy(policy(), { now });
|
const parsed = parseRiskCoveragePolicy(
|
||||||
expect(() =>
|
|
||||||
evaluateRiskCoverage({
|
|
||||||
repositoryRoot: "/repository",
|
|
||||||
inventory: ["src/a.ts"],
|
|
||||||
policy: parsedPolicy,
|
|
||||||
summary: {
|
|
||||||
total: fullMetrics,
|
|
||||||
"src/a.ts": { ...fullMetrics, lines: { pct: Number.NaN } },
|
|
||||||
},
|
|
||||||
changedFiles: [],
|
|
||||||
now,
|
|
||||||
}),
|
|
||||||
).toThrow(/finite/u);
|
|
||||||
expect(() =>
|
|
||||||
evaluateRiskCoverage({
|
|
||||||
repositoryRoot: "/repository",
|
|
||||||
inventory: ["src/a.ts"],
|
|
||||||
policy: parsedPolicy,
|
|
||||||
summary: {
|
|
||||||
total: fullMetrics,
|
|
||||||
"src/a.ts": {
|
|
||||||
...fullMetrics,
|
|
||||||
lines: {
|
|
||||||
total: Number.NaN,
|
|
||||||
covered: 1,
|
|
||||||
skipped: 0,
|
|
||||||
pct: 100,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
changedFiles: [],
|
|
||||||
now,
|
|
||||||
}),
|
|
||||||
).toThrow(/lines\.total.*nonnegative/u);
|
|
||||||
expect(() =>
|
|
||||||
evaluateRiskCoverage({
|
|
||||||
repositoryRoot: "/repository",
|
|
||||||
inventory: ["src/a.ts"],
|
|
||||||
policy: parsedPolicy,
|
|
||||||
summary: {
|
|
||||||
total: fullMetrics,
|
|
||||||
"src/a.ts": { ...fullMetrics, conditions: { pct: 100 } },
|
|
||||||
},
|
|
||||||
changedFiles: [],
|
|
||||||
now,
|
|
||||||
}),
|
|
||||||
).toThrow(/unknown.*metric/u);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("requires inventory-owned critical rows and exact owned future waivers", async () => {
|
|
||||||
const repositoryRoot = await repositoryFixture();
|
|
||||||
const inventory = await buildProductionModuleInventory({
|
|
||||||
repositoryRoot,
|
|
||||||
generatedPaths: ["src/generated.ts"],
|
|
||||||
});
|
|
||||||
const summary = {
|
|
||||||
total: fullMetrics,
|
|
||||||
"src/a.ts": fullMetrics,
|
|
||||||
"src/nested/b.tsx": fullMetrics,
|
|
||||||
};
|
|
||||||
const waiverPolicy = parseRiskCoveragePolicy(
|
|
||||||
policy({
|
policy({
|
||||||
highRiskPaths: ["src/a.ts", "src/nested/b.tsx"],
|
highRiskPaths: ["src/a.ts", "src/nested/b.tsx"],
|
||||||
waivers: [
|
waivers: [
|
||||||
{
|
{
|
||||||
path: "src/nested/b.tsx",
|
path: "src/nested/b.tsx",
|
||||||
owner: "runtime-security",
|
owner: "runtime-security",
|
||||||
reason: "Temporary branch instrumentation gap",
|
reason: "Temporary instrumentation gap",
|
||||||
expiresAt: "2026-08-03T00:00:00.000Z",
|
expiresAt: "2026-08-03T00:00:00.000Z",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
{ now },
|
{ now },
|
||||||
);
|
);
|
||||||
|
const result = evaluateRiskCoverage({
|
||||||
expect(
|
repositoryRoot: "/repository",
|
||||||
evaluateRiskCoverage({
|
inventory: inventory(
|
||||||
repositoryRoot,
|
["src/a.ts", "src/nested/b.tsx"],
|
||||||
inventory,
|
["src/generated.ts"],
|
||||||
policy: waiverPolicy,
|
|
||||||
summary,
|
|
||||||
changedFiles: ["src/nested/b.tsx"],
|
|
||||||
now,
|
|
||||||
}),
|
|
||||||
).toMatchObject({ status: "PASS" });
|
|
||||||
expect(
|
|
||||||
evaluateRiskCoverage({
|
|
||||||
repositoryRoot,
|
|
||||||
inventory,
|
|
||||||
policy: parseRiskCoveragePolicy(
|
|
||||||
policy({ highRiskPaths: ["src/a.ts", "src/nested/b.tsx"] }),
|
|
||||||
{ now },
|
|
||||||
),
|
),
|
||||||
summary,
|
policy: parsed,
|
||||||
changedFiles: ["src/nested/b.tsx"],
|
summary: {
|
||||||
now,
|
total: metrics(2),
|
||||||
}).failures,
|
"src/a.ts": fullMetrics,
|
||||||
).toContain("changed high-risk module has no owner or waiver: src/nested/b.tsx");
|
"src/nested/b.tsx": fullMetrics,
|
||||||
expect(
|
},
|
||||||
evaluateRiskCoverage({
|
});
|
||||||
repositoryRoot,
|
expect(result).toMatchObject({
|
||||||
inventory,
|
ownershipScope: "ALL_POLICY_HIGH_RISK",
|
||||||
policy: parseRiskCoveragePolicy(
|
ownedHighRiskPaths: ["src/a.ts"],
|
||||||
policy({ highRiskPaths: ["src/a.ts", "src/nested/b.tsx"] }),
|
waivedHighRiskPaths: ["src/nested/b.tsx"],
|
||||||
{ now },
|
status: "PASS",
|
||||||
),
|
|
||||||
summary,
|
|
||||||
changedFiles: [],
|
|
||||||
now,
|
|
||||||
}).failures,
|
|
||||||
).toContain("high-risk module has no owner or waiver: src/nested/b.tsx");
|
|
||||||
expect(
|
|
||||||
evaluateRiskCoverage({
|
|
||||||
repositoryRoot,
|
|
||||||
inventory: ["src/nested/b.tsx"],
|
|
||||||
policy: parseRiskCoveragePolicy(policy(), { now }),
|
|
||||||
summary,
|
|
||||||
changedFiles: [],
|
|
||||||
now,
|
|
||||||
}).failures,
|
|
||||||
).toContain("critical module is outside production inventory: src/a.ts");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not let the repository policy delete a required high-risk path", async () => {
|
|
||||||
const rawPolicy = JSON.parse(
|
|
||||||
await readFile("config/testing/risk-coverage.json", "utf8"),
|
|
||||||
) as Record<string, unknown>;
|
|
||||||
rawPolicy.highRiskPaths = (
|
|
||||||
rawPolicy.highRiskPaths as string[]
|
|
||||||
).filter((modulePath) => modulePath !== "src/adapters/http/http-execution-v3.ts");
|
|
||||||
|
|
||||||
expect(() => parseRepositoryRiskCoveragePolicy(rawPolicy, { now })).toThrow(
|
|
||||||
/required high-risk path.*http-execution-v3/u,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[{ path: "src/*.ts", owner: "team", reason: "reason", expiresAt: "2026-08-03T00:00:00.000Z" }, /POSIX/u],
|
[
|
||||||
[{ path: "src/a.ts", owner: "", reason: "reason", expiresAt: "2026-08-03T00:00:00.000Z" }, /owner/u],
|
{ path: "src/a.ts", owner: "Runtime Team", reason: "Temporary instrumentation gap", expiresAt: "2026-08-03T00:00:00.000Z" },
|
||||||
[{ path: "src/a.ts", owner: "team", reason: "", expiresAt: "2026-08-03T00:00:00.000Z" }, /reason/u],
|
/canonical team id/u,
|
||||||
[{ path: "src/a.ts", owner: "team", reason: "reason", expiresAt: "2026-08-01T00:00:00.000Z" }, /expired/u],
|
],
|
||||||
[{ path: "src/stale.ts", owner: "team", reason: "reason", expiresAt: "2026-08-03T00:00:00.000Z" }, /stale/u],
|
[
|
||||||
] as const)("rejects invalid exact-path waivers %#", (waiver, message) => {
|
{ path: "src/a.ts", owner: "runtime-team", reason: "too short", expiresAt: "2026-08-03T00:00:00.000Z" },
|
||||||
|
/12 to 240/u,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ path: "src/a.ts", owner: "runtime-team", reason: "Temporary\ninstrumentation gap", expiresAt: "2026-08-03T00:00:00.000Z" },
|
||||||
|
/control characters/u,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ path: "src/a.ts", owner: "runtime-team", reason: "Temporary instrumentation gap", expiresAt: "2026-08-03T00:00:00Z" },
|
||||||
|
/canonical UTC ISO/u,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ path: "src/a.ts", owner: "runtime-team", reason: "Temporary instrumentation gap", expiresAt: "2026-11-01T00:00:00.000Z" },
|
||||||
|
/90 days/u,
|
||||||
|
],
|
||||||
|
] as const)("rejects invalid waiver controls %#", (waiver, message) => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
parseRiskCoveragePolicy(
|
parseRiskCoveragePolicy(
|
||||||
policy({ highRiskPaths: ["src/a.ts"], waivers: [waiver] }),
|
policy({
|
||||||
|
criticalModules: [
|
||||||
|
{
|
||||||
|
path: "src/owned.ts",
|
||||||
|
owner: "platform-runtime",
|
||||||
|
minimum: policy().summary,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
highRiskPaths: ["src/owned.ts", "src/a.ts"],
|
||||||
|
waivers: [waiver],
|
||||||
|
}),
|
||||||
{ now },
|
{ now },
|
||||||
),
|
),
|
||||||
).toThrow(message);
|
).toThrow(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects simultaneous critical ownership and waiver", () => {
|
||||||
|
expect(() =>
|
||||||
|
parseRiskCoveragePolicy(
|
||||||
|
policy({
|
||||||
|
waivers: [
|
||||||
|
{
|
||||||
|
path: "src/a.ts",
|
||||||
|
owner: "runtime-team",
|
||||||
|
reason: "Temporary instrumentation gap",
|
||||||
|
expiresAt: "2026-08-03T00:00:00.000Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
{ now },
|
||||||
|
),
|
||||||
|
).toThrow(/both a critical owner and waiver/u);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not let repository policy omit or generate-exclude a required high-risk path", async () => {
|
||||||
|
const rawPolicy = JSON.parse(
|
||||||
|
await readFile("config/testing/risk-coverage.json", "utf8"),
|
||||||
|
) as Record<string, unknown>;
|
||||||
|
const missingPolicy = structuredClone(rawPolicy);
|
||||||
|
missingPolicy.highRiskPaths = (missingPolicy.highRiskPaths as string[]).filter(
|
||||||
|
(modulePath) => modulePath !== "src/adapters/http/http-execution-v3.ts",
|
||||||
|
);
|
||||||
|
expect(() => parseRepositoryRiskCoveragePolicy(missingPolicy, { now })).toThrow(
|
||||||
|
/required high-risk path.*http-execution-v3/u,
|
||||||
|
);
|
||||||
|
|
||||||
|
const excludedPolicy = structuredClone(rawPolicy);
|
||||||
|
excludedPolicy.generatedPaths = ["src/adapters/http/http-execution-v3.ts"];
|
||||||
|
expect(() => parseRepositoryRiskCoveragePolicy(excludedPolicy, { now })).toThrow(
|
||||||
|
/required high-risk path cannot be generated-excluded/u,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user