mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-17 12:22:34 +03:00
Adds a shadow release-acceptance report alongside release-green: an inventory/reduce/oracle pipeline under `scripts/quality/release-acceptance/` with a JSON schema, fixtures and a workflow that uploads the report as an artifact. Contained by design, which is why it merges as-is: it runs only on push to `release/v*` and on manual dispatch (never on pull requests), the step is `continue-on-error`, `permissions: contents: read`, `persist-credentials: false`, and it consumes no secrets. Nothing in the product changes; the report is advisory until we decide to promote it. Validated as a combined board first (this PR merged with the 11 siblings of the same batch on the release tip): eslint on every changed file with the suppressions file, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 275 passing / 0 failing focused node:test cases across the 28 test files the batch touches. Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run. Thanks @HouMinXi!
82 lines
2.7 KiB
JavaScript
82 lines
2.7 KiB
JavaScript
#!/usr/bin/env node
|
|
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import Ajv from "ajv";
|
|
import { reduce } from "./release-acceptance/reduce.mjs";
|
|
|
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
|
|
function loadJson(p) {
|
|
return JSON.parse(readFileSync(p, "utf8"));
|
|
}
|
|
|
|
export function exitFor(verdict) {
|
|
if (verdict === "VERIFIED") return 0;
|
|
if (verdict === "FAILED") return 1;
|
|
return 2;
|
|
}
|
|
|
|
export function reduceManifests(plan, manifests) {
|
|
const records = [];
|
|
for (const m of manifests) {
|
|
if (Array.isArray(m.gates)) records.push(...m.gates);
|
|
else records.push(m);
|
|
}
|
|
return reduce(plan, records);
|
|
}
|
|
|
|
export function validateReport(report, schema) {
|
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
const validate = ajv.compile(schema);
|
|
return { ok: validate(report), errors: validate.errors };
|
|
}
|
|
|
|
function parseArgs(argv) {
|
|
const out = { plan: null, manifests: null, out: join(ROOT, "release-acceptance-report.json") };
|
|
for (let i = 2; i < argv.length; i++) {
|
|
if (argv[i] === "--plan") out.plan = argv[++i];
|
|
else if (argv[i] === "--manifests") out.manifests = argv[++i];
|
|
else if (argv[i] === "--out") out.out = argv[++i];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export async function main(argv = process.argv) {
|
|
const args = parseArgs(argv);
|
|
const plan = loadJson(args.plan);
|
|
const schema = loadJson(join(ROOT, "config/quality/release-acceptance.schema.json"));
|
|
const files = readdirSync(args.manifests)
|
|
.filter((f) => f.endsWith(".json"))
|
|
.map((f) => loadJson(join(args.manifests, f)));
|
|
const reduced = reduceManifests(plan, files);
|
|
const report = {
|
|
schema_version: 1,
|
|
identity: plan.identity,
|
|
required_gates: plan.required_gates ?? [],
|
|
gates: reduced.gates,
|
|
evidence_errors: reduced.evidence_errors,
|
|
verdict: reduced.verdict,
|
|
artifact: plan.artifact ?? null,
|
|
};
|
|
const { ok, errors } = validateReport(report, schema);
|
|
if (!ok) {
|
|
if (report.verdict !== "FAILED") report.verdict = "UNVERIFIED";
|
|
const gate =
|
|
Array.isArray(plan.required_gates) && plan.required_gates.length > 0
|
|
? plan.required_gates[0]
|
|
: { gate_id: "schema", suite_id: null, shard_index: null, shard_total: null };
|
|
report.evidence_errors = [
|
|
...(report.evidence_errors ?? []),
|
|
{ code: "schema_invalid", gate, detail: JSON.stringify(errors) },
|
|
];
|
|
}
|
|
mkdirSync(dirname(args.out), { recursive: true });
|
|
writeFileSync(args.out, JSON.stringify(report, null, 2) + "\n");
|
|
return exitFor(report.verdict);
|
|
}
|
|
|
|
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
main().then((code) => process.exit(code));
|
|
}
|