/** * G7 review queue — the named queue into the human review phase. * * G7 is the gap for corrections that are RIGHT but larger than the O2 envelope * (one locator + deletion-only). Measured in R11 §9.6: of the four subtractions * applied in 957ebef, two left a residue. Residues are the normal by-product of * a delete-only envelope, not an exception — and two of the five members are * replacements rather than multi-locator cases, which a deletion-oriented O4 * class would not have fixed. Hence a queue (form b), ratified 2026-08-03. * * This module validates the queue. It deliberately does NOT apply anything: * every entry here is by definition outside the machine-appliable envelope. * * Anchors are verbatim strings, never line numbers — line numbers drift, and * §9.4 measured `line` ≠ `real_line` in 9 of 17 records. An open entry whose * anchor no longer occurs is reported as drift rather than quietly passing, * because the whole purpose of the queue is that a defect cannot fall out of * the programme unnoticed. */ export const ENTRY_CLASSES = new Set(['multi-locator', 'replacement']); export const ENTRY_STATES = new Set(['open', 'resolved']); const REQUIRED = ['id', 'file', 'class', 'status', 'raised', 'summary', 'evidence', 'anchors']; const isAnchor = (a) => typeof a === 'string' || (a !== null && typeof a === 'object' && typeof a.file === 'string' && typeof a.text === 'string'); /** * @param {unknown} entries queue entries * @param {(path: string) => string} readFile * @returns {{ ok: boolean, findings: Array<{id?: string, kind: string, message: string}> }} */ export function validateQueue(entries, readFile) { const findings = []; if (!Array.isArray(entries)) { return { ok: false, findings: [{ kind: 'schema', message: 'queue must be an array of entries' }] }; } const seen = new Set(); for (const entry of entries) { const id = typeof entry?.id === 'string' ? entry.id : ''; const missing = REQUIRED.filter((f) => entry?.[f] === undefined); if (missing.length > 0) { findings.push({ id, kind: 'schema', message: `missing required field(s): ${missing.join(', ')}` }); continue; } if (seen.has(entry.id)) { findings.push({ id, kind: 'schema', message: `duplicate id: ${entry.id}` }); continue; } seen.add(entry.id); if (!ENTRY_CLASSES.has(entry.class)) { findings.push({ id, kind: 'schema', message: `unknown class: ${entry.class}` }); continue; } if (!ENTRY_STATES.has(entry.status)) { findings.push({ id, kind: 'schema', message: `unknown status: ${entry.status}` }); continue; } // A resolved entry has to say what closed it. Without that, "resolved" is // indistinguishable from "quietly dropped" — the exact failure G7 exists to // prevent. Resolved entries are exempt from anchor checking, since a real // fix is expected to have changed the text the anchor pointed at. if (entry.status === 'resolved') { if (typeof entry.resolution !== 'string' || entry.resolution.trim() === '') { findings.push({ id, kind: 'schema', message: 'a resolved entry must record a resolution' }); } continue; } if (!Array.isArray(entry.anchors) || entry.anchors.length === 0) { findings.push({ id, kind: 'schema', message: 'an open entry must carry at least one anchor' }); continue; } // An anchor is either a verbatim string, checked against entry.file, or a // { file, text } pair checked against ITS OWN file. The pair form exists for // class-wide entries — one record for a defect that lives in many files. // Measured 2026-08-11: booking such a class as a single-file entry hid 41 of // 42 locators from this check, which is the 795d494 failure, a locator alive // only in prose while both gates go green. A malformed pair is a schema fault // rather than an anchor that quietly matches nothing. const malformed = entry.anchors.filter((a) => !isAnchor(a)); if (malformed.length > 0) { findings.push({ id, kind: 'schema', message: `anchor must be a verbatim string or a { file, text } pair: ${malformed .map((m) => JSON.stringify(m)) .join(', ')}`, }); continue; } const byFile = new Map(); for (const a of entry.anchors) { const target = typeof a === 'string' ? entry.file : a.file; const text = typeof a === 'string' ? a : a.text; if (!byFile.has(target)) byFile.set(target, []); byFile.get(target).push(text); } for (const [target, texts] of byFile) { let text; try { text = readFile(target); } catch { findings.push({ id, kind: 'file_unreadable', message: `cannot read ${target}` }); continue; } const gone = texts.filter((t) => !text.includes(t)); if (gone.length > 0) { findings.push({ id, kind: 'anchor_drift', message: `anchor no longer occurs in ${target}: ${gone.map((g) => JSON.stringify(g)).join(', ')}`, }); } } } return { ok: findings.length === 0, findings }; }