@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.
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/vendor/adapters/claude-cli.d.ts +11 -1
- package/dist/vendor/adapters/claude-cli.js +285 -71
- package/dist/vendor/adapters/cli-bridge.d.ts +12 -3
- package/dist/vendor/adapters/cli-bridge.js +129 -149
- package/dist/vendor/adapters/codex-cli.d.ts +3 -3
- package/dist/vendor/adapters/codex-cli.js +10 -24
- package/dist/vendor/adapters/codex-host.js +127 -132
- package/dist/vendor/adapters/index.d.ts +2 -1
- package/dist/vendor/adapters/index.js +2 -1
- package/dist/vendor/adapters/openai-compatible.js +88 -6
- package/dist/vendor/adapters/process-supervisor.d.ts +60 -0
- package/dist/vendor/adapters/process-supervisor.js +541 -0
- package/dist/vendor/adapters/verifier-only.d.ts +10 -1
- package/dist/vendor/adapters/verifier-only.js +43 -1
- package/dist/vendor/contracts/external-outcome.d.ts +102 -0
- package/dist/vendor/contracts/external-outcome.js +226 -0
- package/dist/vendor/contracts/index.d.ts +8 -1
- package/dist/vendor/contracts/index.js +2 -0
- package/dist/vendor/contracts/swarm-hosted.d.ts +204 -0
- package/dist/vendor/contracts/swarm-hosted.js +629 -0
- package/dist/vendor/contracts/swarm.d.ts +449 -0
- package/dist/vendor/contracts/swarm.js +550 -0
- package/dist/vendor/core/external-outcome.d.ts +34 -0
- package/dist/vendor/core/external-outcome.js +515 -0
- package/dist/vendor/core/grounding.js +1 -1
- package/dist/vendor/core/index.d.ts +47 -1
- package/dist/vendor/core/index.js +53 -3
- package/dist/vendor/core/leash.d.ts +1 -0
- package/dist/vendor/core/leash.js +13 -0
- package/dist/vendor/core/persistence/integrity.d.ts +4 -0
- package/dist/vendor/core/persistence/integrity.js +19 -6
- package/dist/vendor/core/persistence/swarm-integrity.d.ts +63 -0
- package/dist/vendor/core/persistence/swarm-integrity.js +309 -0
- package/dist/vendor/core/swarm/candidates.d.ts +78 -0
- package/dist/vendor/core/swarm/candidates.js +810 -0
- package/dist/vendor/core/swarm/evidence-projection.d.ts +87 -0
- package/dist/vendor/core/swarm/evidence-projection.js +128 -0
- package/dist/vendor/core/swarm/evidence.d.ts +40 -0
- package/dist/vendor/core/swarm/evidence.js +575 -0
- package/dist/vendor/core/swarm/hosted-export.d.ts +48 -0
- package/dist/vendor/core/swarm/hosted-export.js +362 -0
- package/dist/vendor/core/swarm/hosted-projection.d.ts +5 -0
- package/dist/vendor/core/swarm/hosted-projection.js +258 -0
- package/dist/vendor/core/swarm/index.d.ts +110 -0
- package/dist/vendor/core/swarm/index.js +1060 -0
- package/dist/vendor/core/swarm/integration.d.ts +76 -0
- package/dist/vendor/core/swarm/integration.js +615 -0
- package/dist/vendor/core/swarm/lifecycle.d.ts +72 -0
- package/dist/vendor/core/swarm/lifecycle.js +252 -0
- package/dist/vendor/core/swarm/live-runtime.d.ts +78 -0
- package/dist/vendor/core/swarm/live-runtime.js +1482 -0
- package/dist/vendor/core/swarm/live-store.d.ts +99 -0
- package/dist/vendor/core/swarm/live-store.js +860 -0
- package/dist/vendor/core/swarm/operational-reader.d.ts +17 -0
- package/dist/vendor/core/swarm/operational-reader.js +96 -0
- package/dist/vendor/core/swarm/operations.d.ts +29 -0
- package/dist/vendor/core/swarm/operations.js +128 -0
- package/dist/vendor/core/swarm/parent-receipt.d.ts +13 -0
- package/dist/vendor/core/swarm/parent-receipt.js +489 -0
- package/dist/vendor/core/swarm/receipt-projection.d.ts +26 -0
- package/dist/vendor/core/swarm/receipt-projection.js +120 -0
- package/dist/vendor/core/swarm/scheduler.d.ts +92 -0
- package/dist/vendor/core/swarm/scheduler.js +555 -0
- package/dist/vendor/core/swarm/verification.d.ts +70 -0
- package/dist/vendor/core/swarm/verification.js +295 -0
- package/dist/vendor/core/swarm/workspaces.d.ts +77 -0
- package/dist/vendor/core/swarm/workspaces.js +488 -0
- package/dist/vendor/core/verified-handoff.d.ts +4 -0
- package/dist/vendor/core/verified-handoff.js +4 -0
- package/package.json +1 -1
- 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
|
|
@@ -753,6 +783,7 @@ export async function runMartin(input) {
|
|
|
753
783
|
? { definitionOfDonePreSatisfied: loop.task.definitionOfDonePreSatisfied }
|
|
754
784
|
: {}),
|
|
755
785
|
...(loop.task.repoRoot ? { repoRoot: loop.task.repoRoot } : {}),
|
|
786
|
+
...(input.store?.runsRoot ? { runsRoot: input.store.runsRoot } : {}),
|
|
756
787
|
...(loop.task.allowedPaths ? { allowedPaths: loop.task.allowedPaths } : {}),
|
|
757
788
|
...(loop.task.deniedPaths ? { deniedPaths: loop.task.deniedPaths } : {}),
|
|
758
789
|
...(loop.task.acceptanceCriteria ? { acceptanceCriteria: loop.task.acceptanceCriteria } : {}),
|
|
@@ -766,9 +797,13 @@ export async function runMartin(input) {
|
|
|
766
797
|
focus: distilled.focus,
|
|
767
798
|
remainingBudgetUsd: distilled.constraints.remainingBudgetUsd,
|
|
768
799
|
remainingIterations: distilled.constraints.remainingIterations,
|
|
769
|
-
remainingTokens: distilled.constraints.remainingTokens
|
|
800
|
+
remainingTokens: distilled.constraints.remainingTokens,
|
|
801
|
+
...(input.verificationExecutionOwner
|
|
802
|
+
? { verificationExecutionOwner: input.verificationExecutionOwner }
|
|
803
|
+
: {})
|
|
770
804
|
},
|
|
771
|
-
previousAttempts: loop.attempts
|
|
805
|
+
previousAttempts: loop.attempts,
|
|
806
|
+
...(input.observedUsageGovernor ? { observedUsageGovernor: input.observedUsageGovernor } : {})
|
|
772
807
|
};
|
|
773
808
|
const tracksWorkspaceMutations = request.context.repoRoot !== undefined &&
|
|
774
809
|
executingAdapter.metadata.capabilities?.workspaceMutations !== false;
|
|
@@ -996,7 +1031,8 @@ export async function runMartin(input) {
|
|
|
996
1031
|
repoRoot: request.context.repoRoot,
|
|
997
1032
|
changedFiles,
|
|
998
1033
|
allowedPaths: request.context.allowedPaths,
|
|
999
|
-
deniedPaths: request.context.deniedPaths
|
|
1034
|
+
deniedPaths: request.context.deniedPaths,
|
|
1035
|
+
mutationMode: request.context.mutationMode
|
|
1000
1036
|
});
|
|
1001
1037
|
if (!filesystemDecision.allowed) {
|
|
1002
1038
|
const patchDecision = evaluatePatchDecision({
|
|
@@ -1412,6 +1448,7 @@ export async function runMartin(input) {
|
|
|
1412
1448
|
clearTimeout(wallTimer);
|
|
1413
1449
|
disposeSignalMonitor();
|
|
1414
1450
|
runController.abort();
|
|
1451
|
+
input.signal?.removeEventListener("abort", observeParentAbort);
|
|
1415
1452
|
activeAttemptController?.abort();
|
|
1416
1453
|
// Flush any diagnostic ledger writes queued from signal callbacks
|
|
1417
1454
|
await Promise.all(pendingControlWrites);
|
|
@@ -1581,6 +1618,9 @@ function resolveChangedFiles(result, repoRoot, rollbackBoundary) {
|
|
|
1581
1618
|
return listAttemptChangedFilesSinceBoundary({ repoRoot, boundary: rollbackBoundary });
|
|
1582
1619
|
}
|
|
1583
1620
|
function buildPatchDiff(result, changedFiles) {
|
|
1621
|
+
if (result.execution?.patch) {
|
|
1622
|
+
return result.execution.patch;
|
|
1623
|
+
}
|
|
1584
1624
|
// Use structured diff stats to build a minimal diff header if no raw diff is available
|
|
1585
1625
|
if (result.execution?.changedFiles?.length) {
|
|
1586
1626
|
// Build a synthetic diff header from changed file list
|
|
@@ -1663,7 +1703,11 @@ function resolveExpectedVerifierBinding(request) {
|
|
|
1663
1703
|
return {
|
|
1664
1704
|
runId: request.loopId,
|
|
1665
1705
|
workspaceId: request.workspaceId,
|
|
1706
|
+
attemptId: request.attemptId,
|
|
1666
1707
|
cwd: request.context.repoRoot ?? process.cwd(),
|
|
1708
|
+
...(request.context.runsRoot ? { runsRoot: request.context.runsRoot } : {}),
|
|
1709
|
+
...(request.context.executionProfile ? { executionProfile: request.context.executionProfile } : {}),
|
|
1710
|
+
...(request.context.allowedNetworkDomains?.length ? { allowedNetworkDomains: [...request.context.allowedNetworkDomains] } : {}),
|
|
1667
1711
|
commands,
|
|
1668
1712
|
};
|
|
1669
1713
|
}
|
|
@@ -1806,3 +1850,9 @@ export { createDefaultExitPolicy, evaluateExitPolicy, hashProgressState, toLegac
|
|
|
1806
1850
|
export { SignalDiagnosticError, createFileExitSignalSource, exitSignalPath, readAllExitSignals, readExitSignal, startExitSignalMonitor, writeExitSignal } from "./exit-signal.js";
|
|
1807
1851
|
// ─── R4 Delivery ─────────────────────────────────────────────────────────────
|
|
1808
1852
|
export { cacheMessage, fetchSelectedMessage, getCliInstalledVersion, getMcpInstalledVersion, isCooldownExpired, isDismissed, isNewerVersion, loadDeliveryRecord, parseMessageSelectionResponse, recordDismissed, recordShown, resolveDefaultLedgerPath, saveDeliveryRecord, } from "./delivery/index.js";
|
|
1853
|
+
export { completeDeterministicDemoSwarmRun, createSwarmBudgetLedger, extendSwarmBudgetLease, releaseSwarmBudgetLease, reserveSwarmBudgetLease, selectNextSwarmBatch, settleSwarmBudgetLease, tasksHaveScopeCollision, validateSwarmTaskGraph, runParentSwarmPipeline } from "./swarm/index.js";
|
|
1854
|
+
export { readSwarmOperationalState } from "./swarm/operational-reader.js";
|
|
1855
|
+
export { readSwarmReceiptProjection, verifySwarmReceiptProjection, } from "./swarm/receipt-projection.js";
|
|
1856
|
+
export { buildSwarmShareProjection, readSwarmDossier, verifySwarmEvidence, } from "./swarm/evidence-projection.js";
|
|
1857
|
+
export { buildSwarmHostedEnvelope } from "./swarm/hosted-export.js";
|
|
1858
|
+
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
|
|
211
|
+
return createPrivateIntegrityHmac(key, material);
|
|
212
212
|
}
|
|
213
213
|
async function ensureReceiptIntegrityKey(runsRoot, runId) {
|
|
214
|
-
|
|
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
|
-
|
|
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
|
|
247
|
+
export function resolvePrivateIntegrityKeyPath(runsRoot, keyFileName) {
|
|
238
248
|
const rootHash = sha256(runsRoot).slice(0, 16);
|
|
239
|
-
return join(resolveReceiptIntegrityRoot(), rootHash,
|
|
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
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type ReceiptIntegritySummary, type SwarmCandidate, type SwarmPatchAdmission, type SwarmRunRecord } from "../../contracts/index.js";
|
|
2
|
+
import type { SwarmWorkspaceRuntimeHandle } from "./workspaces.js";
|
|
3
|
+
export interface SwarmCandidateGitCommand {
|
|
4
|
+
readonly cwd: string;
|
|
5
|
+
readonly args: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
export interface SwarmCandidateGitResult {
|
|
8
|
+
readonly stdout: Buffer;
|
|
9
|
+
readonly stderr: Buffer;
|
|
10
|
+
}
|
|
11
|
+
export type SwarmCandidateGitRunner = (command: SwarmCandidateGitCommand) => Promise<SwarmCandidateGitResult>;
|
|
12
|
+
export interface PersistedSwarmCandidateArtifact {
|
|
13
|
+
readonly candidate: SwarmCandidate;
|
|
14
|
+
readonly attemptId: string;
|
|
15
|
+
readonly proposalId: string;
|
|
16
|
+
readonly identitySha256: string;
|
|
17
|
+
readonly candidateTreeHash: string;
|
|
18
|
+
readonly patch: Buffer;
|
|
19
|
+
readonly manifest: Buffer;
|
|
20
|
+
readonly manifestSha256: string;
|
|
21
|
+
readonly receiptIntegrity: ReceiptIntegritySummary;
|
|
22
|
+
}
|
|
23
|
+
export interface SwarmProcessClosureEvidence {
|
|
24
|
+
readonly swarmId: string;
|
|
25
|
+
readonly workspaceId: string;
|
|
26
|
+
readonly childRunId: string;
|
|
27
|
+
readonly agentId: string;
|
|
28
|
+
readonly state: "closed";
|
|
29
|
+
readonly completedAt: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Implementations must durably and atomically reject duplicate candidate IDs.
|
|
33
|
+
* The caller does not receive cleanup authority until both operations resolve.
|
|
34
|
+
*/
|
|
35
|
+
export interface SwarmCandidateArtifactWriter {
|
|
36
|
+
loadProcessClosure(childRunId: string): Promise<SwarmProcessClosureEvidence | undefined>;
|
|
37
|
+
persistCandidate(artifact: PersistedSwarmCandidateArtifact): Promise<void>;
|
|
38
|
+
persistDecision(decision: SwarmPatchAdmission): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export interface CaptureSwarmCandidateInput {
|
|
41
|
+
readonly swarmRecord: SwarmRunRecord;
|
|
42
|
+
readonly canonicalRoot: string;
|
|
43
|
+
readonly swarmId: string;
|
|
44
|
+
readonly candidateId: string;
|
|
45
|
+
readonly admissionId: string;
|
|
46
|
+
readonly attemptId: string;
|
|
47
|
+
readonly proposalId: string;
|
|
48
|
+
readonly childRunId: string;
|
|
49
|
+
readonly agentId: string;
|
|
50
|
+
readonly taskIds: readonly string[];
|
|
51
|
+
readonly expectedBaselineCommit: string;
|
|
52
|
+
readonly workspace: SwarmWorkspaceRuntimeHandle;
|
|
53
|
+
readonly childState: "verified" | "stopped" | "needs_review" | "running" | "queued";
|
|
54
|
+
readonly declaredPaths: readonly string[];
|
|
55
|
+
readonly childAllowedPaths: readonly string[];
|
|
56
|
+
readonly inheritedDeniedPaths: readonly string[];
|
|
57
|
+
readonly taskWriteScope: readonly string[];
|
|
58
|
+
readonly runsRoot: string;
|
|
59
|
+
readonly loopRecordPath: string;
|
|
60
|
+
readonly ledgerPath: string;
|
|
61
|
+
readonly artifactWriter: SwarmCandidateArtifactWriter;
|
|
62
|
+
readonly gitRunner?: SwarmCandidateGitRunner;
|
|
63
|
+
readonly now?: () => string;
|
|
64
|
+
/** Test-only race seam executed after validation and before staging. */
|
|
65
|
+
readonly beforeStage?: () => void | Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
export interface CaptureSwarmCandidateResult {
|
|
68
|
+
readonly admission: SwarmPatchAdmission;
|
|
69
|
+
readonly artifact?: PersistedSwarmCandidateArtifact;
|
|
70
|
+
readonly cleanupAuthorized: boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Internal live-runtime bridge to the candidate module's single NUL-safe Git
|
|
74
|
+
* inventory parser. Policy enforcement remains in captureAndAdmitSwarmCandidate.
|
|
75
|
+
*/
|
|
76
|
+
export declare function inventorySwarmCandidatePaths(workspacePath: string, gitRunner?: SwarmCandidateGitRunner): Promise<string[]>;
|
|
77
|
+
export declare function captureAndAdmitSwarmCandidate(input: CaptureSwarmCandidateInput): Promise<CaptureSwarmCandidateResult>;
|
|
78
|
+
export declare function verifyPersistedSwarmCandidateArtifact(artifact: PersistedSwarmCandidateArtifact): boolean;
|