@martinloop/mcp 0.7.1 → 0.8.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.
Files changed (73) hide show
  1. package/dist/package-version.d.ts +1 -1
  2. package/dist/package-version.js +1 -1
  3. package/dist/vendor/adapters/claude-cli.d.ts +11 -1
  4. package/dist/vendor/adapters/claude-cli.js +285 -71
  5. package/dist/vendor/adapters/cli-bridge.d.ts +12 -3
  6. package/dist/vendor/adapters/cli-bridge.js +129 -149
  7. package/dist/vendor/adapters/codex-cli.d.ts +3 -3
  8. package/dist/vendor/adapters/codex-cli.js +10 -24
  9. package/dist/vendor/adapters/codex-host.js +127 -132
  10. package/dist/vendor/adapters/index.d.ts +2 -1
  11. package/dist/vendor/adapters/index.js +2 -1
  12. package/dist/vendor/adapters/openai-compatible.js +88 -6
  13. package/dist/vendor/adapters/process-supervisor.d.ts +60 -0
  14. package/dist/vendor/adapters/process-supervisor.js +541 -0
  15. package/dist/vendor/adapters/verifier-only.d.ts +10 -1
  16. package/dist/vendor/adapters/verifier-only.js +43 -1
  17. package/dist/vendor/contracts/external-outcome.d.ts +102 -0
  18. package/dist/vendor/contracts/external-outcome.js +226 -0
  19. package/dist/vendor/contracts/index.d.ts +8 -1
  20. package/dist/vendor/contracts/index.js +2 -0
  21. package/dist/vendor/contracts/swarm-hosted.d.ts +204 -0
  22. package/dist/vendor/contracts/swarm-hosted.js +629 -0
  23. package/dist/vendor/contracts/swarm.d.ts +449 -0
  24. package/dist/vendor/contracts/swarm.js +550 -0
  25. package/dist/vendor/core/external-outcome.d.ts +34 -0
  26. package/dist/vendor/core/external-outcome.js +515 -0
  27. package/dist/vendor/core/grounding.js +1 -1
  28. package/dist/vendor/core/index.d.ts +47 -1
  29. package/dist/vendor/core/index.js +53 -3
  30. package/dist/vendor/core/leash.d.ts +1 -0
  31. package/dist/vendor/core/leash.js +13 -0
  32. package/dist/vendor/core/persistence/integrity.d.ts +4 -0
  33. package/dist/vendor/core/persistence/integrity.js +19 -6
  34. package/dist/vendor/core/persistence/swarm-integrity.d.ts +63 -0
  35. package/dist/vendor/core/persistence/swarm-integrity.js +309 -0
  36. package/dist/vendor/core/swarm/candidates.d.ts +78 -0
  37. package/dist/vendor/core/swarm/candidates.js +810 -0
  38. package/dist/vendor/core/swarm/evidence-projection.d.ts +87 -0
  39. package/dist/vendor/core/swarm/evidence-projection.js +128 -0
  40. package/dist/vendor/core/swarm/evidence.d.ts +40 -0
  41. package/dist/vendor/core/swarm/evidence.js +575 -0
  42. package/dist/vendor/core/swarm/hosted-export.d.ts +48 -0
  43. package/dist/vendor/core/swarm/hosted-export.js +362 -0
  44. package/dist/vendor/core/swarm/hosted-projection.d.ts +5 -0
  45. package/dist/vendor/core/swarm/hosted-projection.js +258 -0
  46. package/dist/vendor/core/swarm/index.d.ts +110 -0
  47. package/dist/vendor/core/swarm/index.js +1060 -0
  48. package/dist/vendor/core/swarm/integration.d.ts +76 -0
  49. package/dist/vendor/core/swarm/integration.js +615 -0
  50. package/dist/vendor/core/swarm/lifecycle.d.ts +72 -0
  51. package/dist/vendor/core/swarm/lifecycle.js +252 -0
  52. package/dist/vendor/core/swarm/live-runtime.d.ts +78 -0
  53. package/dist/vendor/core/swarm/live-runtime.js +1482 -0
  54. package/dist/vendor/core/swarm/live-store.d.ts +99 -0
  55. package/dist/vendor/core/swarm/live-store.js +860 -0
  56. package/dist/vendor/core/swarm/operational-reader.d.ts +17 -0
  57. package/dist/vendor/core/swarm/operational-reader.js +96 -0
  58. package/dist/vendor/core/swarm/operations.d.ts +29 -0
  59. package/dist/vendor/core/swarm/operations.js +128 -0
  60. package/dist/vendor/core/swarm/parent-receipt.d.ts +13 -0
  61. package/dist/vendor/core/swarm/parent-receipt.js +489 -0
  62. package/dist/vendor/core/swarm/receipt-projection.d.ts +26 -0
  63. package/dist/vendor/core/swarm/receipt-projection.js +120 -0
  64. package/dist/vendor/core/swarm/scheduler.d.ts +92 -0
  65. package/dist/vendor/core/swarm/scheduler.js +555 -0
  66. package/dist/vendor/core/swarm/verification.d.ts +70 -0
  67. package/dist/vendor/core/swarm/verification.js +295 -0
  68. package/dist/vendor/core/swarm/workspaces.d.ts +77 -0
  69. package/dist/vendor/core/swarm/workspaces.js +488 -0
  70. package/dist/vendor/core/verified-handoff.d.ts +4 -0
  71. package/dist/vendor/core/verified-handoff.js +4 -0
  72. package/package.json +1 -1
  73. package/server.json +2 -2
@@ -0,0 +1,489 @@
1
+ import { createHash } from "node:crypto";
2
+ import { SWARM_PARENT_RECEIPT_SCHEMA_VERSION, SWARM_SCHEMA_VERSION } from "../../contracts/index.js";
3
+ export class SwarmParentReceiptError extends Error {
4
+ code;
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.name = "SwarmParentReceiptError";
8
+ this.code = code;
9
+ }
10
+ }
11
+ export function buildParentSwarmReceipt(sealed) {
12
+ const { plan, events, artifacts } = sealed.model;
13
+ const outcome = sealed.index.outcome;
14
+ if (outcome.state === "running") {
15
+ throw new SwarmParentReceiptError("NON_TERMINAL_SWARM", "A running swarm cannot produce a parent receipt.");
16
+ }
17
+ const terminalOutcome = structuredClone(outcome);
18
+ if (sealed.index.swarmId !== plan.swarmId || sealed.index.planHash !== plan.planHash) {
19
+ throw new SwarmParentReceiptError("SEALED_MODEL_MISMATCH", "The sealed evidence model does not match its index.");
20
+ }
21
+ const blockedActions = projectBlockedActions(events);
22
+ const reassignments = projectReassignments(events);
23
+ const childReceipts = projectChildReceipts(sealed);
24
+ const globalEvent = [...events].reverse().find((event) => (event.type === "GLOBAL_VERIFIER_PASSED" || event.type === "GLOBAL_VERIFIER_FAILED"));
25
+ const integratedTreeHash = optionalText(globalEvent?.payload.integratedTreeHash);
26
+ const globalVerificationId = optionalText(globalEvent?.payload.verificationId);
27
+ const evidenceFiles = sealed.index.files.map((file) => structuredClone(file));
28
+ const evidenceBindings = projectEvidenceBindings(plan, events, sealed.model.artifacts);
29
+ const base = {
30
+ schemaVersion: SWARM_PARENT_RECEIPT_SCHEMA_VERSION,
31
+ swarmId: plan.swarmId,
32
+ planHash: plan.planHash,
33
+ objective: plan.parentContract.objective,
34
+ engine: structuredClone(plan.engine),
35
+ baselineCommit: plan.baselineCommit,
36
+ tasks: projectTerminalTasks(plan.tasks, events),
37
+ agents: projectTerminalAgents(plan.agents, events),
38
+ budget: structuredClone(plan.parentContract.budget),
39
+ budgetLedger: projectBudgetLedger(plan.parentContract.budget, events),
40
+ childReceipts,
41
+ blockedActions,
42
+ reassignments,
43
+ events: events.map((event) => structuredClone(event)),
44
+ evidenceIndexSha256: sha256(sealed.indexBytes),
45
+ evidenceFiles,
46
+ evidenceBindings,
47
+ ...(integratedTreeHash ? { integratedTreeHash } : {}),
48
+ ...(globalVerificationId ? { globalVerificationId } : {}),
49
+ parentOutcome: terminalOutcome,
50
+ taskVerificationState: globalEvent?.type === "GLOBAL_VERIFIER_PASSED"
51
+ ? "passed"
52
+ : globalEvent?.type === "GLOBAL_VERIFIER_FAILED" ? "failed" : "unknown",
53
+ sealedAt: sealed.index.sealedAt
54
+ };
55
+ const receiptSha256 = sha256(stableJson(base));
56
+ return {
57
+ ...base,
58
+ receiptId: `swarm-receipt-${receiptSha256.slice(0, 16)}`,
59
+ receiptSha256
60
+ };
61
+ }
62
+ function projectTerminalTasks(tasks, events) {
63
+ const projected = new Map(tasks.map((task) => [task.taskId, structuredClone(task)]));
64
+ for (const event of [...events].sort(bySequence)) {
65
+ if (!event.taskId)
66
+ continue;
67
+ const task = projected.get(event.taskId);
68
+ if (!task)
69
+ continue;
70
+ if (event.type === "TASK_REASSIGNED") {
71
+ const toAgentId = requiredText(event.payload.toAgentId, "INVALID_REASSIGNMENT_EVENT");
72
+ projected.set(event.taskId, { ...task, assignedAgentId: toAgentId });
73
+ }
74
+ else if (event.type === "CHILD_STARTED") {
75
+ projected.set(event.taskId, { ...task, status: "running" });
76
+ }
77
+ else if (event.type === "CHILD_VERIFIED") {
78
+ projected.set(event.taskId, { ...task, status: "accepted" });
79
+ }
80
+ else if (event.type === "CHILD_STOPPED") {
81
+ projected.set(event.taskId, { ...task, status: "stopped" });
82
+ }
83
+ else if (event.type === "CHILD_NEEDS_REVIEW") {
84
+ projected.set(event.taskId, { ...task, status: "needs_review" });
85
+ }
86
+ }
87
+ return tasks.map((task) => projected.get(task.taskId));
88
+ }
89
+ function projectTerminalAgents(agents, events) {
90
+ const projected = new Map(agents.map((agent) => [agent.agentId, structuredClone(agent)]));
91
+ for (const event of [...events].sort(bySequence)) {
92
+ if (!event.agentId)
93
+ continue;
94
+ const agent = projected.get(event.agentId);
95
+ if (!agent)
96
+ continue;
97
+ if (event.type === "CHILD_STARTED") {
98
+ projected.set(event.agentId, { ...agent, status: "running", ...(event.childRunId ? { childRunId: event.childRunId } : {}) });
99
+ }
100
+ else if (event.type === "CHILD_VERIFIED") {
101
+ projected.set(event.agentId, { ...agent, status: "verified", ...(event.childRunId ? { childRunId: event.childRunId } : {}) });
102
+ }
103
+ else if (event.type === "CHILD_STOPPED") {
104
+ projected.set(event.agentId, { ...agent, status: "stopped", ...(event.childRunId ? { childRunId: event.childRunId } : {}) });
105
+ }
106
+ else if (event.type === "CHILD_NEEDS_REVIEW") {
107
+ projected.set(event.agentId, { ...agent, status: "needs_review", ...(event.childRunId ? { childRunId: event.childRunId } : {}) });
108
+ }
109
+ }
110
+ return agents.map((agent) => projected.get(agent.agentId));
111
+ }
112
+ function projectBudgetLedger(budget, events) {
113
+ const projected = new Map();
114
+ const childLeaseIds = new Map();
115
+ for (const event of [...events].sort(bySequence)) {
116
+ const explicitLeaseId = optionalText(event.payload.leaseId);
117
+ if (explicitLeaseId && event.childRunId)
118
+ childLeaseIds.set(event.childRunId, explicitLeaseId);
119
+ const leaseId = explicitLeaseId ?? (event.childRunId ? childLeaseIds.get(event.childRunId) : undefined);
120
+ if (!leaseId)
121
+ continue;
122
+ const previous = projected.get(leaseId);
123
+ const reservedUsd = event.payload.reservedUsd === undefined
124
+ ? previous?.reservedUsd
125
+ : requiredNumber(event.payload.reservedUsd, "INVALID_CHILD_BUDGET_EVENT");
126
+ const reservedTokens = event.payload.reservedTokens === undefined
127
+ ? previous?.reservedTokens
128
+ : requiredNumber(event.payload.reservedTokens, "INVALID_CHILD_BUDGET_EVENT");
129
+ if (reservedUsd === undefined || reservedTokens === undefined) {
130
+ throw new SwarmParentReceiptError("INVALID_CHILD_BUDGET_EVENT", "Persisted budget evidence omits the reserved lease amount.");
131
+ }
132
+ const statusValue = event.payload.leaseState;
133
+ const status = statusValue === "settled" || statusValue === "overspent" || statusValue === "released" || statusValue === "reserved"
134
+ ? statusValue
135
+ : previous?.status ?? "reserved";
136
+ const actualUsd = optionalNumber(event.payload.actualUsd);
137
+ const actualTokens = optionalNumber(event.payload.actualTokens);
138
+ projected.set(leaseId, {
139
+ leaseId,
140
+ agentId: previous?.agentId ?? requiredText(event.agentId, "INVALID_CHILD_BUDGET_EVENT"),
141
+ taskId: previous?.taskId ?? requiredText(event.taskId, "INVALID_CHILD_BUDGET_EVENT"),
142
+ reservedUsd,
143
+ reservedTokens,
144
+ status,
145
+ ...(actualUsd !== undefined && actualTokens !== undefined
146
+ ? { actualUsage: { usd: actualUsd, tokens: actualTokens } }
147
+ : previous?.actualUsage ? { actualUsage: structuredClone(previous.actualUsage) } : {})
148
+ });
149
+ }
150
+ const leases = [...projected.values()];
151
+ return {
152
+ capUsd: budget.maxUsd,
153
+ ...(budget.maxTokens === undefined ? {} : { capTokens: budget.maxTokens }),
154
+ settledUsd: leases.reduce((sum, lease) => sum + (lease.actualUsage?.usd ?? 0), 0),
155
+ settledTokens: leases.reduce((sum, lease) => sum + (lease.actualUsage?.tokens ?? 0), 0),
156
+ leases
157
+ };
158
+ }
159
+ function projectEvidenceBindings(plan, events, artifacts) {
160
+ const admissions = [];
161
+ const rejections = [];
162
+ const conflicts = [];
163
+ const integration = [];
164
+ const globalVerification = [];
165
+ const cleanup = [];
166
+ for (const artifact of artifacts) {
167
+ const value = artifact.value;
168
+ const nestedDecision = isRecord(value.decision) ? value.decision : undefined;
169
+ const decision = nestedDecision ?? (typeof value.candidateId === "string" ? value : undefined);
170
+ const validDecision = decision ? isCanonicalDecision(decision, plan, events) : false;
171
+ if (validDecision && decision?.state === "admitted")
172
+ admissions.push(artifact.path);
173
+ if (validDecision && decision?.state === "rejected")
174
+ rejections.push(artifact.path);
175
+ if (validDecision && (isRecord(value.conflict) || decision?.reasonCode === "integration_conflict"))
176
+ conflicts.push(artifact.path);
177
+ if ((nestedDecision && decision && validDecision && isCanonicalIntegrationEvent(value.event, decision, plan, events))
178
+ || isCanonicalReconstruction(value, plan))
179
+ integration.push(artifact.path);
180
+ if (isCanonicalGlobalVerification(value, plan, events))
181
+ globalVerification.push(artifact.path);
182
+ if (isCanonicalCleanup(value, plan, artifacts))
183
+ cleanup.push(artifact.path);
184
+ }
185
+ return {
186
+ admissions: uniqueSorted(admissions),
187
+ rejections: uniqueSorted(rejections),
188
+ conflicts: uniqueSorted(conflicts),
189
+ integration: uniqueSorted(integration),
190
+ globalVerification: uniqueSorted(globalVerification),
191
+ cleanup: uniqueSorted(cleanup)
192
+ };
193
+ }
194
+ function isCanonicalDecision(value, plan, events) {
195
+ if (value.schemaVersion !== SWARM_SCHEMA_VERSION
196
+ || value.swarmId !== plan.swarmId
197
+ || value.baselineCommit !== plan.baselineCommit
198
+ || !isNonEmptyText(value.admissionId)
199
+ || typeof value.candidateId !== "string"
200
+ || !isNonEmptyText(value.workspaceId)
201
+ || !isNonEmptyText(value.childRunId)
202
+ || !isNonEmptyText(value.agentId)
203
+ || !isStringArray(value.taskIds, true)
204
+ || !isStringArray(value.changedPaths)
205
+ || !["admitted", "rejected"].includes(String(value.state))
206
+ || !SWARM_ADMISSION_REASON_CODES.has(String(value.reasonCode))
207
+ || (value.state === "admitted" && value.reasonCode !== "admitted")
208
+ || (value.state === "rejected" && value.reasonCode === "admitted")
209
+ || !RECEIPT_INTEGRITY_STATES.has(String(value.receiptIntegrity))
210
+ || (value.state === "admitted" && value.receiptIntegrity !== "verified")
211
+ || !isNonEmptyText(value.decidedAt)
212
+ || !isOptionalText(value.preIntegrationTreeHash)
213
+ || !isOptionalText(value.postIntegrationTreeHash)
214
+ || !isOptionalText(value.diagnostic))
215
+ return false;
216
+ const expectedTypes = value.state === "admitted"
217
+ ? ["CHILD_PATCH_ADMITTED"]
218
+ : value.reasonCode === "integration_conflict" ? ["INTEGRATION_CONFLICT"] : ["CHILD_PATCH_REJECTED"];
219
+ return events.some((event) => (expectedTypes.includes(event.type)
220
+ && event.payload.candidateId === value.candidateId
221
+ && event.swarmId === plan.swarmId
222
+ && event.planHash === plan.planHash
223
+ && event.parentPolicyVersion === plan.parentContract.policyVersion
224
+ && event.taskId !== undefined
225
+ && value.taskIds.includes(event.taskId)
226
+ && event.agentId === value.agentId
227
+ && event.childRunId === value.childRunId
228
+ && Number.isInteger(event.sequence)
229
+ && event.sequence > 0
230
+ && isNonEmptyText(event.idempotencyKey)
231
+ && isNonEmptyText(event.timestamp)));
232
+ }
233
+ function isCanonicalIntegrationEvent(candidateEvent, decision, plan, events) {
234
+ if (!isRecord(candidateEvent) || !isRecord(candidateEvent.payload))
235
+ return false;
236
+ const expectedType = decision.state === "admitted"
237
+ ? "CHILD_PATCH_ADMITTED"
238
+ : decision.reasonCode === "integration_conflict" ? "INTEGRATION_CONFLICT" : "CHILD_PATCH_REJECTED";
239
+ if (candidateEvent.type !== expectedType
240
+ || candidateEvent.swarmId !== plan.swarmId
241
+ || candidateEvent.parentPolicyVersion !== plan.parentContract.policyVersion
242
+ || candidateEvent.payload.candidateId !== decision.candidateId)
243
+ return false;
244
+ return events.some((event) => event.planHash === plan.planHash
245
+ && Number.isInteger(event.sequence)
246
+ && event.sequence > 0
247
+ && isNonEmptyText(event.idempotencyKey)
248
+ && stableJson(projectLiveEvent(event)) === stableJson(candidateEvent));
249
+ }
250
+ function isCanonicalReconstruction(value, plan) {
251
+ return value.schemaVersion === SWARM_SCHEMA_VERSION
252
+ && value.swarmId === plan.swarmId
253
+ && value.baselineCommit === plan.baselineCommit
254
+ && typeof value.reconstructionId === "string"
255
+ && typeof value.failedCandidateId === "string"
256
+ && typeof value.previousWorkspaceId === "string"
257
+ && typeof value.replacementWorkspaceId === "string"
258
+ && ["completed", "failed"].includes(String(value.state))
259
+ && (value.state !== "completed" || value.expectedTreeHash === value.actualTreeHash);
260
+ }
261
+ function isCanonicalGlobalVerification(value, plan, events) {
262
+ if (value.schemaVersion !== SWARM_SCHEMA_VERSION
263
+ || value.swarmId !== plan.swarmId
264
+ || value.parentPolicyVersion !== plan.parentContract.policyVersion
265
+ || value.baselineCommit !== plan.baselineCommit
266
+ || typeof value.verificationId !== "string"
267
+ || !isNonEmptyText(value.workspaceId)
268
+ || typeof value.integratedTreeHash !== "string"
269
+ || !isStringArray(value.commands)
270
+ || stableJson(value.commands) !== stableJson(plan.parentContract.globalVerifierStack.map((step) => step.command))
271
+ || !["pending", "passed", "failed", "unknown"].includes(String(value.commandState))
272
+ || !["pending", "clean", "mutated", "unknown"].includes(String(value.mutationState))
273
+ || !isNonEmptyText(value.startedAt)
274
+ || !isOptionalText(value.completedAt)
275
+ || (["passed", "failed"].includes(String(value.commandState)) && !isNonEmptyText(value.completedAt))
276
+ || !Array.isArray(value.subprocessResults)
277
+ || !value.subprocessResults.every(isCanonicalVerifierSubprocess))
278
+ return false;
279
+ const expectedType = value.commandState === "passed" && value.mutationState === "clean"
280
+ ? "GLOBAL_VERIFIER_PASSED"
281
+ : "GLOBAL_VERIFIER_FAILED";
282
+ const matching = events.filter((event) => ((event.type === "GLOBAL_VERIFIER_PASSED" || event.type === "GLOBAL_VERIFIER_FAILED")
283
+ && event.payload.verificationId === value.verificationId
284
+ && event.payload.integratedTreeHash === value.integratedTreeHash
285
+ && event.swarmId === plan.swarmId
286
+ && event.planHash === plan.planHash
287
+ && event.parentPolicyVersion === plan.parentContract.policyVersion
288
+ && Number.isInteger(event.sequence)
289
+ && event.sequence > 0
290
+ && isNonEmptyText(event.idempotencyKey)
291
+ && isNonEmptyText(event.timestamp)));
292
+ return matching.length === 1 && matching[0]?.type === expectedType;
293
+ }
294
+ function isCanonicalCleanup(value, plan, artifacts) {
295
+ if (value.schemaVersion !== SWARM_SCHEMA_VERSION
296
+ || value.swarmId !== plan.swarmId
297
+ || typeof value.cleanupId !== "string"
298
+ || typeof value.workspaceId !== "string"
299
+ || !["pending", "completed", "failed", "cleanup_pending"].includes(String(value.state))
300
+ || !["child", "integration", "verifier"].includes(String(value.workspaceKind))
301
+ || !["not_required", "pending", "closed", "failed"].includes(String(value.processCloseState))
302
+ || !["pending", "removed", "failed"].includes(String(value.removalState))
303
+ || typeof value.evidencePersisted !== "boolean"
304
+ || !isNonEmptyText(value.attemptedAt)
305
+ || !isOptionalText(value.completedAt)
306
+ || !isOptionalText(value.errorCode)
307
+ || (value.state === "completed" && (!isNonEmptyText(value.completedAt) || value.removalState !== "removed"))
308
+ || (value.state !== "completed" && !isNonEmptyText(value.errorCode)))
309
+ return false;
310
+ return artifacts.some((artifact) => {
311
+ const completionCleanup = artifact.value.cleanup;
312
+ return isRecord(completionCleanup)
313
+ && stableJson(completionCleanup) === stableJson(value);
314
+ });
315
+ }
316
+ function projectLiveEvent(event) {
317
+ return {
318
+ type: event.type,
319
+ swarmId: event.swarmId,
320
+ timestamp: event.timestamp,
321
+ parentPolicyVersion: event.parentPolicyVersion,
322
+ ...(event.taskId ? { taskId: event.taskId } : {}),
323
+ ...(event.agentId ? { agentId: event.agentId } : {}),
324
+ ...(event.childRunId ? { childRunId: event.childRunId } : {}),
325
+ ...(event.failureClass ? { failureClass: event.failureClass } : {}),
326
+ payload: structuredClone(event.payload)
327
+ };
328
+ }
329
+ function isCanonicalVerifierSubprocess(value) {
330
+ return isRecord(value)
331
+ && isNonEmptyText(value.command)
332
+ && typeof value.launched === "boolean"
333
+ && typeof value.completed === "boolean"
334
+ && typeof value.timedOut === "boolean"
335
+ && (value.exitCode === null || (typeof value.exitCode === "number" && Number.isInteger(value.exitCode)))
336
+ && isOptionalText(value.signal)
337
+ && isNonEmptyText(value.startedAt)
338
+ && isOptionalText(value.completedAt);
339
+ }
340
+ function isStringArray(value, requireNonEmpty = false) {
341
+ return Array.isArray(value)
342
+ && (!requireNonEmpty || value.length > 0)
343
+ && value.every((item) => isNonEmptyText(item));
344
+ }
345
+ function isNonEmptyText(value) {
346
+ return typeof value === "string" && value.trim().length > 0;
347
+ }
348
+ function isOptionalText(value) {
349
+ return value === undefined || isNonEmptyText(value);
350
+ }
351
+ const RECEIPT_INTEGRITY_STATES = new Set([
352
+ "verified", "unsigned", "tamper_detected", "relocated", "material_missing", "selector_noncanonical"
353
+ ]);
354
+ const SWARM_ADMISSION_REASON_CODES = new Set([
355
+ "admitted", "child_not_terminal", "process_active", "identity_mismatch", "receipt_not_verified",
356
+ "receipt_scope_mismatch", "stale_baseline", "git_inventory_failed", "invalid_concrete_path", "unsafe_path",
357
+ "undeclared_path", "path_not_allowed", "path_denied", "task_scope_violation", "candidate_changed_during_capture",
358
+ "empty_candidate", "patch_hash_mismatch", "integration_precondition_failed", "integration_conflict",
359
+ "integration_apply_failed", "artifact_persistence_failed"
360
+ ]);
361
+ function requiredNumber(value, code) {
362
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
363
+ throw new SwarmParentReceiptError(code, "Persisted budget evidence is malformed.");
364
+ }
365
+ return value;
366
+ }
367
+ function optionalNumber(value) {
368
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
369
+ }
370
+ function uniqueSorted(values) {
371
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
372
+ }
373
+ export function verifyParentSwarmReceipt(receipt) {
374
+ if (receipt.schemaVersion !== SWARM_PARENT_RECEIPT_SCHEMA_VERSION) {
375
+ return { ok: false, reason: "UNKNOWN_RECEIPT_SCHEMA" };
376
+ }
377
+ try {
378
+ if (stableJson(receipt.blockedActions) !== stableJson(projectBlockedActions(receipt.events))) {
379
+ return { ok: false, reason: "BLOCKED_ACTION_PROJECTION_MISMATCH" };
380
+ }
381
+ if (stableJson(receipt.reassignments) !== stableJson(projectReassignments(receipt.events))) {
382
+ return { ok: false, reason: "REASSIGNMENT_PROJECTION_MISMATCH" };
383
+ }
384
+ }
385
+ catch (error) {
386
+ return { ok: false, reason: error instanceof SwarmParentReceiptError ? error.code : "INVALID_EVENT_PROJECTION" };
387
+ }
388
+ const { receiptId, receiptSha256, ...base } = receipt;
389
+ const expectedHash = sha256(stableJson(base));
390
+ if (receiptSha256 !== expectedHash || receiptId !== `swarm-receipt-${expectedHash.slice(0, 16)}`) {
391
+ return { ok: false, reason: "RECEIPT_HASH_MISMATCH" };
392
+ }
393
+ return { ok: true };
394
+ }
395
+ function projectBlockedActions(events) {
396
+ return events
397
+ .filter((event) => event.type === "ACTION_BLOCKED")
398
+ .map((event) => ({
399
+ eventId: requiredText(event.idempotencyKey, "INVALID_BLOCKED_ACTION_EVENT"),
400
+ sequence: event.sequence,
401
+ agentId: requiredText(event.agentId, "INVALID_BLOCKED_ACTION_EVENT"),
402
+ taskId: requiredText(event.taskId, "INVALID_BLOCKED_ACTION_EVENT"),
403
+ attemptId: requiredText(event.payload.attemptId, "INVALID_BLOCKED_ACTION_EVENT"),
404
+ action: requiredText(event.payload.action, "INVALID_BLOCKED_ACTION_EVENT"),
405
+ reason: requiredText(event.payload.reason, "INVALID_BLOCKED_ACTION_EVENT"),
406
+ timestamp: requiredText(event.timestamp, "INVALID_BLOCKED_ACTION_EVENT")
407
+ }))
408
+ .sort(bySequence);
409
+ }
410
+ function projectReassignments(events) {
411
+ return events
412
+ .filter((event) => event.type === "TASK_REASSIGNED")
413
+ .map((event) => ({
414
+ eventId: requiredText(event.idempotencyKey, "INVALID_REASSIGNMENT_EVENT"),
415
+ sequence: event.sequence,
416
+ taskId: requiredText(event.taskId, "INVALID_REASSIGNMENT_EVENT"),
417
+ fromAgentId: requiredText(event.payload.fromAgentId, "INVALID_REASSIGNMENT_EVENT"),
418
+ toAgentId: requiredText(event.payload.toAgentId, "INVALID_REASSIGNMENT_EVENT"),
419
+ fromAttemptId: requiredText(event.payload.fromAttemptId, "INVALID_REASSIGNMENT_EVENT"),
420
+ toAttemptId: requiredText(event.payload.toAttemptId, "INVALID_REASSIGNMENT_EVENT"),
421
+ reason: requiredText(event.payload.reason, "INVALID_REASSIGNMENT_EVENT"),
422
+ timestamp: requiredText(event.timestamp, "INVALID_REASSIGNMENT_EVENT")
423
+ }))
424
+ .sort(bySequence);
425
+ }
426
+ function projectChildReceipts(sealed) {
427
+ const startedChildRuns = new Set(sealed.model.events
428
+ .filter((event) => event.type === "CHILD_STARTED" && event.childRunId)
429
+ .map((event) => event.childRunId));
430
+ const terminalChildren = sealed.model.events.filter((event) => (event.childRunId
431
+ && startedChildRuns.has(event.childRunId)
432
+ && ["CHILD_VERIFIED", "CHILD_STOPPED", "CHILD_NEEDS_REVIEW"].includes(event.type)));
433
+ return terminalChildren.map((event) => {
434
+ const matches = sealed.model.artifacts.filter((artifact) => (artifact.path.startsWith("evidence/child-completions/") && artifact.value.childRunId === event.childRunId));
435
+ if (matches.length !== 1) {
436
+ throw new SwarmParentReceiptError("MISSING_CHILD_EVIDENCE", "Terminal child evidence must have one completion artifact.");
437
+ }
438
+ const value = matches[0].value;
439
+ const receipt = isRecord(value.receipt) ? value.receipt : undefined;
440
+ const integrity = receipt && isRecord(receipt.integrity) ? receipt.integrity : undefined;
441
+ if (integrity?.state !== "verified") {
442
+ throw new SwarmParentReceiptError("UNVERIFIED_CHILD_EVIDENCE", "Child receipt integrity must be verified.");
443
+ }
444
+ const childRunId = requiredText(value.childRunId, "INVALID_CHILD_EVIDENCE");
445
+ const integrityFile = sealed.index.files.find((file) => file.path === `${childRunId}/receipt-integrity.json`);
446
+ if (!integrityFile) {
447
+ throw new SwarmParentReceiptError("MISSING_CHILD_INTEGRITY_FILE", "Child receipt integrity material is absent from the sealed index.");
448
+ }
449
+ const taskIds = value.taskIds;
450
+ if (!Array.isArray(taskIds) || taskIds.some((taskId) => typeof taskId !== "string" || taskId.length === 0)) {
451
+ throw new SwarmParentReceiptError("INVALID_CHILD_EVIDENCE", "Child completion task IDs are malformed.");
452
+ }
453
+ return {
454
+ childRunId,
455
+ agentId: requiredText(value.agentId, "INVALID_CHILD_EVIDENCE"),
456
+ attemptId: requiredText(value.attemptId, "INVALID_CHILD_EVIDENCE"),
457
+ taskIds: [...taskIds],
458
+ receiptIntegritySha256: integrityFile.sha256
459
+ };
460
+ }).sort((left, right) => left.childRunId.localeCompare(right.childRunId));
461
+ }
462
+ function requiredText(value, code) {
463
+ if (typeof value !== "string" || value.trim().length === 0) {
464
+ throw new SwarmParentReceiptError(code, "Persisted receipt evidence is missing required identity.");
465
+ }
466
+ return value;
467
+ }
468
+ function optionalText(value) {
469
+ return typeof value === "string" && value.length > 0 ? value : undefined;
470
+ }
471
+ function bySequence(left, right) {
472
+ return left.sequence - right.sequence;
473
+ }
474
+ function sha256(value) {
475
+ return createHash("sha256").update(value).digest("hex");
476
+ }
477
+ function stableJson(value) {
478
+ return JSON.stringify(canonicalize(value));
479
+ }
480
+ function canonicalize(value) {
481
+ if (Array.isArray(value))
482
+ return value.map(canonicalize);
483
+ if (!isRecord(value))
484
+ return value;
485
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]));
486
+ }
487
+ function isRecord(value) {
488
+ return typeof value === "object" && value !== null && !Array.isArray(value);
489
+ }
@@ -0,0 +1,26 @@
1
+ import type { SwarmParentReceipt } from "../../contracts/index.js";
2
+ import { type SwarmReceiptIntegritySummary } from "../persistence/swarm-integrity.js";
3
+ interface SwarmReceiptSealCommit {
4
+ schemaVersion: "martin.swarm-receipt-seal.v1";
5
+ swarmId: string;
6
+ planHash: string;
7
+ receiptSha256: string;
8
+ evidenceIndexSha256: string;
9
+ integrityMaterialSha256: string;
10
+ committedAt: string;
11
+ commitHmacSha256: string;
12
+ }
13
+ export interface SwarmReceiptProjection {
14
+ receipt: SwarmParentReceipt;
15
+ integrity: SwarmReceiptIntegritySummary;
16
+ seal: SwarmReceiptSealCommit;
17
+ }
18
+ export declare function readSwarmReceiptProjection(input: {
19
+ runsRoot: string;
20
+ swarmId: string;
21
+ }): Promise<SwarmReceiptProjection>;
22
+ export declare function verifySwarmReceiptProjection(input: {
23
+ runsRoot: string;
24
+ swarmId: string;
25
+ }): Promise<SwarmReceiptIntegritySummary>;
26
+ export {};
@@ -0,0 +1,120 @@
1
+ import { createHash } from "node:crypto";
2
+ import { lstat, readFile, realpath } from "node:fs/promises";
3
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ import { resolveSwarmReceiptIntegrityPath, verifySwarmReceiptSealCommitAuthentication, verifySwarmReceiptIntegrityFromFiles, } from "../persistence/swarm-integrity.js";
5
+ import { readAndSealSwarmEvidence } from "./evidence.js";
6
+ import { buildParentSwarmReceipt } from "./parent-receipt.js";
7
+ import { assertSwarmPathIdentifier } from "./workspaces.js";
8
+ export async function readSwarmReceiptProjection(input) {
9
+ assertSwarmPathIdentifier(input.swarmId, "swarm ID");
10
+ const runsRoot = await realpath(resolve(input.runsRoot));
11
+ const swarmsRoot = await realpath(join(runsRoot, "_swarms"));
12
+ assertContainedOrSame(runsRoot, swarmsRoot, "Swarm projection escaped the runs root.");
13
+ const swarmRoot = await realpath(join(swarmsRoot, input.swarmId));
14
+ assertStrictlyContained(swarmsRoot, swarmRoot, "Swarm projection escaped the swarm root.");
15
+ const evidenceRootExpected = join(swarmRoot, "evidence");
16
+ const evidenceRoot = await resolveExactDirectory(swarmRoot, evidenceRootExpected);
17
+ assertStrictlyContained(swarmRoot, evidenceRoot, "Swarm projection escaped the evidence root.");
18
+ const receiptPath = await resolveExactRegularFile(evidenceRoot, join(evidenceRoot, "parent-receipt.json"));
19
+ const indexPath = await resolveExactRegularFile(evidenceRoot, join(evidenceRoot, "evidence-index.json"));
20
+ const integrityPath = await resolveExactRegularFile(evidenceRoot, resolveSwarmReceiptIntegrityPath(runsRoot, input.swarmId));
21
+ const sealPath = await resolveExactRegularFile(evidenceRoot, join(evidenceRoot, "swarm-receipt-seal.json"));
22
+ for (const path of [receiptPath, indexPath, integrityPath, sealPath]) {
23
+ assertStrictlyContained(evidenceRoot, path, "Swarm projection artifact escaped the evidence root.");
24
+ }
25
+ const [receiptRaw, indexRaw, integrityRaw, sealRaw] = await Promise.all([
26
+ readFile(receiptPath, "utf8"),
27
+ readFile(indexPath, "utf8"),
28
+ readFile(integrityPath, "utf8"),
29
+ readFile(sealPath, "utf8")
30
+ ]);
31
+ const receipt = parseCommittedProjectionJson(receiptRaw, "parent receipt");
32
+ const seal = parseCommittedProjectionJson(sealRaw, "receipt seal");
33
+ if (seal.schemaVersion !== "martin.swarm-receipt-seal.v1"
34
+ || seal.swarmId !== input.swarmId
35
+ || seal.planHash !== receipt.planHash
36
+ || seal.receiptSha256 !== sha256Text(receiptRaw)
37
+ || seal.evidenceIndexSha256 !== sha256Text(indexRaw)
38
+ || seal.integrityMaterialSha256 !== sha256Text(integrityRaw)
39
+ || !await verifySwarmReceiptSealCommitAuthentication({
40
+ runsRoot,
41
+ swarmId: input.swarmId,
42
+ commit: seal
43
+ })) {
44
+ throw codedRuntimeError("SWARM_SEAL_COMMIT_MISMATCH", "Committed swarm receipt seal is missing or tampered.");
45
+ }
46
+ const integrity = await verifySwarmReceiptIntegrityFromFiles({ runsRoot, swarmId: input.swarmId });
47
+ if (integrity.state !== "verified") {
48
+ throw codedRuntimeError("SWARM_SEAL_INTEGRITY_FAILED", "Committed swarm receipt integrity did not verify.");
49
+ }
50
+ const canonical = await readAndSealSwarmEvidence({ rootDir: runsRoot, swarmId: input.swarmId });
51
+ const canonicalReceipt = buildParentSwarmReceipt(canonical);
52
+ if (receiptRaw !== `${JSON.stringify(canonicalReceipt, null, 2)}\n`) {
53
+ throw codedRuntimeError("SWARM_SEAL_COMMIT_MISMATCH", "Committed swarm receipt disagrees with canonical evidence.");
54
+ }
55
+ return { receipt, integrity, seal };
56
+ }
57
+ async function resolveExactDirectory(parent, expected) {
58
+ const metadata = await lstat(expected);
59
+ if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
60
+ throw codedRuntimeError("SWARM_SEAL_PATH_ALIAS", "Swarm seal directory is not an exact canonical directory.");
61
+ }
62
+ const canonical = await realpath(expected);
63
+ assertStrictlyContained(parent, canonical, "Swarm seal directory escaped its parent.");
64
+ if (!sameFilesystemPath(canonical, resolve(expected))) {
65
+ throw codedRuntimeError("SWARM_SEAL_PATH_ALIAS", "Swarm seal directory is aliased.");
66
+ }
67
+ return canonical;
68
+ }
69
+ async function resolveExactRegularFile(parent, expected) {
70
+ const metadata = await lstat(expected);
71
+ if (metadata.isSymbolicLink() || !metadata.isFile()) {
72
+ throw codedRuntimeError("SWARM_SEAL_PATH_ALIAS", "Swarm seal artifact is not an exact regular file.");
73
+ }
74
+ const canonical = await realpath(expected);
75
+ assertStrictlyContained(parent, canonical, "Swarm seal artifact escaped its parent.");
76
+ if (!sameFilesystemPath(canonical, resolve(expected))) {
77
+ throw codedRuntimeError("SWARM_SEAL_PATH_ALIAS", "Swarm seal artifact is aliased.");
78
+ }
79
+ return canonical;
80
+ }
81
+ function sameFilesystemPath(left, right) {
82
+ return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
83
+ }
84
+ export async function verifySwarmReceiptProjection(input) {
85
+ try {
86
+ return (await readSwarmReceiptProjection(input)).integrity;
87
+ }
88
+ catch (error) {
89
+ return {
90
+ state: "tamper_detected",
91
+ reason: error instanceof Error && "code" in error ? String(error.code) : "swarm_seal_unavailable"
92
+ };
93
+ }
94
+ }
95
+ function sha256Text(value) {
96
+ return createHash("sha256").update(value).digest("hex");
97
+ }
98
+ function assertContainedOrSame(parent, candidate, message) {
99
+ const relation = relative(parent, candidate);
100
+ if (relation === "")
101
+ return;
102
+ assertStrictlyContained(parent, candidate, message);
103
+ }
104
+ function assertStrictlyContained(parent, candidate, message) {
105
+ const relation = relative(parent, candidate);
106
+ if (!relation || relation === ".." || relation.startsWith(`..${sep}`) || isAbsolute(relation)) {
107
+ throw codedRuntimeError("LIVE_SWARM_PATH_ESCAPE", message);
108
+ }
109
+ }
110
+ function codedRuntimeError(code, message) {
111
+ return Object.assign(new Error(message), { code });
112
+ }
113
+ function parseCommittedProjectionJson(source, label) {
114
+ try {
115
+ return JSON.parse(source);
116
+ }
117
+ catch {
118
+ throw codedRuntimeError("MALFORMED_JSON", `Committed swarm ${label} is malformed.`);
119
+ }
120
+ }