feat(ms-ai-architect): R22 decision-b Enhet 3 — Status-backfill 25 none + ai-act dual-header-dedup 4 (Missing Status/Last-updated 29+4→0) [skip-docs]

To operasjoner, én økt (⊥ R7), begge ren metadata-normalisering (verdi aldri fabrikkert).

Premiss-korreksjon (ground truth 2026-07-07): roadmap sa «27 none + 4 ai-act».
Målt: 29 mangler bold **Status:** = 25 rene none + 4 ai-act (plain Status: GA).
De «27» inkluderte 2 for mye — 2 filer (custom-dashboards-ai-operations,
zero-trust-ai-services) har bold **Status:** KUN forbi byte 500 (present for
full-fil-audit, usynlig for 500B header-parser) → egen header-slanking-residual
(§8-register), utenfor Enhet 3.

Op A — Status-backfill 25 rene none: utvidet backfill-status.mjs MANIFEST 14→39
(samme statusForFile + insertMetaField + hard per-fil-invariant, idempotent skip
på de 14 R21-gjorte). Alle 25 → **Status:** Established Practice (ingen matcher
template|matrix|benchmarks|register). Diff +25/-0.

Op B — ai-act dual-header-dedup (4 filer): ny driver dedup-plain-header.mjs + 2
rene primitiver i transform.mjs — boldifyPlainField (plain→bold, verdi bevart
byte-eksakt, header-scoped, idempotent) + dropRedundantPlainField (sletter plain
KUN når bold m/ identisk verdi beviser redundans; kaster ved avvik/manglende bold).
Per fil: plain Last updated: + Status: GA → bold (2026-06-18/2026-02, GA bevart),
redundant plain Category: fjernet. Hard per-fil-invariant (net -1 linje, begge
felt bold m/ bevart verdi, ingen plain-header igjen, body byte-identisk). Diff -12/+8.

Verifisering: test-backfill-status 8/8 + test-dedup-plain-header 13/13; audit
Missing Status 29→0, Missing English Last updated 4→0; skills-diff 29 filer
+33/-12 (kun **Status:** + 8 bold-swaps), diff-kontekst inspisert per fil; begge
drivere idempotent (re-run 0 writes); suite 806/806 exit 0; none=8 uendret (Enhet 4).
This commit is contained in:
Kjell Tore Guttormsen 2026-07-07 07:45:27 +02:00
commit de0d94cbc1
35 changed files with 510 additions and 57 deletions

View file

@ -163,10 +163,22 @@ Maskineriet er bygget (krav 1+2+3 + per-kategori-hint); målingen er aldri kjør
**Verifisering:** `test-relabel-dato` 10/10 (inkl. disk-integritet: committed-state + at de 5 dual-filene forblir urørt); git diff **+16/16** på 16 filer (hver endret linje = label-swap, verifisert begge retninger); revers-substitusjon byte-eksakt; idempotent re-run = 0; verdier bevart (`2026-04`, `5. februar 2026`, …); ground truth `Missing English Last updated (non-advisor) 20→4`, gjenstående `**Dato:**` = 13 (5 dual + 8 body); R22-Enhet-1-tool fortsatt no-op (51/51 already-english); suite **792/792** exit 0.
### Enhet 3 — Status-backfill + ai-act dual-header-dedup ✅ 2026-07-07
To operasjoner, én økt (⊥ R7), begge metadata-normalisering (verdi ALDRI fabrikkert).
**Premiss-korreksjon (ground truth 2026-07-07, `audit-corpus-headers.mjs` + worklist):** roadmap sa «27 none + 4 ai-act». Målt: **29** filer mangler bold `**Status:**` = **25 rene «none»** (ingen Status-markør) + **4 ai-act** (plain `Status: GA`). De «27» inkluderte 2 for mye: **2 filer** (`monitoring-observability/custom-dashboards-ai-operations`, `ai-security-engineering/zero-trust-ai-services`) bærer bold `**Status:**` KUN forbi byte 500 → present for full-fil-audit, men **usynlig for 500-byte header-parseren**. De er IKKE manglende felt — de er en egen **header-slanking-residual** (§8-register), utenfor Enhet 3.
**Op A — Status-backfill 25 rene none-filer.** Utvidet den ene Status-mekanismen `backfill-status.mjs` (MANIFEST 14→**39**; samme `statusForFile` + `insertMetaField` + hard per-fil-invariant, idempotent skip på de 14 R21-gjorte). Alle 25 → `**Status:** Established Practice` (ingen matcher `template|matrix|benchmarks|register`-token). Diff **+25/0**, hver = én `**Status:**`-linje i bold-meta-run innenfor 500B.
**Op B — ai-act dual-header-dedup (4 filer).** Ny driver `dedup-plain-header.mjs` + 2 nye rene primitiver i `transform.mjs`: `boldifyPlainField()` (plain `<label>: <verdi>``**<label>:** <verdi>`, verdi bevart byte-eksakt, header-scoped, idempotent) og `dropRedundantPlainField()` (sletter plain-linje KUN når bold `**<label>:**` med **identisk verdi** beviser redundans; **kaster** ved verdi-avvik eller manglende bold — aldri auto-slett). Per fil: plain `Last updated:` + `Status: GA` → bold (verdi bevart: `2026-06-18`/`2026-02`, `GA`), redundant plain `Category:` fjernet (bold Category fra R20 bevart). Hard per-fil-invariant: net 1 linje, begge felt bold m/ bevart verdi, ingen plain-header-felt igjen, body byte-identisk. Diff **12/+8** på 4 filer (net 1/fil).
**Verifisering:** `test-backfill-status` 8/8 + `test-dedup-plain-header` 13/13; audit **Missing Status 29→0**, **Missing English Last updated 4→0**; git diff skills = 29 filer +33/12 (25 Status-insert + 4 dedup), alle tilførte linjer verifisert (kun `**Status:**` + de 8 bold-swappene), diff-kontekst inspisert per fil ([[dual-header-dialect-blindness]]); begge drivere idempotent (re-run 0 writes); suite **806/806** exit 0. `none=8`-dialekt uendret (Enhet 4-scope).
**Gjenstående decision-(b)-enheter (egne økter, ⊥ R7):**
- **Enhet 3:** Status-backfill 27 none-filer (`statusForFile`, R21-mønster) + 4 `ai-act-*` plain-blokk-dedup (bold-ify + fjern redundant plain-linjer).
- **Enhet 3b (NY — dedup):** 5 dual-label-filer med `**Dato:**` (opprettelse) + `**Last updated:**` (endring). Beslutning kreves: behold `**Dato:**` som eget «opprettet»-felt (relabel til `**Created:**`?) vs. fjern som redundant. Fabrikk/slett ALDRI uten operatør-beslutning. Filer: `feedback-loops-continuous-improvement`, `genaiops-llm-specific-practices`, `mlops-security-access-control`, `model-deployment-strategies-azure`, `prompt-flow-production-deployment` (alle mlops-genaiops).
- **Enhet 3b (dedup):** 5 dual-label-filer med `**Dato:**` (opprettelse) + `**Last updated:**` (endring). Beslutning kreves: behold `**Dato:**` som eget «opprettet»-felt (relabel til `**Created:**`?) vs. fjern som redundant. **Målt 2026-07-07:** 4 av 5 har distinkt opprettet≠endret (`**Dato:**` bærer ekte info); 1 (`mlops-security-access-control`) har identiske datoer (`**Dato:**` redundant). Fabrikk/slett ALDRI uten operatør-beslutning. Filer: `feedback-loops-continuous-improvement`, `genaiops-llm-specific-practices`, `mlops-security-access-control`, `model-deployment-strategies-azure`, `prompt-flow-production-deployment` (alle mlops-genaiops).
- **Enhet 4:** 1 datoløs (`decision-trees.md`, git-«innholds-commit»-heuristikk, ALDRI «i dag») + verifiser de 8 «category-none» = ekte gap vs. vindus-artefakt.
- **Residual (header-slanking, §8-register):** 2 filer med bold `**Status:**` forbi byte 500 (`custom-dashboards-ai-operations`, `zero-trust-ai-services`) → header-parseren ser dem ikke. Ikke missing-felt; krever header-omstokking (distinkt, risikofylt operasjon), ikke insert. Lukkes egen økt.
## Parkert (krever eget operatør-go)

View file

@ -1,22 +1,26 @@
#!/usr/bin/env node
// Backfill **Status:** on the 14 English-dialect advisor reference files that carry NO
// status label (R21 — base-field pre-pass del 2, ⊥ R7).
// Backfill **Status:** on English-dialect reference files that carry NO status label
// (R21 + Enhet 3 — base-field pre-pass, ⊥ R7). Single mechanism for the whole corpus.
//
// Ground truth 2026-07-06 (premiss-verified): of the not-due (146) corpus, 21 files lack a
// bold **Status:**. Of those, 3 are Norwegian-dialect (an English Status would mix dialects)
// and 4 (ms-ai-governance/responsible-ai/ai-act-*) carry a PLAIN-TEXT `Status: GA` in a second
// dual-header block that the bold-only regex does not see — inserting a bold **Status:** there
// duplicates/contradicts it. All 7 (3 + 4) are DEFERRED to the decision-b dialect/dual-header
// residual (the same plain block is why R20 duplicated **Category:** on those 4). The clean
// remainder is 14 advisor/architecture files. All 26 not-due Last-updated gaps are also deferred
// (21 already carry a Norwegian date → relabel; 5 are git-unreliable post-R20-churn). This
// applier writes ONLY those 14 Status lines — never a date, never into a Norwegian/dual-header file.
// Scope (ground-truth measured 2026-07-06 → extended 2026-07-07):
// R21 — 14 advisor/architecture files (the clean not-due remainder; 3 Norwegian-dialect
// and 4 dual-header ai-act-* files were held back — see below).
// Enhet 3 — +25 non-advisor files whose header carries no **Status:** at all (audit dialect
// 'none' for Status). Measured 25 — NOT the roadmap's "27": 2 of that count are
// files whose bold **Status:** sits PAST byte 500 (present, but invisible to the
// 500-byte header parser), a separate header-slanking residual, not a missing field.
// Still OUT — the 4 ms-ai-governance/responsible-ai/ai-act-* files carry a PLAIN-TEXT
// `Status: GA` in a dual-header block. Inserting a derived bold **Status:** there
// would duplicate/contradict the authored value; they are handled by the plain→bold
// dedup driver (dedup-plain-header.mjs), which PRESERVES `GA` rather than deriving.
//
// Value rule (operator-approved vocabulary, 2026-07-06): statusForFile() — a filename-token
// map within {Reference, Established Practice}. Reuses the tested insertMetaField primitive,
// with a hard per-file invariant asserted BEFORE any write (exactly one line inserted, it is
// the Status line, body byte-identical). Idempotent: a file already carrying **Status:** is
// skipped, so a re-run is a no-op. Aborts and writes nothing on any drift or invariant breach.
// This applier writes ONLY a derived bold **Status:** line — never a date, never into a
// dual-header ai-act file. Value rule (operator-approved vocabulary): statusForFile() — a
// filename-token map within {Reference, Established Practice}. Reuses the tested insertMetaField
// primitive, with a hard per-file invariant asserted BEFORE any write (exactly one line
// inserted, it is the Status line, body byte-identical). Idempotent: a file already carrying
// **Status:** in its header window is skipped, so a re-run — including the 14 R21 files already
// applied — is a no-op. Aborts and writes nothing on any drift or invariant breach.
//
// Usage: node scripts/kb-update/backfill-status.mjs [--dry]
import { readFileSync, writeFileSync, existsSync, realpathSync } from 'node:fs';
@ -46,10 +50,12 @@ export function statusForFile(relOrBase) {
return /(?:template|matrix|benchmarks|register)/i.test(base) ? 'Reference' : 'Established Practice';
}
// Frozen manifest — the 18 scope-confirmed targets (not-due ∩ missing-**Status:** ∩
// English-dialect), verified against ground truth 2026-07-06. Each value is derived by
// statusForFile() and re-asserted below, so the manifest can never drift from the rule.
// Frozen manifest — the 39 scope-confirmed targets (missing-**Status:** ∩ English-dialect ∩
// not a dual-header ai-act file), verified against ground truth (R21: 14 advisor 2026-07-06;
// Enhet 3: +25 non-advisor 2026-07-07). Each value is derived by statusForFile() and re-asserted
// below, so the manifest can never drift from the rule.
const TARGET_PATHS = [
// R21 — 14 advisor/architecture files (already applied; re-run skips them idempotently).
'skills/ms-ai-advisor/references/architecture/adr-template.md',
'skills/ms-ai-advisor/references/architecture/ai-utredning-template.md',
'skills/ms-ai-advisor/references/architecture/alternativanalyse-methodology.md',
@ -64,6 +70,32 @@ const TARGET_PATHS = [
'skills/ms-ai-advisor/references/architecture/regional-availability-verification.md',
'skills/ms-ai-advisor/references/architecture/security.md',
'skills/ms-ai-advisor/references/architecture/source-traceability-assumption-register.md',
// Enhet 3 — 25 non-advisor files with no **Status:** at all (all → Established Practice).
'skills/ms-ai-engineering/references/mlops-genaiops/cost-optimization-mlops-pipelines.md',
'skills/ms-ai-engineering/references/mlops-genaiops/feedback-loops-continuous-improvement.md',
'skills/ms-ai-engineering/references/mlops-genaiops/genaiops-llm-specific-practices.md',
'skills/ms-ai-engineering/references/mlops-genaiops/governance-audit-ml-operations.md',
'skills/ms-ai-engineering/references/mlops-genaiops/inferencing-optimization-caching.md',
'skills/ms-ai-engineering/references/mlops-genaiops/infrastructure-as-code-mlops.md',
'skills/ms-ai-engineering/references/mlops-genaiops/llm-evaluation-production.md',
'skills/ms-ai-engineering/references/mlops-genaiops/mlops-security-access-control.md',
'skills/ms-ai-engineering/references/mlops-genaiops/mlops-teams-collaboration-tools.md',
'skills/ms-ai-engineering/references/mlops-genaiops/model-deployment-strategies-azure.md',
'skills/ms-ai-engineering/references/mlops-genaiops/monitoring-observability-ml-systems.md',
'skills/ms-ai-engineering/references/mlops-genaiops/prompt-flow-production-deployment.md',
'skills/ms-ai-engineering/references/mlops-genaiops/responsible-ai-mlops-integration.md',
'skills/ms-ai-governance/references/monitoring-observability/anomaly-detection-ai-systems.md',
'skills/ms-ai-governance/references/monitoring-observability/azure-monitor-setup-ai-workloads.md',
'skills/ms-ai-governance/references/monitoring-observability/log-analytics-kql-ai-queries.md',
'skills/ms-ai-governance/references/monitoring-observability/token-usage-tracking-attribution.md',
'skills/ms-ai-governance/references/responsible-ai/ai-center-of-excellence-setup.md',
'skills/ms-ai-governance/references/responsible-ai/ai-governance-structure-framework.md',
'skills/ms-ai-governance/references/responsible-ai/red-teaming-ai-models.md',
'skills/ms-ai-security/references/ai-security-engineering/ai-red-team-operations-practical.md',
'skills/ms-ai-security/references/ai-security-engineering/data-leakage-prevention-ai.md',
'skills/ms-ai-security/references/ai-security-engineering/secure-model-deployment-hardening.md',
'skills/ms-ai-security/references/ai-security-engineering/security-copilot-integration.md',
'skills/ms-ai-security/references/ai-security-engineering/supply-chain-security-ai-models.md',
];
export const MANIFEST = TARGET_PATHS.map((path) => ({ path, status: statusForFile(path) }));

View file

@ -0,0 +1,139 @@
#!/usr/bin/env node
// dedup-plain-header.mjs — Enhet 3 Op B (2026-07-07, ⊥ R7). Normalize the dual-header block on
// the 4 ms-ai-governance/responsible-ai/ai-act-* files.
//
// Those files carry, below the bold header, an authored PLAIN-TEXT block:
// Last updated: <date>
// Status: GA
// Category: <value> ← redundant with a bold **Category:** <same value> (R20)
// This driver, per file:
// 1. boldifyPlainField('Last updated') — plain → **Last updated:**, date PRESERVED byte-exact
// 2. boldifyPlainField('Status') — plain → **Status:** GA, GA PRESERVED (authored, not
// the derived {Reference|Established Practice} vocab)
// 3. dropRedundantPlainField('Category')— delete the plain Category iff a bold **Category:**
// with the IDENTICAL value proves it redundant
// It NEVER derives a value and NEVER deletes a field it cannot prove redundant (the primitive
// throws on a value mismatch or a missing bold field). A hard per-file invariant is asserted
// BEFORE any write: exactly one line removed net, both target fields now bold with the authored
// value, no plain header field remains, and the body (from the first `## `/`---`) is byte-
// identical. Idempotent: a re-run finds the plain fields gone and is a no-op. Aborts, writing
// nothing, on any drift or invariant breach.
//
// Closes the last 4 of the corpus's Missing-Status and all 4 Missing-English-Last-updated.
//
// Usage: node scripts/kb-update/dedup-plain-header.mjs [--dry]
import { readFileSync, writeFileSync, existsSync, realpathSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { boldifyPlainField, dropRedundantPlainField } from './lib/transform.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const PLUGIN_ROOT = join(__dirname, '..', '..');
// Frozen manifest — the 4 dual-header ai-act-* files (ground truth 2026-07-07: the only
// non-advisor files carrying a plain-text Status/Last updated block).
export const MANIFEST = [
'skills/ms-ai-governance/references/responsible-ai/ai-act-classification-methodology.md',
'skills/ms-ai-governance/references/responsible-ai/ai-act-deployer-obligations.md',
'skills/ms-ai-governance/references/responsible-ai/ai-act-fria-template.md',
'skills/ms-ai-governance/references/responsible-ai/ai-act-provider-obligations.md',
];
/**
* The pure Op-B composition: boldify the plain Last updated + Status, then drop the redundant
* plain Category. Order-independent for these files (distinct labels). Pure no I/O.
* @param {string} content
* @returns {string}
*/
export function dedupPlainHeader(content) {
let out = boldifyPlainField(content, 'Last updated');
out = boldifyPlainField(out, 'Status');
out = dropRedundantPlainField(out, 'Category');
return out;
}
// Header region = lines above the first `## `/`---` (mirrors transform.mjs headerEndIndex).
function splitHeaderBody(content) {
const lines = content.split('\n');
let i = 0;
for (; i < lines.length; i++) {
if (/^##\s/.test(lines[i]) || /^---\s*$/.test(lines[i])) break;
}
return { header: lines.slice(0, i).join('\n'), body: lines.slice(i).join('\n') };
}
const RE_PLAIN_META = /^(Last updated|Status|Category):\s*\S/im;
// Assert the hard per-file invariant. Throws (aborting the run) on any breach.
function assertInvariant(rel, oldC, newC) {
const o = oldC.split('\n');
const n = newC.split('\n');
if (n.length !== o.length - 1) {
throw new Error(`ABORT ${rel}: expected exactly one line removed net (was ${o.length}, now ${n.length})`);
}
const oldH = splitHeaderBody(oldC);
const newH = splitHeaderBody(newC);
if (oldH.body !== newH.body) throw new Error(`ABORT ${rel}: body not byte-identical`);
if (RE_PLAIN_META.test(newH.header)) throw new Error(`ABORT ${rel}: a plain header field remains`);
if (!/\*\*Last updated:\*\*\s*\S/.test(newH.header)) throw new Error(`ABORT ${rel}: **Last updated:** missing after dedup`);
if (!/\*\*Status:\*\*\s*\S/.test(newH.header)) throw new Error(`ABORT ${rel}: **Status:** missing after dedup`);
if ((newH.header.match(/\*\*Category:\*\*/g) || []).length !== 1) {
throw new Error(`ABORT ${rel}: expected exactly one bold **Category:** after dedup`);
}
// Value preservation: the authored plain values must survive verbatim in the bold lines.
const oldPlain = (label) => {
const m = new RegExp('^' + label.replace(/ /g, '\\ ') + ':\\s*(\\S.*?)\\s*$', 'im').exec(oldH.header);
return m ? m[1] : null;
};
for (const label of ['Last updated', 'Status']) {
const want = oldPlain(label);
const boldRe = new RegExp('\\*\\*' + label + ':\\*\\*\\s*(\\S.*?)\\s*$', 'im');
const got = boldRe.exec(newH.header);
if (!want || !got || got[1] !== want) {
throw new Error(`ABORT ${rel}: value for '${label}' not preserved (want ${JSON.stringify(want)}, got ${JSON.stringify(got && got[1])})`);
}
}
}
function run({ dry }) {
const planned = [];
const skipped = [];
const missing = [];
for (const rel of MANIFEST) {
const abs = join(PLUGIN_ROOT, rel);
if (!existsSync(abs)) { missing.push(rel); continue; }
const old = readFileSync(abs, 'utf8');
const out = dedupPlainHeader(old);
if (out === old) { skipped.push(rel); continue; } // idempotent: already deduped
assertInvariant(rel, old, out);
planned.push({ rel, 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 dedup: ${planned.length} | already deduped (skipped): ${skipped.length}`);
if (dry) {
console.log('\n(dry run — no writes)');
for (const p of planned) console.log(` ~ ${p.rel}`);
return;
}
for (const p of planned) writeFileSync(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') });

View file

@ -418,6 +418,88 @@ export function insertMetaField(content, label, value) {
return lines.join('\n');
}
// Header region ends at the first horizontal rule or `## ` section — mirrors the body
// boundary the anchor loops above use. A plain-text meta line below this is genuine prose.
function headerEndIndex(lines) {
for (let i = 0; i < lines.length; i++) {
if (/^##\s/.test(lines[i]) || /^---\s*$/.test(lines[i])) return i;
}
return lines.length;
}
/**
* Normalize a PLAIN-TEXT header field `<label>: <value>` to the house bold form
* `**<label>:** <value>`, PRESERVING the authored value byte-exact (Enhet 3 Op B dedup
* primitive for the dual-header ai-act-* files). Unlike insertMetaField this never derives a
* value: it only re-decorates a line the author already wrote. Acts on the FIRST plain match
* in the header region only (a `Status:` in body prose is untouched). Idempotent: if a bold
* `**<label>:**` already sits in the header the content is returned unchanged; likewise if no
* plain line exists. Body (from the first `## `/`---`) is byte-identical.
*
* @param {string} content full existing file content
* @param {string} label the label WITHOUT decoration, e.g. 'Last updated'
* @returns {string} content with the plain line re-decorated, or unchanged
* @throws if label is blank
*/
export function boldifyPlainField(content, label) {
const s = String(content ?? '');
const lab = String(label ?? '').trim();
if (lab === '') throw new Error('boldifyPlainField: label is required');
const esc = lab.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const boldRe = new RegExp('^\\s*\\*\\*' + esc + ':\\*\\*', 'i');
const plainRe = new RegExp('^\\s*' + esc + ':\\s*(\\S.*?)\\s*$', 'i');
const lines = s.split('\n');
const end = headerEndIndex(lines);
let plainIdx = -1;
for (let i = 0; i < end; i++) {
if (boldRe.test(lines[i])) return s; // idempotent: already bold → unchanged
if (plainIdx === -1 && plainRe.test(lines[i])) plainIdx = i;
}
if (plainIdx === -1) return s; // no plain line → unchanged
const val = plainRe.exec(lines[plainIdx])[1];
lines[plainIdx] = `**${lab}:** ${val}`;
return lines.join('\n');
}
/**
* Remove a PLAIN-TEXT header field `<label>: <value>` that is provably redundant with an
* existing bold `**<label>:** <value>` carrying the IDENTICAL value (Enhet 3 Op B dedup
* primitive). Refuses to guess: if no bold field exists, or the bold and plain values differ,
* it THROWS (that is a human decision, never an auto-delete). No-op if the plain line is absent
* (idempotent). Operates in the header region only; body is byte-identical.
*
* @param {string} content full existing file content
* @param {string} label the label WITHOUT decoration, e.g. 'Category'
* @returns {string} content with the redundant plain line removed, or unchanged
* @throws if label is blank, no bold field proves redundancy, or the values differ
*/
export function dropRedundantPlainField(content, label) {
const s = String(content ?? '');
const lab = String(label ?? '').trim();
if (lab === '') throw new Error('dropRedundantPlainField: label is required');
const esc = lab.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const boldRe = new RegExp('^\\s*\\*\\*' + esc + ':\\*\\*\\s*(\\S.*?)\\s*$', 'i');
const plainRe = new RegExp('^\\s*' + esc + ':\\s*(\\S.*?)\\s*$', 'i');
const lines = s.split('\n');
const end = headerEndIndex(lines);
let boldVal = null;
let plainIdx = -1;
let plainVal = null;
for (let i = 0; i < end; i++) {
const bm = boldRe.exec(lines[i]);
if (bm && boldVal === null) boldVal = bm[1];
const pm = plainRe.exec(lines[i]);
if (pm && plainIdx === -1) { plainIdx = i; plainVal = pm[1]; }
}
if (plainIdx === -1) return s; // nothing to drop (idempotent)
if (boldVal === null) throw new Error(`dropRedundantPlainField: no bold **${lab}:** to prove redundancy for plain '${plainVal}'`);
if (boldVal !== plainVal) throw new Error(`dropRedundantPlainField: plain '${plainVal}' ≠ bold '${boldVal}' — not redundant, human decision`);
lines.splice(plainIdx, 1);
return lines.join('\n');
}
/**
* Surgically insert-or-update ONLY the `**Verified:**` and `**Verified by:**` header lines
* on an EXISTING KB file the R7R10 born-verified STAMP primitive. Unlike composeKbFile

View file

@ -5,6 +5,7 @@
**Relevans:** Azure Machine Learning, MLOps-implementering, FinOps for AI
**Type:** reference
**Source:** https://learn.microsoft.com/azure/machine-learning/concept-ml-pipelines
**Status:** Established Practice
## Innhold

View file

@ -6,6 +6,7 @@
**Confidence:** HIGH (basert på offisiell Microsoft-dokumentasjon)
**Type:** reference
**Source:** https://learn.microsoft.com/azure/foundry/concepts/observability
**Status:** Established Practice
## Innhold

View file

@ -6,6 +6,7 @@
**Konfidensgrad:** Høy (basert på 18 MCP-kilder fra Microsoft Learn)
**Type:** reference
**Source:** https://learn.microsoft.com/python/api/overview/azure/ai-evaluation-readme
**Status:** Established Practice
---

View file

@ -5,6 +5,7 @@
**Confidence:** 95% (High — bygger på offisiell Microsoft-dokumentasjon og Azure-referansearkitekturer)
**Type:** reference
**Source:** https://learn.microsoft.com/azure/machine-learning/concept-responsible-ai-dashboard
**Status:** Established Practice
## Innhold

View file

@ -5,6 +5,7 @@
**Forfattet av:** Cosmo Skyberg, Senior Microsoft AI Solution Architect
**Type:** reference
**Source:** https://learn.microsoft.com/azure/machine-learning/concept-onnx
**Status:** Established Practice
## Innhold

View file

@ -5,6 +5,7 @@
**Forfatter:** Cosmo Skyberg, Senior Microsoft AI Solution Architect
**Type:** reference
**Source:** https://learn.microsoft.com/azure/aks/concepts-machine-learning-ops
**Status:** Established Practice
## Innhold

View file

@ -5,6 +5,7 @@
**Confidence:** High (basert på offisiell Microsoft dokumentasjon, Microsoft Foundry SDK, og MLflow 3)
**Type:** reference
**Source:** https://learn.microsoft.com/azure/foundry/concepts/observability
**Status:** Established Practice
---

View file

@ -6,6 +6,7 @@
**Confidence:** HIGH — Basert på offisiell Microsoft Learn dokumentasjon (8 MCP-oppslag, 16 kilder)
**Type:** reference
**Source:** https://learn.microsoft.com/azure/machine-learning/concept-enterprise-security
**Status:** Established Practice
---

View file

@ -6,6 +6,7 @@
**Konfidensgradering:** ⭐⭐⭐⭐⭐ (Verifisert mot offisiell Microsoft-dokumentasjon)
**Type:** reference
**Source:** https://learn.microsoft.com/azure/machine-learning/overview-what-is-azure-machine-learning
**Status:** Established Practice
## Innhold

View file

@ -7,6 +7,7 @@
**Type:** reference
**Source:** https://learn.microsoft.com/azure/machine-learning/concept-model-management-and-deployment
**Category:** MLOps & GenAIOps
**Status:** Established Practice
**Last updated:** 2026-04

View file

@ -6,6 +6,7 @@
**Konfidensgrad:** ⭐⭐⭐⭐⭐ (Verifisert mot offisiell Microsoft-dokumentasjon)
**Type:** reference
**Source:** https://learn.microsoft.com/azure/machine-learning/concept-model-monitoring
**Status:** Established Practice
---

View file

@ -6,6 +6,7 @@
**Confidence:** 🟢 Høy (basert på offisiell Microsoft-dokumentasjon fra Microsoft Foundry og Azure Machine Learning)
**Type:** reference
**Source:** https://learn.microsoft.com/azure/machine-learning/prompt-flow/migrate-prompt-flow-to-agent-framework
**Status:** Established Practice
---

View file

@ -5,6 +5,7 @@
**Confidence:** 95% (basert på offisiell Microsoft-dokumentasjon og Azure Machine Learning-referanser)
**Type:** reference
**Source:** https://learn.microsoft.com/azure/machine-learning/concept-responsible-ai-dashboard
**Status:** Established Practice
---

View file

@ -5,6 +5,7 @@
**Målgruppe:** AI-arkitekter, DevOps-team, MLOps-ingeniører
**Type:** reference
**Source:** https://learn.microsoft.com/azure/ai-services/anomaly-detector/overview
**Status:** Established Practice
## Innhold

View file

@ -4,6 +4,7 @@
**Last updated:** 2026-02-05
**Gjelder for:** Azure OpenAI, Azure AI Services, Azure AI Search, Microsoft Foundry
**Type:** reference
**Status:** Established Practice
---

View file

@ -5,6 +5,7 @@
**Forfatter:** Cosmo Skyberg, AI Solution Architect
**Type:** reference
**Source:** https://learn.microsoft.com/azure/machine-learning/monitor-azure-machine-learning
**Status:** Established Practice
## Innhold

View file

@ -5,6 +5,7 @@
**Versjon:** 1.0
**Type:** reference
**Source:** https://learn.microsoft.com/azure/foundry/concepts/manage-costs
**Status:** Established Practice
## Innhold

View file

@ -2,9 +2,8 @@
**Type:** regulatory
**Category:** Responsible AI & Governance
Last updated: 2026-06-18
Status: GA
Category: Responsible AI & Governance
**Last updated:** 2026-06-18
**Status:** GA
---

View file

@ -2,9 +2,8 @@
**Type:** regulatory
**Category:** Responsible AI & Governance
Last updated: 2026-06-18
Status: GA
Category: Responsible AI & Governance
**Last updated:** 2026-06-18
**Status:** GA
---

View file

@ -2,9 +2,8 @@
**Type:** template
**Category:** Responsible AI & Governance
Last updated: 2026-02
Status: GA
Category: Responsible AI & Governance
**Last updated:** 2026-02
**Status:** GA
---

View file

@ -2,9 +2,8 @@
**Type:** regulatory
**Category:** Responsible AI & Governance
Last updated: 2026-06-18
Status: GA
Category: Responsible AI & Governance
**Last updated:** 2026-06-18
**Status:** GA
---

View file

@ -5,6 +5,7 @@
**Confidence:** HIGH (basert på Microsoft Cloud Adoption Framework og offisiell dokumentasjon)
**Type:** reference
**Source:** https://learn.microsoft.com/azure/cloud-adoption-framework/scenarios/ai/govern
**Status:** Established Practice
## Innhold

View file

@ -5,6 +5,7 @@
**Målgruppe:** Tekniske beslutningstakere, AI-arkitekter, governance-team
**Oppdateringsfrekvens:** Kvartalsvis (Q1 2026)
**Type:** methodology
**Status:** Established Practice
---

View file

@ -6,6 +6,7 @@
**Konfidensgrad:** ⚠️ HIGH — Basert på offisiell Microsoft-dokumentasjon (jun 2026)
**Type:** reference
**Source:** https://learn.microsoft.com/azure/ai-services/content-safety/concepts/jailbreak-detection
**Status:** Established Practice
## Innhold

View file

@ -5,6 +5,7 @@
**Relatert:** ai-prompt-injection-defense.md, ai-jailbreak-prevention.md
**Source:** https://learn.microsoft.com/security/ai-red-team/training
**Type:** reference
**Status:** Established Practice
---

View file

@ -5,6 +5,7 @@
**Målgruppe:** Enterprise AI architects og security teams
**Type:** reference
**Source:** https://learn.microsoft.com/security/benchmark/azure/mcsb-v2-artificial-intelligence-security
**Status:** Established Practice
## Innhold

View file

@ -4,6 +4,7 @@
**Last updated:** 2026-02-05
**Målgruppe:** Arkitekter som skal sikre AI-modeller i produksjonsmiljøer
**Type:** reference
**Status:** Established Practice
## Innhold

View file

@ -5,6 +5,7 @@
**Målgruppe:** Sikkerhetsarkitekter og SOC-ledere som vurderer AI-assistert sikkerhetsoperasjon
**Type:** reference
**Source:** https://learn.microsoft.com/copilot/security/agents-overview
**Status:** Established Practice
## Innhold

View file

@ -5,6 +5,7 @@
**Relatert plattform:** Microsoft Foundry, Azure Machine Learning, Azure DevOps, Microsoft Defender for Cloud
**Type:** reference
**Source:** https://learn.microsoft.com/azure/machine-learning/concept-vulnerability-management
**Status:** Established Practice
---

View file

@ -1,23 +1,28 @@
// tests/kb-update/test-backfill-status.test.mjs
// TDD for R21 — Status backfill on the 18 English-dialect not-due reference files that
// lack **Status:**. The applier (backfill-status.mjs) is a manifest-driven driver over the
// already-tested insertMetaField primitive; the ONLY new production logic is the pure
// deterministic value rule statusForFile() + the frozen MANIFEST. This suite pins both:
// TDD for the Status-backfill mechanism (R21 + Enhet 3). The applier (backfill-status.mjs)
// is a manifest-driven driver over the already-tested insertMetaField primitive; the ONLY
// new production logic is the pure deterministic value rule statusForFile() + the frozen
// MANIFEST. This suite pins both:
// - statusForFile: filename token (template|matrix|benchmarks|register) → 'Reference',
// else 'Established Practice'. Operator-approved vocabulary (2026-07-06), scope-confirmed
// 18 files (ground truth: not-due ∩ missing-**Status:** ∩ English-dialect).
// - MANIFEST self-consistency: 18 distinct entries, each value === statusForFile(path).
// The 21 Last-updated-relabels + 5 unreliable-git + 3 Norwegian-dialect-Status are OUT of
// R21 (corrected decision-b residual) and intentionally NOT in this manifest.
// else 'Established Practice'. Operator-approved vocabulary (2026-07-06).
// - MANIFEST self-consistency: distinct entries, each value === statusForFile(path).
//
// Scope history:
// R21 (2026-07-06) — 14 English-dialect advisor/architecture files (7 Reference + 7 EP).
// Enhet 3 (2026-07-07) — +25 non-advisor "none"-dialect files that lack **Status:** entirely
// (ground-truth measured: 25, NOT the roadmap's "27" — 2 of the roadmap count were files
// whose bold **Status:** sits past byte 500, i.e. present-but-header-invisible, a separate
// header-slanking residual, not a missing-Status file). None of the 25 match the artifact
// token → all 'Established Practice'. The 4 dual-header ai-act-* files are handled by the
// plain→bold dedup driver (dedup-plain-header.mjs), NOT here — they carry an authored
// `Status: GA` we preserve, so they must stay OUT of this derive-a-value manifest.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { statusForFile, MANIFEST, ALLOWED_STATUS } from '../../scripts/kb-update/backfill-status.mjs';
// The 14 scope-confirmed R21 targets (clean advisor/architecture). The 4 ms-ai-governance
// ai-act-* files that matched the token rule are NOT here: they carry a plain-text `Status: GA`
// in a dual-header block (bold-regex-invisible), so they are deferred to the decision-b residual.
const REFERENCE = [
// The 14 R21 advisor targets (clean advisor/architecture).
const ADVISOR_REFERENCE = [
'adr-template.md',
'ai-utredning-template.md',
'capacity-feasibility-benchmarks.md',
@ -26,7 +31,7 @@ const REFERENCE = [
'poc-template.md',
'source-traceability-assumption-register.md',
];
const ESTABLISHED = [
const ADVISOR_ESTABLISHED = [
'alternativanalyse-methodology.md',
'cost-models.md',
'decision-trees.md',
@ -36,12 +41,42 @@ const ESTABLISHED = [
'security.md',
];
// The 25 Enhet-3 non-advisor targets (missing **Status:** entirely). All → Established
// Practice (none carries a template/matrix/benchmarks/register token).
const NON_ADVISOR_ESTABLISHED = [
'skills/ms-ai-engineering/references/mlops-genaiops/cost-optimization-mlops-pipelines.md',
'skills/ms-ai-engineering/references/mlops-genaiops/feedback-loops-continuous-improvement.md',
'skills/ms-ai-engineering/references/mlops-genaiops/genaiops-llm-specific-practices.md',
'skills/ms-ai-engineering/references/mlops-genaiops/governance-audit-ml-operations.md',
'skills/ms-ai-engineering/references/mlops-genaiops/inferencing-optimization-caching.md',
'skills/ms-ai-engineering/references/mlops-genaiops/infrastructure-as-code-mlops.md',
'skills/ms-ai-engineering/references/mlops-genaiops/llm-evaluation-production.md',
'skills/ms-ai-engineering/references/mlops-genaiops/mlops-security-access-control.md',
'skills/ms-ai-engineering/references/mlops-genaiops/mlops-teams-collaboration-tools.md',
'skills/ms-ai-engineering/references/mlops-genaiops/model-deployment-strategies-azure.md',
'skills/ms-ai-engineering/references/mlops-genaiops/monitoring-observability-ml-systems.md',
'skills/ms-ai-engineering/references/mlops-genaiops/prompt-flow-production-deployment.md',
'skills/ms-ai-engineering/references/mlops-genaiops/responsible-ai-mlops-integration.md',
'skills/ms-ai-governance/references/monitoring-observability/anomaly-detection-ai-systems.md',
'skills/ms-ai-governance/references/monitoring-observability/azure-monitor-setup-ai-workloads.md',
'skills/ms-ai-governance/references/monitoring-observability/log-analytics-kql-ai-queries.md',
'skills/ms-ai-governance/references/monitoring-observability/token-usage-tracking-attribution.md',
'skills/ms-ai-governance/references/responsible-ai/ai-center-of-excellence-setup.md',
'skills/ms-ai-governance/references/responsible-ai/ai-governance-structure-framework.md',
'skills/ms-ai-governance/references/responsible-ai/red-teaming-ai-models.md',
'skills/ms-ai-security/references/ai-security-engineering/ai-red-team-operations-practical.md',
'skills/ms-ai-security/references/ai-security-engineering/data-leakage-prevention-ai.md',
'skills/ms-ai-security/references/ai-security-engineering/secure-model-deployment-hardening.md',
'skills/ms-ai-security/references/ai-security-engineering/security-copilot-integration.md',
'skills/ms-ai-security/references/ai-security-engineering/supply-chain-security-ai-models.md',
];
test('statusForFile: artifact-token filenames → Reference', () => {
for (const b of REFERENCE) assert.equal(statusForFile(b), 'Reference', b);
for (const b of ADVISOR_REFERENCE) assert.equal(statusForFile(b), 'Reference', b);
});
test('statusForFile: methodology/guidance/obligation filenames → Established Practice', () => {
for (const b of ESTABLISHED) assert.equal(statusForFile(b), 'Established Practice', b);
for (const b of ADVISOR_ESTABLISHED) assert.equal(statusForFile(b), 'Established Practice', b);
});
test('statusForFile: keys on basename only, not on ancestor folders', () => {
@ -52,29 +87,38 @@ test('statusForFile: keys on basename only, not on ancestor folders', () => {
});
test('statusForFile: always returns an allowed value', () => {
for (const b of [...REFERENCE, ...ESTABLISHED, 'anything-else.md']) {
for (const b of [...ADVISOR_REFERENCE, ...ADVISOR_ESTABLISHED, 'anything-else.md']) {
assert.ok(ALLOWED_STATUS.includes(statusForFile(b)), b);
}
});
test('MANIFEST: exactly 14 distinct entries, each value === statusForFile(path)', () => {
assert.equal(MANIFEST.length, 14);
test('MANIFEST: exactly 39 distinct entries, each value === statusForFile(path)', () => {
assert.equal(MANIFEST.length, 39);
const paths = new Set(MANIFEST.map((m) => m.path));
assert.equal(paths.size, 14, 'duplicate path in MANIFEST');
assert.equal(paths.size, 39, 'duplicate path in MANIFEST');
for (const { path, status } of MANIFEST) {
assert.ok(ALLOWED_STATUS.includes(status), `bad status for ${path}`);
assert.equal(status, statusForFile(path), `manifest/rule mismatch for ${path}`);
}
});
test('MANIFEST: 7 Reference + 7 Established Practice (scope-confirmed split)', () => {
test('MANIFEST: 7 Reference + 32 Established Practice (R21 14 + Enhet 3 25)', () => {
const ref = MANIFEST.filter((m) => m.status === 'Reference').length;
const est = MANIFEST.filter((m) => m.status === 'Established Practice').length;
assert.equal(ref, 7);
assert.equal(est, 7);
assert.equal(est, 32);
});
test('MANIFEST: excludes the 4 dual-header ai-act-* files (decision-b residual)', () => {
test('MANIFEST: all 25 Enhet-3 non-advisor targets present and Established Practice', () => {
for (const p of NON_ADVISOR_ESTABLISHED) {
const entry = MANIFEST.find((m) => m.path === p);
assert.ok(entry, `missing from MANIFEST: ${p}`);
assert.equal(entry.status, 'Established Practice', p);
assert.equal(statusForFile(p), 'Established Practice', p);
}
});
test('MANIFEST: excludes the 4 dual-header ai-act-* files (handled by dedup driver, authored Status: GA preserved)', () => {
const deferred = [
'ai-act-classification-methodology.md',
'ai-act-deployer-obligations.md',
@ -82,6 +126,6 @@ test('MANIFEST: excludes the 4 dual-header ai-act-* files (decision-b residual)'
'ai-act-provider-obligations.md',
];
for (const d of deferred) {
assert.ok(!MANIFEST.some((m) => m.path.endsWith(d)), `${d} must be deferred, not in MANIFEST`);
assert.ok(!MANIFEST.some((m) => m.path.endsWith(d)), `${d} must not be in the Status-derive MANIFEST`);
}
});

View file

@ -0,0 +1,123 @@
// tests/kb-update/test-dedup-plain-header.test.mjs
// TDD for Enhet 3 Op B — the dual-header plain→bold dedup on the 4 ms-ai-governance
// ai-act-* files. Those files carry an authored, PLAIN-TEXT block
// Last updated: <date>
// Status: GA
// Category: <value> ← redundant: a bold **Category:** <same value> already exists
// (R20 inserted the bold Category; the plain block is the original dual header). This is NOT a
// derive-a-value backfill (backfill-status.mjs owns that) — the authored values are PRESERVED
// byte-exact; only their label decoration is normalized to bold, and the provably-redundant
// plain Category line is removed. Two new pure primitives + a pure composition are pinned here.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { boldifyPlainField, dropRedundantPlainField } from '../../scripts/kb-update/lib/transform.mjs';
import { dedupPlainHeader, MANIFEST } from '../../scripts/kb-update/dedup-plain-header.mjs';
// An ai-act-shaped fixture: bold Type+Category (Category from R20), then the plain block,
// then a body that itself mentions "Status:" in prose (must never be touched).
const FIXTURE = [
'# EU AI Act — Systematisk Klassifiseringsmetodikk',
'**Type:** regulatory',
'**Category:** Responsible AI & Governance',
'',
'Last updated: 2026-06-18',
'Status: GA',
'Category: Responsible AI & Governance',
'',
'---',
'',
'## Innhold',
'',
'A body paragraph that says Status: draft in prose — must be left alone.',
'',
].join('\n');
// ── boldifyPlainField ────────────────────────────────────────────────────────
test('boldifyPlainField: plain header field → bold, value preserved byte-exact', () => {
const out = boldifyPlainField(FIXTURE, 'Last updated');
assert.ok(out.includes('**Last updated:** 2026-06-18'), 'bold line present');
assert.ok(!/^Last updated:/m.test(out.split('\n---')[0]), 'plain header line gone');
});
test('boldifyPlainField: idempotent — a bold field already present is unchanged', () => {
const once = boldifyPlainField(FIXTURE, 'Status');
const twice = boldifyPlainField(once, 'Status');
assert.equal(twice, once);
});
test('boldifyPlainField: no plain field in header → unchanged', () => {
assert.equal(boldifyPlainField(FIXTURE, 'Nonexistent'), FIXTURE);
});
test('boldifyPlainField: never touches a match in the body (below the header region)', () => {
const out = boldifyPlainField(FIXTURE, 'Status');
assert.ok(out.includes('says Status: draft in prose'), 'body prose untouched');
// exactly one bold Status inserted (the header one), body "Status:" not bolded
assert.equal((out.match(/\*\*Status:\*\*/g) || []).length, 1);
});
test('boldifyPlainField: body byte-identical (only the header line changes)', () => {
const out = boldifyPlainField(FIXTURE, 'Status');
const bodyOf = (c) => c.slice(c.indexOf('\n---'));
assert.equal(bodyOf(out), bodyOf(FIXTURE));
});
// ── dropRedundantPlainField ──────────────────────────────────────────────────
test('dropRedundantPlainField: drops the plain line when a bold field with the same value exists', () => {
const out = dropRedundantPlainField(FIXTURE, 'Category');
const header = out.split('\n---')[0];
assert.ok(/\*\*Category:\*\* Responsible AI & Governance/.test(header), 'bold Category kept');
assert.ok(!/^Category:/m.test(header), 'plain Category removed');
assert.equal(out.split('\n').length, FIXTURE.split('\n').length - 1, 'exactly one line removed');
});
test('dropRedundantPlainField: throws when plain value ≠ bold value (not redundant, human decision)', () => {
const mismatched = FIXTURE.replace('Category: Responsible AI & Governance', 'Category: Something Else');
assert.throws(() => dropRedundantPlainField(mismatched, 'Category'), /not redundant|≠/);
});
test('dropRedundantPlainField: throws when no bold field exists to prove redundancy', () => {
const noBold = FIXTURE.replace('**Category:** Responsible AI & Governance\n', '');
assert.throws(() => dropRedundantPlainField(noBold, 'Category'), /no bold/i);
});
test('dropRedundantPlainField: no-op when the plain line is absent (idempotent)', () => {
const once = dropRedundantPlainField(FIXTURE, 'Category');
assert.equal(dropRedundantPlainField(once, 'Category'), once);
});
// ── dedupPlainHeader (full composition) ──────────────────────────────────────
test('dedupPlainHeader: boldifies Last updated + Status, drops redundant plain Category', () => {
const out = dedupPlainHeader(FIXTURE);
const header = out.split('\n---')[0];
assert.ok(/\*\*Last updated:\*\* 2026-06-18/.test(header), 'Last updated bolded, value preserved');
assert.ok(/\*\*Status:\*\* GA/.test(header), 'Status bolded, GA preserved');
assert.equal((header.match(/\*\*Category:\*\*/g) || []).length, 1, 'exactly one bold Category');
assert.ok(!/^(Last updated|Status|Category):/m.test(header), 'no plain header fields remain');
});
test('dedupPlainHeader: net one line removed, body byte-identical', () => {
const out = dedupPlainHeader(FIXTURE);
assert.equal(out.split('\n').length, FIXTURE.split('\n').length - 1);
const bodyOf = (c) => c.slice(c.indexOf('\n---'));
assert.equal(bodyOf(out), bodyOf(FIXTURE));
});
test('dedupPlainHeader: idempotent — a second pass is a no-op', () => {
const once = dedupPlainHeader(FIXTURE);
assert.equal(dedupPlainHeader(once), once);
});
// ── MANIFEST ─────────────────────────────────────────────────────────────────
test('MANIFEST: exactly the 4 dual-header ai-act-* files', () => {
assert.equal(MANIFEST.length, 4);
assert.equal(new Set(MANIFEST).size, 4, 'no duplicates');
const expected = [
'skills/ms-ai-governance/references/responsible-ai/ai-act-classification-methodology.md',
'skills/ms-ai-governance/references/responsible-ai/ai-act-deployer-obligations.md',
'skills/ms-ai-governance/references/responsible-ai/ai-act-fria-template.md',
'skills/ms-ai-governance/references/responsible-ai/ai-act-provider-obligations.md',
];
for (const p of expected) assert.ok(MANIFEST.includes(p), `missing ${p}`);
});