#!/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; }