Files
pikasTech-HWLAB/scripts/validate-dev-gate-report.mjs
T
2026-05-24 02:31:36 +00:00

116 lines
4.2 KiB
JavaScript

#!/usr/bin/env node
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { ensureNotRepoReportsPath } from "./src/report-paths.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
function assertObject(value, label) {
assert.ok(value && typeof value === "object" && !Array.isArray(value), `${label} must be an object`);
}
function assertString(value, label) {
assert.equal(typeof value, "string", `${label} must be a string`);
assert.ok(value.trim().length > 0, `${label} must not be empty`);
}
function assertBoolean(value, label) {
assert.equal(typeof value, "boolean", `${label} must be a boolean`);
}
function repoRelative(inputPath) {
const absolute = path.isAbsolute(inputPath) ? inputPath : path.resolve(process.cwd(), inputPath);
return path.relative(repoRoot, absolute).split(path.sep).join("/");
}
async function readJson(inputPath) {
ensureNotRepoReportsPath(repoRoot, inputPath, "validation input");
const absolute = path.isAbsolute(inputPath) ? inputPath : path.resolve(process.cwd(), inputPath);
const raw = await readFile(absolute, "utf8");
return {
path: absolute,
relativePath: repoRelative(absolute),
report: JSON.parse(raw)
};
}
function validateReportShape(report, label) {
assertObject(report, label);
assertString(report.reportKind ?? report.taskId ?? report.kind ?? "temporary-json", `${label}.kind`);
if (Object.hasOwn(report, "devOnly")) assertBoolean(report.devOnly, `${label}.devOnly`);
if (Object.hasOwn(report, "prodDisabled")) assertBoolean(report.prodDisabled, `${label}.prodDisabled`);
if (Array.isArray(report.blockers)) {
for (const [index, blocker] of report.blockers.entries()) {
assertObject(blocker, `${label}.blockers[${index}]`);
assertString(blocker.scope ?? blocker.code ?? blocker.type ?? "blocker", `${label}.blockers[${index}].scope`);
}
}
if (Array.isArray(report.validationCommands)) {
for (const [index, command] of report.validationCommands.entries()) {
assertString(command, `${label}.validationCommands[${index}]`);
}
}
if (Object.hasOwn(report, "dryRunPlan")) {
assertObject(report.dryRunPlan, `${label}.dryRunPlan`);
if (Object.hasOwn(report.dryRunPlan, "dryRunCallsLiveEndpoints")) {
assert.equal(report.dryRunPlan.dryRunCallsLiveEndpoints, false, `${label}.dryRunPlan.dryRunCallsLiveEndpoints`);
}
if (Object.hasOwn(report.dryRunPlan, "liveWriteWillRun")) {
assert.equal(report.dryRunPlan.liveWriteWillRun, false, `${label}.dryRunPlan.liveWriteWillRun`);
}
if (Object.hasOwn(report.dryRunPlan, "evidenceLevel")) {
assert.equal(report.dryRunPlan.evidenceLevel, "DRY-RUN", `${label}.dryRunPlan.evidenceLevel`);
}
assertObject(report.liveOperation, `${label}.liveOperation`);
assert.notEqual(
report.liveOperation.status,
"pass",
`${label}.liveOperation.status cannot be pass when only a DRY-RUN plan is present`
);
for (const field of ["operationId", "traceId", "auditId", "evidenceId"]) {
if (Object.hasOwn(report.liveOperation, field)) {
assert.equal(report.liveOperation[field], "not_observed", `${label}.liveOperation.${field} must remain not_observed for DRY-RUN evidence`);
}
}
}
}
async function main(argv) {
if (argv.length === 0) {
process.stdout.write(`${JSON.stringify({
ok: true,
status: "skipped",
reason: "repository report files are disabled; pass explicit temporary JSON paths to validate them"
})}\n`);
return;
}
const results = [];
for (const inputPath of argv) {
const { relativePath, report } = await readJson(inputPath);
validateReportShape(report, relativePath);
results.push(relativePath);
}
process.stdout.write(`${JSON.stringify({
ok: true,
status: "pass",
validated: results.length,
files: results
})}\n`);
}
try {
await main(process.argv.slice(2));
} catch (error) {
process.stderr.write(`${JSON.stringify({
ok: false,
status: "blocked",
error: error instanceof Error ? error.message : String(error)
})}\n`);
process.exitCode = 1;
}