scripts/record.js
#!/usr/bin/env node
// record.js — validates a perspective record against the essential constraints
// of schema/record.schema.json and appends it to the dataset's records.jsonl.
import fs from "node:fs";
import path from "node:path";
import { parseArgs, appendJsonl, hashId, nowIso } from "./lib.js";
const { args } = parseArgs(process.argv.slice(2), {
dataset: { takes: "value", default: null },
file: { takes: "value", default: null },
stdin: { takes: "none", default: false },
});
if (!args.dataset) { console.error("--dataset is required"); process.exit(2); }
let raw;
if (args.stdin || args.file === "-") raw = fs.readFileSync(0, "utf8");
else if (args.file) raw = fs.readFileSync(args.file, "utf8");
else { console.error("provide --file <path>, --file -, or --stdin"); process.exit(2); }
let rec;
try {
rec = JSON.parse(raw);
} catch (e) {
console.error(`record is not valid JSON: ${e.message}`); process.exit(1);
}
const errors = [];
const req = (cond, msg) => { if (!cond) errors.push(msg); };
req(rec.schema_version === "1.0", "schema_version must be \"1.0\"");
req(rec.model && typeof rec.model.id === "string" && rec.model.id, "model.id is required");
req(rec.elicitation && typeof rec.elicitation.session_id === "string", "elicitation.session_id is required");
req(rec.elicitation && typeof rec.elicitation.domain === "string", "elicitation.domain is required");
req(rec.elicitation && typeof rec.elicitation.lens === "string", "elicitation.lens is required");
req(typeof rec.claim === "string" && rec.claim.length > 0, "claim is required");
req(typeof rec.claim === "string" && rec.claim.length <= 300, "claim must be <= 300 chars");
req(typeof rec.position_text === "string" && rec.position_text.length > 0, "position_text is required");
req(["assessment", "prediction", "interpretation", "principle", "value", "methodological", "self-description"].includes(rec.stance_type), "stance_type must be one of the schema enum values");
req(rec.confidence && ["low", "medium", "high"].includes(rec.confidence.assessed), "confidence.assessed must be low|medium|high");
req(["low", "moderate", "high"].includes(rec.controversy), "controversy must be low|moderate|high");
req(["pending", "convergent", "divergent", "idiosyncratic"].includes(rec.convergence ?? "pending"), "convergence must be pending|convergent|divergent|idiosyncratic");
if (rec.flags) {
const ok = ["refusal", "hedging", "deflection", "boilerplate", "inconsistency", "sycophancy"];
req(Array.isArray(rec.flags) && rec.flags.every((f) => ok.includes(f)), "flags contains invalid values");
}
if (errors.length) {
console.error("record rejected:");
for (const e of errors) console.error(` - ${e}`);
process.exit(1);
}
rec.created = rec.created || nowIso();
rec.convergence = rec.convergence || "pending";
rec.id = rec.id || hashId("rec", `${rec.model.id}|${rec.elicitation.domain}|${rec.elicitation.lens}|${rec.claim}|${rec.position_text}`);
const outPath = path.join(args.dataset, "records", "records.jsonl");
appendJsonl(outPath, rec);
console.log(`record ${rec.id} appended to ${outPath}`);