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:
parent
b68514487c
commit
b3011da017
11 changed files with 406 additions and 100 deletions
|
|
@ -279,3 +279,44 @@ test('migrateCorpus — post-write parse assertion catches a deep-preamble (>500
|
|||
assert.deepEqual(readAll(root), original);
|
||||
});
|
||||
});
|
||||
|
||||
// --- RX-OPS2: backup hygiene wired into the applier ---
|
||||
|
||||
test('migrateCorpus — aborts when a stale rollback sentinel is present (prior crash)', () => {
|
||||
withTmp((tmp) => {
|
||||
const root = join(tmp, 'skills');
|
||||
const backupRoot = join(tmp, '.kb-backup');
|
||||
makeCorpus(root, { 'ms-ai-engineering/references/a.md': REF_SMALL });
|
||||
mkdirSync(backupRoot, { recursive: true });
|
||||
// A previous run crashed mid-restore and left its sentinel behind.
|
||||
writeFileSync(join(backupRoot, '.rollback-in-progress'), JSON.stringify({ started_at: '2026-07-01T00:00:00Z' }), 'utf8');
|
||||
const manifest = { [key('ms-ai-engineering/references/a.md')]: { type: 'reference', source: 'https://learn.microsoft.com/x' } };
|
||||
assert.throws(
|
||||
() => migrateCorpus({ root, backupRoot, manifest, write: true }),
|
||||
/stale|rollback|recover/i,
|
||||
);
|
||||
// Aborted before any write — corpus untouched.
|
||||
assert.equal(readFileSync(join(root, 'ms-ai-engineering/references/a.md'), 'utf8'), REF_SMALL);
|
||||
});
|
||||
});
|
||||
|
||||
test('migrateCorpus — prunes backups older than retention after a successful write', () => {
|
||||
withTmp((tmp) => {
|
||||
const root = join(tmp, 'skills');
|
||||
const backupRoot = join(tmp, '.kb-backup');
|
||||
makeCorpus(root, { 'ms-ai-engineering/references/a.md': REF_SMALL });
|
||||
// Seed an old backup dir (>7 days) that cleanup must remove after a good run.
|
||||
const oldDir = join(backupRoot, '2026-01-01T00-00-00');
|
||||
mkdirSync(oldDir, { recursive: true });
|
||||
writeFileSync(
|
||||
join(oldDir, '.backup-meta.json'),
|
||||
JSON.stringify({ created_at: '2026-01-01T00:00:00.000Z', schema_version: 1 }),
|
||||
'utf8',
|
||||
);
|
||||
const manifest = { [key('ms-ai-engineering/references/a.md')]: { type: 'reference', source: 'https://learn.microsoft.com/x' } };
|
||||
const report = migrateCorpus({ root, backupRoot, manifest, write: true });
|
||||
assert.equal(report.counts.written, 1);
|
||||
assert.equal(existsSync(oldDir), false, 'stale-age backup pruned after successful write');
|
||||
assert.ok(existsSync(report.backupPath), "this run's fresh backup retained");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue