@martinloop/mcp 0.7.1 → 0.8.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.
Files changed (77) 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 +16 -1
  4. package/dist/vendor/adapters/claude-cli.js +286 -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-capabilities.d.ts +4 -1
  8. package/dist/vendor/adapters/codex-capabilities.js +36 -4
  9. package/dist/vendor/adapters/codex-cli.d.ts +10 -3
  10. package/dist/vendor/adapters/codex-cli.js +21 -24
  11. package/dist/vendor/adapters/codex-host.js +150 -132
  12. package/dist/vendor/adapters/index.d.ts +3 -2
  13. package/dist/vendor/adapters/index.js +3 -2
  14. package/dist/vendor/adapters/openai-compatible.js +88 -6
  15. package/dist/vendor/adapters/process-supervisor.d.ts +60 -0
  16. package/dist/vendor/adapters/process-supervisor.js +541 -0
  17. package/dist/vendor/adapters/verifier-only.d.ts +10 -1
  18. package/dist/vendor/adapters/verifier-only.js +43 -1
  19. package/dist/vendor/contracts/external-outcome.d.ts +102 -0
  20. package/dist/vendor/contracts/external-outcome.js +226 -0
  21. package/dist/vendor/contracts/index.d.ts +12 -1
  22. package/dist/vendor/contracts/index.js +2 -0
  23. package/dist/vendor/contracts/swarm-hosted.d.ts +204 -0
  24. package/dist/vendor/contracts/swarm-hosted.js +629 -0
  25. package/dist/vendor/contracts/swarm.d.ts +449 -0
  26. package/dist/vendor/contracts/swarm.js +550 -0
  27. package/dist/vendor/core/external-outcome.d.ts +34 -0
  28. package/dist/vendor/core/external-outcome.js +515 -0
  29. package/dist/vendor/core/grounding.js +1 -1
  30. package/dist/vendor/core/index.d.ts +52 -1
  31. package/dist/vendor/core/index.js +55 -3
  32. package/dist/vendor/core/leash.d.ts +1 -0
  33. package/dist/vendor/core/leash.js +13 -0
  34. package/dist/vendor/core/persistence/integrity.d.ts +4 -0
  35. package/dist/vendor/core/persistence/integrity.js +19 -6
  36. package/dist/vendor/core/persistence/swarm-integrity.d.ts +63 -0
  37. package/dist/vendor/core/persistence/swarm-integrity.js +309 -0
  38. package/dist/vendor/core/policy.d.ts +3 -0
  39. package/dist/vendor/core/policy.js +13 -0
  40. package/dist/vendor/core/swarm/candidates.d.ts +78 -0
  41. package/dist/vendor/core/swarm/candidates.js +810 -0
  42. package/dist/vendor/core/swarm/evidence-projection.d.ts +87 -0
  43. package/dist/vendor/core/swarm/evidence-projection.js +128 -0
  44. package/dist/vendor/core/swarm/evidence.d.ts +40 -0
  45. package/dist/vendor/core/swarm/evidence.js +575 -0
  46. package/dist/vendor/core/swarm/hosted-export.d.ts +48 -0
  47. package/dist/vendor/core/swarm/hosted-export.js +362 -0
  48. package/dist/vendor/core/swarm/hosted-projection.d.ts +5 -0
  49. package/dist/vendor/core/swarm/hosted-projection.js +258 -0
  50. package/dist/vendor/core/swarm/index.d.ts +110 -0
  51. package/dist/vendor/core/swarm/index.js +1060 -0
  52. package/dist/vendor/core/swarm/integration.d.ts +76 -0
  53. package/dist/vendor/core/swarm/integration.js +615 -0
  54. package/dist/vendor/core/swarm/lifecycle.d.ts +72 -0
  55. package/dist/vendor/core/swarm/lifecycle.js +252 -0
  56. package/dist/vendor/core/swarm/live-runtime.d.ts +78 -0
  57. package/dist/vendor/core/swarm/live-runtime.js +1482 -0
  58. package/dist/vendor/core/swarm/live-store.d.ts +99 -0
  59. package/dist/vendor/core/swarm/live-store.js +860 -0
  60. package/dist/vendor/core/swarm/operational-reader.d.ts +17 -0
  61. package/dist/vendor/core/swarm/operational-reader.js +96 -0
  62. package/dist/vendor/core/swarm/operations.d.ts +29 -0
  63. package/dist/vendor/core/swarm/operations.js +128 -0
  64. package/dist/vendor/core/swarm/parent-receipt.d.ts +13 -0
  65. package/dist/vendor/core/swarm/parent-receipt.js +489 -0
  66. package/dist/vendor/core/swarm/receipt-projection.d.ts +26 -0
  67. package/dist/vendor/core/swarm/receipt-projection.js +120 -0
  68. package/dist/vendor/core/swarm/scheduler.d.ts +92 -0
  69. package/dist/vendor/core/swarm/scheduler.js +555 -0
  70. package/dist/vendor/core/swarm/verification.d.ts +70 -0
  71. package/dist/vendor/core/swarm/verification.js +295 -0
  72. package/dist/vendor/core/swarm/workspaces.d.ts +77 -0
  73. package/dist/vendor/core/swarm/workspaces.js +488 -0
  74. package/dist/vendor/core/verified-handoff.d.ts +4 -0
  75. package/dist/vendor/core/verified-handoff.js +4 -0
  76. package/package.json +1 -1
  77. package/server.json +2 -2
@@ -16,6 +16,7 @@ import { verifierActuallyPassed, } from "./verified-handoff.js";
16
16
  export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
17
17
  export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary };
18
18
  export { buildContextGraphSnapshot, queryContextGraph } from "./context-graph.js";
19
+ export { ExternalOutcomeContractError, ExternalOutcomePolicyError, hashExternalOutcomeContract, verifyExternalOutcomes, writeExternalOutcomeEvidence, } from "./external-outcome.js";
19
20
  export { createLocalIdentityAuthority, hasIdentityScope, issueIdentityToken, verifyIdentityToken } from "./identity.js";
20
21
  export { compileExecutionPolicy } from "./policy-compiler.js";
21
22
  export { assessTrajectory, decideCircuitBreak } from "./trajectory.js";
@@ -401,6 +402,34 @@ export async function runMartin(input) {
401
402
  }
402
403
  }
403
404
  }
405
+ let parentAbortObserved = false;
406
+ function observeParentAbort() {
407
+ if (parentAbortObserved || input.signal === undefined)
408
+ return;
409
+ parentAbortObserved = true;
410
+ const reason = input.signal.reason instanceof Error
411
+ ? input.signal.reason.message
412
+ : typeof input.signal.reason === "string"
413
+ ? input.signal.reason
414
+ : "parent cancelled governed child run";
415
+ const parentSignal = {
416
+ kind: "human_interrupt",
417
+ schemaVersion: "exit-signal/1",
418
+ runId: loop.loopId,
419
+ reason: `Parent cancellation: ${reason}`,
420
+ requestedAt: now(),
421
+ requestedBy: "parent_swarm"
422
+ };
423
+ observeSignals([parentSignal]);
424
+ activeAttemptController?.abort(input.signal.reason ?? parentSignal);
425
+ }
426
+ if (input.signal?.aborted)
427
+ observeParentAbort();
428
+ else if (input.signal) {
429
+ input.signal.addEventListener("abort", observeParentAbort, { once: true });
430
+ if (input.signal.aborted)
431
+ observeParentAbort();
432
+ }
404
433
  function queueControlDiagnostic(code, details) {
405
434
  if (!input.store)
406
435
  return;
@@ -443,6 +472,7 @@ export async function runMartin(input) {
443
472
  // snapshot. Draining alone leaves a window for new writes during signing.
444
473
  disposeSignalMonitor();
445
474
  runController.abort();
475
+ input.signal?.removeEventListener("abort", observeParentAbort);
446
476
  await Promise.all(pendingControlWrites.splice(0));
447
477
  }
448
478
  // Snapshot of the current run state for eight-exit evaluation
@@ -570,6 +600,8 @@ export async function runMartin(input) {
570
600
  promptCharCount: distilled.focus.length + loop.task.objective.length * 3,
571
601
  attemptCount: loop.attempts.length,
572
602
  remainingBudgetUsd: distilled.constraints.remainingBudgetUsd,
603
+ remainingTokens: distilled.constraints.remainingTokens,
604
+ minimumViableTokens: executingAdapter.metadata.budgetPreflight?.minimumViableTokens,
573
605
  perAttemptCapUsd: loop.budget.maxUsd * 0.25
574
606
  });
575
607
  if (!budgetPreflight.allowed) {
@@ -753,6 +785,7 @@ export async function runMartin(input) {
753
785
  ? { definitionOfDonePreSatisfied: loop.task.definitionOfDonePreSatisfied }
754
786
  : {}),
755
787
  ...(loop.task.repoRoot ? { repoRoot: loop.task.repoRoot } : {}),
788
+ ...(input.store?.runsRoot ? { runsRoot: input.store.runsRoot } : {}),
756
789
  ...(loop.task.allowedPaths ? { allowedPaths: loop.task.allowedPaths } : {}),
757
790
  ...(loop.task.deniedPaths ? { deniedPaths: loop.task.deniedPaths } : {}),
758
791
  ...(loop.task.acceptanceCriteria ? { acceptanceCriteria: loop.task.acceptanceCriteria } : {}),
@@ -766,9 +799,13 @@ export async function runMartin(input) {
766
799
  focus: distilled.focus,
767
800
  remainingBudgetUsd: distilled.constraints.remainingBudgetUsd,
768
801
  remainingIterations: distilled.constraints.remainingIterations,
769
- remainingTokens: distilled.constraints.remainingTokens
802
+ remainingTokens: distilled.constraints.remainingTokens,
803
+ ...(input.verificationExecutionOwner
804
+ ? { verificationExecutionOwner: input.verificationExecutionOwner }
805
+ : {})
770
806
  },
771
- previousAttempts: loop.attempts
807
+ previousAttempts: loop.attempts,
808
+ ...(input.observedUsageGovernor ? { observedUsageGovernor: input.observedUsageGovernor } : {})
772
809
  };
773
810
  const tracksWorkspaceMutations = request.context.repoRoot !== undefined &&
774
811
  executingAdapter.metadata.capabilities?.workspaceMutations !== false;
@@ -996,7 +1033,8 @@ export async function runMartin(input) {
996
1033
  repoRoot: request.context.repoRoot,
997
1034
  changedFiles,
998
1035
  allowedPaths: request.context.allowedPaths,
999
- deniedPaths: request.context.deniedPaths
1036
+ deniedPaths: request.context.deniedPaths,
1037
+ mutationMode: request.context.mutationMode
1000
1038
  });
1001
1039
  if (!filesystemDecision.allowed) {
1002
1040
  const patchDecision = evaluatePatchDecision({
@@ -1412,6 +1450,7 @@ export async function runMartin(input) {
1412
1450
  clearTimeout(wallTimer);
1413
1451
  disposeSignalMonitor();
1414
1452
  runController.abort();
1453
+ input.signal?.removeEventListener("abort", observeParentAbort);
1415
1454
  activeAttemptController?.abort();
1416
1455
  // Flush any diagnostic ledger writes queued from signal callbacks
1417
1456
  await Promise.all(pendingControlWrites);
@@ -1581,6 +1620,9 @@ function resolveChangedFiles(result, repoRoot, rollbackBoundary) {
1581
1620
  return listAttemptChangedFilesSinceBoundary({ repoRoot, boundary: rollbackBoundary });
1582
1621
  }
1583
1622
  function buildPatchDiff(result, changedFiles) {
1623
+ if (result.execution?.patch) {
1624
+ return result.execution.patch;
1625
+ }
1584
1626
  // Use structured diff stats to build a minimal diff header if no raw diff is available
1585
1627
  if (result.execution?.changedFiles?.length) {
1586
1628
  // Build a synthetic diff header from changed file list
@@ -1663,7 +1705,11 @@ function resolveExpectedVerifierBinding(request) {
1663
1705
  return {
1664
1706
  runId: request.loopId,
1665
1707
  workspaceId: request.workspaceId,
1708
+ attemptId: request.attemptId,
1666
1709
  cwd: request.context.repoRoot ?? process.cwd(),
1710
+ ...(request.context.runsRoot ? { runsRoot: request.context.runsRoot } : {}),
1711
+ ...(request.context.executionProfile ? { executionProfile: request.context.executionProfile } : {}),
1712
+ ...(request.context.allowedNetworkDomains?.length ? { allowedNetworkDomains: [...request.context.allowedNetworkDomains] } : {}),
1667
1713
  commands,
1668
1714
  };
1669
1715
  }
@@ -1806,3 +1852,9 @@ export { createDefaultExitPolicy, evaluateExitPolicy, hashProgressState, toLegac
1806
1852
  export { SignalDiagnosticError, createFileExitSignalSource, exitSignalPath, readAllExitSignals, readExitSignal, startExitSignalMonitor, writeExitSignal } from "./exit-signal.js";
1807
1853
  // ─── R4 Delivery ─────────────────────────────────────────────────────────────
1808
1854
  export { cacheMessage, fetchSelectedMessage, getCliInstalledVersion, getMcpInstalledVersion, isCooldownExpired, isDismissed, isNewerVersion, loadDeliveryRecord, parseMessageSelectionResponse, recordDismissed, recordShown, resolveDefaultLedgerPath, saveDeliveryRecord, } from "./delivery/index.js";
1855
+ export { completeDeterministicDemoSwarmRun, createSwarmBudgetLedger, extendSwarmBudgetLease, releaseSwarmBudgetLease, reserveSwarmBudgetLease, selectNextSwarmBatch, settleSwarmBudgetLease, tasksHaveScopeCollision, validateSwarmTaskGraph, runParentSwarmPipeline } from "./swarm/index.js";
1856
+ export { readSwarmOperationalState } from "./swarm/operational-reader.js";
1857
+ export { readSwarmReceiptProjection, verifySwarmReceiptProjection, } from "./swarm/receipt-projection.js";
1858
+ export { buildSwarmShareProjection, readSwarmDossier, verifySwarmEvidence, } from "./swarm/evidence-projection.js";
1859
+ export { buildSwarmHostedEnvelope } from "./swarm/hosted-export.js";
1860
+ export { assertSwarmPathIdentifier } from "./swarm/workspaces.js";
@@ -32,6 +32,7 @@ export declare function evaluateFilesystemLeash(input: {
32
32
  changedFiles: string[];
33
33
  allowedPaths?: string[];
34
34
  deniedPaths?: string[];
35
+ mutationMode?: "edit" | "read_only" | "verify_only";
35
36
  }): SafetyLeashDecision;
36
37
  export declare function evaluateSecretLeash(input: {
37
38
  values: string[];
@@ -52,6 +52,11 @@ function commandContainsDestructiveRemoval(command) {
52
52
  return false;
53
53
  }
54
54
  const SECRET_PATTERNS = [
55
+ {
56
+ kind: "secret_value",
57
+ pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/giu,
58
+ replacement: "Bearer [REDACTED_SECRET]"
59
+ },
55
60
  {
56
61
  kind: "secret_value",
57
62
  pattern: /\bOPENAI_API_KEY\s*=\s*[^\s"'`]+/giu,
@@ -196,6 +201,14 @@ export function evaluateFilesystemLeash(input) {
196
201
  if (!file) {
197
202
  continue;
198
203
  }
204
+ if (input.mutationMode === "read_only") {
205
+ violations.push({
206
+ kind: "path_not_allowed",
207
+ file,
208
+ message: `Read-only execution changed a file: ${file}`
209
+ });
210
+ continue;
211
+ }
199
212
  if ((input.deniedPaths ?? []).some((pattern) => matchesPathPattern(file, pattern))) {
200
213
  violations.push({
201
214
  kind: "path_denied",
@@ -37,3 +37,7 @@ export declare function verifyReceiptIntegrityFromFiles(input: {
37
37
  ledgerPath: string;
38
38
  }): Promise<ReceiptIntegritySummary>;
39
39
  export declare function resolveReceiptIntegrityPath(runsRoot: string, runId: string): string;
40
+ export declare function ensurePrivateIntegrityKey(runsRoot: string, keyFileName: string): Promise<[string, string] | undefined>;
41
+ export declare function readPrivateIntegrityKey(runsRoot: string, keyFileName: string): Promise<string>;
42
+ export declare function resolvePrivateIntegrityKeyPath(runsRoot: string, keyFileName: string): string;
43
+ export declare function createPrivateIntegrityHmac(key: string, material: unknown): string;
@@ -1,5 +1,5 @@
1
1
  import { createHash, createHmac, randomBytes } from "node:crypto";
2
- import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { lstat, mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
5
  const RECEIPT_INTEGRITY_SCHEMA_VERSION = "martin.receipt-integrity.v1";
@@ -208,10 +208,17 @@ function buildReceiptIntegrityChain(entries) {
208
208
  });
209
209
  }
210
210
  function createReceiptIntegritySignature(key, material) {
211
- return createHmac("sha256", key).update(JSON.stringify(material)).digest("hex");
211
+ return createPrivateIntegrityHmac(key, material);
212
212
  }
213
213
  async function ensureReceiptIntegrityKey(runsRoot, runId) {
214
- const keyPath = resolveReceiptIntegrityKeyPath(runsRoot, runId);
214
+ return ensurePrivateIntegrityKey(runsRoot, `${runId}.key`);
215
+ }
216
+ export async function ensurePrivateIntegrityKey(runsRoot, keyFileName) {
217
+ const keyPath = resolvePrivateIntegrityKeyPath(runsRoot, keyFileName);
218
+ const existingStat = await lstat(keyPath).catch(() => undefined);
219
+ if (existingStat?.isSymbolicLink() || (existingStat && !existingStat.isFile())) {
220
+ return undefined;
221
+ }
215
222
  const existing = await readFile(keyPath, "utf8").catch(() => null);
216
223
  if (existing) {
217
224
  const trimmed = existing.trim();
@@ -231,12 +238,18 @@ async function ensureReceiptIntegrityKey(runsRoot, runId) {
231
238
  return [generated, sha256(generated).slice(0, 16)];
232
239
  }
233
240
  async function readReceiptIntegrityKey(runsRoot, runId) {
234
- const raw = await readFile(resolveReceiptIntegrityKeyPath(runsRoot, runId), "utf8");
241
+ return readPrivateIntegrityKey(runsRoot, `${runId}.key`);
242
+ }
243
+ export async function readPrivateIntegrityKey(runsRoot, keyFileName) {
244
+ const raw = await readFile(resolvePrivateIntegrityKeyPath(runsRoot, keyFileName), "utf8");
235
245
  return raw.trim();
236
246
  }
237
- function resolveReceiptIntegrityKeyPath(runsRoot, runId) {
247
+ export function resolvePrivateIntegrityKeyPath(runsRoot, keyFileName) {
238
248
  const rootHash = sha256(runsRoot).slice(0, 16);
239
- return join(resolveReceiptIntegrityRoot(), rootHash, `${runId}.key`);
249
+ return join(resolveReceiptIntegrityRoot(), rootHash, keyFileName);
250
+ }
251
+ export function createPrivateIntegrityHmac(key, material) {
252
+ return createHmac("sha256", key).update(JSON.stringify(material)).digest("hex");
240
253
  }
241
254
  function serializeStoredJson(value) {
242
255
  return `${JSON.stringify(value, null, 2)}\n`;
@@ -0,0 +1,63 @@
1
+ declare const SWARM_RECEIPT_INTEGRITY_SCHEMA_VERSION: "martin.swarm-receipt-integrity.v1";
2
+ export interface SwarmReceiptIntegrityArtifact {
3
+ path: string;
4
+ sha256: string;
5
+ bytes: number;
6
+ }
7
+ export interface StoredSwarmReceiptIntegrityMaterial {
8
+ schemaVersion: typeof SWARM_RECEIPT_INTEGRITY_SCHEMA_VERSION;
9
+ swarmId: string;
10
+ planHash: string;
11
+ receiptSchemaVersion: string;
12
+ receiptSha256: string;
13
+ evidenceIndexSha256: string;
14
+ eventChainHead: string;
15
+ rootBindingSha256: string;
16
+ keyId: string;
17
+ signedAt: string;
18
+ taskVerificationState: "passed" | "failed" | "unknown";
19
+ artifacts: SwarmReceiptIntegrityArtifact[];
20
+ signatureHmacSha256: string;
21
+ }
22
+ export interface SwarmReceiptIntegritySummary {
23
+ state: "verified" | "material_missing" | "tamper_detected";
24
+ taskVerificationState?: "passed" | "failed" | "unknown";
25
+ keyId?: string;
26
+ signedAt?: string;
27
+ receiptSha256?: string;
28
+ evidenceIndexSha256?: string;
29
+ eventChainHead?: string;
30
+ reason?: string;
31
+ }
32
+ export interface SwarmReceiptSealCommitBase {
33
+ schemaVersion: "martin.swarm-receipt-seal.v1";
34
+ swarmId: string;
35
+ planHash: string;
36
+ receiptSha256: string;
37
+ evidenceIndexSha256: string;
38
+ integrityMaterialSha256: string;
39
+ committedAt: string;
40
+ }
41
+ export declare function authenticateSwarmReceiptSealCommit(input: {
42
+ runsRoot: string;
43
+ swarmId: string;
44
+ commit: SwarmReceiptSealCommitBase;
45
+ }): Promise<string>;
46
+ export declare function verifySwarmReceiptSealCommitAuthentication(input: {
47
+ runsRoot: string;
48
+ swarmId: string;
49
+ commit: SwarmReceiptSealCommitBase & {
50
+ commitHmacSha256?: string;
51
+ };
52
+ }): Promise<boolean>;
53
+ export declare function writeSwarmReceiptIntegrityMaterial(input: {
54
+ runsRoot: string;
55
+ swarmId: string;
56
+ signedAt?: string;
57
+ }): Promise<StoredSwarmReceiptIntegrityMaterial | undefined>;
58
+ export declare function verifySwarmReceiptIntegrityFromFiles(input: {
59
+ runsRoot: string;
60
+ swarmId: string;
61
+ }): Promise<SwarmReceiptIntegritySummary>;
62
+ export declare function resolveSwarmReceiptIntegrityPath(runsRoot: string, swarmId: string): string;
63
+ export {};
@@ -0,0 +1,309 @@
1
+ import { createHash, timingSafeEqual } from "node:crypto";
2
+ import { lstat, open, readFile, realpath } from "node:fs/promises";
3
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ import { verifyParentSwarmReceipt } from "../swarm/parent-receipt.js";
5
+ import { assertSwarmPathIdentifier } from "../swarm/workspaces.js";
6
+ import { createPrivateIntegrityHmac, ensurePrivateIntegrityKey, readPrivateIntegrityKey } from "./integrity.js";
7
+ const SWARM_RECEIPT_INTEGRITY_SCHEMA_VERSION = "martin.swarm-receipt-integrity.v1";
8
+ export async function authenticateSwarmReceiptSealCommit(input) {
9
+ const runsRoot = await realpath(resolve(input.runsRoot));
10
+ const key = await readPrivateIntegrityKey(runsRoot, keyFileName(input.swarmId));
11
+ if (!key)
12
+ throw new Error("SWARM_SEAL_KEY_UNAVAILABLE");
13
+ return createPrivateIntegrityHmac(key, sealAuthenticationBase(runsRoot, input.commit));
14
+ }
15
+ export async function verifySwarmReceiptSealCommitAuthentication(input) {
16
+ const runsRoot = await realpath(resolve(input.runsRoot));
17
+ const key = await readPrivateIntegrityKey(runsRoot, keyFileName(input.swarmId));
18
+ if (!key || typeof input.commit.commitHmacSha256 !== "string")
19
+ return false;
20
+ const { commitHmacSha256, ...commit } = input.commit;
21
+ const expected = createPrivateIntegrityHmac(key, sealAuthenticationBase(runsRoot, commit));
22
+ return constantTimeHexEqual(commitHmacSha256, expected);
23
+ }
24
+ export async function writeSwarmReceiptIntegrityMaterial(input) {
25
+ const bundle = await resolveCanonicalSwarmIntegrityBundle(input.runsRoot, input.swarmId);
26
+ const receipt = parseParentReceipt(bundle.receiptRaw);
27
+ const evidenceIndex = parseEvidenceIndex(bundle.indexRaw);
28
+ validateArtifactIdentities(input.swarmId, receipt, evidenceIndex, bundle.indexRaw, bundle.eventsRaw);
29
+ const keyMaterial = await ensurePrivateIntegrityKey(bundle.runsRoot, keyFileName(input.swarmId));
30
+ if (!keyMaterial)
31
+ return undefined;
32
+ const [key, keyId] = keyMaterial;
33
+ const artifacts = canonicalArtifactManifest(bundle);
34
+ const base = {
35
+ schemaVersion: SWARM_RECEIPT_INTEGRITY_SCHEMA_VERSION,
36
+ swarmId: input.swarmId,
37
+ planHash: receipt.planHash,
38
+ receiptSchemaVersion: receipt.schemaVersion,
39
+ receiptSha256: receipt.receiptSha256,
40
+ evidenceIndexSha256: sha256(bundle.indexRaw),
41
+ eventChainHead: sha256(bundle.eventsRaw),
42
+ rootBindingSha256: sha256(bundle.runsRoot),
43
+ keyId,
44
+ signedAt: input.signedAt ?? new Date().toISOString(),
45
+ taskVerificationState: receipt.taskVerificationState,
46
+ artifacts
47
+ };
48
+ const material = {
49
+ ...base,
50
+ signatureHmacSha256: createPrivateIntegrityHmac(key, base)
51
+ };
52
+ await writeExactOnce(bundle.integrityPath, serializeJson(material));
53
+ return material;
54
+ }
55
+ export async function verifySwarmReceiptIntegrityFromFiles(input) {
56
+ let bundle;
57
+ try {
58
+ bundle = await resolveCanonicalSwarmIntegrityBundle(input.runsRoot, input.swarmId);
59
+ }
60
+ catch {
61
+ return { state: "material_missing", reason: "canonical_swarm_bundle_missing" };
62
+ }
63
+ const [materialRaw, key] = await Promise.all([
64
+ readFile(bundle.integrityPath, "utf8").catch(() => null),
65
+ readPrivateIntegrityKey(bundle.runsRoot, keyFileName(input.swarmId)).catch(() => null)
66
+ ]);
67
+ if (!materialRaw || !key) {
68
+ return { state: "material_missing", reason: "swarm_receipt_integrity_material_incomplete" };
69
+ }
70
+ let material;
71
+ let receipt;
72
+ let evidenceIndex;
73
+ try {
74
+ material = JSON.parse(materialRaw);
75
+ receipt = parseParentReceipt(bundle.receiptRaw);
76
+ evidenceIndex = parseEvidenceIndex(bundle.indexRaw);
77
+ validateArtifactIdentities(input.swarmId, receipt, evidenceIndex, bundle.indexRaw, bundle.eventsRaw);
78
+ }
79
+ catch {
80
+ return { state: "tamper_detected", reason: "malformed_integrity_or_artifact_material" };
81
+ }
82
+ const actualArtifacts = canonicalArtifactManifest(bundle);
83
+ if (!validMaterialShape(material) || !sameManifest(material.artifacts, actualArtifacts)) {
84
+ return tamper(material, "artifact_manifest_mismatch");
85
+ }
86
+ if (material.rootBindingSha256 !== sha256(bundle.runsRoot)) {
87
+ return tamper(material, "root_binding_mismatch");
88
+ }
89
+ if (material.swarmId !== input.swarmId
90
+ || material.planHash !== receipt.planHash
91
+ || material.receiptSchemaVersion !== receipt.schemaVersion
92
+ || material.receiptSha256 !== receipt.receiptSha256
93
+ || material.evidenceIndexSha256 !== sha256(bundle.indexRaw)
94
+ || material.eventChainHead !== sha256(bundle.eventsRaw)
95
+ || material.taskVerificationState !== receipt.taskVerificationState) {
96
+ return tamper(material, "canonical_binding_mismatch");
97
+ }
98
+ const { signatureHmacSha256, ...base } = material;
99
+ const expected = createPrivateIntegrityHmac(key, base);
100
+ if (!constantTimeHexEqual(signatureHmacSha256, expected)) {
101
+ return tamper(material, "signature_mismatch");
102
+ }
103
+ return {
104
+ state: "verified",
105
+ taskVerificationState: receipt.taskVerificationState,
106
+ keyId: material.keyId,
107
+ signedAt: material.signedAt,
108
+ receiptSha256: material.receiptSha256,
109
+ evidenceIndexSha256: material.evidenceIndexSha256,
110
+ eventChainHead: material.eventChainHead
111
+ };
112
+ }
113
+ export function resolveSwarmReceiptIntegrityPath(runsRoot, swarmId) {
114
+ assertSwarmPathIdentifier(swarmId, "swarm ID");
115
+ return join(runsRoot, "_swarms", swarmId, "evidence", "swarm-receipt-integrity.json");
116
+ }
117
+ async function resolveCanonicalSwarmIntegrityBundle(runsRootInput, swarmId) {
118
+ assertSwarmPathIdentifier(swarmId, "swarm ID");
119
+ const runsRoot = await realpath(resolve(runsRootInput));
120
+ const swarmsRoot = await resolveExactDirectory(runsRoot, join(runsRoot, "_swarms"));
121
+ assertContained(runsRoot, swarmsRoot);
122
+ const swarmRoot = await resolveExactDirectory(swarmsRoot, join(swarmsRoot, swarmId));
123
+ assertContained(swarmsRoot, swarmRoot);
124
+ const evidenceRoot = await resolveExactDirectory(swarmRoot, join(swarmRoot, "evidence"));
125
+ assertContained(swarmRoot, evidenceRoot);
126
+ const receiptPath = await resolveExactRegularFile(evidenceRoot, join(evidenceRoot, "parent-receipt.json"));
127
+ const indexPath = await resolveExactRegularFile(evidenceRoot, join(evidenceRoot, "evidence-index.json"));
128
+ const eventsPath = await resolveExactRegularFile(swarmRoot, join(swarmRoot, "events.jsonl"));
129
+ assertContained(evidenceRoot, receiptPath);
130
+ assertContained(evidenceRoot, indexPath);
131
+ assertContained(swarmRoot, eventsPath);
132
+ const [receiptRaw, indexRaw, eventsRaw] = await Promise.all([
133
+ readFile(receiptPath, "utf8"),
134
+ readFile(indexPath, "utf8"),
135
+ readFile(eventsPath, "utf8")
136
+ ]);
137
+ return {
138
+ runsRoot,
139
+ swarmRoot,
140
+ evidenceRoot,
141
+ integrityPath: join(evidenceRoot, "swarm-receipt-integrity.json"),
142
+ receiptRaw,
143
+ indexRaw,
144
+ eventsRaw
145
+ };
146
+ }
147
+ function canonicalArtifactManifest(bundle) {
148
+ return [
149
+ { path: "evidence/evidence-index.json", sha256: sha256(bundle.indexRaw), bytes: Buffer.byteLength(bundle.indexRaw) },
150
+ { path: "evidence/parent-receipt.json", sha256: sha256(bundle.receiptRaw), bytes: Buffer.byteLength(bundle.receiptRaw) }
151
+ ];
152
+ }
153
+ function parseParentReceipt(raw) {
154
+ const receipt = JSON.parse(raw);
155
+ if (verifyParentSwarmReceipt(receipt).ok !== true) {
156
+ throw new Error("INVALID_PARENT_SWARM_RECEIPT");
157
+ }
158
+ return receipt;
159
+ }
160
+ function parseEvidenceIndex(raw) {
161
+ const value = JSON.parse(raw);
162
+ if (!isRecord(value) || value.schemaVersion !== "martin.swarm-evidence-index.v1") {
163
+ throw new Error("INVALID_SWARM_EVIDENCE_INDEX");
164
+ }
165
+ return value;
166
+ }
167
+ function validateArtifactIdentities(swarmId, receipt, evidenceIndex, indexRaw, eventsRaw) {
168
+ if (receipt.swarmId !== swarmId
169
+ || evidenceIndex.swarmId !== swarmId
170
+ || evidenceIndex.planHash !== receipt.planHash) {
171
+ throw new Error("SWARM_ARTIFACT_IDENTITY_MISMATCH");
172
+ }
173
+ if (receipt.evidenceIndexSha256 !== sha256(indexRaw)
174
+ || stableJson(receipt.evidenceFiles) !== stableJson(evidenceIndex.files)
175
+ || `${receipt.events.map((event) => JSON.stringify(event)).join("\n")}\n` !== eventsRaw) {
176
+ throw new Error("SWARM_ARTIFACT_RELATION_MISMATCH");
177
+ }
178
+ }
179
+ async function resolveExactDirectory(parent, expected) {
180
+ const metadata = await lstat(expected);
181
+ if (metadata.isSymbolicLink() || !metadata.isDirectory())
182
+ throw new Error("SWARM_INTEGRITY_PATH_ALIAS");
183
+ const canonical = await realpath(expected);
184
+ assertContained(parent, canonical);
185
+ if (!sameFilesystemPath(canonical, resolve(expected)))
186
+ throw new Error("SWARM_INTEGRITY_PATH_ALIAS");
187
+ return canonical;
188
+ }
189
+ async function resolveExactRegularFile(parent, expected) {
190
+ const metadata = await lstat(expected);
191
+ if (metadata.isSymbolicLink() || !metadata.isFile())
192
+ throw new Error("SWARM_INTEGRITY_PATH_ALIAS");
193
+ const canonical = await realpath(expected);
194
+ assertContained(parent, canonical);
195
+ if (!sameFilesystemPath(canonical, resolve(expected)))
196
+ throw new Error("SWARM_INTEGRITY_PATH_ALIAS");
197
+ return canonical;
198
+ }
199
+ function sealAuthenticationBase(runsRoot, commit) {
200
+ return {
201
+ purpose: "martin.swarm-receipt-seal-commit.v1",
202
+ rootBindingSha256: sha256(runsRoot),
203
+ ...commit
204
+ };
205
+ }
206
+ function sameFilesystemPath(left, right) {
207
+ return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
208
+ }
209
+ function stableJson(value) {
210
+ return JSON.stringify(canonicalize(value));
211
+ }
212
+ function canonicalize(value) {
213
+ if (Array.isArray(value))
214
+ return value.map(canonicalize);
215
+ if (!isRecord(value))
216
+ return value;
217
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]));
218
+ }
219
+ function validMaterialShape(value) {
220
+ return value.schemaVersion === SWARM_RECEIPT_INTEGRITY_SCHEMA_VERSION
221
+ && typeof value.signatureHmacSha256 === "string"
222
+ && typeof value.keyId === "string"
223
+ && typeof value.signedAt === "string"
224
+ && Array.isArray(value.artifacts);
225
+ }
226
+ function sameManifest(stored, actual) {
227
+ if (stored.length !== actual.length)
228
+ return false;
229
+ const seen = new Set();
230
+ return stored.every((entry, index) => {
231
+ if (entry.path.includes("..") || entry.path.includes("\\") || seen.has(entry.path))
232
+ return false;
233
+ seen.add(entry.path);
234
+ const expected = actual[index];
235
+ return expected !== undefined
236
+ && entry.path === expected.path
237
+ && entry.sha256 === expected.sha256
238
+ && entry.bytes === expected.bytes;
239
+ });
240
+ }
241
+ function tamper(material, reason) {
242
+ return {
243
+ state: "tamper_detected",
244
+ ...(typeof material.taskVerificationState === "string"
245
+ ? { taskVerificationState: material.taskVerificationState }
246
+ : {}),
247
+ ...(typeof material.keyId === "string" ? { keyId: material.keyId } : {}),
248
+ ...(typeof material.signedAt === "string" ? { signedAt: material.signedAt } : {}),
249
+ reason
250
+ };
251
+ }
252
+ function constantTimeHexEqual(left, right) {
253
+ if (!/^[a-f0-9]{64}$/u.test(left) || !/^[a-f0-9]{64}$/u.test(right))
254
+ return false;
255
+ const leftBytes = Buffer.from(left, "hex");
256
+ const rightBytes = Buffer.from(right, "hex");
257
+ return leftBytes.byteLength === rightBytes.byteLength && timingSafeEqual(leftBytes, rightBytes);
258
+ }
259
+ function keyFileName(swarmId) {
260
+ return `swarm-${swarmId}.key`;
261
+ }
262
+ function assertContained(parent, child) {
263
+ const value = relative(parent, child);
264
+ if (value === "" || (!value.startsWith(`..${sep}`) && value !== ".." && !isAbsolute(value)))
265
+ return;
266
+ throw new Error("SWARM_INTEGRITY_PATH_ESCAPE");
267
+ }
268
+ function serializeJson(value) {
269
+ return `${JSON.stringify(value, null, 2)}\n`;
270
+ }
271
+ async function writeExactOnce(path, contents) {
272
+ const existing = await lstat(path).catch((error) => {
273
+ if (isNodeError(error, "ENOENT"))
274
+ return undefined;
275
+ throw error;
276
+ });
277
+ if (existing?.isSymbolicLink() || (existing && !existing.isFile()))
278
+ throw new Error("UNSAFE_SWARM_INTEGRITY_PATH");
279
+ if (existing) {
280
+ if (await readFile(path, "utf8") === contents)
281
+ return;
282
+ throw new Error("SWARM_INTEGRITY_IDENTITY_CONFLICT");
283
+ }
284
+ let handle;
285
+ try {
286
+ handle = await open(path, "wx", 0o600);
287
+ await handle.writeFile(contents, "utf8");
288
+ await handle.sync();
289
+ }
290
+ catch (error) {
291
+ if (!isNodeError(error, "EEXIST"))
292
+ throw error;
293
+ if (await readFile(path, "utf8") === contents)
294
+ return;
295
+ throw new Error("SWARM_INTEGRITY_IDENTITY_CONFLICT");
296
+ }
297
+ finally {
298
+ await handle?.close();
299
+ }
300
+ }
301
+ function isNodeError(error, code) {
302
+ return error instanceof Error && "code" in error && error.code === code;
303
+ }
304
+ function sha256(value) {
305
+ return createHash("sha256").update(value).digest("hex");
306
+ }
307
+ function isRecord(value) {
308
+ return typeof value === "object" && value !== null && !Array.isArray(value);
309
+ }
@@ -103,6 +103,9 @@ export interface BudgetPreflightInput {
103
103
  promptCharCount: number;
104
104
  attemptCount: number;
105
105
  remainingBudgetUsd: number;
106
+ remainingTokens?: number;
107
+ /** Adapter-declared estimated floor required to admit a credible first turn. */
108
+ minimumViableTokens?: number;
106
109
  perAttemptCapUsd?: number;
107
110
  pricePerMTokenUsd?: number;
108
111
  }
@@ -365,6 +365,8 @@ export function evaluateBudgetPreflight(input) {
365
365
  const estimatedPromptTokens = Math.ceil(rawPromptTokens * 1.2);
366
366
  const estimatedToolOverheadTokens = 800 + input.attemptCount * 200;
367
367
  const estimatedOutputTokensMax = 4_000;
368
+ const estimatedMinimumViableTokens = Math.max(0, input.minimumViableTokens ?? 0);
369
+ const estimatedTotalTokens = Math.max(estimatedPromptTokens + estimatedToolOverheadTokens + estimatedOutputTokensMax, estimatedMinimumViableTokens);
368
370
  const estimatedVerifierCostUsd = 0.01;
369
371
  const estimatedAttemptCostUsd = roundUsd(((estimatedPromptTokens + estimatedToolOverheadTokens + estimatedOutputTokensMax) /
370
372
  1_000_000) *
@@ -374,10 +376,21 @@ export function evaluateBudgetPreflight(input) {
374
376
  estimatedPromptTokens,
375
377
  estimatedToolOverheadTokens,
376
378
  estimatedOutputTokensMax,
379
+ estimatedMinimumViableTokens,
380
+ estimatedTotalTokens,
377
381
  estimatedVerifierCostUsd,
378
382
  estimatedAttemptCostUsd,
379
383
  provenance
380
384
  };
385
+ if (input.remainingTokens !== undefined
386
+ && estimatedMinimumViableTokens > 0
387
+ && estimatedTotalTokens > input.remainingTokens) {
388
+ return {
389
+ allowed: false,
390
+ reason: `Preflight: ${String(estimatedTotalTokens)} estimated tokens exceed ${String(input.remainingTokens)} remaining tokens.`,
391
+ estimate
392
+ };
393
+ }
381
394
  if (estimatedAttemptCostUsd > input.remainingBudgetUsd) {
382
395
  return {
383
396
  allowed: false,