100 lines
2.8 KiB
TypeScript
100 lines
2.8 KiB
TypeScript
#!/usr/bin/env tsx
|
|
import { promises as fs } from "fs";
|
|
|
|
type Check =
|
|
| {
|
|
id: string;
|
|
category: string;
|
|
description: string;
|
|
op: "equalsOrTrue" | "includesAll";
|
|
path?: string;
|
|
expected?: any;
|
|
severity: "blocker" | "major" | "minor";
|
|
conditions?: Check[];
|
|
}
|
|
| {
|
|
id: string;
|
|
category: string;
|
|
description: string;
|
|
op: "and" | "or";
|
|
severity: "blocker" | "major" | "minor";
|
|
conditions: Check[];
|
|
};
|
|
|
|
const getPath = (obj: any, path?: string) => {
|
|
if (!path) return undefined;
|
|
return path.split(".").reduce((o, k) => (o ? o[k] : undefined), obj);
|
|
};
|
|
|
|
const evalCheck = (facts: any, c: Check): { ok: boolean; reason?: string } => {
|
|
switch (c.op) {
|
|
case "equalsOrTrue": {
|
|
const v = getPath(facts, c.path);
|
|
const ok = c.expected === true ? !!v : v === c.expected;
|
|
return ok
|
|
? { ok }
|
|
: {
|
|
ok,
|
|
reason: `Expected ${c.path} == ${JSON.stringify(
|
|
c.expected
|
|
)} but got ${JSON.stringify(v)}`,
|
|
};
|
|
}
|
|
case "includesAll": {
|
|
const v = String(getPath(facts, c.path) ?? "");
|
|
const need = (c.expected ?? []) as string[];
|
|
const miss = need.filter((n) => !v.includes(n));
|
|
return miss.length === 0
|
|
? { ok: true }
|
|
: {
|
|
ok: false,
|
|
reason: `Missing substrings ${miss.join(", ")} in ${c.path}`,
|
|
};
|
|
}
|
|
case "and": {
|
|
const results = (c.conditions ?? []).map((x) => evalCheck(facts, x));
|
|
const allOk = results.every((r) => r.ok);
|
|
return allOk
|
|
? { ok: true }
|
|
: { ok: false, reason: results.find((r) => !r.ok)?.reason };
|
|
}
|
|
case "or": {
|
|
const results = (c.conditions ?? []).map((x) => evalCheck(facts, x));
|
|
const anyOk = results.some((r) => r.ok);
|
|
return anyOk
|
|
? { ok: true }
|
|
: { ok: false, reason: "All OR branches failed" };
|
|
}
|
|
default:
|
|
return { ok: false, reason: "Unknown op" };
|
|
}
|
|
};
|
|
|
|
(async () => {
|
|
const facts = JSON.parse(
|
|
await fs.readFile("conductor/artifacts/facts.json", "utf8")
|
|
);
|
|
const matrix = JSON.parse(
|
|
await fs.readFile("conductor/rules/regression-matrix.json", "utf8")
|
|
);
|
|
|
|
const failures: { id: string; severity: string; reason: string }[] = [];
|
|
for (const c of matrix.checks as Check[]) {
|
|
const res = evalCheck(facts, c);
|
|
if (!res.ok && (c.severity === "blocker" || c.severity === "major")) {
|
|
failures.push({
|
|
id: c.id,
|
|
severity: c.severity,
|
|
reason: res.reason ?? "failed",
|
|
});
|
|
}
|
|
}
|
|
if (failures.length) {
|
|
console.error("⛔ Regression Shield failed:");
|
|
for (const f of failures)
|
|
console.error(` - [${f.severity}] ${f.id}: ${f.reason}`);
|
|
process.exit(2);
|
|
}
|
|
console.log("✅ Regression Shield passed");
|
|
})();
|