-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathverify.mjs
More file actions
80 lines (67 loc) · 3.5 KB
/
Copy pathverify.mjs
File metadata and controls
80 lines (67 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/usr/bin/env node
// verify.mjs — State Insurance AI Disclosure Tracker verifier.
import { readFileSync, readdirSync } from "node:fs";
import { Ajv2020 } from "ajv/dist/2020.js";
import addFormats from "ajv-formats";
const LEGAL = {
"proposed": new Set(["in-comment-period", "adopted-bulletin", "enacted-awaiting-effective", "withdrawn"]),
"in-comment-period": new Set(["adopted-bulletin", "enacted-awaiting-effective", "withdrawn"]),
"adopted-bulletin": new Set(["effective", "amended", "superseded", "sunset", "withdrawn"]),
"enacted-awaiting-effective":new Set(["effective", "amended", "withdrawn"]),
"effective": new Set(["amended", "superseded", "sunset"]),
"amended": new Set(["effective", "amended", "superseded", "sunset"]),
"superseded": new Set(["amended", "effective"]),
"sunset": new Set([]),
"withdrawn": new Set([])
};
function loadJson(p) { return JSON.parse(readFileSync(p, "utf8")); }
function main() {
const schema = loadJson(new URL("../schema/disclosure-event.schema.json", import.meta.url));
const ajv = new Ajv2020({ allErrors: true, strict: false });
addFormats(ajv);
const validate = ajv.compile(schema);
const statesDir = new URL("../states/", import.meta.url);
let files;
try { files = readdirSync(statesDir).filter((n) => n.endsWith(".ndjson")); }
catch (e) { console.error(`could not read states/: ${e.message}`); process.exit(4); }
if (files.length === 0) { console.log("OK — 0 state streams."); return; }
let schemaErrors = 0, transitionErrors = 0, supersedeErrors = 0, total = 0;
for (const file of files.sort()) {
const raw = readFileSync(new URL(`./${file}`, statesDir), "utf8");
const lines = raw.split(/\r?\n/).filter((l) => l.trim() !== "");
const events = [];
for (const [i, line] of lines.entries()) {
try { events.push(JSON.parse(line)); }
catch (e) { console.error(`${file}:${i+1} not JSON — ${e.message}`); schemaErrors++; }
}
total += events.length;
for (const [i, ev] of events.entries()) {
if (!validate(ev)) {
schemaErrors++;
console.error(`${file}:${i+1} (${ev.event_id ?? "?"}): schema errors`);
for (const e of validate.errors ?? []) console.error(` - ${e.instancePath || "/"} ${e.message}`);
}
}
const seenIds = new Set();
let prev = null;
for (const [i, ev] of events.entries()) {
if (prev && !LEGAL[prev]?.has(ev.lifecycle_state)) {
transitionErrors++;
console.error(`${file}:${i+1} (${ev.event_id}): illegal transition ${prev} -> ${ev.lifecycle_state}`);
}
if ((ev.lifecycle_state === "superseded" || ev.lifecycle_state === "amended") && ev.supersedes_event_id) {
if (!seenIds.has(ev.supersedes_event_id)) {
supersedeErrors++;
console.error(`${file}:${i+1} (${ev.event_id}): supersedes_event_id ${ev.supersedes_event_id} not found earlier in stream`);
}
}
seenIds.add(ev.event_id);
prev = ev.lifecycle_state;
}
}
if (schemaErrors > 0) { console.error(`schema validation failed: ${schemaErrors}/${total}`); process.exit(1); }
if (transitionErrors > 0){ console.error(`illegal transitions: ${transitionErrors}`); process.exit(2); }
if (supersedeErrors > 0) { console.error(`supersedes_event_id invalid: ${supersedeErrors}`); process.exit(3); }
console.log(`OK — ${total} events across ${files.length} state stream(s) validated, transitions legal.`);
}
main();