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.
This commit is contained in:
Kjell Tore Guttormsen 2026-07-15 21:31:29 +02:00
commit b3011da017
11 changed files with 406 additions and 100 deletions

View file

@ -18,10 +18,14 @@
// 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, writeFileSync, existsSync, realpathSync } from 'node:fs';
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, '..', '..');
@ -180,7 +184,7 @@ function run({ dry }) {
}
return;
}
for (const p of planned) writeFileSync(join(PLUGIN_ROOT, p.rel), p.out);
for (const p of planned) atomicWriteSync(join(PLUGIN_ROOT, p.rel), p.out);
console.log(`\nWrote ${planned.length} files.`);
}