From 7b6025a73183ba7d0326ac8e28ddabdbc800f3a7 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 16 Jul 2026 20:19:07 +0200 Subject: [PATCH] =?UTF-8?q?fix(ms-ai-architect):=20Enhet=204=20=E2=80=94?= =?UTF-8?q?=20Last=20updated=20p=C3=A5=20datol=C3=B8s=20advisor=20decision?= =?UTF-8?q?-trees.md=20(git=20content-commit=202026-06-23)=20[skip-docs]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/kb-update/backfill-last-updated.mjs | 104 ++++++++++++++++++ .../references/architecture/decision-trees.md | 1 + .../test-backfill-last-updated.test.mjs | 98 +++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 scripts/kb-update/backfill-last-updated.mjs create mode 100644 tests/kb-update/test-backfill-last-updated.test.mjs diff --git a/scripts/kb-update/backfill-last-updated.mjs b/scripts/kb-update/backfill-last-updated.mjs new file mode 100644 index 0000000..1749749 --- /dev/null +++ b/scripts/kb-update/backfill-last-updated.mjs @@ -0,0 +1,104 @@ +#!/usr/bin/env node +// Backfill **Last updated:** on the ONE advisor reference file that carries no date label at +// all — decision-trees.md (Enhet 4, ⊥ R7). Ground truth (2026-07-16): it is the only +// skills/**/*.md whose header lacks an English **Last updated:**. Sister driver to +// backfill-status.mjs / backfill-category.mjs — same manifest-driven shape over the tested +// insertMetaField primitive. +// +// Value derivation — git "content-commit" heuristic (NEVER "today"): +// The date is the authored date of the last commit that changed decision-trees.md's BODY, +// EXCLUDING the R20/R21 metadata passes (which inserted only a **Category:** / **Status:** +// header line — no body change). Measured 2026-07-16 against `git log -- `: +// 5a0e8d7 2026-07-06 R21 Status-backfill — header-only insert → metadata → EXCLUDED +// a583599 2026-07-06 R20 Category-backfill — header-only insert → metadata → EXCLUDED +// 03d596e 2026-06-23 KB-refresh Foundry namesweep — 3 BODY table rows changed → CONTENT ✓ +// baa2d02 2026-04-08 file birth (plugin add) +// ⇒ last content commit = 03d596e (2026-06-23). Full YYYY-MM-DD dialect (dominant among the +// architecture/ siblings). The date is FROZEN below with this provenance; re-measure against +// git before re-running on a mutated history. "today" (MEASURED_TODAY) is asserted-against so +// a naive last-commit / metadata-pass date can never leak in. +// +// This applier writes ONLY the derived bold **Last updated:** line — reuses insertMetaField, +// with a hard per-file invariant asserted BEFORE any write (exactly one line inserted, it is the +// Last updated line with the frozen date byte-exact, body byte-identical). Idempotent: a file +// already carrying **Last updated:** in its header window is skipped. atomicWriteSync +// (crash-safe: tmp+rename — a reader sees the old file or the new one, never a partial; an +// interrupted run recovers by re-running). Aborts and writes nothing on any drift or breach. +// +// Usage: node scripts/kb-update/backfill-last-updated.mjs [--dry] +import { readFileSync, existsSync, realpathSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { insertMetaField } from './lib/transform.mjs'; +import { atomicWriteSync } from './lib/atomic-write.mjs'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const PLUGIN_ROOT = join(__dirname, '..', '..'); + +// The session date the manifest was measured on. A manifest date equal to this means the +// heuristic fell back to a metadata-pass / naive last-commit date — the never-today guard rejects it. +export const MEASURED_TODAY = '2026-07-16'; + +// Frozen manifest — the single dateless advisor file + its git-content-commit date (03d596e). +// Value is a YYYY-MM-DD string; provenance in the header comment above. +export const MANIFEST = [ + { path: 'skills/ms-ai-advisor/references/architecture/decision-trees.md', lastUpdated: '2026-06-23' }, +]; + +const RE_ISO_DATE = /^\d{4}-\d{2}-\d{2}$/; +// Presence of a dated **Last updated:** in the header window — mirrors audit-corpus-headers.mjs +// RE_LAST_UPDATED (the guard the header audit reads), used here for idempotency. +const RE_LAST_UPDATED = /\*\*Last updated:\*\*\s*[\d-]+/i; + +function run({ dry }) { + const planned = []; + const skipped = []; + const missing = []; + + for (const { path: rel, lastUpdated } of MANIFEST) { + if (!RE_ISO_DATE.test(lastUpdated)) throw new Error(`ABORT ${rel}: date '${lastUpdated}' not YYYY-MM-DD`); + if (lastUpdated === MEASURED_TODAY) throw new Error(`ABORT ${rel}: date equals measured 'today' — never-today guard`); + const abs = join(PLUGIN_ROOT, rel); + if (!existsSync(abs)) { missing.push(rel); continue; } + const old = readFileSync(abs, 'utf8'); + if (RE_LAST_UPDATED.test(old.slice(0, 500))) { skipped.push(rel); continue; } // idempotent + + const out = insertMetaField(old, 'Last updated', lastUpdated); + // Hard invariant: exactly one line inserted (the Last updated line), body byte-identical. + const o = old.split('\n'); + const n = out.split('\n'); + if (n.length !== o.length + 1) throw new Error(`ABORT ${rel}: not exactly one line inserted`); + let d = 0; + while (d < o.length && o[d] === n[d]) d++; + if (n[d] !== `**Last updated:** ${lastUpdated}`) throw new Error(`ABORT ${rel}: inserted line mismatch → ${JSON.stringify(n[d])}`); + if (o.slice(d).join('\n') !== n.slice(d + 1).join('\n')) throw new Error(`ABORT ${rel}: body not byte-identical below the insertion`); + planned.push({ rel, lastUpdated, out }); + } + + if (missing.length) { + console.error(`ABORT — ${missing.length} manifest target(s) not found (corpus drift):`); + missing.forEach((m) => console.error(' ' + m)); + process.exit(1); + } + if (planned.length + skipped.length !== MANIFEST.length) { + throw new Error(`ABORT — accounted ${planned.length + skipped.length} ≠ manifest ${MANIFEST.length}`); + } + + console.log(`Manifest: ${MANIFEST.length} | to insert: ${planned.length} | already present (skipped): ${skipped.length}`); + if (dry) { + console.log('\n(dry run — no writes)'); + for (const p of planned) console.log(` + ${p.rel} → **Last updated:** ${p.lastUpdated}`); + return; + } + for (const p of planned) atomicWriteSync(join(PLUGIN_ROOT, p.rel), p.out); + console.log(`\nWrote ${planned.length} files.`); +} + +const isMain = (() => { + try { + return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url)); + } catch { + return false; + } +})(); +if (isMain) run({ dry: process.argv.includes('--dry') }); diff --git a/skills/ms-ai-advisor/references/architecture/decision-trees.md b/skills/ms-ai-advisor/references/architecture/decision-trees.md index 2446f8e..4a42add 100644 --- a/skills/ms-ai-advisor/references/architecture/decision-trees.md +++ b/skills/ms-ai-advisor/references/architecture/decision-trees.md @@ -1,6 +1,7 @@ # Decision Trees for Microsoft AI **Category:** Solution Architecture & Advisory **Status:** Established Practice +**Last updated:** 2026-06-23 Akkumulerte beslutningstrær og arkitekturmønstre for Microsoft AI-stakken. diff --git a/tests/kb-update/test-backfill-last-updated.test.mjs b/tests/kb-update/test-backfill-last-updated.test.mjs new file mode 100644 index 0000000..e9380ff --- /dev/null +++ b/tests/kb-update/test-backfill-last-updated.test.mjs @@ -0,0 +1,98 @@ +// tests/kb-update/test-backfill-last-updated.test.mjs +// TDD for the Last-updated backfill mechanism (Enhet 4, ⊥ R7). The applier +// (backfill-last-updated.mjs) is a manifest-driven driver over the already-tested +// insertMetaField primitive — the same shape as backfill-status.mjs. The ONLY new production +// data is the frozen single-entry MANIFEST whose value is a git "content-commit" date. +// +// Scope: decision-trees.md is the corpus's ONE dateless file — the only skills/**/*.md whose +// header lacks an English **Last updated:** (ground truth 2026-07-16). Its date is the authored +// date of the last commit that changed the BODY (03d596e, 2026-06-23 — a Foundry namesweep), +// EXCLUDING the R20/R21 metadata passes (a583599/5a0e8d7, which inserted only a header meta line +// and changed no body). The "never today" rule is enforced against MEASURED_TODAY so a naive +// last-commit date can never leak in. +// +// This suite pins: +// - MANIFEST self-consistency: one distinct entry, the known dateless path, a YYYY-MM-DD value +// that is not MEASURED_TODAY. +// - insertMetaField placement/idempotency on an advisor-shaped header fixture. +// - Corpus-state guard (the gap-discipline lock): the committed decision-trees.md carries +// **Last updated:** 2026-06-23 in its 500-byte header window, with title + Category + Status +// and body intact. + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { MANIFEST, MEASURED_TODAY } from '../../scripts/kb-update/backfill-last-updated.mjs'; +import { insertMetaField } from '../../scripts/kb-update/lib/transform.mjs'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const PLUGIN_ROOT = join(__dirname, '..', '..'); + +const RE_ISO_DATE = /^\d{4}-\d{2}-\d{2}$/; +const DECISION_TREES = 'skills/ms-ai-advisor/references/architecture/decision-trees.md'; + +test('MANIFEST: exactly one distinct entry, the known dateless advisor file', () => { + assert.equal(MANIFEST.length, 1); + const paths = new Set(MANIFEST.map((m) => m.path)); + assert.equal(paths.size, 1, 'duplicate path in MANIFEST'); + assert.equal(MANIFEST[0].path, DECISION_TREES); +}); + +test('MANIFEST: value is a YYYY-MM-DD date and NOT MEASURED_TODAY (never-today guard)', () => { + for (const { path, lastUpdated } of MANIFEST) { + assert.ok(RE_ISO_DATE.test(lastUpdated), `${path}: '${lastUpdated}' is not YYYY-MM-DD`); + assert.notEqual(lastUpdated, MEASURED_TODAY, `${path}: date must never equal 'today'`); + } +}); + +test('MANIFEST: the frozen date is the measured content-commit date 2026-06-23', () => { + assert.equal(MANIFEST[0].lastUpdated, '2026-06-23'); +}); + +test('insertMetaField: Last updated lands after the meta run, one line, body byte-identical', () => { + const fixture = [ + '# Decision Trees for Microsoft AI', + '**Category:** Solution Architecture & Advisory', + '**Status:** Established Practice', + '', + 'Akkumulerte beslutningstrær.', + '', + '## 1. Section', + 'body', + ].join('\n'); + const out = insertMetaField(fixture, 'Last updated', '2026-06-23'); + const o = fixture.split('\n'); + const n = out.split('\n'); + // Exactly one line added. + assert.equal(n.length, o.length + 1); + // It lands immediately after the last meta line (Status), before the blank line. + assert.equal(n[3], '**Last updated:** 2026-06-23'); + assert.equal(n[1], '**Category:** Solution Architecture & Advisory'); + assert.equal(n[2], '**Status:** Established Practice'); + // Everything from the insertion point down is byte-identical to the original tail. + assert.equal(o.slice(3).join('\n'), n.slice(4).join('\n')); +}); + +test('insertMetaField: idempotent — a file already carrying Last updated is unchanged', () => { + const withField = [ + '# Title', + '**Last updated:** 2026-06-23', + '**Status:** Established Practice', + '', + '## S', + ].join('\n'); + assert.equal(insertMetaField(withField, 'Last updated', '2026-01-01'), withField); +}); + +test('corpus guard: committed decision-trees.md carries Last updated 2026-06-23 in header window', () => { + const content = readFileSync(join(PLUGIN_ROOT, DECISION_TREES), 'utf8'); + const head = content.slice(0, 500); + assert.match(head, /\*\*Last updated:\*\* 2026-06-23/, 'missing frozen Last updated in header window'); + // Title + the R20/R21 base fields survive, body intact (regression lock). + assert.match(head, /^# Decision Trees for Microsoft AI/); + assert.match(head, /\*\*Category:\*\*/); + assert.match(head, /\*\*Status:\*\*/); + assert.match(content, /## 1\. Plattformvalg/); +});