ms-ai-architect/scripts/kb-eval/lib/g7-queue.mjs
Kjell Tore Guttormsen 59c6c280b1 feat(ms-ai-architect): G7 lukket som mekanisme — navngitt review-kø (form b), valgt på måling
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]
2026-08-03 21:10:00 +02:00

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 };
}