ms-ai-architect/scripts/kb-update/relabel-dialect.mjs
Kjell Tore Guttormsen b3011da017 fix(ms-ai-architect): RX-OPS2 skrive-sikkerhet i driverne — scoped restore + atomiske skriv [skip-docs]
- backup.mjs restore(relPaths): SCOPED per-fil-rollback erstatter hel-tre rmSync(srcDir)+cpSync.
  Ruller kun tilbake kjøringens egne skriv; parallelle økters skriv til søsken-filer overlever;
  intet destruktivt rm→cp-vindu. Nyskapte filer slettes; prior bytes gjenopprettes atomisk.
- migrate-corpus.mjs: returnert restore() er nå en null-arg closure bundet til kjøringens skrive-
  liste (public API uendret) → scoped. detectStaleRollback aborterer ved forrige krasj-sentinel;
  cleanupOldBackups pruner backups forbi retention etter vellykket batch.
- 5 drivere (backfill-status/-category, dedup-plain-header, relabel-dato/-dialect):
  writeFileSync → atomicWriteSync (crash-safe tmp+rename) + recovery-kontrakt i header.
- backfill-category.mjs: refaktorert importerbar (isMain-guard + run()/planCategoryBackfill/
  categoryForFile/FOLDER_CATEGORY-eksporter, parameterisert root) → testbar uten scan-side-effekt.
- Tester (+16): 6 scoped-restore (parallel-preservering, ny-fil-sletting, throws-guard) erstatter
  2 hel-tre; test-backfill-category (frosset taxonomi + hermetisk plan); test-driver-atomic-writes
  (5 drivere); migrate stale-abort + cleanup-wiring. Suite 859→875 exit 0. validate-plugin 250/0.
2026-07-15 21:31:29 +02:00

198 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
// Enhet 1 (decision-b dialect pass, path A, ⊥ R7) — pure value-preserving relabel of the two
// Norwegian header labels to their English equivalents:
// **Sist oppdatert:** → **Last updated:** (31 files)
// **Kategori:** → **Category:** (42 files) [22 files carry both → 73 relabels]
//
// This is a LABEL rename only: the value (date, category text, "(v1.0)" suffix) is byte-preserved,
// so no fact is ever fabricated — this is the safe core of the corrected decision-b residual.
//
// Scope is the header block (lines above the first `---` thematic break OR first `## ` heading),
// so a `**Kategori:**`/`**Sist oppdatert:**` inside a BODY template block is never touched. The
// **Dato:** dialect (16 files) is DELIBERATELY excluded: it also appears in body templates
// (`**Dato:** [YYYY-MM-DD]`) and needs a header-scoped ISO-date guard — deferred to Unit 2.
// Status backfill (27+4) and the ai-act dual-block dedup are Unit 3; decision-trees date is Unit 4.
//
// Safety: a frozen MANIFEST (ground-truth-verified 2026-07-06) + a hard per-file invariant asserted
// BEFORE any write (exactly `applied.length` lines changed, each equals the old line with only the
// label token swapped, everything else byte-identical). Idempotent: a file with no norsk header
// label is skipped, so a re-run is a no-op. Aborts and writes nothing on any drift or invariant breach.
//
// Recovery contract: writes are crash-safe (atomicWriteSync tmp+rename — a reader sees the old
// file or the new one, never a partial); an interrupted run is recovered by re-running (idempotent).
//
// Usage: node scripts/kb-update/relabel-dialect.mjs [--dry]
import { readFileSync, existsSync, realpathSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { atomicWriteSync } from './lib/atomic-write.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const PLUGIN_ROOT = join(__dirname, '..', '..');
// The two norsk→english label pairs. **Dato:** is intentionally absent (body-template trap).
export const LABEL_MAP = [
['**Sist oppdatert:**', '**Last updated:**'],
['**Kategori:**', '**Category:**'],
];
// The header block ends at the first thematic break (`---`) or the first `## ` section heading.
// Everything below is body (templates, prose) and is out of relabel scope.
function headerEnd(lines) {
for (let i = 0; i < lines.length; i++) {
if (/^---\s*$/.test(lines[i]) || /^##\s/.test(lines[i])) return i;
}
return lines.length;
}
/**
* Relabel the norsk header labels to English, in the header block only. Pure.
* Guards: a norsk label appearing >1× in the header block, or an English target label already
* present in the header block (would duplicate), abort. A norsk label only in the body is ignored.
* @param {string} content
* @param {[string,string][]} [labelMap]
* @returns {{content: string, applied: {from:string,to:string,line:number}[]}}
*/
export function relabelHeaderDialect(content, labelMap = LABEL_MAP) {
const lines = String(content ?? '').split('\n');
const end = headerEnd(lines);
const header = lines.slice(0, end);
const applied = [];
for (const [from, to] of labelMap) {
let occ = 0;
let idx = -1;
for (let i = 0; i < header.length; i++) {
const c = header[i].split(from).length - 1;
if (c > 0) { occ += c; idx = i; }
}
if (occ === 0) continue; // norsk label absent from header → nothing to do
if (occ > 1) throw new Error(`ABORT: '${from}' appears ${occ}× in header block`);
if (header.some((l) => l.includes(to))) throw new Error(`ABORT: target '${to}' already present in header (would duplicate)`);
header[idx] = header[idx].replace(from, to);
applied.push({ from, to, line: idx });
}
return { content: [...header, ...lines.slice(end)].join('\n'), applied };
}
// Frozen manifest — the 51 scope-confirmed targets (files carrying **Sist oppdatert:** and/or
// **Kategori:** in their header block), 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.
export const MANIFEST = [
'skills/ms-ai-advisor/references/architecture/ai-utredning-template.md',
'skills/ms-ai-advisor/references/architecture/alternativanalyse-methodology.md',
'skills/ms-ai-advisor/references/architecture/capacity-feasibility-benchmarks.md',
'skills/ms-ai-advisor/references/architecture/diagram-prompt-templates.md',
'skills/ms-ai-advisor/references/architecture/regional-availability-verification.md',
'skills/ms-ai-advisor/references/architecture/source-traceability-assumption-register.md',
'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/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/application-insights-llm-monitoring.md',
'skills/ms-ai-governance/references/monitoring-observability/azure-monitor-setup-ai-workloads.md',
'skills/ms-ai-governance/references/monitoring-observability/custom-dashboards-ai-operations.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/norwegian-public-sector-governance/gevinstrealisering-dfo-methodology.md',
'skills/ms-ai-governance/references/norwegian-public-sector-governance/norwegian-nlp-benchmarks.md',
'skills/ms-ai-governance/references/norwegian-public-sector-governance/ros-ai-threat-library.md',
'skills/ms-ai-governance/references/norwegian-public-sector-governance/ros-dpia-security-integration.md',
'skills/ms-ai-governance/references/norwegian-public-sector-governance/ros-maestro-multiagent.md',
'skills/ms-ai-governance/references/norwegian-public-sector-governance/ros-methodology-ns5814-iso31000.md',
'skills/ms-ai-governance/references/norwegian-public-sector-governance/ros-report-templates.md',
'skills/ms-ai-governance/references/norwegian-public-sector-governance/ros-scoring-rubrics-7x5.md',
'skills/ms-ai-governance/references/norwegian-public-sector-governance/ros-sector-checklists.md',
'skills/ms-ai-governance/references/norwegian-public-sector-governance/samfunnsokonomisk-analyse-nnv.md',
'skills/ms-ai-governance/references/responsible-ai/ai-act-annex-iii-checklist.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/adversarial-input-robustness-testing.md',
'skills/ms-ai-security/references/ai-security-engineering/ai-prompt-shield-network.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/entra-agent-id-zero-trust.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/security-copilot-integration.md',
'skills/ms-ai-security/references/ai-security-engineering/security-scoring-rubrics-6x5.md',
'skills/ms-ai-security/references/ai-security-engineering/supply-chain-security-ai-models.md',
'skills/ms-ai-security/references/ai-security-engineering/zero-trust-ai-services.md',
'skills/ms-ai-security/references/cost-optimization/deterministic-cost-calculation-model.md',
];
function run({ dry }) {
const planned = [];
const missing = [];
let dateRelabels = 0;
let catRelabels = 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);
if (applied.length === 0) continue; // idempotent — already relabeled (re-run)
// 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.
const o = old.split('\n');
const n = out.split('\n');
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`);
for (const a of applied) {
if (a.from === '**Sist oppdatert:**') dateRelabels++;
else if (a.from === '**Kategori:**') catRelabels++;
}
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(` ${dateRelabels} **Sist oppdatert:** → **Last updated:**`);
console.log(` ${catRelabels} **Kategori:** → **Category:**`);
console.log(` ${dateRelabels + catRelabels} total relabels`);
if (dry) {
console.log('\n(dry run — no writes)');
for (const p of planned) {
console.log(` ~ ${p.rel} [${p.applied.map((a) => a.from.replace(/\*/g, '')).join(', ')}]`);
}
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') });