@fleettools/squawk 0.1.0 → 0.1.1
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/package.json +1 -1
- package/AGENTS.md +0 -28
- package/dist/src/db/checkpoint-storage.d.ts +0 -19
- package/dist/src/db/checkpoint-storage.d.ts.map +0 -1
- package/dist/src/db/checkpoint-storage.js +0 -355
- package/dist/src/db/checkpoint-storage.js.map +0 -1
- package/dist/src/db/index.d.ts +0 -30
- package/dist/src/db/index.d.ts.map +0 -1
- package/dist/src/db/index.js +0 -329
- package/dist/src/db/index.js.map +0 -1
- package/dist/src/db/sqlite.d.ts +0 -31
- package/dist/src/db/sqlite.d.ts.map +0 -1
- package/dist/src/db/sqlite.js +0 -558
- package/dist/src/db/sqlite.js.map +0 -1
- package/dist/src/db/types.d.ts +0 -611
- package/dist/src/db/types.d.ts.map +0 -1
- package/dist/src/db/types.js +0 -4
- package/dist/src/db/types.js.map +0 -1
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -285
- package/dist/src/index.js.map +0 -1
- package/dist/src/recovery/checkpointing.d.ts +0 -244
- package/dist/src/recovery/checkpointing.d.ts.map +0 -1
- package/dist/src/recovery/checkpointing.js +0 -511
- package/dist/src/recovery/checkpointing.js.map +0 -1
- package/dist/src/recovery/detection.d.ts +0 -137
- package/dist/src/recovery/detection.d.ts.map +0 -1
- package/dist/src/recovery/detection.js +0 -240
- package/dist/src/recovery/detection.js.map +0 -1
- package/dist/src/recovery/detector.d.ts +0 -34
- package/dist/src/recovery/detector.d.ts.map +0 -1
- package/dist/src/recovery/detector.js +0 -42
- package/dist/src/recovery/detector.js.map +0 -1
- package/dist/src/recovery/index.d.ts +0 -3
- package/dist/src/recovery/index.d.ts.map +0 -1
- package/dist/src/recovery/index.js +0 -3
- package/dist/src/recovery/index.js.map +0 -1
- package/dist/src/recovery/restorer.d.ts +0 -51
- package/dist/src/recovery/restorer.d.ts.map +0 -1
- package/dist/src/recovery/restorer.js +0 -266
- package/dist/src/recovery/restorer.js.map +0 -1
- package/dist/src/schemas.d.ts +0 -142
- package/dist/src/schemas.d.ts.map +0 -1
- package/dist/src/schemas.js +0 -110
- package/dist/src/schemas.js.map +0 -1
- package/src/db/checkpoint-storage.ts +0 -443
- package/src/db/index.d.ts +0 -30
- package/src/db/index.d.ts.map +0 -1
- package/src/db/index.js.map +0 -1
- package/src/db/index.ts +0 -417
- package/src/db/schema.sql +0 -112
- package/src/db/sqlite.d.ts +0 -31
- package/src/db/sqlite.d.ts.map +0 -1
- package/src/db/sqlite.js +0 -667
- package/src/db/sqlite.js.map +0 -1
- package/src/db/sqlite.ts +0 -677
- package/src/db/types.d.ts +0 -612
- package/src/db/types.d.ts.map +0 -1
- package/src/db/types.js +0 -4
- package/src/db/types.js.map +0 -1
- package/src/db/types.ts +0 -771
- package/src/index.ts +0 -332
- package/src/recovery/detector.ts +0 -82
- package/src/recovery/index.ts +0 -3
- package/src/recovery/restorer.ts +0 -377
package/src/recovery/restorer.ts
DELETED
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import type {
|
|
3
|
-
Checkpoint,
|
|
4
|
-
RecoveryContext,
|
|
5
|
-
SortieSnapshot,
|
|
6
|
-
LockSnapshot,
|
|
7
|
-
MessageSnapshot,
|
|
8
|
-
LockResult,
|
|
9
|
-
AppendEventInput
|
|
10
|
-
} from '../db/types.js';
|
|
11
|
-
|
|
12
|
-
export interface RestoreOptions {
|
|
13
|
-
dryRun?: boolean;
|
|
14
|
-
forceLocks?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface RestoreResult {
|
|
18
|
-
success: boolean;
|
|
19
|
-
checkpoint_id: string;
|
|
20
|
-
mission_id: string;
|
|
21
|
-
recovery_context: RecoveryContext;
|
|
22
|
-
restored: {
|
|
23
|
-
sorties: number;
|
|
24
|
-
locks: number;
|
|
25
|
-
messages: number;
|
|
26
|
-
};
|
|
27
|
-
errors: string[];
|
|
28
|
-
warnings: string[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export class StateRestorer {
|
|
32
|
-
constructor(
|
|
33
|
-
private db: {
|
|
34
|
-
checkpoints: {
|
|
35
|
-
getById: (id: string) => Promise<Checkpoint | null>;
|
|
36
|
-
getLatestByMission: (missionId: string) => Promise<Checkpoint | null>;
|
|
37
|
-
markConsumed: (id: string) => Promise<void>;
|
|
38
|
-
};
|
|
39
|
-
sorties: {
|
|
40
|
-
update: (id: string, data: any) => Promise<void>;
|
|
41
|
-
};
|
|
42
|
-
locks: {
|
|
43
|
-
acquire: (input: any) => Promise<LockResult>;
|
|
44
|
-
forceRelease: (id: string) => Promise<void>;
|
|
45
|
-
};
|
|
46
|
-
messages: {
|
|
47
|
-
requeue: (id: string) => Promise<void>;
|
|
48
|
-
};
|
|
49
|
-
events: {
|
|
50
|
-
append: (input: AppendEventInput) => Promise<any>;
|
|
51
|
-
};
|
|
52
|
-
beginTransaction: () => Promise<void>;
|
|
53
|
-
commitTransaction: () => Promise<void>;
|
|
54
|
-
rollbackTransaction: () => Promise<void>;
|
|
55
|
-
}
|
|
56
|
-
) {}
|
|
57
|
-
|
|
58
|
-
async restoreFromCheckpoint(checkpointId: string, options: RestoreOptions = {}): Promise<RestoreResult> {
|
|
59
|
-
const { dryRun = false, forceLocks = false } = options;
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const checkpoint = await this.db.checkpoints.getById(checkpointId);
|
|
63
|
-
if (!checkpoint) {
|
|
64
|
-
return {
|
|
65
|
-
success: false,
|
|
66
|
-
checkpoint_id: checkpointId,
|
|
67
|
-
mission_id: '',
|
|
68
|
-
recovery_context: {} as RecoveryContext,
|
|
69
|
-
restored: { sorties: 0, locks: 0, messages: 0 },
|
|
70
|
-
errors: [`Checkpoint not found: ${checkpointId}`],
|
|
71
|
-
warnings: []
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return await this.restore(checkpoint, { dryRun, forceLocks });
|
|
76
|
-
} catch (error) {
|
|
77
|
-
return {
|
|
78
|
-
success: false,
|
|
79
|
-
checkpoint_id: checkpointId,
|
|
80
|
-
mission_id: '',
|
|
81
|
-
recovery_context: {} as RecoveryContext,
|
|
82
|
-
restored: { sorties: 0, locks: 0, messages: 0 },
|
|
83
|
-
errors: [`Restore failed: ${error instanceof Error ? error.message : String(error)}`],
|
|
84
|
-
warnings: []
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async restoreLatest(missionId: string, options: RestoreOptions = {}): Promise<RestoreResult> {
|
|
90
|
-
const { dryRun = false, forceLocks = false } = options;
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
const checkpoint = await this.db.checkpoints.getLatestByMission(missionId);
|
|
94
|
-
if (!checkpoint) {
|
|
95
|
-
return {
|
|
96
|
-
success: false,
|
|
97
|
-
checkpoint_id: '',
|
|
98
|
-
mission_id: missionId,
|
|
99
|
-
recovery_context: {} as RecoveryContext,
|
|
100
|
-
restored: { sorties: 0, locks: 0, messages: 0 },
|
|
101
|
-
errors: [`No checkpoints found for mission: ${missionId}`],
|
|
102
|
-
warnings: []
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return await this.restore(checkpoint, { dryRun, forceLocks });
|
|
107
|
-
} catch (error) {
|
|
108
|
-
return {
|
|
109
|
-
success: false,
|
|
110
|
-
checkpoint_id: '',
|
|
111
|
-
mission_id: missionId,
|
|
112
|
-
recovery_context: {} as RecoveryContext,
|
|
113
|
-
restored: { sorties: 0, locks: 0, messages: 0 },
|
|
114
|
-
errors: [`Restore failed: ${error instanceof Error ? error.message : String(error)}`],
|
|
115
|
-
warnings: []
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
private async restore(checkpoint: Checkpoint, options: RestoreOptions): Promise<RestoreResult> {
|
|
121
|
-
const { dryRun = false, forceLocks = false } = options;
|
|
122
|
-
const now = new Date().toISOString();
|
|
123
|
-
|
|
124
|
-
const result: RestoreResult = {
|
|
125
|
-
success: true,
|
|
126
|
-
checkpoint_id: checkpoint.id,
|
|
127
|
-
mission_id: checkpoint.mission_id,
|
|
128
|
-
recovery_context: {
|
|
129
|
-
last_action: 'Restored from checkpoint',
|
|
130
|
-
next_steps: this.extractNextSteps(checkpoint),
|
|
131
|
-
blockers: [],
|
|
132
|
-
files_modified: this.extractModifiedFiles(checkpoint),
|
|
133
|
-
mission_summary: `${checkpoint.mission_id} checkpoint restoration`,
|
|
134
|
-
elapsed_time_ms: Date.now() - new Date(checkpoint.timestamp).getTime(),
|
|
135
|
-
last_activity_at: checkpoint.timestamp
|
|
136
|
-
},
|
|
137
|
-
restored: { sorties: 0, locks: 0, messages: 0 },
|
|
138
|
-
errors: [],
|
|
139
|
-
warnings: []
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
if (!dryRun) {
|
|
144
|
-
await this.db.beginTransaction();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// 1. Restore sortie states
|
|
148
|
-
for (const sortie of checkpoint.sorties || []) {
|
|
149
|
-
try {
|
|
150
|
-
if (!dryRun) {
|
|
151
|
-
await this.db.sorties.update(sortie.id, {
|
|
152
|
-
status: sortie.status,
|
|
153
|
-
assigned_to: sortie.assigned_to,
|
|
154
|
-
files: sortie.files,
|
|
155
|
-
progress: sortie.progress,
|
|
156
|
-
progress_notes: sortie.progress_notes
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
result.restored.sorties++;
|
|
160
|
-
} catch (error) {
|
|
161
|
-
result.errors.push(`Failed to restore sortie ${sortie.id}: ${error}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
for (const lock of checkpoint.active_locks || []) {
|
|
167
|
-
try {
|
|
168
|
-
const lockAge = Date.now() - new Date(lock.acquired_at).getTime();
|
|
169
|
-
const isExpired = lockAge > (lock.timeout_ms || 30000);
|
|
170
|
-
|
|
171
|
-
if (isExpired) {
|
|
172
|
-
result.warnings.push(`Lock ${lock.id} expired, skipping re-acquisition`);
|
|
173
|
-
result.recovery_context.blockers.push(`Expired lock on ${lock.file} (held by ${lock.held_by})`);
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (!dryRun) {
|
|
178
|
-
const lockResult = await this.db.locks.acquire({
|
|
179
|
-
file: lock.file,
|
|
180
|
-
specialist_id: lock.held_by,
|
|
181
|
-
timeout_ms: lock.timeout_ms,
|
|
182
|
-
purpose: lock.purpose
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
if (lockResult.conflict) {
|
|
186
|
-
if (forceLocks) {
|
|
187
|
-
await this.db.locks.forceRelease(lockResult.existing_lock?.id || '');
|
|
188
|
-
await this.db.locks.acquire({
|
|
189
|
-
file: lock.file,
|
|
190
|
-
specialist_id: lock.held_by,
|
|
191
|
-
timeout_ms: lock.timeout_ms,
|
|
192
|
-
purpose: lock.purpose
|
|
193
|
-
});
|
|
194
|
-
} else {
|
|
195
|
-
result.warnings.push(`Lock conflict on ${lock.file}, skipping re-acquisition`);
|
|
196
|
-
result.recovery_context.blockers.push(`Lock conflict on ${lock.file} (held by ${lock.held_by})`);
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
result.restored.locks++;
|
|
202
|
-
} catch (error) {
|
|
203
|
-
result.errors.push(`Failed to restore lock ${lock.id}: ${error}`);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// 3. Requeue pending messages
|
|
208
|
-
for (const message of checkpoint.pending_messages || []) {
|
|
209
|
-
try {
|
|
210
|
-
if (!message.delivered) {
|
|
211
|
-
if (!dryRun) {
|
|
212
|
-
await this.db.messages.requeue(message.id);
|
|
213
|
-
}
|
|
214
|
-
result.restored.messages++;
|
|
215
|
-
}
|
|
216
|
-
} catch (error) {
|
|
217
|
-
result.errors.push(`Failed to requeue message ${message.id}: ${error}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// 4. Mark checkpoint as consumed (only if not dry run and successful)
|
|
222
|
-
if (!dryRun && result.errors.length === 0) {
|
|
223
|
-
await this.db.checkpoints.markConsumed(checkpoint.id);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// 5. Emit fleet_recovered event
|
|
227
|
-
if (!dryRun && result.errors.length === 0) {
|
|
228
|
-
await this.db.events.append({
|
|
229
|
-
event_type: 'fleet_recovered',
|
|
230
|
-
stream_type: 'mission',
|
|
231
|
-
stream_id: checkpoint.mission_id,
|
|
232
|
-
data: {
|
|
233
|
-
checkpoint_id: checkpoint.id,
|
|
234
|
-
restored_at: now,
|
|
235
|
-
sorties_restored: result.restored.sorties,
|
|
236
|
-
locks_restored: result.restored.locks,
|
|
237
|
-
messages_requeued: result.restored.messages,
|
|
238
|
-
warnings: result.warnings.length
|
|
239
|
-
},
|
|
240
|
-
occurred_at: now
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (!dryRun) {
|
|
245
|
-
await this.db.commitTransaction();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return result;
|
|
249
|
-
} catch (error) {
|
|
250
|
-
if (!dryRun) {
|
|
251
|
-
try {
|
|
252
|
-
await this.db.rollbackTransaction();
|
|
253
|
-
} catch (rollbackError) {
|
|
254
|
-
result.errors.push(`Failed to rollback transaction: ${rollbackError}`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
result.success = false;
|
|
259
|
-
result.errors.push(`Transaction failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
260
|
-
return result;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
private extractNextSteps(checkpoint: Checkpoint): string[] {
|
|
265
|
-
const steps: string[] = [];
|
|
266
|
-
|
|
267
|
-
const inProgressSorties = checkpoint.sorties?.filter((s: any) => s.status === 'in_progress') || [];
|
|
268
|
-
const blockedSorties = checkpoint.sorties?.filter((s: any) =>
|
|
269
|
-
s.progress_notes && s.progress_notes.toLowerCase().includes('blocked')
|
|
270
|
-
) || [];
|
|
271
|
-
|
|
272
|
-
if (inProgressSorties && inProgressSorties.length > 0) {
|
|
273
|
-
steps.push('Continue work on in-progress sorties');
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (blockedSorties && blockedSorties.length > 0) {
|
|
277
|
-
steps.push('Resolve blockers for stuck sorties');
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (checkpoint.active_locks && checkpoint.active_locks.length > 0) {
|
|
281
|
-
steps.push('Verify file lock integrity');
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (checkpoint.pending_messages && checkpoint.pending_messages.length > 0) {
|
|
285
|
-
steps.push('Process pending messages');
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (steps.length === 0) {
|
|
289
|
-
steps.push('Review mission status and continue');
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return steps;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
private extractModifiedFiles(checkpoint: Checkpoint): string[] {
|
|
296
|
-
const files: Set<string> = new Set();
|
|
297
|
-
|
|
298
|
-
checkpoint.sorties?.forEach((sortie: any) => {
|
|
299
|
-
sortie.files?.forEach((file: any) => files.add(file));
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
checkpoint.active_locks?.forEach((lock: any) => {
|
|
303
|
-
files.add(lock.file);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
return Array.from(files);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
formatRecoveryPrompt(result: RestoreResult): string {
|
|
310
|
-
const { recovery_context, restored, errors, warnings } = result;
|
|
311
|
-
|
|
312
|
-
const sections = [
|
|
313
|
-
'# Mission Recovery Context',
|
|
314
|
-
'',
|
|
315
|
-
'## Mission Summary',
|
|
316
|
-
recovery_context.mission_summary,
|
|
317
|
-
'',
|
|
318
|
-
'## Last Action',
|
|
319
|
-
recovery_context.last_action,
|
|
320
|
-
'',
|
|
321
|
-
'## Time Context',
|
|
322
|
-
`- Last activity: ${recovery_context.last_activity_at}`,
|
|
323
|
-
`- Elapsed time: ${Math.round(recovery_context.elapsed_time_ms / 60000)} minutes`,
|
|
324
|
-
'',
|
|
325
|
-
'## Next Steps',
|
|
326
|
-
...recovery_context.next_steps.map((step: any) => `- ${step}`),
|
|
327
|
-
''
|
|
328
|
-
];
|
|
329
|
-
|
|
330
|
-
if (recovery_context.files_modified.length > 0) {
|
|
331
|
-
sections.push(
|
|
332
|
-
'## Files Modified',
|
|
333
|
-
...recovery_context.files_modified.map((file: any) => `- ${file}`),
|
|
334
|
-
''
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (recovery_context.blockers.length > 0) {
|
|
339
|
-
sections.push(
|
|
340
|
-
'## Blockers',
|
|
341
|
-
...recovery_context.blockers.map((blocker: any) => `- ${blocker}`),
|
|
342
|
-
''
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
sections.push(
|
|
347
|
-
'## Restoration Summary',
|
|
348
|
-
`- Sorties restored: ${restored.sorties}`,
|
|
349
|
-
`- Locks restored: ${restored.locks}`,
|
|
350
|
-
`- Messages requeued: ${restored.messages}`,
|
|
351
|
-
''
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
if (warnings.length > 0) {
|
|
355
|
-
sections.push(
|
|
356
|
-
'## Warnings',
|
|
357
|
-
...warnings.map(warning => `- ${warning}`),
|
|
358
|
-
''
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
if (errors.length > 0) {
|
|
363
|
-
sections.push(
|
|
364
|
-
'## Errors',
|
|
365
|
-
...errors.map(error => `- ${error}`),
|
|
366
|
-
''
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
sections.push(
|
|
371
|
-
'---',
|
|
372
|
-
'*This context was automatically generated from checkpoint restoration.*'
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
return sections.join('\n');
|
|
376
|
-
}
|
|
377
|
-
}
|