feat(migration): add verified import and rollback workflow

This commit is contained in:
chick
2026-07-18 14:13:11 +08:00
parent 293c015f89
commit ae27b522ab
12 changed files with 902 additions and 44 deletions
@@ -103,6 +103,7 @@ describe('legacy import', () => {
description: 'primary',
tags: ['prod', 'blue'],
capabilities: ['status.read', 'sim.restart'],
authMode: 'password',
credentialPresent: true,
status: 'planned',
});
@@ -142,6 +143,15 @@ describe('legacy import', () => {
expect(scan(error)).not.toContain(password);
});
it('never includes a malformed source excerpt in an error', async () => {
const path = await legacyFile({});
const marker = 'malformed-secret-never-echo';
await writeFile(path, `{"instances":[],"password":"${marker}" trailing`);
const error = await rejection(() => previewLegacyImport(path));
expect(error.message).toBe('Legacy config is not valid JSON');
expect(scan(error)).not.toContain(marker);
});
it('rejects symlinks and non-regular files', async () => {
const path = await legacyFile(valid);
const link = `${path}.link`;
@@ -243,6 +253,23 @@ describe('legacy import', () => {
await expect(confirmLegacyImport(fresh, tampered, db)).rejects.toThrow(/digest/i);
});
it('binds status and auth mode into the execution plan digest', async () => {
const path = await legacyFile(valid);
const db = database();
const preview = await previewLegacyImport(path, db);
const instance = preview.instances[0]!;
const statusTampered = {
...preview,
instances: [{ ...instance, status: 'unchanged' as const }, ...preview.instances.slice(1)],
} as LegacyImportPreview;
await expect(confirmLegacyImport(path, statusTampered, db)).rejects.toThrow(/digest/i);
const authTampered = {
...preview,
instances: [{ ...instance, authMode: 'none' as const }, ...preview.instances.slice(1)],
} as LegacyImportPreview;
await expect(confirmLegacyImport(path, authTampered, db)).rejects.toThrow(/digest/i);
});
it('reports conflicts and confirmation rejects the entire transaction', async () => {
const path = await legacyFile(valid);
const db = database();