feat(ms-ai-architect): RX-KB1b — deterministisk footer-dato-avvik-deteksjon + label-whitelist i corpus-audit (flag-to-human, 3 flagg) [skip-docs]
This commit is contained in:
parent
6125f933be
commit
722a131eb7
2 changed files with 119 additions and 0 deletions
|
|
@ -53,6 +53,49 @@ const RE_CLEAN_DATE = /^\d{4}-\d{2}(-\d{2})?$/;
|
|||
// 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';
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
|
@ -76,6 +119,9 @@ function auditContent(content) {
|
|||
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),
|
||||
|
|
@ -83,6 +129,7 @@ function auditContent(content) {
|
|||
dialect,
|
||||
verified,
|
||||
plainVerified: RE_PLAIN_VERIFIED.test(head),
|
||||
footerConflict,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +152,7 @@ export function auditHeaders(paths, readFile = (p) => readFileSync(p, 'utf8')) {
|
|||
verifiedPipe: 0,
|
||||
verifiedDuplicate: 0,
|
||||
plainVerifiedPipe: 0,
|
||||
footerConflicts: 0,
|
||||
missingTitle: 0,
|
||||
missingStatus: 0,
|
||||
missingEnglishLastUpdated: 0,
|
||||
|
|
@ -131,6 +179,7 @@ export function auditHeaders(paths, readFile = (p) => readFileSync(p, 'utf8')) {
|
|||
if (r.verified.duplicateWithinHeaderRegion) aggregate.verifiedDuplicate++;
|
||||
}
|
||||
if (r.plainVerified) aggregate.plainVerifiedPipe++;
|
||||
if (r.footerConflict) aggregate.footerConflicts++;
|
||||
if (!r.title) aggregate.missingTitle++;
|
||||
if (!r.status) aggregate.missingStatus++;
|
||||
if (!r.lastUpdatedEnglish) aggregate.missingEnglishLastUpdated++;
|
||||
|
|
@ -184,6 +233,11 @@ function main(argv) {
|
|||
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(` Missing English Last updated: ${a.missingEnglishLastUpdated}`);
|
||||
console.log(` Missing Status: ${a.missingStatus}`);
|
||||
console.log(` Missing title: ${a.missingTitle}`);
|
||||
|
|
|
|||
|
|
@ -163,3 +163,68 @@ test('auditHeaders — an unreadable file is reported, not thrown', () => {
|
|||
assert.equal(r.aggregate.files, 1);
|
||||
assert.equal(r.aggregate.readErrors, 1);
|
||||
});
|
||||
|
||||
// ── RX-KB1b: footer date-conflict + label-whitelist ────────────────────────
|
||||
// A file that claims one **Last updated:** date in the header and a DIFFERENT date
|
||||
// under a norsk "Sist oppdatert" footer contradicts itself. Only the last-updated-
|
||||
// EQUIVALENT label ("Sist oppdatert") is conflict-bearing; "Verifisert"/"Sist
|
||||
// verifisert" record an independent axis (when content was checked vs source) and are
|
||||
// whitelisted. Detection is read-only and flag-to-human — the audit never mutates.
|
||||
|
||||
// header 2026-06-24, italic footer "Sist oppdatert: 2026-02" — the
|
||||
// stakeholder-communication-ai-decisions.md shape (genuine self-contradiction).
|
||||
const FOOTER_CONFLICT =
|
||||
'# T\n\n**Category:** x\n**Status:** GA\n**Last updated:** 2026-06-24\n\n---\n\n' +
|
||||
'## A\n\ntekst\n\n*Sist oppdatert: 2026-02 av Cosmo Skyberg (AI Architect Plugin)*\n';
|
||||
|
||||
// header and footer agree on the month — no conflict.
|
||||
const FOOTER_MATCH =
|
||||
'# T\n\n**Category:** x\n**Status:** GA\n**Last updated:** 2026-02-03\n\n---\n\n' +
|
||||
'## A\n\ntekst\n\n**Sist oppdatert:** 2026-02-03\n';
|
||||
|
||||
// footer carries a DIFFERENT date but under the whitelisted "Sist verifisert" label —
|
||||
// an independent provenance axis, NOT a conflict.
|
||||
const FOOTER_VERIFISERT_INDEP =
|
||||
'# T\n\n**Category:** x\n**Status:** GA\n**Last updated:** 2026-06-24\n\n---\n\n' +
|
||||
'## A\n\ntekst\n\n**Sist verifisert:** 2026-02-04\n';
|
||||
|
||||
// placeholder footer ([Dato]) is template scaffolding, not a real date — never a conflict.
|
||||
const FOOTER_PLACEHOLDER =
|
||||
'# T\n\n**Category:** x\n**Status:** GA\n**Last updated:** 2026-06-24\n\n---\n\n' +
|
||||
'## A\n\n## Versjon og godkjenning\n- **Sist oppdatert:** [Dato]\n';
|
||||
|
||||
test('auditHeaders — footer "Sist oppdatert" date ≠ header Last updated is flagged as a conflict', () => {
|
||||
const r = auditHeaders(['a.md'], reader({ 'a.md': FOOTER_CONFLICT }));
|
||||
const f = r.files['a.md'];
|
||||
assert.ok(f.footerConflict, 'conflict object present');
|
||||
assert.equal(f.footerConflict.headerLastUpdated, '2026-06-24');
|
||||
assert.deepEqual(f.footerConflict.footerDates, ['2026-02']);
|
||||
assert.equal(r.aggregate.footerConflicts, 1);
|
||||
});
|
||||
|
||||
test('auditHeaders — footer "Sist oppdatert" matching the header month raises no conflict', () => {
|
||||
const r = auditHeaders(['b.md'], reader({ 'b.md': FOOTER_MATCH }));
|
||||
assert.equal(r.files['b.md'].footerConflict, null);
|
||||
assert.equal(r.aggregate.footerConflicts, 0);
|
||||
});
|
||||
|
||||
test('auditHeaders — a differing "Sist verifisert" footer is whitelisted, not a conflict', () => {
|
||||
const r = auditHeaders(['c.md'], reader({ 'c.md': FOOTER_VERIFISERT_INDEP }));
|
||||
assert.equal(r.files['c.md'].footerConflict, null);
|
||||
assert.equal(r.aggregate.footerConflicts, 0);
|
||||
});
|
||||
|
||||
test('auditHeaders — a placeholder "[Dato]" footer never counts as a conflict', () => {
|
||||
const r = auditHeaders(['d.md'], reader({ 'd.md': FOOTER_PLACEHOLDER }));
|
||||
assert.equal(r.files['d.md'].footerConflict, null);
|
||||
assert.equal(r.aggregate.footerConflicts, 0);
|
||||
});
|
||||
|
||||
test('auditHeaders — footer conflict needs BOTH a header date and a norsk footer date', () => {
|
||||
// V_NONDATE has a norsk "Sist oppdatert" but no English header Last updated → no conflict.
|
||||
// V_DATE has an English header date but no norsk footer → no conflict.
|
||||
const r = auditHeaders(['a.md', 'b.md'], reader({ 'a.md': V_NONDATE, 'b.md': V_DATE }));
|
||||
assert.equal(r.files['a.md'].footerConflict, null);
|
||||
assert.equal(r.files['b.md'].footerConflict, null);
|
||||
assert.equal(r.aggregate.footerConflicts, 0);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue