Operatør-ratifisert 2026-08-03. G7 var gapet for korreksjoner som er RIKTIGE men større enn O2-konvolutten (én lokator + kun-sletting). Uten eier falt de stille ut: O2-triagen avviser dem, O3 dekker dem ikke, og review-sporet hadde ingen inngangskø. Formvalget ble tatt på §9.6-målingen, ikke på preferanse: - 2 av 4 anvendte subtraksjoner etterlot en rest -> rester er delete-only- konvoluttens NORMALE biprodukt, ikke et unntak. - 2 av 5 medlemmer (idx 17, 33) er ERSTATNINGER, ikke fler-lokator. En delete-orientert O4-klasse med egen retur-kontrakt ville ikke fikset dem — altså feil dimensjonert mot evidensen. Køen absorberer begge klasser. Artefakter (TDD, test-først): - data/g7-review-queue.json — 6 entries, tracked - lib/g7-queue.mjs — validateQueue, lukkede vokabular - check-g7-queue.mjs — exit 1 ved drift eller skjemafeil - tests/kb-eval/test-g7-queue.test.mjs — 15 tester Kontrakten: ankere er ORDRETTE strenger, aldri linjenummer (line != real_line i 9 av 17 R11-records). En åpen entry hvis anker slutter å matche gir anchor_drift og exit 1 — den kan ikke falle stille ut, som er hele hensikten. En resolved entry MÅ føre resolution, ellers er "resolved" ikke til å skille fra "stille droppet". Ingenting i køen er maskin-anvendbart per definisjon. Innhold: 5 åpne (26, 27, 33, 36, 18), 1 lukket (17). idx 27 kom hit ved å falle ut av O2 på cond 3; idx 26 ved operatørens avvisning av delvis fiks. Suite 1047/1047. [skip-docs]
100 lines
3.7 KiB
JavaScript
100 lines
3.7 KiB
JavaScript
/**
|
|
* 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'];
|
|
|
|
/**
|
|
* @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 : '<no 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;
|
|
}
|
|
|
|
let text;
|
|
try {
|
|
text = readFile(entry.file);
|
|
} catch {
|
|
findings.push({ id, kind: 'file_unreadable', message: `cannot read ${entry.file}` });
|
|
continue;
|
|
}
|
|
|
|
const gone = entry.anchors.filter((a) => !text.includes(a));
|
|
if (gone.length > 0) {
|
|
findings.push({
|
|
id,
|
|
kind: 'anchor_drift',
|
|
message: `anchor no longer occurs in ${entry.file}: ${gone.map((g) => JSON.stringify(g)).join(', ')}`,
|
|
});
|
|
}
|
|
}
|
|
|
|
return { ok: findings.length === 0, findings };
|
|
}
|