Ren value-preserving label-relabel av de to norske header-labelene til engelsk på 51 ref-filer (22 bærer begge). Ny testet ren primitiv relabelHeaderDialect() (header-blokk-scoped, kollisjons-/multiforekomst-guard) + manifest-drevet driver relabel-dialect.mjs (frosset 51-fil-manifest, hard per-fil-invariant, idempotent, isMain-guard). **Dato:** bevisst UTE (body-template-felle → Enhet 2). Premiss-korreksjon i roadmap R22: tredje Dato-dialekt (16), 0 bold-duplikater (ikke 4), 1 datoløs (ikke 5), category-none = vindus-artefakt. test-relabel-dialect 11/11; diff +73/-73 0 linjer utover label; suite 782/782 exit 0.
194 lines
11 KiB
JavaScript
194 lines
11 KiB
JavaScript
#!/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.
|
||
//
|
||
// Usage: node scripts/kb-update/relabel-dialect.mjs [--dry]
|
||
import { readFileSync, writeFileSync, existsSync, realpathSync } from 'node:fs';
|
||
import { join, dirname } from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
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) 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') });
|