Løser dual-header der filer bærer BÅDE **Dato:** (opprettelse) OG **Last updated:** (endring). Ground truth 2026-07-16 + git first-commit-map (2026-04-08 for alle 5): - 4 distinkte (**Dato:** 2026-02-04, FØR git first-commit → ekte opprettelsesdato): relabel **Dato:** → **Created:**, verdi byte-bevart (norsk→engelsk label-rename). - 1 identisk (mlops-security-access-control: **Dato:**=**Last updated:**=2026-06-19, ETTER git first-commit → metadata-artefakt, ikke ekte opprettelsesdato): drop redundant **Dato:**. Relabel ville påstått falsk opprettelsesdato — drop er sannferdig. Ny ren primitiv dropRedundantBoldField (kaster uten identisk-verdi bevis-felt), gjenbrukt relabelHeaderDialect m/ CREATED_MAP. Manifest-drevet dedup-dato.mjs med hard per-fil-invariant FØR skriv (relabel: 1 linje, kun label-token; drop: net -1, Last updated bevart), atomicWriteSync, idempotent. Ingen validator håndhever lukket feltliste → **Created:** er additiv/konform. +15 tester (dropRedundantBoldField, CREATED_MAP-relabel, applyOp, manifest, 2 corpus- guards) + oppdatert stale Enhet-2 carve-out-guard til post-3b-sannhet. Suite 910/910 exit 0. validate-plugin 250 PASS/0 FAIL. Datoløse dual-**Dato:**-filer 5→0.
164 lines
8.2 KiB
JavaScript
164 lines
8.2 KiB
JavaScript
#!/usr/bin/env node
|
|
// dedup-dato.mjs — Enhet 3b (2026-07-16, ⊥ R7). Resolve the dual-**Dato:** header on the 5
|
|
// mlops-genaiops files that carry BOTH a bold **Dato:** (creation) AND a bold **Last updated:**
|
|
// (modification).
|
|
//
|
|
// Ground truth 2026-07-16, re-measured against the committed corpus + the git first-commit
|
|
// "Created-map" (git first-commit = 2026-04-08 for all 5):
|
|
// - 4 files carry a DISTINCT creation date — **Dato:** 2026-02-04, authored BEFORE the git
|
|
// first-commit → a genuine creation date. Action: RELABEL **Dato:** → **Created:**, value
|
|
// byte-preserved (the same norsk→english label-rename class as Enhet 1/2; **Created:** is a
|
|
// new-but-additive header label — no validator enforces a closed field list).
|
|
// - 1 file (mlops-security-access-control) carries IDENTICAL dates — **Dato:** = **Last updated:**
|
|
// = 2026-06-19, both AFTER the git first-commit → NOT a genuine creation date (a metadata-pass
|
|
// artifact). Action: DROP the redundant **Dato:**. Relabeling it to **Created:** 2026-06-19
|
|
// would assert a FALSE creation date, so removal is the truthful action (the Created-map is
|
|
// what distinguishes the two cases).
|
|
//
|
|
// Never derives a value; never deletes a field it cannot prove redundant (dropRedundantBoldField
|
|
// THROWS unless a bold **Last updated:** with the IDENTICAL value proves it). A hard per-file
|
|
// invariant is asserted BEFORE any write:
|
|
// relabel — exactly ONE line changed, that line is the old line with ONLY the label token
|
|
// swapped (**Dato:**→**Created:**) and the frozen value intact; line count unchanged;
|
|
// body byte-identical.
|
|
// drop — exactly ONE line removed (the **Dato:** line); no **Dato:** remains in the header;
|
|
// **Last updated:** survives; body byte-identical.
|
|
// Idempotent: a re-run finds **Dato:** gone and is a no-op. Aborts, writing nothing, on any drift.
|
|
//
|
|
// 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/dedup-dato.mjs [--dry]
|
|
import { readFileSync, existsSync, realpathSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dropRedundantBoldField } from './lib/transform.mjs';
|
|
import { relabelHeaderDialect } from './relabel-dialect.mjs';
|
|
import { atomicWriteSync } from './lib/atomic-write.mjs';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const PLUGIN_ROOT = join(__dirname, '..', '..');
|
|
const DIR = 'skills/ms-ai-engineering/references/mlops-genaiops';
|
|
|
|
// The single norsk→english label pair for the relabel action. **Created:** is intentionally NOT in
|
|
// relabel-dialect.mjs's LABEL_MAP (that pass is Sist oppdatert/Kategori) nor relabel-dato.mjs's map
|
|
// (that maps **Dato:**→**Last updated:** for the 16 "rene" single-date files) — the 5 dual files
|
|
// were deliberately excluded there and are resolved only here.
|
|
export const CREATED_MAP = [['**Dato:**', '**Created:**']];
|
|
|
|
// The label whose identical value proves a **Dato:** redundant in the drop case.
|
|
export const DROP_LABEL = 'Dato';
|
|
export const PROOF_LABEL = 'Last updated';
|
|
|
|
// Frozen manifest — the 5 dual-**Dato:** mlops-genaiops files (ground truth 2026-07-16). `created`
|
|
// freezes the authored creation date the relabel must preserve; a corpus drift where the value
|
|
// changed is caught by the per-file invariant below.
|
|
export const MANIFEST = [
|
|
{ rel: `${DIR}/feedback-loops-continuous-improvement.md`, action: 'relabel', created: '2026-02-04' },
|
|
{ rel: `${DIR}/genaiops-llm-specific-practices.md`, action: 'relabel', created: '2026-02-04' },
|
|
{ rel: `${DIR}/model-deployment-strategies-azure.md`, action: 'relabel', created: '2026-02-04' },
|
|
{ rel: `${DIR}/prompt-flow-production-deployment.md`, action: 'relabel', created: '2026-02-04' },
|
|
{ rel: `${DIR}/mlops-security-access-control.md`, action: 'drop' },
|
|
];
|
|
|
|
/**
|
|
* The pure per-file op: relabel **Dato:**→**Created:** or drop the redundant bold **Dato:**. Pure.
|
|
* @param {string} content
|
|
* @param {'relabel'|'drop'} action
|
|
* @returns {string}
|
|
*/
|
|
export function applyOp(content, action) {
|
|
if (action === 'relabel') return relabelHeaderDialect(content, CREATED_MAP).content;
|
|
if (action === 'drop') return dropRedundantBoldField(content, DROP_LABEL, PROOF_LABEL);
|
|
throw new Error(`applyOp: unknown action '${action}'`);
|
|
}
|
|
|
|
// 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') };
|
|
}
|
|
|
|
// Assert the hard per-file invariant. Throws (aborting the run) on any breach.
|
|
function assertInvariant(m, oldC, newC) {
|
|
const { rel, action } = m;
|
|
const o = oldC.split('\n');
|
|
const n = newC.split('\n');
|
|
const oldH = splitHeaderBody(oldC);
|
|
const newH = splitHeaderBody(newC);
|
|
if (oldH.body !== newH.body) throw new Error(`ABORT ${rel}: body not byte-identical`);
|
|
if (/\*\*Dato:\*\*/.test(newH.header)) throw new Error(`ABORT ${rel}: header still carries **Dato:** after op`);
|
|
|
|
if (action === 'relabel') {
|
|
if (n.length !== o.length) throw new Error(`ABORT ${rel}: line count changed (${o.length}→${n.length})`);
|
|
let diffs = 0;
|
|
let changedLine = -1;
|
|
for (let i = 0; i < o.length; i++) {
|
|
if (o[i] === n[i]) continue;
|
|
diffs++;
|
|
changedLine = i;
|
|
}
|
|
if (diffs !== 1) throw new Error(`ABORT ${rel}: expected exactly one line changed, got ${diffs}`);
|
|
// The changed line must be the old line with ONLY the label token swapped.
|
|
if (n[changedLine].replace('**Created:**', '**Dato:**') !== o[changedLine]) {
|
|
throw new Error(`ABORT ${rel}: changed line ${changedLine} altered beyond the **Dato:**→**Created:** label token`);
|
|
}
|
|
if (!new RegExp(`\\*\\*Created:\\*\\* ${m.created}(?:\\s|$)`).test(newH.header)) {
|
|
throw new Error(`ABORT ${rel}: **Created:** ${m.created} (frozen value) missing after relabel`);
|
|
}
|
|
} else {
|
|
// drop
|
|
if (n.length !== o.length - 1) throw new Error(`ABORT ${rel}: expected exactly one line removed (was ${o.length}, now ${n.length})`);
|
|
if (!/\*\*Last updated:\*\*\s*\S/.test(newH.header)) throw new Error(`ABORT ${rel}: **Last updated:** missing after drop`);
|
|
if (/\*\*Created:\*\*/.test(newH.header)) throw new Error(`ABORT ${rel}: drop must not introduce **Created:**`);
|
|
}
|
|
}
|
|
|
|
function run({ dry }) {
|
|
const planned = [];
|
|
const skipped = [];
|
|
const missing = [];
|
|
|
|
for (const m of MANIFEST) {
|
|
const abs = join(PLUGIN_ROOT, m.rel);
|
|
if (!existsSync(abs)) { missing.push(m.rel); continue; }
|
|
const old = readFileSync(abs, 'utf8');
|
|
const out = applyOp(old, m.action);
|
|
if (out === old) { skipped.push(m.rel); continue; } // idempotent: already resolved
|
|
assertInvariant(m, old, out);
|
|
planned.push({ rel: m.rel, action: m.action, out });
|
|
}
|
|
|
|
if (missing.length) {
|
|
console.error(`ABORT — ${missing.length} manifest target(s) not found (corpus drift):`);
|
|
missing.forEach((x) => console.error(' ' + x));
|
|
process.exit(1);
|
|
}
|
|
if (planned.length + skipped.length !== MANIFEST.length) {
|
|
throw new Error(`ABORT — accounted ${planned.length + skipped.length} ≠ manifest ${MANIFEST.length}`);
|
|
}
|
|
|
|
const relabels = planned.filter((p) => p.action === 'relabel').length;
|
|
const drops = planned.filter((p) => p.action === 'drop').length;
|
|
console.log(`Manifest: ${MANIFEST.length} | to resolve: ${planned.length} (${relabels} relabel + ${drops} drop) | already resolved (skipped): ${skipped.length}`);
|
|
if (dry) {
|
|
console.log('\n(dry run — no writes)');
|
|
for (const p of planned) console.log(` ~ ${p.rel} [${p.action}]`);
|
|
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') });
|