ms-ai-architect/scripts/kb-update/audit-corpus-headers.mjs
Kjell Tore Guttormsen 76df39670d feat(ms-ai-architect): RX-HDR oppløst → permanent R7-ustempelbar audit-signal (premiss 10→2) [skip-docs]
Premiss-verifisering mot ground truth (simulert R7-stempler insertVerifiedFields
over 327 non-advisor-filer): RX-HDR-populasjonen kollapser fra påstått 10 til 2.
De 9 'base-felt forbi 500B'-filene er falske positiver — stempleren setter Verified
øverst, uavhengig av hvor Status/Category ligger. Kun 2 filer er ekte ustempelbare
(gpt5-gpt41-pricing-models, entra-agent-id-zero-trust): headere med fete **Status:**-
prosahaler (175B/199B) der begge stempler (Verified + Verified by, judge-v3.1) ikke
får plass i 500B. Feltomstokking løser dem ikke — verdi-slanking kreves (utenfor
RX-HDRs scope), så de folder inn i R7s eksisterende 'header-slanking kreves'-flagg-sti.

audit-corpus-headers.mjs: ny ren isStampable() kjører R7s ekte stempler med fast
representativ probe (dato + judge-v3.1); per-fil 'stampable' + aggregat 'unstampable'
+ FLAG-liste. Gjør populasjonen permanent så den ikke stille kan vokse (gap-disiplin).
+2 tester (dense→ustempelbar, clean→stampable). Suite 924→926 exit 0.
2026-07-17 03:43:04 +02:00

285 lines
13 KiB
JavaScript

#!/usr/bin/env node
// audit-corpus-headers.mjs — Spor 1 Step 1: read-only audit of the corpus header state.
//
// Measures, per non-advisor reference file and in aggregate, the empirical ground
// truth the migration plan rests on: header-region **Verified:** values (clean date
// vs stale "MCP <date>"), body-duplicates and pipe-delimited meta rows inside the
// 500-byte header window, base-field presence (English **Last updated:**, **Status:**,
// title) and the header dialect (**Category:** / **Kategori:** / pipe). Mutates nothing.
//
// Usage:
// node scripts/kb-update/audit-corpus-headers.mjs # human summary
// node scripts/kb-update/audit-corpus-headers.mjs --json # full report to stdout
// node scripts/kb-update/audit-corpus-headers.mjs --write # persist data/corpus-header-audit.json
import { readdirSync, readFileSync, existsSync, realpathSync } from 'node:fs';
import { join, relative, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { atomicWriteJson } from './lib/atomic-write.mjs';
import { insertVerifiedFields } from './lib/transform.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const PLUGIN_ROOT = join(__dirname, '..', '..');
const SKILLS_DIR = join(PLUGIN_ROOT, 'skills');
const OUT_PATH = join(__dirname, 'data', 'corpus-header-audit.json');
// The four non-advisor skills — advisor is out of audit scope (S-Cosmo: never mutated,
// and its header state is irrelevant to the Step-5/9 blast radius this audit pins).
const NON_ADVISOR_SKILLS = [
'ms-ai-engineering',
'ms-ai-governance',
'ms-ai-infrastructure',
'ms-ai-security',
];
// Mirrors kb-headers.mjs — the Port-1 contract reads only the top 500 bytes.
const HEADER_REGION_BYTES = 500;
// Base-field regexes mirror transform.mjs validateKbFile (RE_LAST_UPDATED:261 is not
// exported; replicated verbatim so the audit measures exactly what the guard reads).
const RE_TITLE = /^#\s+\S/m;
const RE_LAST_UPDATED = /\*\*Last updated:\*\*\s*[\d-]+/i;
const RE_STATUS = /\*\*Status:\*\*\s*\S/i;
// Verified value = everything after the label up to a pipe separator or line end,
// so "MCP 2026-06" survives intact while a pipe row stops at its sibling token.
const RE_VERIFIED = /\*\*Verified:\*\*\s*([^|\n]*)/;
const RE_CLEAN_DATE = /^\d{4}-\d{2}(-\d{2})?$/;
// PLAIN (non-bold) `Verified:` in the header window — the RX-KB1 poison class: a `| Verified:
// <date>` tail on the **Last updated:** line, claiming a verification the judge never made and
// INVISIBLE to the bold-only RE_VERIFIED above (this blindness was M4). The `[^*]` guard keeps
// a bold `**Verified:**` from matching (its 'V' is preceded by '*'). Surfaced as a separate
// signal so a stale plain tail can never silently re-enter the corpus (strip-stale-verified-
// pipe.mjs removes the current 87; this detects any reappearance within the non-advisor scope).
const RE_PLAIN_VERIFIED = /(?:^|[^*])Verified:/m;
// ── RX-KB1b: footer date-conflict + label-whitelist ────────────────────────
// A file that carries a header **Last updated:** date AND a norsk footer date under a
// last-updated-EQUIVALENT label ("Sist oppdatert") with a different value contradicts
// itself — it asserts two distinct last-updated dates. That is the only norsk footer
// label that can conflict with the header. LABEL WHITELIST: "Verifisert"/"Sist
// verifisert" record an INDEPENDENT axis (when content was last checked against source,
// not when the file was edited), so a differing date there is expected and is never a
// conflict. Detection is read-only and flag-to-human: the audit reports, the operator
// resolves per file against git/source (aldri-auto-fiks — a footer date can be right and
// the header stale, or vice versa; only a human can adjudicate which).
// Header **Last updated:** DATE — captured value (RE_LAST_UPDATED above only tests presence).
const RE_HEADER_LAST_UPDATED_DATE = /\*\*Last updated:\*\*\s*(\d{4}-\d{2}(?:-\d{2})?)/i;
// Any norsk footer meta-line carrying a date: "**Label:** DATE", "*Label: DATE", "Label: DATE".
// A valid month (01-12) is required, which also excludes template placeholders ([Dato],
// YYYY-MM-DD — they never match \d{4}-(0[1-9]|1[0-2])). Whole-file scan: footers live well
// outside the 500-byte header window. Group 1 = label, group 2 = date.
const RE_FOOTER_META =
/(?:^|\n)[ \t>*_-]*\*{0,2}\s*(Sist oppdatert|Sist verifisert|Verifisert)\s*:?\**\s*(\d{4}-(?:0[1-9]|1[0-2])(?:-\d{2})?)/gi;
// Only this label is last-updated-equivalent (conflict-bearing). Everything else the
// RE_FOOTER_META captures ("sist verifisert", "verifisert") is on the whitelist.
const FOOTER_LABEL_LAST_UPDATED = 'sist oppdatert';
// ── RX-HDR (dissolved → permanent audit signal): R7-unstampability ──────────
// RX-HDR was replanned as a 10-file header-slanking unit; premiss-verification collapsed
// the population to 1 — the only real class is a header so byte-dense that BOTH R7 stamps
// (**Verified:** + **Verified by:**) cannot fit inside the 500-byte window. insertVerifiedFields
// throws exactly there ("header-slanking required"), so the ground-truth test is to run the
// real stamper and catch the throw. Field-reorder cannot fix such a file (its meta lines carry
// fat prose tails needing a VALUE-slim — out of scope), so it folds into R7's existing
// "header-slanking kreves" flag path. This signal keeps the population from silently growing.
// A fixed representative stamp (deterministic; matches R7's byte-length: YYYY-MM-DD + judge-v3.1).
const STAMP_PROBE = { verified: '2026-01-01', verified_by: 'judge-v3.1' };
/** True iff R7's insertVerifiedFields can stamp this content within the 500-byte window. Pure. */
function isStampable(content) {
try {
insertVerifiedFields(content, STAMP_PROBE);
return true;
} catch {
return false;
}
}
/**
* Footer date-conflict for one file's full content. Returns null when there is no header
* Last-updated date, no last-updated-equivalent footer, or all such footers agree on the
* YYYY-MM prefix. Otherwise returns the header date and the conflicting footer date(s). Pure.
*/
function detectFooterConflict(content, headerLastUpdated) {
if (!headerLastUpdated) return null;
const footerDates = [];
for (const m of content.matchAll(RE_FOOTER_META)) {
if (m[1].toLowerCase() === FOOTER_LABEL_LAST_UPDATED) footerDates.push(m[2]);
}
if (footerDates.length === 0) return null;
const headerPrefix = headerLastUpdated.slice(0, 7);
const conflicting = footerDates.filter((d) => d.slice(0, 7) !== headerPrefix);
if (conflicting.length === 0) return null;
return { headerLastUpdated, footerDates: conflicting };
}
/** Audit a single file's content. Pure. */
function auditContent(content) {
const head = content.slice(0, HEADER_REGION_BYTES);
const verifiedMatches = [...head.matchAll(new RegExp(RE_VERIFIED.source, 'g'))];
let verified = null;
if (verifiedMatches.length > 0) {
const value = verifiedMatches[0][1].trim();
const line = head.slice(0, verifiedMatches[0].index).split('\n').length - 1;
const lineText = head.split('\n')[line] ?? '';
verified = {
value,
isDate: RE_CLEAN_DATE.test(value),
pipe: lineText.includes(' | '),
duplicateWithinHeaderRegion: verifiedMatches.length > 1,
};
}
let dialect = 'none';
if (/\*\*[^*\n]+:\*\*[^\n]* \| \*\*/.test(head)) dialect = 'pipe';
else if (/\*\*Category:\*\*/i.test(head)) dialect = 'category';
else if (/\*\*Kategori:\*\*/i.test(head)) dialect = 'kategori';
const headerLuMatch = head.match(RE_HEADER_LAST_UPDATED_DATE);
const footerConflict = detectFooterConflict(content, headerLuMatch ? headerLuMatch[1] : null);
return {
title: RE_TITLE.test(content),
status: RE_STATUS.test(content),
lastUpdatedEnglish: RE_LAST_UPDATED.test(content),
dialect,
verified,
plainVerified: RE_PLAIN_VERIFIED.test(head),
footerConflict,
stampable: isStampable(content),
};
}
/**
* Audit a batch of files. Pure + dependency-injected reader (validate-kb-file.mjs
* pattern) so tests never touch disk. An unreadable file is reported, not thrown.
*
* @param {string[]} paths
* @param {(p: string) => string} [readFile]
* @returns {{files: Record<string, object>, aggregate: object}}
*/
export function auditHeaders(paths, readFile = (p) => readFileSync(p, 'utf8')) {
const files = {};
const aggregate = {
files: 0,
readErrors: 0,
verifiedPresent: 0,
verifiedDate: 0,
verifiedNonDate: 0,
verifiedPipe: 0,
verifiedDuplicate: 0,
plainVerifiedPipe: 0,
footerConflicts: 0,
unstampable: 0,
missingTitle: 0,
missingStatus: 0,
missingEnglishLastUpdated: 0,
dialects: { category: 0, kategori: 0, pipe: 0, none: 0 },
};
for (const path of paths ?? []) {
aggregate.files++;
let content;
try {
content = readFile(path);
} catch (err) {
files[path] = { error: err.message };
aggregate.readErrors++;
continue;
}
const r = auditContent(content);
files[path] = r;
if (r.verified) {
aggregate.verifiedPresent++;
if (r.verified.isDate) aggregate.verifiedDate++;
else aggregate.verifiedNonDate++;
if (r.verified.pipe) aggregate.verifiedPipe++;
if (r.verified.duplicateWithinHeaderRegion) aggregate.verifiedDuplicate++;
}
if (r.plainVerified) aggregate.plainVerifiedPipe++;
if (r.footerConflict) aggregate.footerConflicts++;
if (!r.stampable) aggregate.unstampable++;
if (!r.title) aggregate.missingTitle++;
if (!r.status) aggregate.missingStatus++;
if (!r.lastUpdatedEnglish) aggregate.missingEnglishLastUpdated++;
aggregate.dialects[r.dialect]++;
}
return { files, aggregate };
}
// Walk directory recursively for .md files (build-registry.mjs shape).
function walkMd(dir) {
const results = [];
if (!existsSync(dir)) return results;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...walkMd(full));
} else if (entry.name.endsWith('.md') && entry.name !== 'SKILL.md') {
results.push(full);
}
}
return results;
}
function main(argv) {
const json = argv.includes('--json');
const write = argv.includes('--write');
const paths = NON_ADVISOR_SKILLS
.flatMap((skill) => walkMd(join(SKILLS_DIR, skill, 'references')))
.sort();
const report = auditHeaders(paths);
// Re-key on plugin-relative paths for a stable, machine-diffable artifact.
const relFiles = {};
for (const [abs, r] of Object.entries(report.files)) {
relFiles[relative(PLUGIN_ROOT, abs)] = r;
}
const out = { generated_by: 'audit-corpus-headers.mjs', files: relFiles, aggregate: report.aggregate };
if (write) {
atomicWriteJson(OUT_PATH, out);
console.log(`Wrote ${relative(PLUGIN_ROOT, OUT_PATH)}`);
}
if (json) {
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
}
if (!json) {
const a = report.aggregate;
console.log(`Corpus header audit — ${a.files} non-advisor reference files`);
console.log(` Verified header present: ${a.verifiedPresent} (date: ${a.verifiedDate}, stale non-date: ${a.verifiedNonDate})`);
console.log(` Verified pipe rows: ${a.verifiedPipe}, duplicates within 500B: ${a.verifiedDuplicate}`);
console.log(` Plain (non-bold) Verified tails (RX-KB1 poison class): ${a.plainVerifiedPipe}`);
console.log(` Footer "Sist oppdatert" ≠ header Last updated (RX-KB1b, flag-to-human): ${a.footerConflicts}`);
for (const [abs, r] of Object.entries(report.files)) {
if (!r.footerConflict) continue;
console.log(` FLAG ${relative(PLUGIN_ROOT, abs)} — header ${r.footerConflict.headerLastUpdated} vs footer ${r.footerConflict.footerDates.join(', ')}`);
}
console.log(` R7-unstampable (both stamps land past 500B → header-slanking kreves, R7 flag path): ${a.unstampable}`);
for (const [abs, r] of Object.entries(report.files)) {
if (r.stampable !== false) continue;
console.log(` FLAG ${relative(PLUGIN_ROOT, abs)} — insertVerifiedFields would throw (value-slim required)`);
}
console.log(` Missing English Last updated: ${a.missingEnglishLastUpdated}`);
console.log(` Missing Status: ${a.missingStatus}`);
console.log(` Missing title: ${a.missingTitle}`);
console.log(` Dialects: category=${a.dialects.category} kategori=${a.dialects.kategori} pipe=${a.dialects.pipe} none=${a.dialects.none}`);
}
}
const isMain = (() => {
try {
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
} catch {
return false;
}
})();
if (isMain) main(process.argv.slice(2));