From ed92d65385767bbaeecfed23fddb78288966228c Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Sat, 4 Jul 2026 09:25:44 +0200 Subject: [PATCH] =?UTF-8?q?test(ms-ai-architect):=20Spor=201=20=E2=80=94?= =?UTF-8?q?=20akseptansetester=20(residual=20scoped,=20worklist-aktivering?= =?UTF-8?q?,=20CT5/N4,=20poison-doc)=20[skip-docs]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/kb-eval/test-eval.test.mjs | 38 ++++++++++++++ .../test-full-pass-worklist.test.mjs | 49 +++++++++++++++++++ .../kb-update/test-validate-kb-file.test.mjs | 48 +++++++++++++++++- 3 files changed, 134 insertions(+), 1 deletion(-) diff --git a/tests/kb-eval/test-eval.test.mjs b/tests/kb-eval/test-eval.test.mjs index 44471db..413d23d 100644 --- a/tests/kb-eval/test-eval.test.mjs +++ b/tests/kb-eval/test-eval.test.mjs @@ -18,6 +18,7 @@ import { checkN5, checkCT5, } from '../../scripts/kb-eval/eval.mjs'; +import { insertHeaderFields, insertToc } from '../../scripts/kb-update/lib/transform.mjs'; test('splitFrontmatter — separates frontmatter from body', () => { const c = '---\nname: x\ndescription: hi\n---\n# Body\nline2\n'; @@ -294,3 +295,40 @@ test('checkCT5 — 0.80 is the pass threshold (mirrors the old K8 ratio target)' assert.equal(checkCT5(mk(5, 4)).pass, true, '4/5 = 0.80 passes'); assert.equal(checkCT5(mk(5, 3)).pass, false, '3/5 = 0.60 fails'); }); + +// --- Spor 1 acceptance: CT5 sourcedness ACTIVATES and N4 TOC passes on the migrated +// substrate. Fixtures are produced by the REAL Session-2 primitives (insertHeaderFields +// + insertToc) applied to legacy files — so this asserts the substrate makes both +// signals move, not that a hand-forged header does. + +// A legacy LARGE reference file (>100 lines), no Type/Source/TOC. +const LEGACY_LARGE = '# Big Reference\n\n**Status:** GA\n\n---\n\n' + + Array.from({ length: 8 }, (_, i) => `## Section ${i + 1}\n\n${'body paragraph.\n'.repeat(14)}`).join('\n'); +const MIGRATED_LARGE = insertToc(insertHeaderFields(LEGACY_LARGE, { + type: 'reference', source: 'https://learn.microsoft.com/azure/big', +})); +const MIGRATED_SMALL = insertHeaderFields('# Small Reference\n\n**Status:** GA\n\n---\n\n## A\n\ntekst\n', { + type: 'reference', source: 'https://learn.microsoft.com/azure/small', +}); +const MIGRATED_REFS = [ + { path: 'references/big.md', content: MIGRATED_LARGE }, + { path: 'references/small.md', content: MIGRATED_SMALL }, +]; + +test('acceptance — CT5 activates on the migrated substrate (referenceFiles>0, fully sourced, pass)', () => { + const r = checkCT5(MIGRATED_REFS); + // referenceFiles>0 is the score-layer's "available" predicate — CT5 is no longer dormant. + assert.ok(r.referenceFiles > 0, 'CT5 available: at least one type:reference file'); + assert.equal(r.referenceFiles, 2); + assert.equal(r.sourced, 2); + assert.equal(r.ratio, 1); + assert.equal(r.pass, true); +}); + +test('acceptance — N4 passes: the migrated large file carries a recognised ## Innhold TOC', () => { + const r = checkN4(MIGRATED_REFS); + assert.ok(r.largeFiles >= 1, 'the large file is in N4 scope'); + assert.equal(r.withToc, r.largeFiles, 'every large file has a TOC'); + assert.equal(r.ratio, 1); + assert.equal(r.pass, true); +}); diff --git a/tests/kb-update/test-full-pass-worklist.test.mjs b/tests/kb-update/test-full-pass-worklist.test.mjs index 744637e..0390926 100644 --- a/tests/kb-update/test-full-pass-worklist.test.mjs +++ b/tests/kb-update/test-full-pass-worklist.test.mjs @@ -22,6 +22,12 @@ import { classifyFullPassDue, buildFullPassReport, } from '../../scripts/kb-update/lib/full-pass-worklist.mjs'; +import { normalizeStaleVerified } from '../../scripts/kb-update/lib/transform.mjs'; +import { + parseTypeHeader, + parseSourceHeader, + parseVerifiedHeader, +} from '../../scripts/kb-update/lib/kb-headers.mjs'; const TODAY = '2026-06-29'; const MAX_AGE = 90; // threshold = 2026-03-31 (verified strictly before → cadence-due) @@ -195,3 +201,46 @@ test('buildFullPassReport — no source map still works (annotation null)', () = assert.equal(r.worklist.length, 1); assert.equal(r.worklist[0].newestSourceLastmod, null); }); + +// --- Spor 1 acceptance: worklist ACTIVATION + the **Verified:** MCP poison it fixes --- + +test('acceptance — migrated reference headers (type+source, verified:null) land in never-verified; unmigrated===0', () => { + const reader = (p) => ({ + 'm1.md': { type: 'reference', source: 'https://learn.microsoft.com/1', verified: null }, + 'm2.md': { type: 'reference', source: 'https://learn.microsoft.com/2', verified: null }, + }[p] ?? null); + const r = buildFullPassReport(['m1.md', 'm2.md'], reader, new Map(), opts); + assert.equal(r.counts.neverVerified, 2); + assert.ok(r.counts.due > 0, 'the worklist activates — due>0'); + assert.equal(r.counts.unmigrated, 0, 'no legacy no-Type files remain'); + assert.ok(r.worklist.every((e) => e.reason === 'never-verified')); +}); + +// The poison, exercised through the REAL parser + normalizer (not a hand-forged +// header value): a header-region `**Verified:** MCP ` parses to the non-date +// token 'MCP', which the cadence classifier reads as a recent verification and drops +// the file to `fresh` — omitted from the worklist AND falsely "verified". This is the +// exact failure mode normalizeStaleVerified removes. +const POISONED = + '# Poisoned Ref\n\n**Status:** GA\n**Verified:** MCP 2026-06\n**Type:** reference\n' + + '**Source:** https://learn.microsoft.com/x\n\n---\n\n## A\n\ntekst\n'; +const hdr = (c) => ({ type: parseTypeHeader(c), source: parseSourceHeader(c), verified: parseVerifiedHeader(c) }); + +test('acceptance — **Verified:** MCP poison drops a never-verified ref to fresh (the bug the normalizer fixes)', () => { + assert.equal(parseVerifiedHeader(POISONED), 'MCP'); // the poison: a non-date token read as "verified" + const r = buildFullPassReport(['p.md'], (p) => ({ 'p.md': hdr(POISONED) }[p] ?? null), new Map(), opts); + assert.equal(r.counts.fresh, 1, 'wrongly counted fresh'); + assert.equal(r.worklist.length, 0, 'omitted from the worklist — the poison'); + assert.equal(r.counts.unmigrated, 0); +}); + +test('acceptance — normalizeStaleVerified restores the poisoned file to a due never-verified entry', () => { + const fixed = normalizeStaleVerified(POISONED); + assert.equal(parseVerifiedHeader(fixed), null); // stale token stripped + assert.equal(parseTypeHeader(fixed), 'reference'); // Type/Source untouched + assert.equal(parseSourceHeader(fixed), 'https://learn.microsoft.com/x'); + const r = buildFullPassReport(['p.md'], (p) => ({ 'p.md': hdr(fixed) }[p] ?? null), new Map(), opts); + assert.equal(r.worklist.length, 1); + assert.equal(r.worklist[0].reason, 'never-verified'); + assert.ok(r.counts.due > 0); +}); diff --git a/tests/kb-update/test-validate-kb-file.test.mjs b/tests/kb-update/test-validate-kb-file.test.mjs index 656da2a..b27c522 100644 --- a/tests/kb-update/test-validate-kb-file.test.mjs +++ b/tests/kb-update/test-validate-kb-file.test.mjs @@ -10,7 +10,12 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { validatePaths } from '../../scripts/kb-update/validate-kb-file.mjs'; -import { buildKbHeader } from '../../scripts/kb-update/lib/transform.mjs'; +import { + buildKbHeader, + insertHeaderFields, + validateKbFile, +} from '../../scripts/kb-update/lib/transform.mjs'; +import { parseTypeHeader, parseSourceHeader } from '../../scripts/kb-update/lib/kb-headers.mjs'; const GOOD = buildKbHeader({ title: 'Azure AI Search - Hybrid Retrieval', @@ -78,3 +83,44 @@ test('validatePaths — no paths → ok:true (nothing to gate)', () => { assert.equal(r.ok, true); assert.deepEqual(r.results, []); }); + +// --- Spor 1 acceptance: the residual criterion after the substrate migration --- +// These generate the migrated file through the REAL Session-2 primitive +// (insertHeaderFields), then assert the create-guard's residual — proving the +// substrate closes the worklist-relevant fields (type+source) even where base +// fields lag. `verified`/`verified_by` stay open by design (the judge fills them, +// next brief), so the residual for a conformant file is EXACTLY those two. + +// A base-field-CONFORMANT legacy file: title + English **Last updated:** + **Status:**, +// small (no TOC required). Migrated → Type + Source stamped, still no Verified. +const LEGACY_CONFORMANT = + '# Azure AI Foundry\n\n**Last updated:** 2026-06\n**Status:** GA\n\n---\n\n## Oversikt\n\ntekst\n'; +// A base-field-INCOMPLETE legacy file: Norwegian **Sist oppdatert:** (RE_LAST_UPDATED is +// English-only) + **Kategori:** but NO **Status:**. Migrated → Type + Source stamped. +const LEGACY_INCOMPLETE = + '# Copilot Studio\n\n**Kategori:** Styring\n**Sist oppdatert:** 2026-06\n\n---\n\n## Oversikt\n\ntekst\n'; + +test('acceptance — a migrated base-field-conformant reference has residual EXACTLY [verified, verified_by]', () => { + const migrated = insertHeaderFields(LEGACY_CONFORMANT, { + type: 'reference', + source: 'https://learn.microsoft.com/azure/ai-foundry', + }); + assert.equal(parseTypeHeader(migrated), 'reference'); + assert.equal(parseSourceHeader(migrated), 'https://learn.microsoft.com/azure/ai-foundry'); + assert.deepEqual(validateKbFile(migrated).missing, ['verified', 'verified_by']); +}); + +test('acceptance — a migrated base-field-incomplete reference has residual ⊆ the 4-set, with type+source present', () => { + const migrated = insertHeaderFields(LEGACY_INCOMPLETE, { + type: 'reference', + source: 'https://learn.microsoft.com/power-platform/copilot', + }); + const { missing } = validateKbFile(migrated); + const FOUR_SET = ['last_updated', 'status', 'verified', 'verified_by']; + assert.ok(missing.every((m) => FOUR_SET.includes(m)), `residual ${JSON.stringify(missing)} ⊆ 4-set`); + // The substrate DID complete the worklist-relevant fields — type + source are present, + // never in the residual, even though base fields (Norwegian date / no Status) lag. + assert.ok(!missing.includes('source')); + assert.equal(parseTypeHeader(migrated), 'reference'); + assert.equal(parseSourceHeader(migrated), 'https://learn.microsoft.com/power-platform/copilot'); +});