@dharma-ai-labs/agent-fabric 0.2.186 → 0.2.187

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/index.d.ts +74 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +296 -12
  4. package/dist/index.js.map +1 -1
  5. package/dist/repositoryCandidateSync.d.ts +109 -2
  6. package/dist/repositoryCandidateSync.d.ts.map +1 -1
  7. package/dist/repositoryCandidateSync.js +327 -22
  8. package/dist/repositoryCandidateSync.js.map +1 -1
  9. package/dist/repositoryCandidateSync.test.js +912 -6
  10. package/dist/repositoryCandidateSync.test.js.map +1 -1
  11. package/dist/repositoryKnowledgeRetention.test.js +319 -3
  12. package/dist/repositoryKnowledgeRetention.test.js.map +1 -1
  13. package/dist/repositorySourceInventoryClient.d.ts +1 -0
  14. package/dist/repositorySourceInventoryClient.d.ts.map +1 -1
  15. package/dist/repositorySourceInventoryClient.js +4 -1
  16. package/dist/repositorySourceInventoryClient.js.map +1 -1
  17. package/dist/repositorySourceRestoration.d.ts +47 -0
  18. package/dist/repositorySourceRestoration.d.ts.map +1 -0
  19. package/dist/repositorySourceRestoration.js +140 -0
  20. package/dist/repositorySourceRestoration.js.map +1 -0
  21. package/dist/repositorySourceRestorationCommand.test.d.ts +2 -0
  22. package/dist/repositorySourceRestorationCommand.test.d.ts.map +1 -0
  23. package/dist/repositorySourceRestorationCommand.test.js +754 -0
  24. package/dist/repositorySourceRestorationCommand.test.js.map +1 -0
  25. package/dist/repositorySourceSync.d.ts +1 -0
  26. package/dist/repositorySourceSync.d.ts.map +1 -1
  27. package/dist/repositorySourceSync.js +10 -4
  28. package/dist/repositorySourceSync.js.map +1 -1
  29. package/dist/repositorySourceSync.test.js +38 -4
  30. package/dist/repositorySourceSync.test.js.map +1 -1
  31. package/dist/schemas/repository-candidate-fence-v1.schema.json +55 -0
  32. package/dist/schemas/repository-candidate-upload-v1.schema.json +60 -0
  33. package/dist/schemas/repository-knowledge-restoration-v1.schema.json +16 -0
  34. package/dist/usage.d.ts.map +1 -1
  35. package/dist/usage.js +3 -1
  36. package/dist/usage.js.map +1 -1
  37. package/package.json +1 -1
@@ -0,0 +1,754 @@
1
+ import assert from 'node:assert/strict';
2
+ import { createHash } from 'node:crypto';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { runInNewContext } from 'node:vm';
5
+ import test from 'node:test';
6
+ import ts from 'typescript';
7
+ import { loadDeviceEnrollmentAnchor } from '@dharma-ai-labs/agent-fabric-relay-client';
8
+ const text = await readFile(new URL('../src/index.ts', import.meta.url), 'utf8');
9
+ const source = ts.createSourceFile('index.ts', text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
10
+ const declaration = source.statements.filter(node => ts.isFunctionDeclaration(node) && node.name?.text === 'repositoryRestoreCommand');
11
+ assert.equal(declaration.length, 1);
12
+ const code = ts.transpileModule(declaration[0].getText(source), {
13
+ compilerOptions: { target: ts.ScriptTarget.ES2023, module: ts.ModuleKind.CommonJS },
14
+ }).outputText;
15
+ const saveDeclaration = source.statements.find(node => ts.isFunctionDeclaration(node) && node.name?.text === 'saveWorkspaceRecord');
16
+ assert.ok(saveDeclaration);
17
+ const saveCode = ts.transpileModule(saveDeclaration.getText(source), {
18
+ compilerOptions: { target: ts.ScriptTarget.ES2023, module: ts.ModuleKind.CommonJS },
19
+ }).outputText;
20
+ test('relay adopts a committed registry receipt before retryable marker failure, but never before a failed save', async () => {
21
+ let poll;
22
+ const visit = (node) => {
23
+ if (ts.isIfStatement(node) && node.expression.getText(source) === 'current && canonicalWorkspace.repositoryPackage')
24
+ poll = node;
25
+ ts.forEachChild(node, visit);
26
+ };
27
+ visit(source);
28
+ assert.ok(poll);
29
+ const pollCode = ts.transpileModule(`async function pollOnce(current: any) { ${poll.getText(source)} }`, { compilerOptions: { target: ts.ScriptTarget.ES2023, module: ts.ModuleKind.CommonJS } }).outputText;
30
+ for (const mode of ['marker-failed', 'save-failed', 'same']) {
31
+ const original = { workspaceId: 'workspace', organizationId: 'org_fixture', repositoryBindingId: 'binding', repositoryAgentId: 'agent',
32
+ repositoryPackage: { state: 'processing', candidateId: 'candidate', operationId: 'operation', generation: 18 } };
33
+ let records = [structuredClone(original)], saves = 0, checkpoints = 0, locked = false;
34
+ const context = { exports: {}, structuredClone, canonicalize: JSON.stringify,
35
+ canonicalWorkspace: structuredClone(original), publishedLocalSnapshotHash: null, repositorySourceWatcher: { invalidate: () => { } },
36
+ advanceRepositorySourceBaseline: () => ({ publishedLocalSnapshotHash: 'local' }), repositorySourcePollInvalidatesWatcher: () => false,
37
+ currentBootstrapHostScope: () => undefined, workspaceRegistryPath: () => '/synthetic/registry.json',
38
+ dharmaHome: () => '/synthetic', resolve: (...args) => args.join('/'),
39
+ acquirePidLock: async () => { locked = true; return async () => { locked = false; }; },
40
+ readFile: async () => JSON.stringify(records), saveRegistry: async (next) => {
41
+ assert.equal(locked, true);
42
+ if (mode === 'save-failed')
43
+ throw new Error('registry_failure');
44
+ records = structuredClone(next);
45
+ saves++;
46
+ },
47
+ checkpointRepositoryRestorationRegistry: async () => {
48
+ assert.equal(locked, true);
49
+ if (++checkpoints === 1 && mode === 'marker-failed')
50
+ throw new Error('marker_failure');
51
+ }, };
52
+ const run = runInNewContext(saveCode + '\n' + pollCode + '\npollOnce', context, { timeout: 1000, contextCodeGeneration: { strings: false, wasm: false } });
53
+ const current = { state: 'published', candidateId: 'candidate', operationId: 'operation', snapshotHash: 'snapshot', releaseId: 'release' };
54
+ if (mode === 'same')
55
+ await run(current);
56
+ else
57
+ await assert.rejects(run(current), mode === 'marker-failed' ? /marker_failure/ : /registry_failure/);
58
+ assert.equal(locked, false);
59
+ assert.equal(context.canonicalWorkspace.repositoryPackage.state, mode === 'save-failed' ? 'processing' : 'published');
60
+ if (mode === 'marker-failed') {
61
+ await run(current);
62
+ assert.equal(checkpoints, 2);
63
+ assert.equal(saves, 2);
64
+ }
65
+ if (mode === 'save-failed') {
66
+ assert.equal(checkpoints, 0);
67
+ assert.equal(saves, 0);
68
+ }
69
+ }
70
+ });
71
+ test('official guarded registry save compares the current locked record before any outbox checkpoint', async () => {
72
+ for (const mode of ['same', 'operation-changed', 'scope-changed', 'missing', 'checkpoint-failed']) {
73
+ const expected = { workspaceId: 'workspace', organizationId: 'org_fixture', repositoryPackage: { operationId: 'previous' } };
74
+ let records = [structuredClone(expected), { workspaceId: 'sibling', marker: 'preserve' }];
75
+ const calls = [];
76
+ let locked = false;
77
+ const fn = runInNewContext(saveCode + '\nsaveWorkspaceRecord', { exports: {}, structuredClone,
78
+ canonicalize: JSON.stringify, currentBootstrapHostScope: () => undefined, workspaceRegistryPath: () => '/synthetic/registry.json',
79
+ acquirePidLock: async () => {
80
+ calls.push('lock');
81
+ locked = true;
82
+ if (mode === 'operation-changed')
83
+ records[0] = { ...expected, repositoryPackage: { operationId: 'newer' } };
84
+ if (mode === 'scope-changed')
85
+ records[0] = { ...expected, organizationId: 'org_other' };
86
+ if (mode === 'missing')
87
+ records = records.slice(1);
88
+ return async () => { calls.push('release'); locked = false; };
89
+ },
90
+ readFile: async () => { assert.equal(locked, true); calls.push('read-current'); return JSON.stringify(records); },
91
+ saveRegistry: async (next) => { assert.equal(locked, true); calls.push('save'); records = next; },
92
+ }, { timeout: 1000, contextCodeGeneration: { strings: false, wasm: false } });
93
+ const guard = { expected, beforeSave: async () => {
94
+ assert.equal(locked, true);
95
+ calls.push('checkpoint');
96
+ if (mode === 'checkpoint-failed')
97
+ throw new Error('checkpoint_failure');
98
+ } };
99
+ const updated = { ...expected, repositoryPackage: { operationId: 'recovered' } };
100
+ if (mode === 'same') {
101
+ await fn(updated, guard);
102
+ assert.deepEqual(calls, ['lock', 'read-current', 'checkpoint', 'save', 'release']);
103
+ assert.equal(records.find(row => row.workspaceId === 'workspace').repositoryPackage.operationId, 'recovered');
104
+ }
105
+ else {
106
+ await assert.rejects(fn(updated, guard), mode === 'checkpoint-failed' ? /checkpoint_failure/ : /compare_and_swap_failed/);
107
+ assert.ok(!calls.includes('save'));
108
+ if (mode !== 'checkpoint-failed')
109
+ assert.ok(!calls.includes('checkpoint'));
110
+ }
111
+ assert.equal(locked, false);
112
+ assert.equal(records.find(row => row.workspaceId === 'sibling').marker, 'preserve');
113
+ }
114
+ });
115
+ function fixture() {
116
+ const calls = [];
117
+ const workspace = { organizationId: 'org_fixture', workspaceId: 'workspace', repositoryBindingId: 'binding',
118
+ repositoryAgentId: 'agent', accessMode: 'source_connected', path: '/synthetic',
119
+ repositoryPackage: { state: 'published', generation: 18, pendingLocalOperationId: null } };
120
+ const overrides = {};
121
+ let savedRecord;
122
+ const defaults = {
123
+ required: (flags, key) => { if (!flags.get(key))
124
+ throw new Error('missing_flag'); return flags.get(key); },
125
+ readDeviceConfig: async () => ({ organizationId: 'org_fixture' }), registry: async () => [workspace],
126
+ relayProcessState: async () => { calls.push('relay-state'); return 'stopped'; },
127
+ relaySupervisorProcessState: async () => { calls.push('supervisor-state'); return 'stopped'; },
128
+ resolve: (...args) => args.join('/'),
129
+ loadVerifiedWorkspacePolicy: async () => { calls.push('policy'); return { serverAuthorization: {} }; },
130
+ assertWorkspaceAuthorizationCurrent: async () => { calls.push('authorization'); },
131
+ loadDeviceEnrollmentAnchor: async (input) => {
132
+ calls.push(input.readOnly ? 'protected-anchor-readonly' : 'protected-anchor');
133
+ return { serverPublicKeyEd25519: 'public' };
134
+ },
135
+ currentBootstrapHostScope: () => undefined, configPath: () => '/synthetic/device', protocolStatePath: () => '/synthetic/protocol',
136
+ AgentFabricClient: { open: async () => { calls.push('client'); return { openSession: async () => { calls.push('session'); } }; } },
137
+ VERSION: 'fixture', createPublicKey: () => ({}), repositorySourceComparisonBaseline: () => null,
138
+ fetchRepositorySourceAuthorization: async () => ({ generationId: 'current-generation', policy: { automaticValidatedPublication: true } }),
139
+ validateRepositorySourceAuthorization: (value) => value,
140
+ recoverRepositoryRestorationCandidate: async () => null,
141
+ readRepositoryRestorationAttempt: async () => undefined,
142
+ checkpointRepositoryRestorationCandidate: async (input) => {
143
+ calls.push('outbox-write');
144
+ return input.recovered.candidate;
145
+ },
146
+ checkpointRepositoryRestorationRegistry: async () => { calls.push('registry-checkpoint'); },
147
+ collectRepositorySourceRestoration: async () => {
148
+ calls.push('collect');
149
+ return {
150
+ local: { manifest: { snapshotHash: 'local' } },
151
+ snapshot: { manifest: { snapshotHash: 'snapshot', sourceAuthorization: {
152
+ generationId: 'current-generation', policy: { automaticValidatedPublication: true }
153
+ } } }, expectedLatestSourceFingerprint: 'head',
154
+ headCandidateId: 'rolled-back-head', headSnapshotHash: 'head-snapshot', operationId: 'operation',
155
+ priorReleaseId: 'stable', priorGeneration: 17, assertCurrent: async () => { calls.push('recheck'); },
156
+ assertHeadCurrent: async () => { calls.push('head-recheck'); },
157
+ assertLocalCurrent: async () => { calls.push('local-recheck'); }
158
+ };
159
+ },
160
+ canonicalize: JSON.stringify, sha256: () => 'reviewed-plan', dharmaHome: () => '/synthetic/device',
161
+ writeRepositoryPackageSnapshot: async (input) => {
162
+ assert.equal(input.candidateOnly, true);
163
+ calls.push(`snapshot-write:${input.snapshot.manifest.snapshotHash}`);
164
+ },
165
+ synchronizeRepositoryCandidate: async (input) => {
166
+ calls.push('submit');
167
+ assert.equal(input.initialRepository, false);
168
+ assert.equal(input.expectedLatestSourceFingerprint, 'head');
169
+ assert.equal(input.restoration.headCandidateId, 'rolled-back-head');
170
+ return { state: 'accepted', operationId: 'operation', candidateId: 'candidate', snapshotHash: 'snapshot', releaseId: null };
171
+ },
172
+ saveWorkspaceRecord: async (record, guard) => {
173
+ await guard?.beforeSave?.();
174
+ if (guard?.preparePackage)
175
+ record = { ...record, repositoryPackage: await guard.preparePackage() };
176
+ calls.push('registry-write');
177
+ savedRecord = record;
178
+ await guard?.afterSave?.(record);
179
+ },
180
+ };
181
+ const run = (flags) => {
182
+ const fn = runInNewContext(code + '\nrepositoryRestoreCommand', { exports: {}, ...defaults, ...overrides }, { timeout: 1000, contextCodeGeneration: { strings: false, wasm: false } });
183
+ return fn(flags);
184
+ };
185
+ const flags = (...entries) => new Map([['workspace-id', 'workspace'], ...entries]);
186
+ return { workspace, overrides, calls, run, flags, saved: () => savedRecord };
187
+ }
188
+ test('restoration requires one explicit mode and rejects arbitrary/conflicting flags before access', async () => {
189
+ for (const entries of [[], [['shell', true]], [['apply', true]], [['dry-run', true], ['apply', true]],
190
+ [['dry-run', 'true']], [['dry-run', true], ['plan-hash', 'reviewed-plan']]]) {
191
+ const f = fixture();
192
+ await assert.rejects(f.run(f.flags(...entries)), /option_invalid/);
193
+ assert.deepEqual(f.calls, []);
194
+ }
195
+ });
196
+ test('explicit fencing is guarded metadata recovery, never a source upload or activation', async () => {
197
+ const f = fixture();
198
+ f.overrides.fenceRepositoryRestorationUpload = async (input) => {
199
+ assert.equal(input.expectedPreviousOperationId, null);
200
+ f.calls.push('fence');
201
+ return { candidate: null, fence: { fenceId: 'terminal-fence' } };
202
+ };
203
+ f.overrides.checkpointRepositoryRestorationFence = async () => { f.calls.push('fence-checkpoint'); };
204
+ const result = await f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'], ['fence-upload', true]));
205
+ assert.equal(result.stage, 'repository_restoration_upload_fenced');
206
+ assert.equal(result.activated, false);
207
+ assert.equal(result.serverSourceMutation, false);
208
+ assert.ok(f.calls.indexOf('fence-checkpoint') > f.calls.indexOf('fence'));
209
+ assert.ok(!f.calls.some(call => /collect|submit|snapshot-write|outbox-write/.test(call)));
210
+ });
211
+ test('fencing refuses changed authority and running clients before checkpoint retirement', async () => {
212
+ for (const mode of ['pending', 'revoked', 'restarted', 'candidate-won']) {
213
+ const f = fixture();
214
+ if (mode === 'pending')
215
+ f.workspace.repositoryPackage.pendingLocalOperationId = 'existing';
216
+ f.overrides.fenceRepositoryRestorationUpload = async () => {
217
+ f.calls.push('fence');
218
+ if (mode === 'revoked')
219
+ f.overrides.fetchRepositorySourceAuthorization = async () => ({ generationId: 'changed', policy: {} });
220
+ return { candidate: mode === 'candidate-won' ? { candidateId: 'existing' } : null,
221
+ fence: mode === 'candidate-won' ? null : { fenceId: 'terminal' } };
222
+ };
223
+ let checks = 0;
224
+ f.overrides.fetchRepositorySourceAuthorization = async () => ({ generationId: mode === 'revoked' && ++checks > 2 ? 'changed' : 'current-generation',
225
+ policy: { automaticValidatedPublication: true } });
226
+ let relayChecks = 0;
227
+ if (mode === 'restarted')
228
+ f.overrides.relayProcessState = async () => ++relayChecks > 2 ? 'running' : 'stopped';
229
+ f.overrides.checkpointRepositoryRestorationFence = async () => { f.calls.push('fence-checkpoint'); };
230
+ const run = f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'], ['fence-upload', true]));
231
+ if (mode === 'candidate-won')
232
+ assert.equal((await run).stage, 'repository_restoration_admission_found');
233
+ else
234
+ await assert.rejects(run, /workspace_not_authorized|policy_changed|requires_stopped_relay/);
235
+ assert.equal(f.calls.includes('fence-checkpoint'), mode === 'revoked');
236
+ assert.ok(!f.calls.includes('submit'));
237
+ }
238
+ });
239
+ test('restoration planning has no source, registry or activation writes', async () => {
240
+ const f = fixture(), result = await f.run(f.flags(['dry-run', true]));
241
+ assert.equal(result.planHash, 'reviewed-plan');
242
+ assert.equal(result.activated, false);
243
+ assert.equal(result.published, false);
244
+ assert.equal(result.localMutation, false);
245
+ assert.equal(result.serverSourceMutation, false);
246
+ assert.ok(f.calls.includes('protected-anchor-readonly'));
247
+ assert.ok(!f.calls.includes('protected-anchor'));
248
+ assert.ok(!f.calls.some(call => /write|submit|relay-state/.test(call)));
249
+ });
250
+ test('fresh restoration rejects an ordinary adopted nonterminal candidate before source collection', async () => {
251
+ for (const state of ['accepted', 'processing']) {
252
+ for (const mode of ['dry-run', 'apply']) {
253
+ const f = fixture();
254
+ f.workspace.repositoryPackage.state = state;
255
+ await assert.rejects(f.run(mode === 'apply' ? f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])
256
+ : f.flags(['dry-run', true])), /publication_pending/);
257
+ assert.ok(!f.calls.some(call => /collect|snapshot-write|submit|registry-write/.test(call)));
258
+ }
259
+ }
260
+ });
261
+ test('review regression: fresh registry CAS failure precedes every source write and admission', async () => {
262
+ const f = fixture();
263
+ f.overrides.saveWorkspaceRecord = async () => { throw new Error('workspace_registry_compare_and_swap_failed'); };
264
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /compare_and_swap_failed/);
265
+ assert.ok(!f.calls.some(call => /snapshot-write|submit/.test(call)));
266
+ });
267
+ test('review regression: recovery revalidates changed source authority inside the registry lock', async () => {
268
+ const f = fixture();
269
+ let locked = false;
270
+ f.overrides.recoverRepositoryRestorationCandidate = async () => ({ planHash: 'reviewed-plan',
271
+ localSnapshotHash: 'local', baselineSnapshotHash: null, previousOperationId: null,
272
+ priorReleaseId: 'prior', sourceManifestHash: 'reviewed-plan',
273
+ candidate: { state: 'published', operationId: 'operation', candidateId: 'candidate', snapshotHash: 'snapshot', releaseId: 'release' } });
274
+ f.overrides.readRepositoryPackageSnapshot = async () => ({ manifest: {
275
+ organizationId: 'org_fixture', workspaceId: 'workspace', knowledge: { repositoryAgentId: 'agent' },
276
+ sourceAuthorization: { generationId: 'current-generation', policy: { automaticValidatedPublication: true } }
277
+ } });
278
+ f.overrides.fetchRepositorySourceAuthorization = async () => ({ generationId: locked ? 'replacement' : 'current-generation',
279
+ policy: { automaticValidatedPublication: true } });
280
+ f.overrides.saveWorkspaceRecord = async (_record, guard) => {
281
+ locked = true;
282
+ await guard.beforeSave?.();
283
+ f.calls.push('registry-write');
284
+ };
285
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /policy_changed/);
286
+ assert.ok(!f.calls.some(call => /outbox-write|registry-write/.test(call)));
287
+ });
288
+ test('review regression: final local currentness is checked after authority waits', async () => {
289
+ const f = fixture();
290
+ let stale = false;
291
+ f.overrides.collectRepositorySourceRestoration = async () => ({
292
+ local: { manifest: { snapshotHash: 'local' } }, snapshot: { manifest: { snapshotHash: 'snapshot',
293
+ sourceAuthorization: { generationId: 'current-generation', policy: { automaticValidatedPublication: true } } } },
294
+ expectedLatestSourceFingerprint: 'head', headCandidateId: 'rolled-back-head', headSnapshotHash: 'head-snapshot',
295
+ operationId: 'operation', priorReleaseId: 'stable', priorGeneration: 17,
296
+ assertCurrent: async () => { f.calls.push('recheck'); }, assertHeadCurrent: async () => { },
297
+ assertLocalCurrent: async () => { if (stale)
298
+ throw new Error('repository_source_restoration_workspace_changed'); },
299
+ });
300
+ let reads = 0;
301
+ f.overrides.fetchRepositorySourceAuthorization = async () => {
302
+ if (++reads >= 2)
303
+ stale = true;
304
+ return { generationId: 'current-generation', policy: { automaticValidatedPublication: true } };
305
+ };
306
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /workspace_changed/);
307
+ assert.ok(!f.calls.some(call => /snapshot-write|submit/.test(call)));
308
+ });
309
+ test('review regression: a relay or supervisor restart during the final local scan prevents all source writes', async () => {
310
+ for (const process of ['relay', 'supervisor']) {
311
+ const f = fixture();
312
+ let restarted = false;
313
+ f.overrides.relayProcessState = async () => process === 'relay' && restarted ? 'running' : 'stopped';
314
+ f.overrides.relaySupervisorProcessState = async () => process === 'supervisor' && restarted ? 'running' : 'stopped';
315
+ f.overrides.collectRepositorySourceRestoration = async () => ({
316
+ local: { manifest: { snapshotHash: 'local' } }, snapshot: { manifest: { snapshotHash: 'snapshot',
317
+ sourceAuthorization: { generationId: 'current-generation', policy: { automaticValidatedPublication: true } } } },
318
+ expectedLatestSourceFingerprint: 'head', headCandidateId: 'rolled-back-head', headSnapshotHash: 'head-snapshot',
319
+ operationId: 'operation', priorReleaseId: 'stable', priorGeneration: 17,
320
+ assertCurrent: async () => { }, assertHeadCurrent: async () => { }, assertLocalCurrent: async () => { restarted = true; },
321
+ });
322
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /requires_stopped_relay/);
323
+ assert.ok(!f.calls.some(call => /snapshot-write|submit|registry-write/.test(call)));
324
+ }
325
+ });
326
+ test('review regression: source or workspace authority lost during the final local scan prevents all source writes', async () => {
327
+ for (const mode of ['source', 'workspace']) {
328
+ const f = fixture();
329
+ let scanned = false;
330
+ const authorization = { generationId: 'current-generation', policy: { automaticValidatedPublication: true } };
331
+ f.overrides.collectRepositorySourceRestoration = async () => ({
332
+ local: { manifest: { snapshotHash: 'local' } }, snapshot: { manifest: { snapshotHash: 'snapshot', sourceAuthorization: authorization } },
333
+ expectedLatestSourceFingerprint: 'head', headCandidateId: 'rolled-back-head', headSnapshotHash: 'head-snapshot',
334
+ operationId: 'operation', priorReleaseId: 'stable', priorGeneration: 17,
335
+ assertCurrent: async () => { }, assertHeadCurrent: async () => { }, assertLocalCurrent: async () => { scanned = true; },
336
+ });
337
+ f.overrides.fetchRepositorySourceAuthorization = async () => mode === 'source' && scanned
338
+ ? { ...authorization, generationId: 'replacement-generation' } : authorization;
339
+ f.overrides.assertWorkspaceAuthorizationCurrent = async () => {
340
+ if (mode === 'workspace' && scanned)
341
+ throw new Error('workspace_authorization_superseded');
342
+ };
343
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /policy_changed|workspace_authorization_superseded/);
344
+ assert.ok(!f.calls.some(call => /snapshot-write|submit|registry-write/.test(call)));
345
+ }
346
+ });
347
+ test('review regression: admitted restoration reconciles historical authority after policy rollover without republishing', async () => {
348
+ for (const state of ['accepted', 'processing', 'published', 'blocked']) {
349
+ const f = fixture();
350
+ f.workspace.repositoryPackage.state = state;
351
+ const historical = { generationId: 'admission-generation', policy: { automaticValidatedPublication: true } };
352
+ f.overrides.recoverRepositoryRestorationCandidate = async () => ({ planHash: 'reviewed-plan',
353
+ localSnapshotHash: 'local', baselineSnapshotHash: null, previousOperationId: null,
354
+ priorReleaseId: 'prior', sourceManifestHash: 'reviewed-plan',
355
+ candidate: { state, operationId: 'operation', candidateId: 'candidate', snapshotHash: 'snapshot',
356
+ releaseId: state === 'published' ? 'release' : null } });
357
+ f.overrides.readRepositoryPackageSnapshot = async () => ({ manifest: {
358
+ organizationId: 'org_fixture', workspaceId: 'workspace', knowledge: { repositoryAgentId: 'agent' },
359
+ sourceAuthorization: historical
360
+ } });
361
+ let historicalChecks = 0, currentChecks = 0;
362
+ f.overrides.validateRepositorySourceAuthorization = (value, _scope, now) => {
363
+ if (value.generationId === historical.generationId) {
364
+ historicalChecks++;
365
+ assert.equal(now, undefined);
366
+ }
367
+ else {
368
+ currentChecks++;
369
+ assert.ok(now);
370
+ }
371
+ return value;
372
+ };
373
+ const result = await f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan']));
374
+ assert.equal(result.stage, 'repository_restoration_candidate_recovered');
375
+ assert.equal(result.serverSourceMutation, false);
376
+ assert.equal(f.saved()?.repositoryPackage.sourcePolicyGenerationId, 'admission-generation');
377
+ assert.ok(historicalChecks >= 2);
378
+ assert.ok(currentChecks >= 3);
379
+ assert.ok(!f.calls.some(call => /snapshot-write|submit|collect/.test(call)));
380
+ }
381
+ });
382
+ test('review regression: identical-content rollback identity changing during the local scan prevents admission', async () => {
383
+ const f = fixture();
384
+ let changed = false;
385
+ f.overrides.collectRepositorySourceRestoration = async () => ({
386
+ local: { manifest: { snapshotHash: 'local' } }, snapshot: { manifest: { snapshotHash: 'snapshot',
387
+ sourceAuthorization: { generationId: 'current-generation', policy: { automaticValidatedPublication: true } } } },
388
+ expectedLatestSourceFingerprint: 'head', headCandidateId: 'rolled-back-head', headSnapshotHash: 'head-snapshot',
389
+ operationId: 'operation', priorReleaseId: 'stable', priorGeneration: 17, assertCurrent: async () => { },
390
+ assertLocalCurrent: async () => { changed = true; },
391
+ assertHeadCurrent: async () => { if (changed)
392
+ throw new Error('repository_source_restoration_head_changed'); },
393
+ });
394
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /head_changed/);
395
+ assert.ok(!f.calls.some(call => /snapshot-write|submit|registry-write/.test(call)));
396
+ });
397
+ test('review regression: workspace edits during the final head lookup prevent stale snapshot admission', async () => {
398
+ for (const wait of ['head', 'authority']) {
399
+ const f = fixture();
400
+ let changed = false, headChecked = false, localChecks = 0;
401
+ const authorization = { generationId: 'current-generation', policy: { automaticValidatedPublication: true } };
402
+ f.overrides.collectRepositorySourceRestoration = async () => ({
403
+ local: { manifest: { snapshotHash: 'local' } }, snapshot: { manifest: { snapshotHash: 'snapshot', sourceAuthorization: authorization } },
404
+ expectedLatestSourceFingerprint: 'head', headCandidateId: 'rolled-back-head', headSnapshotHash: 'head-snapshot',
405
+ operationId: 'operation', priorReleaseId: 'stable', priorGeneration: 17, assertCurrent: async () => { },
406
+ assertLocalCurrent: async () => { localChecks++; if (changed)
407
+ throw new Error('repository_source_restoration_workspace_changed'); },
408
+ assertHeadCurrent: async () => { await Promise.resolve(); headChecked = true; if (wait === 'head')
409
+ changed = true; },
410
+ });
411
+ f.overrides.fetchRepositorySourceAuthorization = async () => {
412
+ await Promise.resolve();
413
+ if (wait === 'authority' && headChecked)
414
+ changed = true;
415
+ return authorization;
416
+ };
417
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /workspace_changed/);
418
+ assert.ok(localChecks >= 2);
419
+ assert.ok(!f.calls.some(call => /snapshot-write|submit|registry-write/.test(call)));
420
+ }
421
+ });
422
+ test('review regression: head or authority changed during the last local scan prevents source writes', async () => {
423
+ for (const mode of ['head', 'source', 'workspace']) {
424
+ const f = fixture();
425
+ let localChecks = 0, changed = false;
426
+ const authorization = { generationId: 'current-generation', policy: { automaticValidatedPublication: true } };
427
+ f.overrides.collectRepositorySourceRestoration = async () => ({
428
+ local: { manifest: { snapshotHash: 'local' } }, snapshot: { manifest: { snapshotHash: 'snapshot', sourceAuthorization: authorization } },
429
+ expectedLatestSourceFingerprint: 'head', headCandidateId: 'rolled-back-head', headSnapshotHash: 'head-snapshot',
430
+ operationId: 'operation', priorReleaseId: 'stable', priorGeneration: 17, assertCurrent: async () => { },
431
+ assertLocalCurrent: async () => { await Promise.resolve(); if (++localChecks === 2)
432
+ changed = true; },
433
+ assertHeadCurrent: async () => { if (mode === 'head' && changed)
434
+ throw new Error('repository_source_restoration_head_changed'); },
435
+ });
436
+ f.overrides.fetchRepositorySourceAuthorization = async () => mode === 'source' && changed
437
+ ? { ...authorization, generationId: 'replacement-generation' } : authorization;
438
+ f.overrides.assertWorkspaceAuthorizationCurrent = async () => {
439
+ if (mode === 'workspace' && changed)
440
+ throw new Error('workspace_authorization_superseded');
441
+ };
442
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /head_changed|policy_changed|workspace_authorization_superseded/);
443
+ assert.ok(!f.calls.some(call => /snapshot-write|submit|registry-write/.test(call)));
444
+ }
445
+ });
446
+ test('review regression: runtime starting during submission retains admission without committing a registry receipt', async () => {
447
+ for (const process of ['relay', 'supervisor']) {
448
+ const f = fixture();
449
+ let submitted = false;
450
+ f.overrides.relayProcessState = async () => process === 'relay' && submitted ? 'running' : 'stopped';
451
+ f.overrides.relaySupervisorProcessState = async () => process === 'supervisor' && submitted ? 'running' : 'stopped';
452
+ f.overrides.synchronizeRepositoryCandidate = async () => {
453
+ submitted = true;
454
+ f.calls.push('submit');
455
+ return { state: 'accepted', operationId: 'operation', candidateId: 'candidate', snapshotHash: 'snapshot', releaseId: null };
456
+ };
457
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /requires_stopped_relay/);
458
+ assert.equal(f.calls.filter(call => call === 'submit').length, 1);
459
+ assert.ok(!f.calls.some(call => /registry-write|registry-checkpoint/.test(call)));
460
+ }
461
+ });
462
+ test('review regression: an acknowledged fence survives a later source-policy rollover', async () => {
463
+ const f = fixture();
464
+ let changed = false;
465
+ f.overrides.fetchRepositorySourceAuthorization = async () => ({ generationId: changed ? 'replacement' : 'current-generation',
466
+ policy: { automaticValidatedPublication: true } });
467
+ f.overrides.fenceRepositoryRestorationUpload = async () => { changed = true; return { candidate: null, fence: { fenceId: 'terminal' } }; };
468
+ f.overrides.checkpointRepositoryRestorationFence = async () => { f.calls.push('fence-checkpoint'); };
469
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'], ['fence-upload', true])), /policy_changed/);
470
+ assert.ok(f.calls.includes('fence-checkpoint'));
471
+ assert.ok(!f.calls.includes('registry-write'));
472
+ });
473
+ test('dry-run reads the official legacy enrollment anchor in place without secure-store migration', async () => {
474
+ const f = fixture(), writes = [];
475
+ const config = { schema: 'dharma.device-config/v1', hqUrl: 'https://fixture.example', organizationId: 'org_fixture',
476
+ deviceId: '44444444-4444-4444-8444-444444444444', deviceName: 'Synthetic', platform: 'linux',
477
+ publicKeyEd25519: 'A'.repeat(43), serverPublicKeyEd25519: 'B'.repeat(43), relayUrl: 'wss://relay.example',
478
+ enrolledAt: '2026-10-01T00:00:00.000Z' };
479
+ const legacy = 'device-enrollment-' + createHash('sha256').update([config.hqUrl, config.organizationId].join(':')).digest('hex').slice(0, 32);
480
+ const anchor = JSON.stringify({ schema: 'dharma.device-enrollment-anchor/v1', hqUrl: config.hqUrl,
481
+ organizationId: config.organizationId, deviceId: config.deviceId, devicePublicKeyEd25519: config.publicKeyEd25519,
482
+ serverPublicKeyEd25519: config.serverPublicKeyEd25519, enrolledAt: config.enrolledAt });
483
+ const values = new Map([[legacy, anchor]]), before = new Map(values);
484
+ const store = { backend: 'linux-secret-service', get: async (key) => values.get(key) ?? null,
485
+ getFresh: async (key) => values.get(key) ?? null,
486
+ put: async (key, value) => { writes.push(key); values.set(key, value); }, delete: async (key) => { writes.push(key); values.delete(key); } };
487
+ f.overrides.loadDeviceEnrollmentAnchor = async (input) => loadDeviceEnrollmentAnchor({ config, store, readOnly: input.readOnly });
488
+ await f.run(f.flags(['dry-run', true]));
489
+ assert.deepEqual(values, before);
490
+ assert.deepEqual(writes, []);
491
+ });
492
+ test('restoration submits only the reviewed plan through normal candidate gates without activation', async () => {
493
+ const f = fixture(), result = await f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan']));
494
+ assert.equal(result.stage, 'repository_restoration_candidate_submitted');
495
+ assert.equal(result.activated, false);
496
+ assert.deepEqual(f.calls.slice(-26), ['authorization', 'supervisor-state', 'relay-state', 'recheck', 'supervisor-state', 'relay-state', 'authorization', 'supervisor-state', 'relay-state',
497
+ 'local-recheck', 'head-recheck', 'authorization', 'supervisor-state', 'relay-state', 'local-recheck', 'head-recheck', 'authorization', 'supervisor-state', 'relay-state',
498
+ 'snapshot-write:local', 'snapshot-write:snapshot', 'submit', 'supervisor-state', 'relay-state', 'registry-write', 'registry-checkpoint']);
499
+ assert.equal(f.saved()?.repositoryPackage.localBaselineSnapshotHash, 'local');
500
+ assert.equal(f.saved()?.repositoryPackage.pendingLocalSnapshotHash, 'local');
501
+ assert.equal(f.saved()?.repositoryPackage.snapshotHash, 'snapshot');
502
+ assert.equal(f.saved()?.repositoryPackage.sourcePolicyGenerationId, 'current-generation');
503
+ });
504
+ test('published restoration keeps the actual local baseline rather than remote-only candidate content', async () => {
505
+ const f = fixture();
506
+ f.overrides.synchronizeRepositoryCandidate = async () => ({ state: 'published', operationId: 'operation',
507
+ candidateId: 'candidate', snapshotHash: 'snapshot', releaseId: 'release' });
508
+ await f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan']));
509
+ assert.equal(f.saved()?.repositoryPackage.localBaselineSnapshotHash, 'local');
510
+ assert.equal(f.saved()?.repositoryPackage.publishedLocalSnapshotHash, 'local');
511
+ assert.equal(f.saved()?.repositoryPackage.pendingLocalSnapshotHash, null);
512
+ });
513
+ test('submission survives a lost registry write and recovery checkpoints before retrying the registry', async () => {
514
+ for (const state of ['accepted', 'processing', 'published', 'blocked']) {
515
+ const f = fixture();
516
+ let checkpoint = null, writes = 0, submissions = 0;
517
+ const candidate = { state, operationId: 'operation', candidateId: 'candidate', snapshotHash: 'snapshot',
518
+ releaseId: state === 'published' ? 'release' : null };
519
+ f.overrides.synchronizeRepositoryCandidate = async (input) => {
520
+ submissions++;
521
+ checkpoint = { ...input.restoration, candidate, sourceManifestHash: 'reviewed-plan' };
522
+ return candidate;
523
+ };
524
+ f.overrides.recoverRepositoryRestorationCandidate = async () => checkpoint;
525
+ f.overrides.readRepositoryPackageSnapshot = async (_path, hash) => ({ manifest: {
526
+ snapshotHash: hash, organizationId: 'org_fixture', workspaceId: 'workspace', knowledge: { repositoryAgentId: 'agent' },
527
+ sourceAuthorization: { generationId: 'current-generation', policy: { automaticValidatedPublication: true } }
528
+ } });
529
+ f.overrides.saveWorkspaceRecord = async (record, guard) => {
530
+ await guard?.beforeSave?.();
531
+ if (guard?.preparePackage)
532
+ record = { ...record, repositoryPackage: await guard.preparePackage() };
533
+ f.calls.push('registry-write');
534
+ if (++writes === 1)
535
+ throw new Error('simulated_registry_write_failure');
536
+ Object.assign(f.workspace, record);
537
+ await guard?.afterSave?.(record);
538
+ };
539
+ const flags = f.flags(['apply', true], ['plan-hash', 'reviewed-plan']);
540
+ await assert.rejects(f.run(flags), /simulated_registry_write_failure/);
541
+ f.overrides.collectRepositorySourceRestoration = async () => { throw new Error('operation_already_used'); };
542
+ for (let replay = 0; replay < 2; replay++) {
543
+ const result = await f.run(flags);
544
+ assert.equal(result.stage, 'repository_restoration_candidate_recovered');
545
+ assert.equal(result.serverSourceMutation, false);
546
+ assert.equal(result.activated, false);
547
+ assert.deepEqual(f.calls.slice(-6), ['authorization', 'supervisor-state', 'relay-state', 'outbox-write', 'registry-write', 'registry-checkpoint']);
548
+ assert.equal(f.workspace.repositoryPackage.sourcePolicyGenerationId, 'current-generation');
549
+ assert.equal(f.workspace.repositoryPackage.pendingLocalOperationId, state === 'accepted' || state === 'processing' ? 'operation' : null);
550
+ assert.equal(f.workspace.repositoryPackage.publishedLocalSnapshotHash, state === 'published' ? 'local' : null);
551
+ }
552
+ assert.equal(submissions, 1);
553
+ }
554
+ });
555
+ test('recovery rejects unavailable CAS, revoked publication, or a restarted supervisor without registry mutation', async () => {
556
+ for (const mode of ['cas', 'revoked', 'final-revoked', 'changed-generation', 'snapshot-generation', 'supervisor', 'registry']) {
557
+ const f = fixture();
558
+ f.overrides.recoverRepositoryRestorationCandidate = async () => ({ planHash: 'reviewed-plan',
559
+ localSnapshotHash: 'local', baselineSnapshotHash: null, previousOperationId: mode === 'registry' ? 'different' : null,
560
+ priorReleaseId: 'prior', sourceManifestHash: 'reviewed-plan',
561
+ candidate: { state: 'published', operationId: 'operation', candidateId: 'candidate', snapshotHash: 'snapshot', releaseId: 'release' } });
562
+ f.overrides.readRepositoryPackageSnapshot = async (_path, hash) => mode === 'cas' ? null : ({ manifest: {
563
+ organizationId: 'org_fixture', workspaceId: 'workspace', knowledge: { repositoryAgentId: 'agent' },
564
+ sourceAuthorization: { generationId: mode === 'snapshot-generation' && hash === 'local' ? 'foreign-generation' : 'current-generation',
565
+ policy: { automaticValidatedPublication: true } }
566
+ } });
567
+ if (mode === 'revoked')
568
+ f.overrides.fetchRepositorySourceAuthorization = async () => ({ policy: { automaticValidatedPublication: false } });
569
+ let authorityChecks = 0;
570
+ if (mode === 'final-revoked')
571
+ f.overrides.assertWorkspaceAuthorizationCurrent = async () => {
572
+ if (++authorityChecks > 1)
573
+ throw new Error('revoked');
574
+ };
575
+ let sourceChecks = 0;
576
+ if (mode === 'changed-generation')
577
+ f.overrides.fetchRepositorySourceAuthorization = async () => ({
578
+ generationId: ++sourceChecks === 1 ? 'current-generation' : 'replacement-generation', policy: { automaticValidatedPublication: true }
579
+ });
580
+ let checks = 0;
581
+ if (mode === 'supervisor')
582
+ f.overrides.relaySupervisorProcessState = async () => ++checks === 1 ? 'stopped' : 'running';
583
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])));
584
+ assert.ok(!f.calls.some(call => /write|submit|collect/.test(call)));
585
+ }
586
+ });
587
+ test('running or unknown supervisor, including a final-check restart, cannot write or submit', async () => {
588
+ for (const state of ['running', 'unknown'])
589
+ for (const stoppedChecks of [0, 1, 2, 3]) {
590
+ const f = fixture();
591
+ let checks = 0;
592
+ f.overrides.relaySupervisorProcessState = async () => ++checks <= stoppedChecks ? 'stopped' : state;
593
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /requires_stopped_relay/);
594
+ assert.ok(!f.calls.some(call => /write|submit/.test(call)));
595
+ }
596
+ });
597
+ test('workspace authority superseded during final awaited reads cannot persist or submit restoration', async () => {
598
+ const f = fixture();
599
+ let superseded = false;
600
+ f.overrides.assertWorkspaceAuthorizationCurrent = async () => {
601
+ if (superseded)
602
+ throw new Error('workspace_authorization_superseded');
603
+ };
604
+ f.overrides.collectRepositorySourceRestoration = async () => ({
605
+ local: { manifest: { snapshotHash: 'local' } },
606
+ snapshot: { manifest: { snapshotHash: 'snapshot', sourceAuthorization: {
607
+ generationId: 'current-generation', policy: { automaticValidatedPublication: true }
608
+ } } },
609
+ expectedLatestSourceFingerprint: 'head', headCandidateId: 'rolled-back-head', headSnapshotHash: 'head-snapshot',
610
+ operationId: 'operation', priorReleaseId: 'stable', priorGeneration: 17,
611
+ assertCurrent: async () => { await Promise.resolve(); superseded = true; }, assertHeadCurrent: async () => { },
612
+ });
613
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /workspace_authorization_superseded/);
614
+ assert.ok(!f.calls.some(call => /write|submit/.test(call)));
615
+ });
616
+ test('source policy rollover during final awaited reads cannot write a stale pending outbox', async () => {
617
+ const f = fixture();
618
+ let changed = false;
619
+ f.overrides.fetchRepositorySourceAuthorization = async () => ({
620
+ generationId: changed ? 'replacement-generation' : 'current-generation', policy: { automaticValidatedPublication: true }
621
+ });
622
+ f.overrides.collectRepositorySourceRestoration = async () => ({
623
+ local: { manifest: { snapshotHash: 'local' } },
624
+ snapshot: { manifest: { snapshotHash: 'snapshot', sourceAuthorization: {
625
+ generationId: 'current-generation', policy: { automaticValidatedPublication: true }
626
+ } } },
627
+ expectedLatestSourceFingerprint: 'head', headCandidateId: 'rolled-back-head', headSnapshotHash: 'head-snapshot',
628
+ operationId: 'operation', priorReleaseId: 'stable', priorGeneration: 17,
629
+ assertCurrent: async () => { await Promise.resolve(); changed = true; }, assertHeadCurrent: async () => { },
630
+ });
631
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /policy_changed/);
632
+ assert.ok(!f.calls.some(call => /write|submit/.test(call)));
633
+ });
634
+ test('matching pending restoration retries reconcile a later terminal receipt without fresh admission', async () => {
635
+ for (const state of ['accepted', 'processing']) {
636
+ const f = fixture();
637
+ f.workspace.repositoryPackage.pendingLocalOperationId = 'operation';
638
+ Object.assign(f.workspace.repositoryPackage, { operationId: 'operation' });
639
+ f.overrides.recoverRepositoryRestorationCandidate = async () => ({ planHash: 'reviewed-plan',
640
+ localSnapshotHash: 'local', baselineSnapshotHash: null, previousOperationId: null,
641
+ priorReleaseId: 'prior', sourceManifestHash: 'reviewed-plan',
642
+ candidate: { state, operationId: 'operation', candidateId: 'candidate', snapshotHash: 'snapshot', releaseId: null } });
643
+ f.overrides.readRepositoryPackageSnapshot = async () => ({ manifest: { organizationId: 'org_fixture', workspaceId: 'workspace',
644
+ knowledge: { repositoryAgentId: 'agent' }, sourceAuthorization: {
645
+ generationId: 'current-generation', policy: { automaticValidatedPublication: true }
646
+ } } });
647
+ const flags = f.flags(['apply', true], ['plan-hash', 'reviewed-plan']);
648
+ assert.equal((await f.run(flags)).stage, 'repository_restoration_candidate_recovered');
649
+ f.overrides.recoverRepositoryRestorationCandidate = async () => ({ planHash: 'reviewed-plan',
650
+ localSnapshotHash: 'local', baselineSnapshotHash: null, previousOperationId: null,
651
+ priorReleaseId: 'prior', sourceManifestHash: 'reviewed-plan',
652
+ candidate: { state: 'published', operationId: 'operation', candidateId: 'candidate', snapshotHash: 'snapshot', releaseId: 'release' } });
653
+ assert.equal((await f.run(flags)).stage, 'repository_restoration_candidate_recovered');
654
+ assert.equal(f.saved()?.repositoryPackage.pendingLocalOperationId, null);
655
+ assert.ok(!f.calls.includes('collect'));
656
+ assert.ok(!f.calls.includes('submit'));
657
+ f.workspace.repositoryPackage.pendingLocalOperationId = 'foreign-operation';
658
+ const priorWrites = f.calls.filter(call => call.includes('write')).length;
659
+ await assert.rejects(f.run(flags), /checkpoint_registry_changed/);
660
+ assert.equal(f.calls.filter(call => call.includes('write')).length, priorWrites);
661
+ }
662
+ });
663
+ test('terminal history collision and expired final conflict approval stop before any candidate writes', async () => {
664
+ for (const reason of ['repository_source_restoration_operation_already_used', 'repository_source_resolution_expired_or_invalid']) {
665
+ const f = fixture();
666
+ f.overrides.collectRepositorySourceRestoration = async () => ({ snapshot: { manifest: { snapshotHash: 'snapshot' } },
667
+ local: { manifest: { snapshotHash: 'local' } },
668
+ expectedLatestSourceFingerprint: 'head', headCandidateId: 'rolled-back-head', headSnapshotHash: 'head-snapshot',
669
+ operationId: 'operation', priorReleaseId: 'stable', priorGeneration: 17,
670
+ assertCurrent: async () => { throw new Error(reason); } });
671
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), new RegExp(reason));
672
+ assert.ok(!f.calls.some(call => /write|submit/.test(call)));
673
+ }
674
+ });
675
+ test('recovery validates a non-null comparison baseline before checkpoint or registry writes', async () => {
676
+ for (const state of ['processing', 'published', 'blocked'])
677
+ for (const mode of ['missing', 'foreign', 'foreign-binding']) {
678
+ const f = fixture();
679
+ f.overrides.recoverRepositoryRestorationCandidate = async () => ({ planHash: 'reviewed-plan',
680
+ localSnapshotHash: 'local', baselineSnapshotHash: 'baseline', previousOperationId: null,
681
+ priorReleaseId: 'prior', sourceManifestHash: 'reviewed-plan', candidate: {
682
+ state, operationId: 'operation', candidateId: 'candidate', snapshotHash: 'snapshot', releaseId: state === 'published' ? 'new-release' : null
683
+ } });
684
+ f.overrides.readRepositoryPackageSnapshot = async (_path, hash) => hash === 'baseline' && mode === 'missing' ? null : ({ manifest: {
685
+ organizationId: hash === 'baseline' && mode === 'foreign' ? 'org_other' : 'org_fixture', workspaceId: 'workspace',
686
+ knowledge: { repositoryAgentId: 'agent' }, sourceAuthorization: {
687
+ repositoryBindingId: hash === 'baseline' && mode === 'foreign-binding' ? 'foreign-binding' : 'binding',
688
+ generationId: 'current-generation', policy: { automaticValidatedPublication: true }
689
+ }
690
+ } });
691
+ f.overrides.validateRepositorySourceAuthorization = (value, scope) => {
692
+ if (value.repositoryBindingId && value.repositoryBindingId !== scope.repositoryBindingId)
693
+ throw new Error('baseline_foreign_binding');
694
+ return value;
695
+ };
696
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /checkpoint_snapshot_invalid|baseline_foreign_binding/);
697
+ assert.ok(!f.calls.some(call => /write|submit/.test(call)));
698
+ }
699
+ });
700
+ test('archived release identity cannot be recorded as a fresh or recovered restoration', async () => {
701
+ for (const recovered of [false, true]) {
702
+ const f = fixture();
703
+ const candidate = { state: 'published', operationId: 'operation', candidateId: 'candidate', snapshotHash: 'snapshot', releaseId: 'stable' };
704
+ f.overrides.synchronizeRepositoryCandidate = async () => candidate;
705
+ if (recovered)
706
+ f.overrides.recoverRepositoryRestorationCandidate = async () => ({
707
+ planHash: 'reviewed-plan', localSnapshotHash: 'local', baselineSnapshotHash: null, previousOperationId: null,
708
+ priorReleaseId: 'stable', sourceManifestHash: 'reviewed-plan', candidate
709
+ });
710
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /archived_release_reused/);
711
+ assert.ok(!f.calls.includes('outbox-write'));
712
+ assert.ok(!f.calls.includes('registry-write'));
713
+ }
714
+ });
715
+ test('runtime restarted during final authority or registry-lock waits stops before protected writes', async () => {
716
+ const f = fixture();
717
+ let running = false, checks = 0;
718
+ f.overrides.assertWorkspaceAuthorizationCurrent = async () => { if (++checks === 3)
719
+ running = true; };
720
+ f.overrides.relaySupervisorProcessState = async () => running ? 'running' : 'stopped';
721
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /requires_stopped_relay/);
722
+ assert.ok(!f.calls.some(call => /write|submit/.test(call)));
723
+ const r = fixture();
724
+ r.overrides.recoverRepositoryRestorationCandidate = async () => ({ planHash: 'reviewed-plan', localSnapshotHash: 'local',
725
+ baselineSnapshotHash: null, previousOperationId: null, priorReleaseId: 'prior', sourceManifestHash: 'reviewed-plan',
726
+ candidate: { state: 'processing', operationId: 'operation', candidateId: 'candidate', snapshotHash: 'snapshot', releaseId: null } });
727
+ r.overrides.readRepositoryPackageSnapshot = async () => ({ manifest: { organizationId: 'org_fixture', workspaceId: 'workspace',
728
+ knowledge: { repositoryAgentId: 'agent' }, sourceAuthorization: { generationId: 'current-generation', policy: { automaticValidatedPublication: true } } } });
729
+ let restart = false;
730
+ r.overrides.relaySupervisorProcessState = async () => restart ? 'running' : 'stopped';
731
+ r.overrides.saveWorkspaceRecord = async (_record, guard) => {
732
+ restart = true;
733
+ await guard.beforeSave();
734
+ throw new Error('unexpected_registry_write');
735
+ };
736
+ await assert.rejects(r.run(r.flags(['apply', true], ['plan-hash', 'reviewed-plan'])), /requires_stopped_relay/);
737
+ assert.ok(!r.calls.some(call => /write|submit/.test(call)));
738
+ });
739
+ test('changed plan, revoked authorization, pending source and live/unknown relay cannot submit', async () => {
740
+ for (const mode of ['changed-plan', 'revoked', 'pending', 'knowledge_only', 'running', 'unknown']) {
741
+ const f = fixture();
742
+ if (mode === 'revoked')
743
+ f.overrides.assertWorkspaceAuthorizationCurrent = async () => { throw new Error('revoked'); };
744
+ if (mode === 'pending')
745
+ f.workspace.repositoryPackage.pendingLocalOperationId = 'other';
746
+ if (mode === 'knowledge_only')
747
+ f.workspace.accessMode = mode;
748
+ if (mode === 'running' || mode === 'unknown')
749
+ f.overrides.relayProcessState = async () => mode;
750
+ await assert.rejects(f.run(f.flags(['apply', true], ['plan-hash', mode === 'changed-plan' ? 'changed' : 'reviewed-plan'])));
751
+ assert.ok(!f.calls.some(call => /write|submit/.test(call)));
752
+ }
753
+ });
754
+ //# sourceMappingURL=repositorySourceRestorationCommand.test.js.map