veritas-kanban/scripts/check-coverage-policy.mjs
Brad Groux 93e946693d
test: establish critical-path coverage ratchets (#1183)
* test: add critical-path coverage ratchets

* fix: harden coverage ratchet enforcement

* fix: fetch coverage comparison history

* fix: close coverage policy downgrade paths

* fix: verify changed critical statements

* fix: ignore non-executable coverage changes

* fix: distinguish mixed type-only coverage edits

* fix: classify coverage changes by runtime output

* fix: enforce runtime-significant whitespace changes

* fix: classify multiline coverage changes as hunks

* fix: group equal-line coverage rewrites
2026-08-23 13:48:41 -05:00

249 lines
9.2 KiB
JavaScript

#!/usr/bin/env node
import { execFileSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { coverageExceptionErrors } from './coverage-policy-utils.mjs';
const EXPECTED_PACKAGES = ['server', 'web', 'cli', 'mcp', 'desktop'];
const METRICS = ['lines', 'branches', 'functions', 'statements'];
const COMMIT_SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
const GLOB_PATTERN = /[*?[\]{}!]/;
export function compareCoveragePolicy(current, baseline) {
const errors = [];
for (const baselinePackage of baseline?.packages ?? []) {
const currentPackage = current.packages?.find(({ id }) => id === baselinePackage.id);
if (!currentPackage) {
errors.push(`coverage package ${baselinePackage.id} cannot be removed`);
continue;
}
for (const baselineBoundary of baselinePackage.boundaries ?? []) {
const fullId = `${baselinePackage.id}/${baselineBoundary.id}`;
const currentBoundary = currentPackage.boundaries?.find(
({ id }) => id === baselineBoundary.id
);
if (!currentBoundary) {
errors.push(`coverage boundary ${fullId} cannot be removed`);
continue;
}
for (const metric of METRICS) {
if (currentBoundary.thresholds?.[metric] < baselineBoundary.thresholds?.[metric]) {
errors.push(`${fullId} ${metric} threshold cannot decrease`);
}
}
for (const pattern of baselineBoundary.include ?? []) {
if (!currentBoundary.include?.includes(pattern)) {
errors.push(`${fullId} cannot remove governed include pattern ${pattern}`);
}
}
}
for (const field of ['testFiles', 'triggerPatterns']) {
for (const input of baselinePackage.runner?.[field] ?? []) {
if (!currentPackage.runner?.[field]?.includes(input)) {
errors.push(`${baselinePackage.id} cannot remove governed runner ${field} input ${input}`);
}
}
}
}
return errors;
}
export function validateCoveragePolicy({
policy,
baselinePolicy,
packageJson,
workflow,
configs,
fileExists = () => true,
today = new Date().toISOString().slice(0, 10),
}) {
const errors = [];
const packageIds = policy.packages?.map(({ id }) => id) ?? [];
const coverageJob = workflow.match(
/(?:^|\n) {2}critical-path-coverage:\n[\s\S]*?(?=\n {2}[a-zA-Z0-9_-]+:\n|$)/
)?.[0];
if (policy.schemaVersion !== 'critical-path-coverage/v1') {
errors.push('coverage policy must use critical-path-coverage/v1');
}
if (JSON.stringify(packageIds) !== JSON.stringify(EXPECTED_PACKAGES)) {
errors.push(`coverage policy packages must be ${EXPECTED_PACKAGES.join(', ')}`);
}
for (const metric of METRICS) {
if (policy.longTermTarget?.[metric] < 80) {
errors.push(`long-term ${metric} target must be at least 80%`);
}
}
const boundaryIds = new Set();
for (const packagePolicy of policy.packages ?? []) {
if (packagePolicy.report !== `coverage/${packagePolicy.id}/coverage-summary.json`) {
errors.push(`${packagePolicy.id} must use its canonical machine-readable coverage summary`);
}
for (const testFile of packagePolicy.runner?.testFiles ?? []) {
if (
path.posix.normalize(testFile) !== testFile ||
path.posix.isAbsolute(testFile) ||
GLOB_PATTERN.test(testFile) ||
!/\.test\.[cm]?[jt]sx?$/.test(testFile)
) {
errors.push(`${packagePolicy.id} coverage test files must be exact test paths`);
}
if (!fileExists(`${packagePolicy.id}/${testFile}`)) {
errors.push(`${packagePolicy.id} governed coverage test file does not exist: ${testFile}`);
}
}
for (const boundary of packagePolicy.boundaries ?? []) {
const fullId = `${packagePolicy.id}/${boundary.id}`;
if (boundaryIds.has(fullId)) errors.push(`duplicate boundary ${fullId}`);
boundaryIds.add(fullId);
if (
!boundary.description ||
!Array.isArray(boundary.include) ||
boundary.include.length === 0
) {
errors.push(`${fullId} requires a description and include patterns`);
}
for (const metric of METRICS) {
const threshold = boundary.thresholds?.[metric];
if (typeof threshold !== 'number' || threshold <= 0 || threshold > 100) {
errors.push(`${fullId} ${metric} threshold must be between 0 and 100`);
}
}
for (const exception of boundary.exceptions ?? []) {
errors.push(...coverageExceptionErrors(exception, fullId, today));
}
}
}
if (!packageJson.scripts?.['test:coverage']?.includes('scripts/run-coverage.mjs')) {
errors.push('package.json must expose the documented test:coverage command');
}
if (!packageJson.devDependencies?.['@vitest/coverage-v8']) {
errors.push('the root workspace must own @vitest/coverage-v8');
}
if (!workflow.includes('critical-path-coverage:')) {
errors.push('CI must define the Critical Path Coverage job');
}
if (!workflow.includes('needs.select-tests.outputs.coverage_packages')) {
errors.push('CI coverage must use the deterministic package selection output');
}
if (!workflow.includes('pnpm test:coverage --packages')) {
errors.push('CI must execute the documented coverage command');
}
if (!workflow.includes('COVERAGE_BASE_REF:')) {
errors.push('CI must compare coverage policy and changed critical files with the base commit');
}
if (!workflow.includes('needs.select-tests.outputs.base_sha')) {
errors.push('CI coverage must use the selector base SHA for every event type');
}
if (!coverageJob?.includes('fetch-depth: 0')) {
errors.push('CI coverage checkout must fetch the base commit used by coverage ratchets');
}
if (!workflow.includes('if: always() && needs.select-tests.outputs.coverage_packages')) {
errors.push('CI must preserve coverage artifacts after a failed ratchet');
}
if (!workflow.includes('actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a')) {
errors.push('CI must upload coverage with the immutable approved action');
}
for (const packageId of EXPECTED_PACKAGES) {
const config = configs[packageId] ?? '';
if (!config.includes("provider: 'v8'") || !config.includes('all: true')) {
errors.push(`${packageId} coverage must use V8 and include untested source`);
}
if (!config.includes("'src/**/*.d.ts'")) {
errors.push(`${packageId} coverage must exclude type declarations`);
}
for (const pattern of [
'src/**/__fixtures__/**',
'src/**/fixtures/**',
'src/**/generated/**',
'src/**/*.generated.*',
'src/**/types.ts',
'src/types/**/*.ts',
]) {
if (!config.includes(pattern)) errors.push(`${packageId} coverage must exclude ${pattern}`);
}
}
for (const packageId of ['server', 'web']) {
if (
(policy.packages.find(({ id }) => id === packageId)?.runner?.testFiles?.length ?? 0) === 0
) {
errors.push(`${packageId} critical coverage must use exact test files`);
}
}
errors.push(...compareCoveragePolicy(policy, baselinePolicy));
return errors;
}
function readBaselinePolicy(repoRoot) {
const baselineRef = process.env.COVERAGE_BASE_REF;
if (!baselineRef) return undefined;
if (!COMMIT_SHA_PATTERN.test(baselineRef)) {
throw new Error('COVERAGE_BASE_REF must be a hexadecimal commit ID.');
}
execFileSync('git', ['cat-file', '-e', `${baselineRef}^{commit}`], {
cwd: repoRoot,
stdio: ['ignore', 'ignore', 'ignore'],
});
let baselineText;
try {
baselineText = execFileSync(
'git',
['show', `${baselineRef}:docs/testing/critical-path-coverage.json`],
{
cwd: repoRoot,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
}
);
} catch {
return undefined;
}
return JSON.parse(baselineText);
}
async function main(repoRoot = process.cwd()) {
const [policy, packageJson, workflow, ...configTexts] = await Promise.all([
readFile(path.join(repoRoot, 'docs/testing/critical-path-coverage.json'), 'utf8').then(
JSON.parse
),
readFile(path.join(repoRoot, 'package.json'), 'utf8').then(JSON.parse),
readFile(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8'),
...EXPECTED_PACKAGES.map((packageId) =>
readFile(path.join(repoRoot, packageId, 'vitest.config.ts'), 'utf8')
),
]);
const configs = Object.fromEntries(
EXPECTED_PACKAGES.map((packageId, index) => [packageId, configTexts[index]])
);
const errors = validateCoveragePolicy({
policy,
baselinePolicy: readBaselinePolicy(repoRoot),
packageJson,
workflow,
configs,
fileExists: (relativePath) => existsSync(path.join(repoRoot, relativePath)),
});
if (errors.length > 0) {
throw new Error(`Coverage policy check failed:\n- ${errors.join('\n- ')}`);
}
console.log(
`Critical-path coverage policy covers ${EXPECTED_PACKAGES.length} packages and ${policy.packages.reduce((sum, packagePolicy) => sum + packagePolicy.boundaries.length, 0)} boundaries.`
);
}
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
if (isMain) {
main().catch((error) => {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
});
}