// PreferenceSample — entry in exports/preference/chosen_rejected.jsonl. // Source: real disagreements (audit_discrepancies, scrum ladder retries). // Validator pins: chosen != rejected, both source_run_ids present, reason // is non-empty. No synthesized preferences. import { ValidationResult, requireString, requireIsoTimestamp, requireProvenance, } from "./types"; export const PREFERENCE_SAMPLE_SCHEMA_VERSION = 1; export interface PreferenceSample { schema_version: number; prompt: string; chosen: string; rejected: string; reason: string; // why chosen > rejected — must be non-empty source_run_ids: { chosen: string; rejected: string; }; exported_at: string; provenance: { source_file: string; line_offset?: number; sig_hash: string; recorded_at: string }; } export function validatePreferenceSample(input: unknown): ValidationResult { const errors: string[] = []; if (typeof input !== "object" || input === null) return { valid: false, errors: ["expected object"] }; const r = input as Record; let ok = true; if (r.schema_version !== PREFERENCE_SAMPLE_SCHEMA_VERSION) { errors.push(`schema_version: expected ${PREFERENCE_SAMPLE_SCHEMA_VERSION}, got ${JSON.stringify(r.schema_version)}`); ok = false; } ok = requireString(r.prompt, "prompt", errors) && ok; ok = requireString(r.chosen, "chosen", errors) && ok; ok = requireString(r.rejected, "rejected", errors) && ok; ok = requireString(r.reason, "reason", errors) && ok; ok = requireIsoTimestamp(r.exported_at, "exported_at", errors) && ok; ok = requireProvenance(r.provenance, "provenance", errors) && ok; // Self-pairing guard. if (r.chosen === r.rejected && typeof r.chosen === "string") { errors.push("chosen and rejected must differ — preference data needs a real disagreement"); ok = false; } if (typeof r.reason === "string" && (r.reason as string).trim().length === 0) { errors.push("reason: must be non-whitespace (every preference needs WHY chosen > rejected)"); ok = false; } if (typeof r.source_run_ids !== "object" || r.source_run_ids === null) { errors.push("source_run_ids: expected object {chosen, rejected}"); ok = false; } else { const s = r.source_run_ids as Record; ok = requireString(s.chosen, "source_run_ids.chosen", errors) && ok; ok = requireString(s.rejected, "source_run_ids.rejected", errors) && ok; if (s.chosen === s.rejected && typeof s.chosen === "string") { errors.push("source_run_ids.chosen and .rejected must differ — same run can't disagree with itself"); ok = false; } } if (!ok) return { valid: false, errors }; return { valid: true, value: r as unknown as PreferenceSample }; }