@longarc/mdash 3.0.0
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.
- package/README.md +278 -0
- package/dist/checkpoint/engine.d.ts +208 -0
- package/dist/checkpoint/engine.d.ts.map +1 -0
- package/dist/checkpoint/engine.js +369 -0
- package/dist/checkpoint/engine.js.map +1 -0
- package/dist/context/engine.d.ts +197 -0
- package/dist/context/engine.d.ts.map +1 -0
- package/dist/context/engine.js +392 -0
- package/dist/context/engine.js.map +1 -0
- package/dist/core/commitment.d.ts +154 -0
- package/dist/core/commitment.d.ts.map +1 -0
- package/dist/core/commitment.js +305 -0
- package/dist/core/commitment.js.map +1 -0
- package/dist/core/crypto.d.ts +100 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +243 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +234 -0
- package/dist/index.js.map +1 -0
- package/dist/mcca/engine.d.ts +260 -0
- package/dist/mcca/engine.d.ts.map +1 -0
- package/dist/mcca/engine.js +518 -0
- package/dist/mcca/engine.js.map +1 -0
- package/dist/physics/engine.d.ts +165 -0
- package/dist/physics/engine.d.ts.map +1 -0
- package/dist/physics/engine.js +371 -0
- package/dist/physics/engine.js.map +1 -0
- package/dist/tee/engine.d.ts +285 -0
- package/dist/tee/engine.d.ts.map +1 -0
- package/dist/tee/engine.js +505 -0
- package/dist/tee/engine.js.map +1 -0
- package/dist/warrant/engine.d.ts +195 -0
- package/dist/warrant/engine.d.ts.map +1 -0
- package/dist/warrant/engine.js +409 -0
- package/dist/warrant/engine.js.map +1 -0
- package/dist/zk/engine.d.ts +243 -0
- package/dist/zk/engine.d.ts.map +1 -0
- package/dist/zk/engine.js +489 -0
- package/dist/zk/engine.js.map +1 -0
- package/package.json +25 -0
- package/src/__tests__/phase1.test.ts +1120 -0
- package/src/__tests__/phase2-4.test.ts +898 -0
- package/src/checkpoint/engine.ts +532 -0
- package/src/context/engine.ts +598 -0
- package/src/core/commitment.ts +438 -0
- package/src/core/crypto.ts +304 -0
- package/src/index.ts +320 -0
- package/src/mcca/engine.ts +778 -0
- package/src/physics/engine.ts +563 -0
- package/src/tee/engine.ts +810 -0
- package/src/warrant/engine.ts +625 -0
- package/src/zk/engine.ts +730 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mdash v3.0 - Checkpoint Surety
|
|
3
|
+
*
|
|
4
|
+
* Event-driven checkpoints replace time-driven intervals.
|
|
5
|
+
* "State transition triggers seal, not time intervals."
|
|
6
|
+
*
|
|
7
|
+
* Target Latency:
|
|
8
|
+
* - Checkpoint creation: <0.5ms P50, <1ms P99
|
|
9
|
+
*
|
|
10
|
+
* Key Property:
|
|
11
|
+
* - Single action blast radius: if an action fails, only that action is affected
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
Hash,
|
|
16
|
+
Seal,
|
|
17
|
+
Timestamp,
|
|
18
|
+
CheckpointId,
|
|
19
|
+
WarrantId,
|
|
20
|
+
generateCheckpointId,
|
|
21
|
+
generateTimestamp,
|
|
22
|
+
sha256Object,
|
|
23
|
+
hmacSeal,
|
|
24
|
+
hmacVerify,
|
|
25
|
+
deriveKey,
|
|
26
|
+
} from '../core/crypto';
|
|
27
|
+
|
|
28
|
+
import { CommitmentEngine } from '../core/commitment';
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// CHECKPOINT TYPES
|
|
32
|
+
// ============================================================================
|
|
33
|
+
|
|
34
|
+
export type CheckpointTrigger =
|
|
35
|
+
| 'action_start'
|
|
36
|
+
| 'action_complete'
|
|
37
|
+
| 'state_change'
|
|
38
|
+
| 'constraint_check'
|
|
39
|
+
| 'error'
|
|
40
|
+
| 'recovery'
|
|
41
|
+
| 'manual';
|
|
42
|
+
|
|
43
|
+
export type CheckpointStatus = 'pending' | 'sealed' | 'verified' | 'failed';
|
|
44
|
+
|
|
45
|
+
export interface CheckpointState {
|
|
46
|
+
/** Current execution state */
|
|
47
|
+
execution_state: Record<string, unknown>;
|
|
48
|
+
/** Active warrant at checkpoint */
|
|
49
|
+
warrant_id: WarrantId | null;
|
|
50
|
+
/** Action being performed */
|
|
51
|
+
action: string;
|
|
52
|
+
/** Action parameters */
|
|
53
|
+
params: Record<string, unknown>;
|
|
54
|
+
/** Result (if action complete) */
|
|
55
|
+
result?: unknown;
|
|
56
|
+
/** Error (if failed) */
|
|
57
|
+
error?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface Checkpoint {
|
|
61
|
+
/** Unique checkpoint identifier */
|
|
62
|
+
id: CheckpointId;
|
|
63
|
+
/** Agent this checkpoint belongs to */
|
|
64
|
+
agent_id: string;
|
|
65
|
+
/** Trigger that created this checkpoint */
|
|
66
|
+
trigger: CheckpointTrigger;
|
|
67
|
+
/** Current status */
|
|
68
|
+
status: CheckpointStatus;
|
|
69
|
+
/** State at checkpoint */
|
|
70
|
+
state: CheckpointState;
|
|
71
|
+
/** Parent checkpoint (for happens-before) */
|
|
72
|
+
parent_id: CheckpointId | null;
|
|
73
|
+
/** Physics validation score */
|
|
74
|
+
physics_score: number;
|
|
75
|
+
/** Timestamp of creation */
|
|
76
|
+
created_at: Timestamp;
|
|
77
|
+
/** Content hash */
|
|
78
|
+
hash: Hash;
|
|
79
|
+
/** HMAC seal */
|
|
80
|
+
seal: Seal;
|
|
81
|
+
/** L1 commitment reference */
|
|
82
|
+
commitment_id: string | null;
|
|
83
|
+
/** Protocol version */
|
|
84
|
+
version: 'v3.0';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface CheckpointChain {
|
|
88
|
+
/** Head of the chain */
|
|
89
|
+
head: Checkpoint;
|
|
90
|
+
/** Previous checkpoints */
|
|
91
|
+
predecessors: CheckpointId[];
|
|
92
|
+
/** Chain length */
|
|
93
|
+
length: number;
|
|
94
|
+
/** Chain hash (rolling) */
|
|
95
|
+
chain_hash: Hash;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// HAPPENS-BEFORE LATTICE
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Lamport-style happens-before ordering for checkpoints
|
|
104
|
+
* Ensures causal consistency in distributed execution
|
|
105
|
+
*/
|
|
106
|
+
export class HappensBeforeLattice {
|
|
107
|
+
private checkpoints: Map<CheckpointId, Checkpoint> = new Map();
|
|
108
|
+
private children: Map<CheckpointId, CheckpointId[]> = new Map();
|
|
109
|
+
private roots: CheckpointId[] = [];
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Add a checkpoint to the lattice
|
|
113
|
+
*/
|
|
114
|
+
add(checkpoint: Checkpoint): void {
|
|
115
|
+
this.checkpoints.set(checkpoint.id, checkpoint);
|
|
116
|
+
|
|
117
|
+
if (checkpoint.parent_id) {
|
|
118
|
+
const siblings = this.children.get(checkpoint.parent_id) || [];
|
|
119
|
+
siblings.push(checkpoint.id);
|
|
120
|
+
this.children.set(checkpoint.parent_id, siblings);
|
|
121
|
+
} else {
|
|
122
|
+
this.roots.push(checkpoint.id);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check if a happens-before b
|
|
128
|
+
*/
|
|
129
|
+
happensBefore(a: CheckpointId, b: CheckpointId): boolean {
|
|
130
|
+
const cpB = this.checkpoints.get(b);
|
|
131
|
+
if (!cpB) return false;
|
|
132
|
+
|
|
133
|
+
// Walk up from b to see if we reach a
|
|
134
|
+
let current: CheckpointId | null = b;
|
|
135
|
+
while (current) {
|
|
136
|
+
if (current === a) return true;
|
|
137
|
+
const cp = this.checkpoints.get(current);
|
|
138
|
+
current = cp?.parent_id || null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if two checkpoints are concurrent (neither happens-before the other)
|
|
146
|
+
*/
|
|
147
|
+
areConcurrent(a: CheckpointId, b: CheckpointId): boolean {
|
|
148
|
+
return !this.happensBefore(a, b) && !this.happensBefore(b, a);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get the causal history of a checkpoint
|
|
153
|
+
*/
|
|
154
|
+
getCausalHistory(id: CheckpointId): Checkpoint[] {
|
|
155
|
+
const history: Checkpoint[] = [];
|
|
156
|
+
let current: CheckpointId | null = id;
|
|
157
|
+
|
|
158
|
+
while (current) {
|
|
159
|
+
const cp = this.checkpoints.get(current);
|
|
160
|
+
if (!cp) break;
|
|
161
|
+
history.push(cp);
|
|
162
|
+
current = cp.parent_id;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return history;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get all checkpoints after a given one
|
|
170
|
+
*/
|
|
171
|
+
getSuccessors(id: CheckpointId): Checkpoint[] {
|
|
172
|
+
const successors: Checkpoint[] = [];
|
|
173
|
+
const queue = this.children.get(id) || [];
|
|
174
|
+
|
|
175
|
+
while (queue.length > 0) {
|
|
176
|
+
const current = queue.shift()!;
|
|
177
|
+
const cp = this.checkpoints.get(current);
|
|
178
|
+
if (cp) {
|
|
179
|
+
successors.push(cp);
|
|
180
|
+
queue.push(...(this.children.get(current) || []));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return successors;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Find the latest common ancestor of two checkpoints
|
|
189
|
+
*/
|
|
190
|
+
findLCA(a: CheckpointId, b: CheckpointId): CheckpointId | null {
|
|
191
|
+
const ancestorsA = new Set<CheckpointId>();
|
|
192
|
+
let current: CheckpointId | null = a;
|
|
193
|
+
|
|
194
|
+
while (current) {
|
|
195
|
+
ancestorsA.add(current);
|
|
196
|
+
const cp = this.checkpoints.get(current);
|
|
197
|
+
current = cp?.parent_id || null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
current = b;
|
|
201
|
+
while (current) {
|
|
202
|
+
if (ancestorsA.has(current)) {
|
|
203
|
+
return current;
|
|
204
|
+
}
|
|
205
|
+
const cp = this.checkpoints.get(current);
|
|
206
|
+
current = cp?.parent_id || null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get statistics
|
|
214
|
+
*/
|
|
215
|
+
getStats(): { total: number; roots: number; maxDepth: number } {
|
|
216
|
+
let maxDepth = 0;
|
|
217
|
+
|
|
218
|
+
for (const id of this.checkpoints.keys()) {
|
|
219
|
+
const history = this.getCausalHistory(id);
|
|
220
|
+
maxDepth = Math.max(maxDepth, history.length);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
total: this.checkpoints.size,
|
|
225
|
+
roots: this.roots.length,
|
|
226
|
+
maxDepth,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ============================================================================
|
|
232
|
+
// CHECKPOINT ENGINE
|
|
233
|
+
// ============================================================================
|
|
234
|
+
|
|
235
|
+
export class CheckpointEngine {
|
|
236
|
+
private key: CryptoKey | null = null;
|
|
237
|
+
private commitmentEngine: CommitmentEngine;
|
|
238
|
+
private lattice: HappensBeforeLattice;
|
|
239
|
+
private checkpoints: Map<CheckpointId, Checkpoint> = new Map();
|
|
240
|
+
private agentHeads: Map<string, CheckpointId> = new Map(); // Latest checkpoint per agent
|
|
241
|
+
|
|
242
|
+
constructor(commitmentEngine: CommitmentEngine) {
|
|
243
|
+
this.commitmentEngine = commitmentEngine;
|
|
244
|
+
this.lattice = new HappensBeforeLattice();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Initialize the engine with a seal key
|
|
249
|
+
*/
|
|
250
|
+
async initialize(sealKey: string): Promise<void> {
|
|
251
|
+
this.key = await deriveKey(sealKey);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Create a checkpoint
|
|
256
|
+
* Target: <0.5ms P50, <1ms P99
|
|
257
|
+
*/
|
|
258
|
+
async createCheckpoint(params: {
|
|
259
|
+
agent_id: string;
|
|
260
|
+
trigger: CheckpointTrigger;
|
|
261
|
+
state: CheckpointState;
|
|
262
|
+
physics_score?: number;
|
|
263
|
+
}): Promise<Checkpoint> {
|
|
264
|
+
if (!this.key) {
|
|
265
|
+
throw new Error('Engine not initialized. Call initialize() first.');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const startTime = performance.now();
|
|
269
|
+
|
|
270
|
+
const id = generateCheckpointId();
|
|
271
|
+
const now = generateTimestamp();
|
|
272
|
+
const parentId = this.agentHeads.get(params.agent_id) || null;
|
|
273
|
+
|
|
274
|
+
// Compute state hash
|
|
275
|
+
const hash = await sha256Object({
|
|
276
|
+
id,
|
|
277
|
+
agent_id: params.agent_id,
|
|
278
|
+
trigger: params.trigger,
|
|
279
|
+
state: params.state,
|
|
280
|
+
parent_id: parentId,
|
|
281
|
+
created_at: now,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Create checkpoint data for sealing
|
|
285
|
+
const checkpointData = {
|
|
286
|
+
_v: 1,
|
|
287
|
+
id,
|
|
288
|
+
agent_id: params.agent_id,
|
|
289
|
+
trigger: params.trigger,
|
|
290
|
+
hash,
|
|
291
|
+
parent_id: parentId,
|
|
292
|
+
created_at: now,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// Seal the checkpoint
|
|
296
|
+
const seal = await hmacSeal(checkpointData, this.key);
|
|
297
|
+
|
|
298
|
+
const checkpoint: Checkpoint = {
|
|
299
|
+
id,
|
|
300
|
+
agent_id: params.agent_id,
|
|
301
|
+
trigger: params.trigger,
|
|
302
|
+
status: 'pending',
|
|
303
|
+
state: params.state,
|
|
304
|
+
parent_id: parentId,
|
|
305
|
+
physics_score: params.physics_score ?? 1.0,
|
|
306
|
+
created_at: now,
|
|
307
|
+
hash,
|
|
308
|
+
seal,
|
|
309
|
+
commitment_id: null,
|
|
310
|
+
version: 'v3.0',
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Commit to L1 (async, don't wait)
|
|
314
|
+
const commitmentId = `checkpoint:${id}`;
|
|
315
|
+
this.commitmentEngine.commit(checkpoint, commitmentId).then(() => {
|
|
316
|
+
checkpoint.commitment_id = commitmentId;
|
|
317
|
+
checkpoint.status = 'sealed';
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Store and index
|
|
321
|
+
this.checkpoints.set(id, checkpoint);
|
|
322
|
+
this.agentHeads.set(params.agent_id, id);
|
|
323
|
+
this.lattice.add(checkpoint);
|
|
324
|
+
|
|
325
|
+
const elapsed = performance.now() - startTime;
|
|
326
|
+
if (elapsed > 1) {
|
|
327
|
+
console.warn(`Checkpoint creation exceeded P99: ${elapsed.toFixed(2)}ms`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return checkpoint;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Create checkpoint for action start
|
|
335
|
+
*/
|
|
336
|
+
async onActionStart(params: {
|
|
337
|
+
agent_id: string;
|
|
338
|
+
warrant_id: WarrantId;
|
|
339
|
+
action: string;
|
|
340
|
+
params: Record<string, unknown>;
|
|
341
|
+
}): Promise<Checkpoint> {
|
|
342
|
+
return this.createCheckpoint({
|
|
343
|
+
agent_id: params.agent_id,
|
|
344
|
+
trigger: 'action_start',
|
|
345
|
+
state: {
|
|
346
|
+
execution_state: { status: 'starting' },
|
|
347
|
+
warrant_id: params.warrant_id,
|
|
348
|
+
action: params.action,
|
|
349
|
+
params: params.params,
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Create checkpoint for action completion
|
|
356
|
+
*/
|
|
357
|
+
async onActionComplete(params: {
|
|
358
|
+
agent_id: string;
|
|
359
|
+
warrant_id: WarrantId;
|
|
360
|
+
action: string;
|
|
361
|
+
params: Record<string, unknown>;
|
|
362
|
+
result: unknown;
|
|
363
|
+
physics_score: number;
|
|
364
|
+
}): Promise<Checkpoint> {
|
|
365
|
+
return this.createCheckpoint({
|
|
366
|
+
agent_id: params.agent_id,
|
|
367
|
+
trigger: 'action_complete',
|
|
368
|
+
state: {
|
|
369
|
+
execution_state: { status: 'complete' },
|
|
370
|
+
warrant_id: params.warrant_id,
|
|
371
|
+
action: params.action,
|
|
372
|
+
params: params.params,
|
|
373
|
+
result: params.result,
|
|
374
|
+
},
|
|
375
|
+
physics_score: params.physics_score,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Create checkpoint for error
|
|
381
|
+
*/
|
|
382
|
+
async onError(params: {
|
|
383
|
+
agent_id: string;
|
|
384
|
+
warrant_id: WarrantId | null;
|
|
385
|
+
action: string;
|
|
386
|
+
error: string;
|
|
387
|
+
}): Promise<Checkpoint> {
|
|
388
|
+
return this.createCheckpoint({
|
|
389
|
+
agent_id: params.agent_id,
|
|
390
|
+
trigger: 'error',
|
|
391
|
+
state: {
|
|
392
|
+
execution_state: { status: 'error' },
|
|
393
|
+
warrant_id: params.warrant_id,
|
|
394
|
+
action: params.action,
|
|
395
|
+
params: {},
|
|
396
|
+
error: params.error,
|
|
397
|
+
},
|
|
398
|
+
physics_score: 0,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Verify a checkpoint
|
|
404
|
+
*/
|
|
405
|
+
async verify(checkpoint: Checkpoint): Promise<boolean> {
|
|
406
|
+
if (!this.key) {
|
|
407
|
+
throw new Error('Engine not initialized. Call initialize() first.');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Verify hash
|
|
411
|
+
const expectedHash = await sha256Object({
|
|
412
|
+
id: checkpoint.id,
|
|
413
|
+
agent_id: checkpoint.agent_id,
|
|
414
|
+
trigger: checkpoint.trigger,
|
|
415
|
+
state: checkpoint.state,
|
|
416
|
+
parent_id: checkpoint.parent_id,
|
|
417
|
+
created_at: checkpoint.created_at,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
if (expectedHash !== checkpoint.hash) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Verify seal
|
|
425
|
+
const checkpointData = {
|
|
426
|
+
_v: 1,
|
|
427
|
+
id: checkpoint.id,
|
|
428
|
+
agent_id: checkpoint.agent_id,
|
|
429
|
+
trigger: checkpoint.trigger,
|
|
430
|
+
hash: checkpoint.hash,
|
|
431
|
+
parent_id: checkpoint.parent_id,
|
|
432
|
+
created_at: checkpoint.created_at,
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
return hmacVerify(checkpointData, checkpoint.seal, this.key);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Get checkpoint by ID
|
|
440
|
+
*/
|
|
441
|
+
get(id: CheckpointId): Checkpoint | null {
|
|
442
|
+
return this.checkpoints.get(id) || null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Get the latest checkpoint for an agent
|
|
447
|
+
*/
|
|
448
|
+
getHead(agentId: string): Checkpoint | null {
|
|
449
|
+
const headId = this.agentHeads.get(agentId);
|
|
450
|
+
if (!headId) return null;
|
|
451
|
+
return this.checkpoints.get(headId) || null;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Get checkpoint chain for an agent
|
|
456
|
+
*/
|
|
457
|
+
getChain(agentId: string, limit: number = 100): Checkpoint[] {
|
|
458
|
+
const headId = this.agentHeads.get(agentId);
|
|
459
|
+
if (!headId) return [];
|
|
460
|
+
|
|
461
|
+
return this.lattice.getCausalHistory(headId).slice(0, limit);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Check if checkpoint a happened before checkpoint b
|
|
466
|
+
*/
|
|
467
|
+
happensBefore(a: CheckpointId, b: CheckpointId): boolean {
|
|
468
|
+
return this.lattice.happensBefore(a, b);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Get statistics
|
|
473
|
+
*/
|
|
474
|
+
getStats(): {
|
|
475
|
+
total: number;
|
|
476
|
+
agents: number;
|
|
477
|
+
latticeStats: ReturnType<HappensBeforeLattice['getStats']>;
|
|
478
|
+
} {
|
|
479
|
+
return {
|
|
480
|
+
total: this.checkpoints.size,
|
|
481
|
+
agents: this.agentHeads.size,
|
|
482
|
+
latticeStats: this.lattice.getStats(),
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ============================================================================
|
|
488
|
+
// BLAST RADIUS ANALYZER
|
|
489
|
+
// ============================================================================
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Analyzes the impact scope of a failed action
|
|
493
|
+
* "Single action blast radius" means failures are isolated
|
|
494
|
+
*/
|
|
495
|
+
export class BlastRadiusAnalyzer {
|
|
496
|
+
private lattice: HappensBeforeLattice;
|
|
497
|
+
|
|
498
|
+
constructor(lattice: HappensBeforeLattice) {
|
|
499
|
+
this.lattice = lattice;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Analyze the blast radius of a failed checkpoint
|
|
504
|
+
* Returns affected checkpoints that depend on the failed one
|
|
505
|
+
*/
|
|
506
|
+
analyze(failedId: CheckpointId): {
|
|
507
|
+
affected: Checkpoint[];
|
|
508
|
+
isolated: boolean;
|
|
509
|
+
recoveryPoint: CheckpointId | null;
|
|
510
|
+
} {
|
|
511
|
+
const successors = this.lattice.getSuccessors(failedId);
|
|
512
|
+
const history = this.lattice.getCausalHistory(failedId);
|
|
513
|
+
|
|
514
|
+
// Find the last good checkpoint (recovery point)
|
|
515
|
+
const failedCheckpoint = history[0];
|
|
516
|
+
const recoveryPoint = failedCheckpoint?.parent_id ?? null;
|
|
517
|
+
|
|
518
|
+
return {
|
|
519
|
+
affected: successors,
|
|
520
|
+
isolated: successors.length === 0,
|
|
521
|
+
recoveryPoint,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Check if an action can be safely retried
|
|
527
|
+
*/
|
|
528
|
+
canRetry(checkpointId: CheckpointId): boolean {
|
|
529
|
+
const { isolated } = this.analyze(checkpointId);
|
|
530
|
+
return isolated;
|
|
531
|
+
}
|
|
532
|
+
}
|