diff --git a/docs/plugin-roadmap-2026-07.md b/docs/plugin-roadmap-2026-07.md index f44f261..495602d 100644 --- a/docs/plugin-roadmap-2026-07.md +++ b/docs/plugin-roadmap-2026-07.md @@ -149,9 +149,23 @@ Maskineriet er bygget (krav 1+2+3 + per-kategori-hint); målingen er aldri kjør **Verifisering:** `test-relabel-dialect` 11/11; git diff **+73/−73** på 51 filer (0 linjer endret utover label-token, verifisert via `grep` på begge dialekt-retninger); revers-substitusjon rekonstruerer original byte-eksakt; idempotent re-run = 0 relabels; ground truth `Kategori 42→0`, `Sist oppdatert 31→0`, `Missing English Last updated 52→21`; suite **782/782** exit 0. +### Enhet 2 — `**Dato:**`→`**Last updated:**` ✅ 2026-07-06 + +**Premiss-korreksjon (ground-truth `gt-dato-audit` over alle 28 `**Dato:**`-filer, samme header-blokk-def):** roadmap-tallet «`**Dato:**` (16)» var ufullstendig. Faktisk **29 forekomster i 28 filer**, klassifisert: +- **21 header-kandidater** (header-blokk, ikke list-item, ekte 20xx-dato). +- **16 «rene»** (`**Dato:**` er ENESTE datofelt) → relabelet i Enhet 2. Dette er de 16 «genuint manglende engelsk». +- **5 «dual-label»** bærer BÅDE `**Dato:**` (opprettelsesdato) OG `**Last updated:**` (endringsdato) med *distinkte* verdier (f.eks. `Dato 2026-02-04` + `Last updated 2026-06-24`). Blind relabel ⇒ duplikat label / tapt proveniens ⇒ **utskilt til dedup-enhet** (kollisjons-guarden aborterer på dem uansett). +- **8 body/list/placeholder** (3 advisor `YYYY-MM-DD`-maler, 2 `ros-report-templates` `[YYYY-MM-DD]`, 3 body list-items inkl. `[Dato]`) — aldri i scope (header-scoping + `isRealDateValue`). + +**Design-forfining:** en naiv ISO-streng guard (`\d{4}-\d{2}-\d{2}`) ville feilaktig ekskludert ekte verdier `2026-04`, `2026-05`, `5. februar 2026`. Guarden er derfor `isRealDateValue()`: avviser placeholder (`[…]`, `YYYY`/`MM`/`DD`), aksepterer enhver verdi med et 20xx-år. Faktisk sikkerhet = frosset 16-manifest + header-scoping + kollisjons-guard + byte-invariant; `isRealDateValue` er dokumentert forsvar-i-dybde mot template-fellen. + +**Utført:** `**Dato:**`→`**Last updated:**` = **16 relabels** på **16** filer. Gjenbrukte Enhet-1-primitiv `relabelHeaderDialect()` med `DATO_LABEL_MAP`; ny driver `scripts/kb-update/relabel-dato.mjs` (frosset 16-manifest, `isRealDateValue`-guard, samme harde per-fil-byte-invariant, idempotent skip, `isMain`-guard). De 5 dual-filene bevisst UTE av manifestet. + +**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. + **Gjenstående decision-(b)-enheter (egne økter, ⊥ R7):** -- **Enhet 2:** `**Dato:**`→`**Last updated:**` (16) med header-scoping + ISO-dato-guard som ekskluderer template-blokker (fellen). - **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 4:** 1 datoløs (`decision-trees.md`, git-«innholds-commit»-heuristikk, ALDRI «i dag») + verifiser de 8 «category-none» = ekte gap vs. vindus-artefakt. ## Parkert (krever eget operatør-go) diff --git a/scripts/kb-update/relabel-dato.mjs b/scripts/kb-update/relabel-dato.mjs new file mode 100644 index 0000000..dd50bfa --- /dev/null +++ b/scripts/kb-update/relabel-dato.mjs @@ -0,0 +1,139 @@ +#!/usr/bin/env node +// Enhet 2 (decision-b dialect pass, path A, ⊥ R7) — pure value-preserving relabel of the third +// Norwegian header date label to its English equivalent: +// **Dato:** → **Last updated:** (16 files) +// +// This is a LABEL rename only: the date value (`2026-06-19`, `2026-04`, `5. februar 2026`, …) is +// byte-preserved, so no fact is ever fabricated. It reuses the Enhet-1 primitive relabelHeaderDialect() +// — header-scoped (above the first `---`/`## `), multi-occurrence-guarded, target-collision-guarded, +// with a hard per-file byte invariant re-proven before any write. +// +// Why **Dato:** needed its own unit (deferred from Enhet 1): unlike **Sist oppdatert:**/**Kategori:**, +// **Dato:** ALSO appears in BODY templates as a placeholder (`**Dato:** [YYYY-MM-DD]`, `YYYY-MM-DD`, +// `[Dato]`). Header-scoping already excludes those, but this unit adds isRealDateValue() as an explicit +// defense-in-depth guard that documents — and enforces — the trap: a manifest file's header **Dato:** +// value must be a real date, never a placeholder. +// +// Scope is EXACTLY 16 files: those where **Dato:** is the SOLE date field. Ground-truth audit +// (2026-07-06) found 21 header **Dato:** candidates; 5 of them ALSO carry **Last updated:** (a distinct +// creation-vs-modified date pair) → carved out to a later dedup unit, and the collision-guard aborts on +// them regardless. 8 further occurrences are body/list/placeholder and are never in scope. +// +// Usage: node scripts/kb-update/relabel-dato.mjs [--dry] +import { readFileSync, writeFileSync, existsSync, realpathSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { relabelHeaderDialect } from './relabel-dialect.mjs'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const PLUGIN_ROOT = join(__dirname, '..', '..'); + +// The single Dato→English pair for this unit. +export const DATO_LABEL_MAP = [['**Dato:**', '**Last updated:**']]; + +/** + * Placeholder guard: a real **Dato:** value carries a 20xx year and no template markers. + * Accepts every real dialect in the corpus (`2026-06-19`, `2026-04`, `5. februar 2026`); + * rejects the body-template traps (`[YYYY-MM-DD]`, `YYYY-MM-DD`, `[Dato]`, empty, `TBD`). + * @param {string} value the text following `**Dato:**` on the header line + * @returns {boolean} + */ +export function isRealDateValue(value) { + const v = String(value ?? '').trim(); + if (/[[\]]/.test(v)) return false; // bracketed placeholder: [YYYY-MM-DD], [Dato] + if (/Y{2,}|M{2,}|D{2,}/.test(v)) return false; // literal placeholder: YYYY-MM-DD + return /20\d\d/.test(v); // must carry a real year +} + +// Frozen manifest — the 16 files where **Dato:** is the SOLE date field (header block, real date, +// NO pre-existing **Last updated:**), verified against ground truth 2026-07-06. The per-file invariant +// below re-proves each write; the manifest only bounds WHICH files may be touched. The 5 dual-label +// files (which carry both **Dato:** and **Last updated:**) are DELIBERATELY absent. +export const MANIFEST = [ + 'skills/ms-ai-engineering/references/mlops-genaiops/cost-optimization-mlops-pipelines.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/monitoring-observability-ml-systems.md', + 'skills/ms-ai-governance/references/monitoring-observability/anomaly-detection-ai-systems.md', + 'skills/ms-ai-governance/references/monitoring-observability/application-insights-llm-monitoring.md', + 'skills/ms-ai-governance/references/monitoring-observability/distributed-tracing-ai-pipelines.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-governance-structure-framework.md', + 'skills/ms-ai-governance/references/responsible-ai/red-teaming-ai-models.md', + 'skills/ms-ai-security/references/ai-security-engineering/adversarial-input-robustness-testing.md', + 'skills/ms-ai-security/references/ai-security-engineering/model-fingerprinting-watermarking.md', + 'skills/ms-ai-security/references/ai-security-engineering/secure-model-deployment-hardening.md', + 'skills/ms-ai-security/references/ai-security-engineering/supply-chain-security-ai-models.md', +]; + +function run({ dry }) { + const planned = []; + const missing = []; + let relabels = 0; + + for (const rel of MANIFEST) { + const abs = join(PLUGIN_ROOT, rel); + if (!existsSync(abs)) { missing.push(rel); continue; } + const old = readFileSync(abs, 'utf8'); + const { content: out, applied } = relabelHeaderDialect(old, DATO_LABEL_MAP); + if (applied.length === 0) continue; // idempotent — already relabeled (re-run) + + const o = old.split('\n'); + const n = out.split('\n'); + + // Placeholder guard: every relabeled **Dato:** value must be a real date, not a template. + for (const a of applied) { + const value = o[a.line].split('**Dato:**')[1] ?? ''; + if (!isRealDateValue(value)) { + throw new Error(`ABORT ${rel}: **Dato:** value is not a real date (placeholder trap): "${value.trim()}"`); + } + } + + // Hard invariant: exactly `applied.length` lines changed, each is the old line with ONLY the + // label token swapped; line count unchanged; everything else byte-identical. + if (n.length !== o.length) throw new Error(`ABORT ${rel}: line count changed (${o.length}→${n.length})`); + let diffs = 0; + for (let i = 0; i < o.length; i++) { + if (o[i] === n[i]) continue; + diffs++; + const hit = applied.find((a) => a.line === i); + if (!hit) throw new Error(`ABORT ${rel}: line ${i} changed but not in applied set`); + if (n[i].replace(hit.to, hit.from) !== o[i]) throw new Error(`ABORT ${rel}: line ${i} changed beyond the label token`); + } + if (diffs !== applied.length) throw new Error(`ABORT ${rel}: ${diffs} lines changed ≠ ${applied.length} applied`); + + relabels += applied.length; + planned.push({ rel, applied, 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); + } + + console.log(`Manifest: ${MANIFEST.length} | files to relabel: ${planned.length} | already-english (skipped): ${MANIFEST.length - planned.length}`); + console.log(` ${relabels} **Dato:** → **Last updated:**`); + + if (dry) { + console.log('\n(dry run — no writes)'); + for (const p of planned) { + const value = p.out.split('\n')[p.applied[0].line].split('**Last updated:**')[1].trim(); + console.log(` ~ ${p.rel} [${value}]`); + } + 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') }); diff --git a/skills/ms-ai-engineering/references/mlops-genaiops/cost-optimization-mlops-pipelines.md b/skills/ms-ai-engineering/references/mlops-genaiops/cost-optimization-mlops-pipelines.md index a7e11b4..1a27551 100644 --- a/skills/ms-ai-engineering/references/mlops-genaiops/cost-optimization-mlops-pipelines.md +++ b/skills/ms-ai-engineering/references/mlops-genaiops/cost-optimization-mlops-pipelines.md @@ -1,6 +1,6 @@ # Kostnadsoptimalisering i MLOps-pipelines -**Dato:** 2026-04 +**Last updated:** 2026-04 **Category:** MLOps & GenAIOps **Relevans:** Azure Machine Learning, MLOps-implementering, FinOps for AI **Type:** reference diff --git a/skills/ms-ai-engineering/references/mlops-genaiops/governance-audit-ml-operations.md b/skills/ms-ai-engineering/references/mlops-genaiops/governance-audit-ml-operations.md index e198f42..8284c07 100644 --- a/skills/ms-ai-engineering/references/mlops-genaiops/governance-audit-ml-operations.md +++ b/skills/ms-ai-engineering/references/mlops-genaiops/governance-audit-ml-operations.md @@ -1,7 +1,7 @@ # Governance and Audit Trails in MLOps **Category:** MLOps & GenAIOps -**Dato:** 2026-06-19 +**Last updated:** 2026-06-19 **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 diff --git a/skills/ms-ai-engineering/references/mlops-genaiops/inferencing-optimization-caching.md b/skills/ms-ai-engineering/references/mlops-genaiops/inferencing-optimization-caching.md index dae85b4..2baf914 100644 --- a/skills/ms-ai-engineering/references/mlops-genaiops/inferencing-optimization-caching.md +++ b/skills/ms-ai-engineering/references/mlops-genaiops/inferencing-optimization-caching.md @@ -1,7 +1,7 @@ # Inferencing Optimization and Caching **Category:** MLOps & GenAIOps -**Dato:** 2026-06-19 +**Last updated:** 2026-06-19 **Forfattet av:** Cosmo Skyberg, Senior Microsoft AI Solution Architect **Type:** reference **Source:** https://learn.microsoft.com/azure/machine-learning/concept-onnx diff --git a/skills/ms-ai-engineering/references/mlops-genaiops/infrastructure-as-code-mlops.md b/skills/ms-ai-engineering/references/mlops-genaiops/infrastructure-as-code-mlops.md index 4896acd..18869c9 100644 --- a/skills/ms-ai-engineering/references/mlops-genaiops/infrastructure-as-code-mlops.md +++ b/skills/ms-ai-engineering/references/mlops-genaiops/infrastructure-as-code-mlops.md @@ -1,6 +1,6 @@ # Infrastructure as Code for MLOps -**Dato:** 2026-06-19 +**Last updated:** 2026-06-19 **Category:** MLOps & GenAIOps **Forfatter:** Cosmo Skyberg, Senior Microsoft AI Solution Architect **Type:** reference diff --git a/skills/ms-ai-engineering/references/mlops-genaiops/monitoring-observability-ml-systems.md b/skills/ms-ai-engineering/references/mlops-genaiops/monitoring-observability-ml-systems.md index 76ab4b6..b6e1cf8 100644 --- a/skills/ms-ai-engineering/references/mlops-genaiops/monitoring-observability-ml-systems.md +++ b/skills/ms-ai-engineering/references/mlops-genaiops/monitoring-observability-ml-systems.md @@ -1,7 +1,7 @@ # Monitoring and Observability for ML Systems **Category:** MLOps & GenAIOps -**Dato:** 2026-04 +**Last updated:** 2026-04 **Kilder:** Microsoft Learn (azure-machine-learning, azure-monitor) **Konfidensgrad:** ⭐⭐⭐⭐⭐ (Verifisert mot offisiell Microsoft-dokumentasjon) **Type:** reference diff --git a/skills/ms-ai-governance/references/monitoring-observability/anomaly-detection-ai-systems.md b/skills/ms-ai-governance/references/monitoring-observability/anomaly-detection-ai-systems.md index 13670a5..acebddd 100644 --- a/skills/ms-ai-governance/references/monitoring-observability/anomaly-detection-ai-systems.md +++ b/skills/ms-ai-governance/references/monitoring-observability/anomaly-detection-ai-systems.md @@ -1,6 +1,6 @@ # Anomaly Detection for AI Systems -**Dato:** 5. februar 2026 +**Last updated:** 5. februar 2026 **Category:** Monitoring & Observability **Målgruppe:** AI-arkitekter, DevOps-team, MLOps-ingeniører **Type:** reference diff --git a/skills/ms-ai-governance/references/monitoring-observability/application-insights-llm-monitoring.md b/skills/ms-ai-governance/references/monitoring-observability/application-insights-llm-monitoring.md index f05e7ef..5ea9317 100644 --- a/skills/ms-ai-governance/references/monitoring-observability/application-insights-llm-monitoring.md +++ b/skills/ms-ai-governance/references/monitoring-observability/application-insights-llm-monitoring.md @@ -1,7 +1,7 @@ # Application Insights for LLM Monitoring **Category:** Monitoring & Observability -**Dato:** 2026-02-05 +**Last updated:** 2026-02-05 **Status:** Komplett **Type:** reference diff --git a/skills/ms-ai-governance/references/monitoring-observability/distributed-tracing-ai-pipelines.md b/skills/ms-ai-governance/references/monitoring-observability/distributed-tracing-ai-pipelines.md index 0445bf9..31cae7a 100644 --- a/skills/ms-ai-governance/references/monitoring-observability/distributed-tracing-ai-pipelines.md +++ b/skills/ms-ai-governance/references/monitoring-observability/distributed-tracing-ai-pipelines.md @@ -1,7 +1,7 @@ # Distributed Tracing for AI Pipelines **Category:** Monitoring & Observability -**Dato:** 2026-06-19 +**Last updated:** 2026-06-19 **Status:** ✅ Komplett **Type:** reference **Source:** https://learn.microsoft.com/azure/azure-monitor/app/opentelemetry-overview diff --git a/skills/ms-ai-governance/references/monitoring-observability/log-analytics-kql-ai-queries.md b/skills/ms-ai-governance/references/monitoring-observability/log-analytics-kql-ai-queries.md index b291c09..c2c36f9 100644 --- a/skills/ms-ai-governance/references/monitoring-observability/log-analytics-kql-ai-queries.md +++ b/skills/ms-ai-governance/references/monitoring-observability/log-analytics-kql-ai-queries.md @@ -1,7 +1,7 @@ # Log Analytics KQL Queries for AI **Category:** Monitoring & Observability -**Dato:** 2026-05 +**Last updated:** 2026-05 **Forfatter:** Cosmo Skyberg, AI Solution Architect **Type:** reference **Source:** https://learn.microsoft.com/azure/machine-learning/monitor-azure-machine-learning diff --git a/skills/ms-ai-governance/references/monitoring-observability/token-usage-tracking-attribution.md b/skills/ms-ai-governance/references/monitoring-observability/token-usage-tracking-attribution.md index 553f3e1..0d6767b 100644 --- a/skills/ms-ai-governance/references/monitoring-observability/token-usage-tracking-attribution.md +++ b/skills/ms-ai-governance/references/monitoring-observability/token-usage-tracking-attribution.md @@ -1,7 +1,7 @@ # Token Usage Tracking and Attribution **Category:** Monitoring & Observability -**Dato:** 2026-06-19 +**Last updated:** 2026-06-19 **Versjon:** 1.0 **Type:** reference **Source:** https://learn.microsoft.com/azure/foundry/concepts/manage-costs diff --git a/skills/ms-ai-governance/references/responsible-ai/ai-governance-structure-framework.md b/skills/ms-ai-governance/references/responsible-ai/ai-governance-structure-framework.md index 4c3d9c9..9284b9e 100644 --- a/skills/ms-ai-governance/references/responsible-ai/ai-governance-structure-framework.md +++ b/skills/ms-ai-governance/references/responsible-ai/ai-governance-structure-framework.md @@ -1,6 +1,6 @@ # AI Governance Structure - Building an Organizational Framework -**Dato:** 2026-02-03 +**Last updated:** 2026-02-03 **Category:** Responsible AI & Governance **Målgruppe:** Tekniske beslutningstakere, AI-arkitekter, governance-team **Oppdateringsfrekvens:** Kvartalsvis (Q1 2026) diff --git a/skills/ms-ai-governance/references/responsible-ai/red-teaming-ai-models.md b/skills/ms-ai-governance/references/responsible-ai/red-teaming-ai-models.md index 0b9388f..0dca15b 100644 --- a/skills/ms-ai-governance/references/responsible-ai/red-teaming-ai-models.md +++ b/skills/ms-ai-governance/references/responsible-ai/red-teaming-ai-models.md @@ -1,6 +1,6 @@ # Red Teaming AI Models - Adversarial Testing & Security -**Dato:** 2026-06-19 +**Last updated:** 2026-06-19 **Category:** Responsible AI & Governance **Målgruppe:** Arkitekter, sikkerhetsteam, AI-utviklere **Konfidensgrad:** ⚠️ HIGH — Basert på offisiell Microsoft-dokumentasjon (jun 2026) diff --git a/skills/ms-ai-security/references/ai-security-engineering/adversarial-input-robustness-testing.md b/skills/ms-ai-security/references/ai-security-engineering/adversarial-input-robustness-testing.md index 87d5e62..54aee8a 100644 --- a/skills/ms-ai-security/references/ai-security-engineering/adversarial-input-robustness-testing.md +++ b/skills/ms-ai-security/references/ai-security-engineering/adversarial-input-robustness-testing.md @@ -1,7 +1,7 @@ # Adversarial Input Robustness Testing and Fuzzing **Category:** AI Security Engineering -**Dato:** 2026-06-19 +**Last updated:** 2026-06-19 **Status:** Aktiv **Type:** reference **Source:** https://learn.microsoft.com/azure/foundry/concepts/ai-red-teaming-agent diff --git a/skills/ms-ai-security/references/ai-security-engineering/model-fingerprinting-watermarking.md b/skills/ms-ai-security/references/ai-security-engineering/model-fingerprinting-watermarking.md index c03608f..85a6466 100644 --- a/skills/ms-ai-security/references/ai-security-engineering/model-fingerprinting-watermarking.md +++ b/skills/ms-ai-security/references/ai-security-engineering/model-fingerprinting-watermarking.md @@ -1,7 +1,7 @@ # Model Fingerprinting and Watermarking for Attribution **Category:** AI Security Engineering -**Dato:** 2026-06-19 +**Last updated:** 2026-06-19 **Status:** Active **Type:** reference **Source:** https://learn.microsoft.com/azure/machine-learning/concept-model-management-and-deployment diff --git a/skills/ms-ai-security/references/ai-security-engineering/secure-model-deployment-hardening.md b/skills/ms-ai-security/references/ai-security-engineering/secure-model-deployment-hardening.md index f3bceed..b0f59dd 100644 --- a/skills/ms-ai-security/references/ai-security-engineering/secure-model-deployment-hardening.md +++ b/skills/ms-ai-security/references/ai-security-engineering/secure-model-deployment-hardening.md @@ -1,7 +1,7 @@ # Secure Model Deployment and Runtime Hardening **Category:** AI Security Engineering -**Dato:** 2026-02-05 +**Last updated:** 2026-02-05 **Målgruppe:** Arkitekter som skal sikre AI-modeller i produksjonsmiljøer **Type:** reference diff --git a/skills/ms-ai-security/references/ai-security-engineering/supply-chain-security-ai-models.md b/skills/ms-ai-security/references/ai-security-engineering/supply-chain-security-ai-models.md index 73bc4a2..41da776 100644 --- a/skills/ms-ai-security/references/ai-security-engineering/supply-chain-security-ai-models.md +++ b/skills/ms-ai-security/references/ai-security-engineering/supply-chain-security-ai-models.md @@ -1,7 +1,7 @@ # Supply Chain Security for AI Models and Dependencies **Category:** AI Security Engineering -**Dato:** 2026-06-19 +**Last updated:** 2026-06-19 **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 diff --git a/tests/kb-update/test-relabel-dato.test.mjs b/tests/kb-update/test-relabel-dato.test.mjs new file mode 100644 index 0000000..b7f0f7d --- /dev/null +++ b/tests/kb-update/test-relabel-dato.test.mjs @@ -0,0 +1,151 @@ +// tests/kb-update/test-relabel-dato.test.mjs +// TDD for Enhet 2 (decision-b, path A, ⊥ R7) — value-preserving relabel of the third Norwegian +// header date label to its English equivalent, on the 16 ref files where **Dato:** is the SOLE +// date field (no pre-existing **Last updated:**): +// **Dato:** → **Last updated:** (16 files) +// +// Enhet 2 reuses the proven Enhet-1 primitive relabelHeaderDialect() (header-scoped, collision- +// guarded, byte-invariant) with a Dato-specific label map, plus ONE new piece of production logic: +// isRealDateValue() — a placeholder guard that rejects the body-template trap (`[YYYY-MM-DD]`, +// `YYYY-MM-DD`, `[Dato]`) while accepting every real date dialect actually present in the corpus +// (`2026-06-19`, `2026-04`, `5. februar 2026`). +// +// DELIBERATELY OUT of Enhet 2 (carved out after ground-truth audit 2026-07-06): +// - 5 DUAL-LABEL files that carry BOTH **Dato:** (creation date) AND **Last updated:** (modified +// date) with distinct values. Relabeling would duplicate the label / destroy provenance; the +// collision-guard aborts on them by design. Deferred to a dedup unit (Enhet 3-adjacent). +// - 8 body/list/placeholder occurrences (advisor YYYY-MM-DD templates, ros-report [YYYY-MM-DD], +// 3 body list-items incl. [Dato]) — excluded by header-scoping + isRealDateValue. + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { existsSync, readFileSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { relabelHeaderDialect } from '../../scripts/kb-update/relabel-dialect.mjs'; +import { + DATO_LABEL_MAP, + isRealDateValue, + MANIFEST, +} from '../../scripts/kb-update/relabel-dato.mjs'; + +const PLUGIN_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); + +// The 5 dual-label files carved OUT of Enhet 2 (they already carry **Last updated:**). +const DUAL_LABEL_FILES = [ + '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/mlops-security-access-control.md', + 'skills/ms-ai-engineering/references/mlops-genaiops/model-deployment-strategies-azure.md', + 'skills/ms-ai-engineering/references/mlops-genaiops/prompt-flow-production-deployment.md', +]; + +// ── primitive reused with the Dato label map ─────────────────────────────── + +test('relabels **Dato:** → **Last updated:**, value byte-identical', () => { + const input = '# T\n\n**Category:** X\n**Dato:** 2026-06-19\n**Type:** reference\n\n---\n\n## Body\n'; + const { content, applied } = relabelHeaderDialect(input, DATO_LABEL_MAP); + assert.equal(applied.length, 1); + assert.match(content, /\*\*Last updated:\*\* 2026-06-19/); + assert.doesNotMatch(content, /\*\*Dato:\*\*/); + // Only the label token changed — reversing the sub reconstructs the original exactly. + assert.equal(content.replace('**Last updated:**', '**Dato:**'), input); +}); + +test('header-scoping: a **Dato:** in the BODY (past first --- / ##) is NOT touched', () => { + // The body-template trap this whole unit exists to avoid. + const input = + '# T\n\n**Dato:** 2026-06-19\n\n---\n\n## Mal\n**Dato:** [YYYY-MM-DD]\n'; + const { content, applied } = relabelHeaderDialect(input, DATO_LABEL_MAP); + assert.equal(applied.length, 1); + assert.match(content, /^\*\*Last updated:\*\* 2026-06-19/m); + // The body template line survives verbatim. + assert.match(content, /## Mal\n\*\*Dato:\*\* \[YYYY-MM-DD\]/); +}); + +test('collision-guard: throws when the header already carries **Last updated:** (dual-label file)', () => { + // This is the safety net for the 5 carved-out dual-label files: even if one were mis-added to + // the manifest, the relabel aborts rather than duplicating the label. + const input = '# T\n\n**Dato:** 2026-02-04\n**Last updated:** 2026-06-19\n\n## Body\n'; + assert.throws(() => relabelHeaderDialect(input, DATO_LABEL_MAP), /already present/i); +}); + +test('idempotent: re-running on the relabeled output is a no-op', () => { + const input = '# T\n\n**Dato:** 2026-06-19\n**Category:** X\n\n## Body\n'; + const once = relabelHeaderDialect(input, DATO_LABEL_MAP); + const twice = relabelHeaderDialect(once.content, DATO_LABEL_MAP); + assert.equal(twice.applied.length, 0); + assert.equal(twice.content, once.content); +}); + +// ── isRealDateValue: the placeholder guard ───────────────────────────────── + +test('isRealDateValue accepts every real date dialect present in the corpus', () => { + for (const v of [' 2026-06-19', ' 2026-04', ' 2026-05', ' 5. februar 2026', ' 2026-02-03']) { + assert.ok(isRealDateValue(v), `should accept "${v}"`); + } +}); + +test('isRealDateValue rejects the template placeholders (the trap)', () => { + for (const v of [' [YYYY-MM-DD]', ' YYYY-MM-DD', ' [Dato]', '', ' ', ' TBD']) { + assert.ok(!isRealDateValue(v), `should reject "${v}"`); + } +}); + +// ── MANIFEST: structural well-formedness ─────────────────────────────────── + +test('MANIFEST: exactly 16 distinct, well-formed reference paths', () => { + assert.equal(MANIFEST.length, 16); + const set = new Set(MANIFEST); + assert.equal(set.size, 16, 'duplicate path in MANIFEST'); + for (const p of MANIFEST) { + assert.match(p, /^skills\/ms-ai-[a-z]+\/references\/.+\.md$/, p); + } +}); + +test('MANIFEST: excludes the 5 dual-label files (carved out to a dedup unit)', () => { + for (const dual of DUAL_LABEL_FILES) { + assert.ok(!MANIFEST.includes(dual), `dual-label file must not be in manifest: ${dual}`); + } +}); + +// ── MANIFEST ↔ disk: committed-state ground-truth integrity (guards drift/reversion) ── +// These pin the DURABLE post-relabel invariant of the committed corpus — re-running the relabel +// is a no-op, and every manifest file now carries the English label with its date preserved. + +function headerBlock(content) { + const lines = content.split('\n'); + for (let i = 0; i < lines.length; i++) { + if (/^---\s*$/.test(lines[i]) || /^##\s/.test(lines[i])) return lines.slice(0, i); + } + return lines; +} + +test('MANIFEST ↔ disk: committed state — English label present, no header **Dato:**, real date, idempotent', () => { + for (const rel of MANIFEST) { + const abs = join(PLUGIN_ROOT, rel); + assert.ok(existsSync(abs), `missing: ${rel}`); + const content = readFileSync(abs, 'utf8'); + const header = headerBlock(content); + // Enhet 2 replaced the sole header date label: **Last updated:** present, **Dato:** gone. + const luLine = header.find((l) => l.startsWith('**Last updated:**')); + assert.ok(luLine, `expected header **Last updated:** in ${rel}`); + assert.ok(!header.some((l) => l.includes('**Dato:**')), `stray header **Dato:** remains in ${rel}`); + // The preserved value is still a real date, not a placeholder. + const value = luLine.split('**Last updated:**')[1] ?? ''; + assert.ok(isRealDateValue(value), `Last-updated value not a real date in ${rel}: "${value.trim()}"`); + // Idempotent: the relabel is a no-op on the committed file. + const { applied } = relabelHeaderDialect(content, DATO_LABEL_MAP); + assert.equal(applied.length, 0, `relabel is not idempotent on committed ${rel}`); + } +}); + +test('carve-out ↔ disk: the 5 dual-label files still carry BOTH labels (untouched by Enhet 2)', () => { + for (const rel of DUAL_LABEL_FILES) { + const abs = join(PLUGIN_ROOT, rel); + assert.ok(existsSync(abs), `missing: ${rel}`); + const header = headerBlock(readFileSync(abs, 'utf8')); + assert.ok(header.some((l) => l.includes('**Dato:**')), `dual file lost its **Dato:** — ${rel}`); + assert.ok(header.some((l) => l.startsWith('**Last updated:**')), `dual file lost **Last updated:** — ${rel}`); + } +});