@martinloop/mcp 0.7.0 → 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,515 @@
1
+ import { createHash } from "node:crypto";
2
+ import { lookup } from "node:dns/promises";
3
+ import { mkdir, rename, writeFile } from "node:fs/promises";
4
+ import { request as httpRequest } from "node:http";
5
+ import { request as httpsRequest } from "node:https";
6
+ import { isIP } from "node:net";
7
+ import { join, relative, resolve } from "node:path";
8
+ import { performance } from "node:perf_hooks";
9
+ import { EXTERNAL_OUTCOME_LIMITS, EXTERNAL_OUTCOME_RESULT_SCHEMA_VERSION, externalOutcomeValuesEqual, resolveExternalOutcomeJsonPointer, validateExternalOutcomeContract, } from "../contracts/index.js";
10
+ export class ExternalOutcomeContractError extends Error {
11
+ errors;
12
+ constructor(errors) {
13
+ super(errors.map((item) => `${item.path || "<root>"}: ${item.message}`).join("; "));
14
+ this.name = "ExternalOutcomeContractError";
15
+ this.errors = errors;
16
+ }
17
+ }
18
+ export class ExternalOutcomePolicyError extends Error {
19
+ code;
20
+ constructor(code, message) {
21
+ super(message);
22
+ this.name = "ExternalOutcomePolicyError";
23
+ this.code = code;
24
+ }
25
+ }
26
+ export function hashExternalOutcomeContract(contract) {
27
+ return createHash("sha256").update(canonicalJson(contract)).digest("hex");
28
+ }
29
+ export async function verifyExternalOutcomes(contract, options = {}) {
30
+ const errors = validateExternalOutcomeContract(contract);
31
+ if (errors.length > 0)
32
+ throw new ExternalOutcomeContractError(errors);
33
+ const env = options.env ?? process.env;
34
+ const now = options.now ?? (() => new Date());
35
+ const startedWall = now();
36
+ const startedMono = performance.now();
37
+ const deadlineMono = startedMono + contract.deadlineMs;
38
+ const binding = readBinding(env);
39
+ const states = contract.actions.map((action) => createPollState(action, startedMono));
40
+ while (states.some((state) => state.result === undefined) && performance.now() < deadlineMono) {
41
+ if (options.signal?.aborted) {
42
+ for (const state of states) {
43
+ if (!state.result)
44
+ state.result = finishPollState(state, "unknown", "cancelled", []);
45
+ }
46
+ break;
47
+ }
48
+ const current = performance.now();
49
+ const due = states
50
+ .filter((state) => state.result === undefined && state.nextPollAt <= current)
51
+ .slice(0, EXTERNAL_OUTCOME_LIMITS.maxConcurrency);
52
+ if (due.length === 0) {
53
+ const nextAt = Math.min(deadlineMono, ...states.filter((state) => state.result === undefined).map((state) => state.nextPollAt));
54
+ await wait(Math.max(0, nextAt - performance.now()), options.signal);
55
+ continue;
56
+ }
57
+ await Promise.all(due.map(async (state) => {
58
+ const remaining = Math.max(1, deadlineMono - performance.now());
59
+ const observation = await observe(state.action, contract.allowedOrigins, {
60
+ env,
61
+ allowLocal: options.allowLocal === true,
62
+ signal: options.signal,
63
+ timeoutMs: Math.min(contract.requestTimeoutMs, remaining),
64
+ now,
65
+ });
66
+ state.observationCount += 1;
67
+ state.firstObservedAt ??= observation.observedAt;
68
+ state.lastObservedAt = observation.observedAt;
69
+ if (observation.kind === "terminal_unknown") {
70
+ state.result = finishPollState(state, "unknown", observation.reasonCode, []);
71
+ return;
72
+ }
73
+ if (observation.kind === "transient") {
74
+ state.lastTransient = observation.reasonCode;
75
+ state.nextPollAt = performance.now() + contract.pollIntervalMs;
76
+ return;
77
+ }
78
+ if (observation.kind === "missing") {
79
+ state.lastPending = { state: "pending", reasonCode: "record_missing", assertions: [] };
80
+ state.nextPollAt = performance.now() + contract.pollIntervalMs;
81
+ return;
82
+ }
83
+ const evaluated = evaluatePayload(state.action, observation.payload);
84
+ if (evaluated.state === "passed") {
85
+ state.result = finishPollState(state, "passed", "passed", evaluated.assertions);
86
+ }
87
+ else if (evaluated.state === "failed") {
88
+ state.result = finishPollState(state, "failed", evaluated.reasonCode, evaluated.assertions);
89
+ }
90
+ else if (evaluated.state === "unknown") {
91
+ state.result = finishPollState(state, "unknown", evaluated.reasonCode, evaluated.assertions);
92
+ }
93
+ else {
94
+ state.lastPending = evaluated;
95
+ state.nextPollAt = performance.now() + contract.pollIntervalMs;
96
+ }
97
+ }));
98
+ }
99
+ for (const state of states) {
100
+ if (state.result)
101
+ continue;
102
+ if (options.signal?.aborted) {
103
+ state.result = finishPollState(state, "unknown", "cancelled", []);
104
+ }
105
+ else if (state.lastPending) {
106
+ state.result = finishPollState(state, "failed", state.lastPending.reasonCode, state.lastPending.assertions);
107
+ }
108
+ else {
109
+ state.result = finishPollState(state, "unknown", state.observationCount === 0 ? "deadline_exceeded" : state.lastTransient, []);
110
+ }
111
+ }
112
+ const results = states.map((state) => state.result);
113
+ const claimed = results.filter((item) => item.claimedDone);
114
+ return {
115
+ schemaVersion: EXTERNAL_OUTCOME_RESULT_SCHEMA_VERSION,
116
+ contractId: contract.contractId,
117
+ contractSha256: hashExternalOutcomeContract(contract),
118
+ startedAt: startedWall.toISOString(),
119
+ completedAt: now().toISOString(),
120
+ ...(binding ? { binding } : {}),
121
+ aggregate: buildAggregate(claimed),
122
+ actions: results,
123
+ };
124
+ }
125
+ function buildAggregate(claimed) {
126
+ const claimedDone = claimed.length;
127
+ const passed = claimed.filter((item) => item.status === "passed").length;
128
+ const failed = claimed.filter((item) => item.status === "failed").length;
129
+ const unknown = claimed.filter((item) => item.status === "unknown").length;
130
+ const checked = claimed.filter((item) => item.observationCount > 0).length;
131
+ return {
132
+ claimedDone,
133
+ checked,
134
+ passed,
135
+ failed,
136
+ unknown,
137
+ rejectedClaimRate: claimedDone > 0 ? failed / claimedDone : 0,
138
+ unknownClaimRate: claimedDone > 0 ? unknown / claimedDone : 0,
139
+ coverage: claimedDone > 0 ? checked / claimedDone : 0,
140
+ };
141
+ }
142
+ export async function writeExternalOutcomeEvidence(result, options) {
143
+ const runsRoot = resolve(options.runsRoot);
144
+ const safeContractId = result.contractId.replace(/[^A-Za-z0-9._-]/gu, "_");
145
+ const stamp = result.startedAt.replaceAll(/[^0-9]/gu, "").slice(0, 17);
146
+ const fileName = `${safeContractId}-${result.contractSha256.slice(0, 16)}-${stamp}.json`;
147
+ const directory = options.runId
148
+ ? join(runsRoot, options.runId, "external-outcomes")
149
+ : join(runsRoot, "external-outcomes", "standalone");
150
+ await mkdir(directory, { recursive: true, mode: 0o700 });
151
+ const path = join(directory, fileName);
152
+ const bytes = `${JSON.stringify(result, null, 2)}\n`;
153
+ const temp = `${path}.tmp-${process.pid}-${Date.now()}`;
154
+ await writeFile(temp, bytes, { encoding: "utf8", flag: "wx", mode: 0o600 });
155
+ await rename(temp, path);
156
+ const sha256 = createHash("sha256").update(bytes).digest("hex");
157
+ const relativePath = options.runId
158
+ ? relative(join(runsRoot, options.runId), path).replaceAll("\\", "/")
159
+ : relative(runsRoot, path).replaceAll("\\", "/");
160
+ return {
161
+ path,
162
+ relativePath,
163
+ sha256,
164
+ reference: {
165
+ kind: "external_outcome",
166
+ contractId: result.contractId,
167
+ sha256,
168
+ path: relativePath,
169
+ },
170
+ };
171
+ }
172
+ function createPollState(action, startedMono) {
173
+ const parsed = new URL(action.source.url);
174
+ return {
175
+ action,
176
+ source: {
177
+ origin: parsed.origin,
178
+ },
179
+ startedMono,
180
+ nextPollAt: startedMono,
181
+ observationCount: 0,
182
+ lastTransient: "source_unavailable",
183
+ };
184
+ }
185
+ function finishPollState(state, status, reasonCode, assertions) {
186
+ return {
187
+ actionId: state.action.actionId,
188
+ claimedDone: state.action.claimedDone,
189
+ status,
190
+ reasonCode,
191
+ observationCount: state.observationCount,
192
+ ...(state.firstObservedAt ? { firstObservedAt: state.firstObservedAt } : {}),
193
+ ...(state.lastObservedAt ? { lastObservedAt: state.lastObservedAt } : {}),
194
+ elapsedMs: Math.max(0, Math.round(performance.now() - state.startedMono)),
195
+ source: state.source,
196
+ assertions,
197
+ };
198
+ }
199
+ async function observe(action, allowedOrigins, options) {
200
+ const observedAt = options.now().toISOString();
201
+ if (action.source.authEnv && !options.env[action.source.authEnv]) {
202
+ return { kind: "terminal_unknown", reasonCode: "auth_denied", observedAt };
203
+ }
204
+ let response;
205
+ try {
206
+ response = await readJsonOverPinnedGet(action.source.url, {
207
+ allowedOrigins,
208
+ authToken: action.source.authEnv ? options.env[action.source.authEnv] : undefined,
209
+ allowLocal: options.allowLocal,
210
+ signal: options.signal,
211
+ timeoutMs: options.timeoutMs,
212
+ });
213
+ }
214
+ catch (error) {
215
+ if (error instanceof ExternalOutcomePolicyError)
216
+ throw error;
217
+ const code = error instanceof Error ? error.message : String(error);
218
+ if (code === "response_too_large")
219
+ return { kind: "terminal_unknown", reasonCode: "response_too_large", observedAt };
220
+ if (options.signal?.aborted)
221
+ return { kind: "terminal_unknown", reasonCode: "cancelled", observedAt };
222
+ return { kind: "transient", reasonCode: code === "deadline_exceeded" ? "deadline_exceeded" : "source_unavailable", observedAt };
223
+ }
224
+ if (response.statusCode === 401 || response.statusCode === 403) {
225
+ return { kind: "terminal_unknown", reasonCode: "auth_denied", observedAt };
226
+ }
227
+ if (response.statusCode >= 300 && response.statusCode < 400) {
228
+ return { kind: "terminal_unknown", reasonCode: "redirect_blocked", observedAt };
229
+ }
230
+ if (response.statusCode === 404) {
231
+ return { kind: "missing", observedAt };
232
+ }
233
+ if (response.statusCode === 408 || response.statusCode === 425 || response.statusCode === 429 || response.statusCode >= 500) {
234
+ return { kind: "transient", reasonCode: "source_unavailable", observedAt };
235
+ }
236
+ if (response.statusCode < 200 || response.statusCode >= 300) {
237
+ return { kind: "terminal_unknown", reasonCode: "invalid_response", observedAt };
238
+ }
239
+ try {
240
+ return { kind: "payload", payload: JSON.parse(response.body), observedAt };
241
+ }
242
+ catch {
243
+ return { kind: "terminal_unknown", reasonCode: "invalid_response", observedAt };
244
+ }
245
+ }
246
+ function evaluatePayload(action, payload) {
247
+ const recordsRef = resolveExternalOutcomeJsonPointer(payload, action.recordPointer);
248
+ if (!recordsRef.found) {
249
+ return { state: "unknown", reasonCode: "invalid_response", assertions: [] };
250
+ }
251
+ const records = Array.isArray(recordsRef.value)
252
+ ? recordsRef.value
253
+ : (recordsRef.value && typeof recordsRef.value === "object" && action.expectedCount === 1)
254
+ ? [recordsRef.value]
255
+ : null;
256
+ if (!records) {
257
+ return { state: "unknown", reasonCode: "invalid_response", assertions: [] };
258
+ }
259
+ const identityObservations = records.map((record) => ({
260
+ record,
261
+ identity: resolveExternalOutcomeJsonPointer(record, action.identity.pointer),
262
+ }));
263
+ if (records.length > 0 && identityObservations.every((item) => !item.identity.found)) {
264
+ return { state: "unknown", reasonCode: "invalid_response", assertions: [] };
265
+ }
266
+ const matches = identityObservations
267
+ .filter((item) => item.identity.found && externalOutcomeValuesEqual(item.identity.value, action.identity.equals))
268
+ .map((item) => item.record);
269
+ if (matches.length === 0) {
270
+ return {
271
+ state: "pending",
272
+ reasonCode: records.length === 0 ? "record_missing" : "wrong_identity",
273
+ assertions: [],
274
+ };
275
+ }
276
+ if (matches.length > action.expectedCount) {
277
+ return { state: "failed", reasonCode: "duplicate_record", assertions: [] };
278
+ }
279
+ if (matches.length < action.expectedCount) {
280
+ return { state: "pending", reasonCode: "record_missing", assertions: [] };
281
+ }
282
+ const assertionResults = [];
283
+ for (const assertion of action.assertions) {
284
+ let passed = true;
285
+ let missing = false;
286
+ for (const record of matches) {
287
+ const observed = resolveExternalOutcomeJsonPointer(record, assertion.pointer);
288
+ if (!observed.found) {
289
+ missing = true;
290
+ passed = false;
291
+ break;
292
+ }
293
+ if (!externalOutcomeValuesEqual(observed.value, assertion.equals)) {
294
+ passed = false;
295
+ break;
296
+ }
297
+ }
298
+ assertionResults.push({
299
+ pointer: assertion.pointer,
300
+ passed,
301
+ reasonCode: passed ? "passed" : missing ? "missing_pointer" : "value_mismatch",
302
+ });
303
+ }
304
+ if (assertionResults.some((item) => item.reasonCode === "missing_pointer")) {
305
+ return { state: "unknown", reasonCode: "invalid_response", assertions: assertionResults };
306
+ }
307
+ if (assertionResults.some((item) => !item.passed)) {
308
+ return { state: "failed", reasonCode: "value_mismatch", assertions: assertionResults };
309
+ }
310
+ if (action.freshness) {
311
+ for (const record of matches) {
312
+ const observed = resolveExternalOutcomeJsonPointer(record, action.freshness.pointer);
313
+ if (!observed.found) {
314
+ return {
315
+ state: "unknown",
316
+ reasonCode: "invalid_response",
317
+ assertions: [...assertionResults, { pointer: action.freshness.pointer, passed: false, reasonCode: "missing_pointer" }],
318
+ };
319
+ }
320
+ if (Object.prototype.hasOwnProperty.call(action.freshness, "notEquals")
321
+ && externalOutcomeValuesEqual(observed.value, action.freshness.notEquals)) {
322
+ return {
323
+ state: "failed",
324
+ reasonCode: "stale_record",
325
+ assertions: [...assertionResults, { pointer: action.freshness.pointer, passed: false, reasonCode: "stale_record" }],
326
+ };
327
+ }
328
+ if (action.freshness.notBefore) {
329
+ if (typeof observed.value !== "string") {
330
+ return { state: "unknown", reasonCode: "invalid_response", assertions: assertionResults };
331
+ }
332
+ const observedTime = Date.parse(observed.value);
333
+ if (!Number.isFinite(observedTime) || observedTime < Date.parse(action.freshness.notBefore)) {
334
+ return { state: "failed", reasonCode: "stale_record", assertions: assertionResults };
335
+ }
336
+ }
337
+ }
338
+ }
339
+ return { state: "passed", assertions: assertionResults };
340
+ }
341
+ async function readJsonOverPinnedGet(rawUrl, options) {
342
+ const url = new URL(rawUrl);
343
+ const normalizedAllowedOrigins = options.allowedOrigins.map((origin) => new URL(origin).origin);
344
+ if (!normalizedAllowedOrigins.includes(url.origin)) {
345
+ throw new ExternalOutcomePolicyError("origin_not_allowed", "External outcome source origin is not allowlisted.");
346
+ }
347
+ if (url.username || url.password) {
348
+ throw new ExternalOutcomePolicyError("url_credentials_forbidden", "External outcome URLs cannot contain credentials.");
349
+ }
350
+ if (url.protocol !== "https:" && !(options.allowLocal && url.protocol === "http:")) {
351
+ throw new ExternalOutcomePolicyError("https_required", "External outcome read-back requires HTTPS unless local/staging access is explicitly enabled.");
352
+ }
353
+ const pinned = await resolvePinnedAddress(url.hostname, options.allowLocal);
354
+ const transport = url.protocol === "https:" ? httpsRequest : httpRequest;
355
+ const headers = {
356
+ Accept: "application/json",
357
+ "Cache-Control": "no-cache",
358
+ "User-Agent": "MartinLoop-ExternalOutcome/1",
359
+ };
360
+ if (options.authToken)
361
+ headers.Authorization = `Bearer ${options.authToken}`;
362
+ return await new Promise((resolvePromise, rejectPromise) => {
363
+ let settled = false;
364
+ const settleReject = (error) => {
365
+ if (settled)
366
+ return;
367
+ settled = true;
368
+ cleanup();
369
+ rejectPromise(error);
370
+ };
371
+ const settleResolve = (value) => {
372
+ if (settled)
373
+ return;
374
+ settled = true;
375
+ cleanup();
376
+ resolvePromise(value);
377
+ };
378
+ const onAbort = () => req.destroy(new Error("cancelled"));
379
+ const requestOptions = {
380
+ protocol: url.protocol,
381
+ hostname: url.hostname,
382
+ port: url.port || undefined,
383
+ path: `${url.pathname}${url.search}`,
384
+ method: "GET",
385
+ headers,
386
+ timeout: Math.max(1, Math.floor(options.timeoutMs)),
387
+ agent: false,
388
+ lookup: (_hostname, _lookupOptions, callback) => {
389
+ callback(null, pinned.address, pinned.family);
390
+ },
391
+ };
392
+ const req = transport(requestOptions, (res) => {
393
+ const chunks = [];
394
+ let bytes = 0;
395
+ res.on("data", (chunk) => {
396
+ bytes += chunk.length;
397
+ if (bytes > EXTERNAL_OUTCOME_LIMITS.maxResponseBytes) {
398
+ req.destroy(new Error("response_too_large"));
399
+ return;
400
+ }
401
+ chunks.push(Buffer.from(chunk));
402
+ });
403
+ res.on("end", () => settleResolve({
404
+ statusCode: res.statusCode ?? 0,
405
+ body: Buffer.concat(chunks).toString("utf8"),
406
+ }));
407
+ res.on("error", settleReject);
408
+ });
409
+ const cleanup = () => options.signal?.removeEventListener("abort", onAbort);
410
+ req.once("timeout", () => req.destroy(new Error("deadline_exceeded")));
411
+ req.once("error", settleReject);
412
+ if (options.signal) {
413
+ options.signal.addEventListener("abort", onAbort, { once: true });
414
+ if (options.signal.aborted)
415
+ onAbort();
416
+ }
417
+ req.end();
418
+ });
419
+ }
420
+ async function resolvePinnedAddress(hostname, allowLocal) {
421
+ const normalizedHostname = hostname.startsWith("[") && hostname.endsWith("]")
422
+ ? hostname.slice(1, -1)
423
+ : hostname;
424
+ const literalFamily = isIP(normalizedHostname);
425
+ const addresses = literalFamily
426
+ ? [{ address: normalizedHostname, family: literalFamily }]
427
+ : await lookup(normalizedHostname, { all: true, verbatim: true });
428
+ if (addresses.length === 0)
429
+ throw new ExternalOutcomePolicyError("dns_resolution_failed", "External outcome source did not resolve.");
430
+ if (!allowLocal && addresses.some(({ address }) => isPrivateOrSpecialAddress(address))) {
431
+ throw new ExternalOutcomePolicyError("private_target_blocked", "External outcome source resolves to a private, local, metadata, or reserved address.");
432
+ }
433
+ const selected = addresses[0];
434
+ return { address: selected.address, family: selected.family };
435
+ }
436
+ function isPrivateOrSpecialAddress(address) {
437
+ if (isIP(address) === 4) {
438
+ const octets = address.split(".").map(Number);
439
+ const [a = 0, b = 0, c = 0] = octets;
440
+ return a === 0
441
+ || a === 10
442
+ || a === 127
443
+ || (a === 100 && b >= 64 && b <= 127)
444
+ || (a === 169 && b === 254)
445
+ || (a === 172 && b >= 16 && b <= 31)
446
+ || (a === 192 && b === 0 && (c === 0 || c === 2))
447
+ || (a === 192 && b === 168)
448
+ || (a === 198 && (b === 18 || b === 19))
449
+ || (a === 198 && b === 51 && c === 100)
450
+ || (a === 203 && b === 0 && c === 113)
451
+ || a >= 224;
452
+ }
453
+ if (isIP(address) === 6) {
454
+ const normalized = address.toLowerCase();
455
+ const mappedDotted = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/u.exec(normalized)?.[1];
456
+ if (mappedDotted && isIP(mappedDotted) === 4) {
457
+ return isPrivateOrSpecialAddress(mappedDotted);
458
+ }
459
+ const mappedHex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/u.exec(normalized);
460
+ if (mappedHex?.[1] && mappedHex[2]) {
461
+ const high = Number.parseInt(mappedHex[1], 16);
462
+ const low = Number.parseInt(mappedHex[2], 16);
463
+ const mappedIpv4 = [
464
+ (high >> 8) & 0xff,
465
+ high & 0xff,
466
+ (low >> 8) & 0xff,
467
+ low & 0xff,
468
+ ].join(".");
469
+ return isPrivateOrSpecialAddress(mappedIpv4);
470
+ }
471
+ return normalized === "::"
472
+ || normalized === "::1"
473
+ || normalized.startsWith("fc")
474
+ || normalized.startsWith("fd")
475
+ || /^fe[89ab]/u.test(normalized)
476
+ || normalized.startsWith("ff")
477
+ || normalized.startsWith("2001:db8:");
478
+ }
479
+ return true;
480
+ }
481
+ async function wait(durationMs, signal) {
482
+ if (durationMs <= 0 || signal?.aborted)
483
+ return;
484
+ await new Promise((resolvePromise) => {
485
+ const timer = setTimeout(done, durationMs);
486
+ const onAbort = () => done();
487
+ function done() {
488
+ clearTimeout(timer);
489
+ signal?.removeEventListener("abort", onAbort);
490
+ resolvePromise();
491
+ }
492
+ signal?.addEventListener("abort", onAbort, { once: true });
493
+ });
494
+ }
495
+ function readBinding(env) {
496
+ const runId = env.MARTIN_RUN_ID?.trim();
497
+ const attemptId = env.MARTIN_ATTEMPT_ID?.trim();
498
+ const workspaceId = env.MARTIN_WORKSPACE_ID?.trim();
499
+ if (!runId && !attemptId && !workspaceId)
500
+ return undefined;
501
+ return {
502
+ ...(runId ? { runId } : {}),
503
+ ...(attemptId ? { attemptId } : {}),
504
+ ...(workspaceId ? { workspaceId } : {}),
505
+ };
506
+ }
507
+ function canonicalJson(value) {
508
+ if (Array.isArray(value))
509
+ return `[${value.map(canonicalJson).join(",")}]`;
510
+ if (value && typeof value === "object") {
511
+ const record = value;
512
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
513
+ }
514
+ return JSON.stringify(value);
515
+ }
@@ -7,7 +7,7 @@ import { promisify } from "node:util";
7
7
  const execFileAsync = promisify(execFile);
8
8
  const TEXT_EXTENSIONS = new Set([
9
9
  ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
10
- ".json", ".md", ".yaml", ".yml", ".py", ".go",
10
+ ".json", ".md", ".txt", ".yaml", ".yml", ".py", ".go",
11
11
  ".rs", ".java", ".sh"
12
12
  ]);
13
13
  const IGNORED_DIRS = new Set([
@@ -1,4 +1,4 @@
1
- import { type ApprovalPolicy, type AgentExecutionIntent, type ContextHandoffReceipt, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type ProviderUsageSettlement, type MutationMode, type LoopRecord, type LoopTask, type ReceiptScope } from "../contracts/index.js";
1
+ import { type ApprovalPolicy, type AgentExecutionIntent, type ContextHandoffReceipt, type CostProvenance, type ExecutionProfile, type ExternalOutcomeEvidenceReference, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type ProviderUsageSettlement, type MutationMode, type LoopRecord, type LoopTask, type ReceiptScope } from "../contracts/index.js";
2
2
  import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, type ExitDecision } from "./policy.js";
3
3
  import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
4
4
  import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
@@ -14,6 +14,8 @@ export type { BudgetPreflightDecision, BudgetPreflightInput, CostGovernorState,
14
14
  export type { ResolvedExecutionProfile, SafetyLeashDecision, SafetyViolation } from "./leash.js";
15
15
  export type { GroundingScanResult, GroundingViolation, GroundingViolationKind, RepoGroundingHit, RepoGroundingIndex } from "./grounding.js";
16
16
  export { buildContextGraphSnapshot, queryContextGraph } from "./context-graph.js";
17
+ export { ExternalOutcomeContractError, ExternalOutcomePolicyError, hashExternalOutcomeContract, verifyExternalOutcomes, writeExternalOutcomeEvidence, } from "./external-outcome.js";
18
+ export type { ExternalOutcomeEvidenceWrite, VerifyExternalOutcomesOptions, WriteExternalOutcomeEvidenceOptions, } from "./external-outcome.js";
17
19
  export { createLocalIdentityAuthority, hasIdentityScope, issueIdentityToken, verifyIdentityToken } from "./identity.js";
18
20
  export type { LocalIdentityAuthority } from "./identity.js";
19
21
  export { compileExecutionPolicy } from "./policy-compiler.js";
@@ -61,6 +63,8 @@ export interface MartinAdapterRequest {
61
63
  definitionOfDonePreSatisfied?: boolean;
62
64
  /** Absolute path to the repository root. */
63
65
  repoRoot?: string;
66
+ /** Authoritative persistence root for this governed run when available. */
67
+ runsRoot?: string;
64
68
  /** Glob patterns for files the agent may modify. Empty = no restriction. */
65
69
  allowedPaths?: string[];
66
70
  /** Glob patterns for files the agent must never modify. */
@@ -76,11 +80,34 @@ export interface MartinAdapterRequest {
76
80
  remainingBudgetUsd: number;
77
81
  remainingIterations: number;
78
82
  remainingTokens?: number;
83
+ /** Who executes verifier commands. Defaults to provider_and_host for standalone compatibility. */
84
+ verificationExecutionOwner?: "provider_and_host" | "host_only";
79
85
  };
80
86
  previousAttempts: LoopAttempt[];
87
+ /** Non-persisted synchronous parent authority for cumulative live usage. */
88
+ observedUsageGovernor?: MartinObservedUsageGovernor;
81
89
  /** Abort signal propagated from the harness — adapters should honour it. */
82
90
  signal?: AbortSignal;
83
91
  }
92
+ export interface MartinObservedUsage {
93
+ /** Provider-observed cumulative cost for this child process. */
94
+ cumulativeUsd: number;
95
+ /** Provider-observed cumulative tokens, including cache read/creation tokens. */
96
+ cumulativeTokens: number;
97
+ turns: number;
98
+ final: boolean;
99
+ }
100
+ export type MartinObservedUsageDecision = {
101
+ action: "continue";
102
+ grantedUsd: number;
103
+ grantedTokens: number;
104
+ } | {
105
+ action: "terminate";
106
+ grantedUsd: number;
107
+ grantedTokens: number;
108
+ reason: string;
109
+ };
110
+ export type MartinObservedUsageGovernor = (observation: MartinObservedUsage) => MartinObservedUsageDecision;
84
111
  export interface MartinVerificationStep {
85
112
  command: string;
86
113
  launched: boolean;
@@ -90,6 +117,7 @@ export interface MartinVerificationStep {
90
117
  timedOut: boolean;
91
118
  fastFail?: boolean;
92
119
  detail?: string;
120
+ evidence?: ExternalOutcomeEvidenceReference;
93
121
  }
94
122
  export interface MartinVerificationOutcome {
95
123
  passed: boolean;
@@ -120,6 +148,8 @@ export interface MartinAdapterResult {
120
148
  };
121
149
  execution?: {
122
150
  changedFiles?: string[];
151
+ /** Complete git patch for the files changed by this execution. */
152
+ patch?: string;
123
153
  diffStats?: {
124
154
  filesChanged: number;
125
155
  addedLines: number;
@@ -215,6 +245,12 @@ export interface RunMartinInput {
215
245
  producerReceiptVerified?: boolean;
216
246
  /** Map of sha256 → true for every artifact available to the verifier. */
217
247
  availableArtifacts?: ReadonlyMap<string, true>;
248
+ /** Keep standalone behavior by default; Swarm children use host_only. */
249
+ verificationExecutionOwner?: "provider_and_host" | "host_only";
250
+ /** Live-only parent budget authority; never written into durable loop state. */
251
+ observedUsageGovernor?: MartinObservedUsageGovernor;
252
+ /** Optional parent cancellation signal. Relayed into the active governed attempt. */
253
+ signal?: AbortSignal;
218
254
  }
219
255
  export interface RunMartinResult {
220
256
  loop: LoopRecord;
@@ -230,3 +266,13 @@ export { SignalDiagnosticError, createFileExitSignalSource, exitSignalPath, read
230
266
  export type { ExitSignalSource, SignalDiagnostic, SignalReadResult } from "./exit-signal.js";
231
267
  export { cacheMessage, fetchSelectedMessage, getCliInstalledVersion, getMcpInstalledVersion, isCooldownExpired, isDismissed, isNewerVersion, loadDeliveryRecord, parseMessageSelectionResponse, recordDismissed, recordShown, resolveDefaultLedgerPath, saveDeliveryRecord, } from "./delivery/index.js";
232
268
  export type { MessageClientOptions, MessageSelectRequest, ParseFailure, ParseResult, ValidationError, } from "./delivery/index.js";
269
+ export { completeDeterministicDemoSwarmRun, createSwarmBudgetLedger, extendSwarmBudgetLease, releaseSwarmBudgetLease, reserveSwarmBudgetLease, selectNextSwarmBatch, settleSwarmBudgetLease, tasksHaveScopeCollision, validateSwarmTaskGraph, runParentSwarmPipeline } from "./swarm/index.js";
270
+ export { readSwarmOperationalState } from "./swarm/operational-reader.js";
271
+ export { readSwarmReceiptProjection, verifySwarmReceiptProjection, } from "./swarm/receipt-projection.js";
272
+ export { buildSwarmShareProjection, readSwarmDossier, verifySwarmEvidence, } from "./swarm/evidence-projection.js";
273
+ export { buildSwarmHostedEnvelope } from "./swarm/hosted-export.js";
274
+ export { assertSwarmPathIdentifier } from "./swarm/workspaces.js";
275
+ export type { CompleteDeterministicDemoSwarmRunInput, CompleteDeterministicDemoSwarmRunResult, DeterministicDemoVerifierExecutionFacts, ParentSwarmPipelineEvidenceStore, PersistedSwarmChildCompletionEvidence, SwarmPipelineWorkspaceFailureEvidence, RunParentSwarmPipelineInput, RunParentSwarmPipelineResult, SwarmBatchResult, SwarmBudgetProtectedUsage, SwarmBudgetTransitionResult, SwarmGraphValidationResult, SwarmSchedulerError } from "./swarm/index.js";
276
+ export type { SwarmOperationalSelector, SwarmOperationalState, } from "./swarm/operational-reader.js";
277
+ export type { SwarmReceiptProjection } from "./swarm/receipt-projection.js";
278
+ export type { SwarmDossierProjection, SwarmEvidenceSelector, SwarmEvidenceVerification, SwarmShareProjection, SwarmStatusCounts, } from "./swarm/evidence-projection.js";