Operator ratifiserte utvidelsen 2026-08-11. Beslutning #17 (én klasse-entry) og grunnen bak #14 (ingen lokator usynlig for gaten) var begge ekte og kolliderte i skjemaet. De kolliderer ikke lenger. SKJEMAET: et anker er enten en ordrett STRENG, sjekket mot entry.file som foer, eller et { file, text }-PAR sjekket mot SIN EGEN fil. Bakoverkompatibelt — alle 47 eksisterende entries er uroert. Et misformet par er en schema-feil, ikke et anker som stille matcher ingenting. TDD, og testene maatte skjerpes foer de var ekte: fem tester skrevet og kjoert FOERST. To av dem PASSERTE mot gammel kode — av feil grunn: den gamle koden stringify-er ankerobjektet inn i drift-meldingen, saa /other\.md/ traff tilfeldig. En test som ikke kan feile beviser ingenting. Begge fikk en diskriminator (meldingen skal IKKE navne entry.file) og feilet deretter. 5 feilende -> implementasjon -> 20/20 i fila, 1052/1052 i suiten. idx-26an: 42 { file, text }-ankere, ett per medlem, hvert re-verifisert ordrett OG unikt i sin egen fil med split-telling foer skriving. MEKANISMEN ER BEVIST, IKKE ANTATT: kjoert mot den ekte validatoren med ett medlems anker fjernet i en lesestubb — ok=false, ett anchor_drift, riktig entry-id, og meldingen navner agent-evaluation-testing-frameworks.md. Det er nettopp garantien #14 fantes for, naa baaret av én entry i stedet for 42. MAALT HASARD BOKFOERT I ENTRYEN: to av de 42 ankertekstene er strenge prefikser av andre medlemmers ankere («**Total MCP calls:** 6» i ai-services-cost-optimization.md, «**MCP Calls:** 3» i reserved-capacity-planning.md). Hver er unik i SIN fil, saa koen er trygg — en kryss-fil search-and-replace er det ikke. Slett per fil, og rest-soek etter hver edit. Koen: 47 -> 48 entries (28 open, 20 resolved). Bokfoeringen av #17 er dermed komplett: 1 klasse-entry + 16 individuelle = 17. Ingen korpusfil roert.
132 lines
5.1 KiB
JavaScript
132 lines
5.1 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'];
|
|
|
|
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 : '<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;
|
|
}
|
|
|
|
// 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 };
|
|
}
|