feat(ms-ai-architect): G7-ankere kan baere sin egen fil — klasse-entryen for de 42 er skrevet, og #17 og #14 er begge oppfylt

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.
This commit is contained in:
Kjell Tore Guttormsen 2026-08-11 22:23:13 +02:00
commit 8ea54ec00e
3 changed files with 287 additions and 12 deletions

View file

@ -23,6 +23,9 @@ 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
@ -78,21 +81,50 @@ export function validateQueue(entries, readFile) {
continue;
}
let text;
try {
text = readFile(entry.file);
} catch {
findings.push({ id, kind: 'file_unreadable', message: `cannot read ${entry.file}` });
// 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 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(', ')}`,
});
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(', ')}`,
});
}
}
}