@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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type SwarmGlobalVerification, type SwarmPatchAdmission, type SwarmRunRecord, type SwarmVerifierSubprocessResult, type VerificationStep } from "../../contracts/index.js";
|
|
2
|
+
import { type PersistedSwarmCandidateArtifact } from "./candidates.js";
|
|
3
|
+
import type { SwarmWorkspaceManager, SwarmWorkspaceRuntimeHandle } from "./workspaces.js";
|
|
4
|
+
export interface SwarmVerificationGitCommand {
|
|
5
|
+
readonly cwd: string;
|
|
6
|
+
readonly args: readonly string[];
|
|
7
|
+
readonly stdin?: Buffer;
|
|
8
|
+
}
|
|
9
|
+
export interface SwarmVerificationGitResult {
|
|
10
|
+
readonly stdout: Buffer;
|
|
11
|
+
readonly stderr: Buffer;
|
|
12
|
+
}
|
|
13
|
+
export type SwarmVerificationGitRunner = (command: SwarmVerificationGitCommand) => Promise<SwarmVerificationGitResult>;
|
|
14
|
+
export interface SwarmVerifierBinding {
|
|
15
|
+
readonly swarmId: string;
|
|
16
|
+
readonly workspaceId: string;
|
|
17
|
+
readonly cwd: string;
|
|
18
|
+
readonly parentPolicyVersion: string;
|
|
19
|
+
readonly baselineCommit: string;
|
|
20
|
+
readonly integratedTreeHash: string;
|
|
21
|
+
readonly commands: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
export interface SwarmVerifierExecutionRequest {
|
|
24
|
+
readonly swarmId: string;
|
|
25
|
+
readonly workspaceId: string;
|
|
26
|
+
readonly cwd: string;
|
|
27
|
+
readonly parentPolicyVersion: string;
|
|
28
|
+
readonly baselineCommit: string;
|
|
29
|
+
readonly integratedTreeHash: string;
|
|
30
|
+
readonly commands: readonly VerificationStep[];
|
|
31
|
+
readonly signal: AbortSignal;
|
|
32
|
+
}
|
|
33
|
+
export interface SwarmVerifierExecutionResult {
|
|
34
|
+
readonly passed: boolean;
|
|
35
|
+
readonly processCloseState: "closed" | "not_required" | "failed";
|
|
36
|
+
readonly binding: SwarmVerifierBinding;
|
|
37
|
+
readonly subprocessResults: readonly SwarmVerifierSubprocessResult[];
|
|
38
|
+
}
|
|
39
|
+
export interface SwarmVerifierExecutor {
|
|
40
|
+
execute(request: SwarmVerifierExecutionRequest): Promise<SwarmVerifierExecutionResult>;
|
|
41
|
+
}
|
|
42
|
+
export interface SwarmGlobalVerificationEvidenceStore {
|
|
43
|
+
loadPersistedCandidate(candidateId: string): Promise<PersistedSwarmCandidateArtifact | undefined>;
|
|
44
|
+
loadPersistedAdmission(candidateId: string): Promise<SwarmPatchAdmission | undefined>;
|
|
45
|
+
persistGlobalVerification(evidence: SwarmGlobalVerification): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
export interface VerifyIntegratedSwarmResultInput {
|
|
48
|
+
readonly swarmRecord: SwarmRunRecord;
|
|
49
|
+
readonly workspaceManager: SwarmWorkspaceManager;
|
|
50
|
+
readonly verifierWorkspaceId: string;
|
|
51
|
+
readonly admittedCandidateIds: readonly string[];
|
|
52
|
+
readonly integratedTreeHash: string;
|
|
53
|
+
readonly evidenceStore: SwarmGlobalVerificationEvidenceStore;
|
|
54
|
+
readonly executor: SwarmVerifierExecutor | ((request: SwarmVerifierExecutionRequest) => Promise<SwarmVerifierExecutionResult>);
|
|
55
|
+
readonly signal: AbortSignal;
|
|
56
|
+
readonly gitRunner?: SwarmVerificationGitRunner;
|
|
57
|
+
readonly now?: () => string;
|
|
58
|
+
readonly createVerificationId?: () => string;
|
|
59
|
+
}
|
|
60
|
+
export interface VerifyIntegratedSwarmResult {
|
|
61
|
+
readonly passed: boolean;
|
|
62
|
+
readonly workspace: SwarmWorkspaceRuntimeHandle;
|
|
63
|
+
readonly evidence: SwarmGlobalVerification;
|
|
64
|
+
readonly replayedCandidateIds: readonly string[];
|
|
65
|
+
readonly reconstructedTreeHash: string;
|
|
66
|
+
readonly cleanupAuthorized: boolean;
|
|
67
|
+
readonly processCloseState: "closed" | "not_required" | "failed";
|
|
68
|
+
readonly diagnostic?: string;
|
|
69
|
+
}
|
|
70
|
+
export declare function verifyIntegratedSwarmResult(input: VerifyIntegratedSwarmResultInput): Promise<VerifyIntegratedSwarmResult>;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { SWARM_SCHEMA_VERSION, validateSwarmRunRecord } from "../../contracts/index.js";
|
|
4
|
+
import { verifyPersistedSwarmCandidateArtifact } from "./candidates.js";
|
|
5
|
+
const MAX_GIT_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
6
|
+
export async function verifyIntegratedSwarmResult(input) {
|
|
7
|
+
const gitRunner = input.gitRunner ?? runGit;
|
|
8
|
+
const now = input.now ?? (() => new Date().toISOString());
|
|
9
|
+
const verificationId = (input.createVerificationId ?? (() => `verification-${randomUUID()}`))();
|
|
10
|
+
const startedAt = now();
|
|
11
|
+
assertParentAuthority(input);
|
|
12
|
+
const workspace = await input.workspaceManager.createWorkspace({
|
|
13
|
+
kind: "verifier",
|
|
14
|
+
workspaceId: input.verifierWorkspaceId
|
|
15
|
+
});
|
|
16
|
+
const commands = input.swarmRecord.parentContract.globalVerifierStack.map((step) => step.command);
|
|
17
|
+
const replayedCandidateIds = [];
|
|
18
|
+
let reconstructedTreeHash = "";
|
|
19
|
+
let commandState = "unknown";
|
|
20
|
+
let mutationState = "unknown";
|
|
21
|
+
let subprocessResults = [];
|
|
22
|
+
let processCloseState = "not_required";
|
|
23
|
+
let diagnostic;
|
|
24
|
+
try {
|
|
25
|
+
assertVerifierWorkspace(input, workspace);
|
|
26
|
+
reconstructedTreeHash = await assertBaselineWorkspace(gitRunner, workspace.path, input.workspaceManager.baselineCommit);
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
for (const candidateId of input.admittedCandidateIds) {
|
|
29
|
+
if (candidateId.trim().length === 0 || seen.has(candidateId)) {
|
|
30
|
+
throw new Error("Global verification requires a unique recorded candidate sequence.");
|
|
31
|
+
}
|
|
32
|
+
seen.add(candidateId);
|
|
33
|
+
const artifact = await input.evidenceStore.loadPersistedCandidate(candidateId);
|
|
34
|
+
const admission = await input.evidenceStore.loadPersistedAdmission(candidateId);
|
|
35
|
+
if (!artifact || !admission) {
|
|
36
|
+
throw new Error(`Persisted admitted candidate evidence is missing: ${candidateId}`);
|
|
37
|
+
}
|
|
38
|
+
const snapshot = snapshotArtifact(artifact);
|
|
39
|
+
assertReplayEvidence(input, snapshot, admission, reconstructedTreeHash);
|
|
40
|
+
await applyPatch(gitRunner, workspace.path, snapshot.patch);
|
|
41
|
+
const postTreeHash = await writeTree(gitRunner, workspace.path);
|
|
42
|
+
if (postTreeHash !== admission.postIntegrationTreeHash) {
|
|
43
|
+
throw new Error(`Candidate replay tree mismatch: ${candidateId}`);
|
|
44
|
+
}
|
|
45
|
+
await assertWorktreeMatchesIndex(gitRunner, workspace.path);
|
|
46
|
+
replayedCandidateIds.push(candidateId);
|
|
47
|
+
reconstructedTreeHash = postTreeHash;
|
|
48
|
+
}
|
|
49
|
+
if (reconstructedTreeHash !== input.integratedTreeHash) {
|
|
50
|
+
throw new Error("Reconstructed verifier tree does not match the recorded integration tree.");
|
|
51
|
+
}
|
|
52
|
+
const preCommandTree = await writeTree(gitRunner, workspace.path);
|
|
53
|
+
if (preCommandTree !== input.integratedTreeHash) {
|
|
54
|
+
throw new Error("Verifier index changed before global commands started.");
|
|
55
|
+
}
|
|
56
|
+
await assertWorktreeMatchesIndex(gitRunner, workspace.path);
|
|
57
|
+
const preUntracked = await readUntracked(gitRunner, workspace.path);
|
|
58
|
+
if (preUntracked.length !== 0) {
|
|
59
|
+
throw new Error("Verifier workspace is not clean before global commands started.");
|
|
60
|
+
}
|
|
61
|
+
let execution;
|
|
62
|
+
try {
|
|
63
|
+
execution = await executeVerifier(input.executor, {
|
|
64
|
+
swarmId: input.swarmRecord.swarmId,
|
|
65
|
+
workspaceId: workspace.record.workspaceId,
|
|
66
|
+
cwd: workspace.path,
|
|
67
|
+
parentPolicyVersion: input.swarmRecord.parentContract.policyVersion,
|
|
68
|
+
baselineCommit: input.workspaceManager.baselineCommit,
|
|
69
|
+
integratedTreeHash: input.integratedTreeHash,
|
|
70
|
+
commands: input.swarmRecord.parentContract.globalVerifierStack.map((step) => ({ ...step })),
|
|
71
|
+
signal: input.signal
|
|
72
|
+
});
|
|
73
|
+
subprocessResults = execution.subprocessResults.map((step) => ({ ...step }));
|
|
74
|
+
processCloseState = execution.processCloseState;
|
|
75
|
+
commandState = verifierExecutionPassed(input, workspace, execution) ? "passed" : "failed";
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
commandState = "unknown";
|
|
79
|
+
diagnostic = sanitizeDiagnostic(error);
|
|
80
|
+
}
|
|
81
|
+
mutationState = await verifierWorkspaceUnchanged(gitRunner, workspace.path, preCommandTree, preUntracked) ? "clean" : "mutated";
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
diagnostic = sanitizeDiagnostic(error);
|
|
85
|
+
}
|
|
86
|
+
const evidence = Object.freeze({
|
|
87
|
+
schemaVersion: SWARM_SCHEMA_VERSION,
|
|
88
|
+
verificationId,
|
|
89
|
+
swarmId: input.swarmRecord.swarmId,
|
|
90
|
+
workspaceId: workspace.record.workspaceId,
|
|
91
|
+
parentPolicyVersion: input.swarmRecord.parentContract.policyVersion,
|
|
92
|
+
baselineCommit: input.workspaceManager.baselineCommit,
|
|
93
|
+
integratedTreeHash: input.integratedTreeHash,
|
|
94
|
+
commands: Object.freeze([...commands]),
|
|
95
|
+
commandState,
|
|
96
|
+
mutationState,
|
|
97
|
+
startedAt,
|
|
98
|
+
completedAt: now(),
|
|
99
|
+
subprocessResults: Object.freeze(subprocessResults.map((step) => Object.freeze({ ...step })))
|
|
100
|
+
});
|
|
101
|
+
let cleanupAuthorized = true;
|
|
102
|
+
try {
|
|
103
|
+
await input.evidenceStore.persistGlobalVerification(evidence);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
cleanupAuthorized = false;
|
|
107
|
+
diagnostic ??= sanitizeDiagnostic(error);
|
|
108
|
+
}
|
|
109
|
+
const passed = cleanupAuthorized
|
|
110
|
+
&& replayedCandidateIds.length === input.admittedCandidateIds.length
|
|
111
|
+
&& reconstructedTreeHash === input.integratedTreeHash
|
|
112
|
+
&& evidence.commandState === "passed"
|
|
113
|
+
&& evidence.mutationState === "clean"
|
|
114
|
+
&& processCloseState === "closed"
|
|
115
|
+
&& !input.signal.aborted;
|
|
116
|
+
return Object.freeze({
|
|
117
|
+
passed,
|
|
118
|
+
workspace,
|
|
119
|
+
evidence,
|
|
120
|
+
replayedCandidateIds: Object.freeze([...replayedCandidateIds]),
|
|
121
|
+
reconstructedTreeHash,
|
|
122
|
+
cleanupAuthorized,
|
|
123
|
+
processCloseState,
|
|
124
|
+
...(diagnostic ? { diagnostic } : {})
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function assertParentAuthority(input) {
|
|
128
|
+
if (!validateSwarmRunRecord(input.swarmRecord).ok
|
|
129
|
+
|| input.swarmRecord.outcome.state !== "running"
|
|
130
|
+
|| input.swarmRecord.swarmId.trim().length === 0
|
|
131
|
+
|| input.verifierWorkspaceId.trim().length === 0
|
|
132
|
+
|| !/^[0-9a-f]{40,64}$/u.test(input.integratedTreeHash)
|
|
133
|
+
|| (input.swarmRecord.tasks.some((task) => task.required && task.mutationMode === "write" && task.status === "accepted")
|
|
134
|
+
&& input.admittedCandidateIds.length === 0)) {
|
|
135
|
+
throw new Error("Global verification requires current parent and integration authority.");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function assertVerifierWorkspace(input, workspace) {
|
|
139
|
+
if (workspace.record.kind !== "verifier"
|
|
140
|
+
|| workspace.record.workspaceId !== input.verifierWorkspaceId
|
|
141
|
+
|| workspace.record.swarmId !== input.swarmRecord.swarmId
|
|
142
|
+
|| workspace.record.baselineCommit !== input.workspaceManager.baselineCommit
|
|
143
|
+
|| workspace.record.state !== "active") {
|
|
144
|
+
throw new Error("Workspace manager returned a verifier handle outside parent authority.");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function assertReplayEvidence(input, artifact, admission, expectedPreTreeHash) {
|
|
148
|
+
const candidate = artifact.candidate;
|
|
149
|
+
if (!verifyPersistedSwarmCandidateArtifact(artifact)
|
|
150
|
+
|| candidate.swarmId !== input.swarmRecord.swarmId
|
|
151
|
+
|| candidate.baselineCommit !== input.workspaceManager.baselineCommit
|
|
152
|
+
|| admission.schemaVersion !== SWARM_SCHEMA_VERSION
|
|
153
|
+
|| admission.state !== "admitted"
|
|
154
|
+
|| admission.reasonCode !== "admitted"
|
|
155
|
+
|| admission.receiptIntegrity !== "verified"
|
|
156
|
+
|| admission.candidateId !== candidate.candidateId
|
|
157
|
+
|| admission.swarmId !== candidate.swarmId
|
|
158
|
+
|| admission.workspaceId !== candidate.workspaceId
|
|
159
|
+
|| admission.childRunId !== candidate.childRunId
|
|
160
|
+
|| admission.agentId !== candidate.agentId
|
|
161
|
+
|| admission.baselineCommit !== candidate.baselineCommit
|
|
162
|
+
|| !sameStrings(admission.taskIds, candidate.taskIds)
|
|
163
|
+
|| !sameStrings(admission.changedPaths, candidate.changedPaths)
|
|
164
|
+
|| admission.preIntegrationTreeHash !== expectedPreTreeHash
|
|
165
|
+
|| !admission.postIntegrationTreeHash) {
|
|
166
|
+
throw new Error(`Persisted candidate admission is invalid: ${candidate.candidateId}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function verifierExecutionPassed(input, workspace, execution) {
|
|
170
|
+
const expectedCommands = input.swarmRecord.parentContract.globalVerifierStack.map((step) => step.command);
|
|
171
|
+
const binding = execution.binding;
|
|
172
|
+
return execution.passed
|
|
173
|
+
&& execution.processCloseState === "closed"
|
|
174
|
+
&& !input.signal.aborted
|
|
175
|
+
&& binding.swarmId === input.swarmRecord.swarmId
|
|
176
|
+
&& binding.workspaceId === workspace.record.workspaceId
|
|
177
|
+
&& binding.cwd === workspace.path
|
|
178
|
+
&& binding.parentPolicyVersion === input.swarmRecord.parentContract.policyVersion
|
|
179
|
+
&& binding.baselineCommit === input.workspaceManager.baselineCommit
|
|
180
|
+
&& binding.integratedTreeHash === input.integratedTreeHash
|
|
181
|
+
&& sameStrings(binding.commands, expectedCommands)
|
|
182
|
+
&& execution.subprocessResults.length === expectedCommands.length
|
|
183
|
+
&& execution.subprocessResults.every((step, index) => (step.command === expectedCommands[index]
|
|
184
|
+
&& step.launched
|
|
185
|
+
&& step.completed
|
|
186
|
+
&& !step.timedOut
|
|
187
|
+
&& step.exitCode === 0
|
|
188
|
+
&& typeof step.completedAt === "string"
|
|
189
|
+
&& step.completedAt.length > 0));
|
|
190
|
+
}
|
|
191
|
+
async function executeVerifier(executor, request) {
|
|
192
|
+
return typeof executor === "function" ? executor(request) : executor.execute(request);
|
|
193
|
+
}
|
|
194
|
+
async function assertBaselineWorkspace(gitRunner, cwd, baselineCommit) {
|
|
195
|
+
const expected = await gitScalar(gitRunner, cwd, ["rev-parse", `${baselineCommit}^{tree}`]);
|
|
196
|
+
const actual = await writeTree(gitRunner, cwd);
|
|
197
|
+
if (actual !== expected)
|
|
198
|
+
throw new Error("Verifier workspace does not start at the immutable baseline tree.");
|
|
199
|
+
await assertWorktreeMatchesIndex(gitRunner, cwd);
|
|
200
|
+
if ((await readUntracked(gitRunner, cwd)).length !== 0) {
|
|
201
|
+
throw new Error("Verifier baseline workspace contains untracked files.");
|
|
202
|
+
}
|
|
203
|
+
return actual;
|
|
204
|
+
}
|
|
205
|
+
async function applyPatch(gitRunner, cwd, patch) {
|
|
206
|
+
await gitRunner({ cwd, args: ["-c", "core.autocrlf=false", "apply", "--check", "--index", "-"], stdin: patch });
|
|
207
|
+
await gitRunner({ cwd, args: ["-c", "core.autocrlf=false", "apply", "--index", "-"], stdin: patch });
|
|
208
|
+
}
|
|
209
|
+
async function verifierWorkspaceUnchanged(gitRunner, cwd, expectedTreeHash, expectedUntracked) {
|
|
210
|
+
try {
|
|
211
|
+
return await writeTree(gitRunner, cwd) === expectedTreeHash
|
|
212
|
+
&& await worktreeMatchesIndex(gitRunner, cwd)
|
|
213
|
+
&& sameStrings(await readUntracked(gitRunner, cwd), expectedUntracked);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function assertWorktreeMatchesIndex(gitRunner, cwd) {
|
|
220
|
+
if (!await worktreeMatchesIndex(gitRunner, cwd)) {
|
|
221
|
+
throw new Error("Verifier tracked worktree content does not match its index.");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function worktreeMatchesIndex(gitRunner, cwd) {
|
|
225
|
+
try {
|
|
226
|
+
await gitRunner({ cwd, args: ["diff-files", "--quiet", "--"] });
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async function readUntracked(gitRunner, cwd) {
|
|
234
|
+
const result = await gitRunner({
|
|
235
|
+
cwd,
|
|
236
|
+
args: ["ls-files", "--others", "--exclude-standard", "-z"]
|
|
237
|
+
});
|
|
238
|
+
const fields = result.stdout.toString("utf8").split("\0");
|
|
239
|
+
if (fields.at(-1) !== "")
|
|
240
|
+
throw new Error("Git returned a malformed untracked inventory.");
|
|
241
|
+
fields.pop();
|
|
242
|
+
return fields;
|
|
243
|
+
}
|
|
244
|
+
async function writeTree(gitRunner, cwd) {
|
|
245
|
+
return gitScalar(gitRunner, cwd, ["write-tree"]);
|
|
246
|
+
}
|
|
247
|
+
async function gitScalar(gitRunner, cwd, args) {
|
|
248
|
+
const result = await gitRunner({ cwd, args });
|
|
249
|
+
return result.stdout.toString("utf8").trim();
|
|
250
|
+
}
|
|
251
|
+
function snapshotArtifact(artifact) {
|
|
252
|
+
return {
|
|
253
|
+
candidate: {
|
|
254
|
+
...artifact.candidate,
|
|
255
|
+
taskIds: [...artifact.candidate.taskIds],
|
|
256
|
+
changedPaths: [...artifact.candidate.changedPaths]
|
|
257
|
+
},
|
|
258
|
+
attemptId: artifact.attemptId,
|
|
259
|
+
proposalId: artifact.proposalId,
|
|
260
|
+
identitySha256: artifact.identitySha256,
|
|
261
|
+
candidateTreeHash: artifact.candidateTreeHash,
|
|
262
|
+
patch: Buffer.from(artifact.patch),
|
|
263
|
+
manifest: Buffer.from(artifact.manifest),
|
|
264
|
+
manifestSha256: artifact.manifestSha256,
|
|
265
|
+
receiptIntegrity: { ...artifact.receiptIntegrity }
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function sameStrings(left, right) {
|
|
269
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
270
|
+
}
|
|
271
|
+
function sanitizeDiagnostic(error) {
|
|
272
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
273
|
+
return message.replace(/[\r\n\0]+/gu, " ").trim().slice(0, 512) || "Global verification failed.";
|
|
274
|
+
}
|
|
275
|
+
function runGit(command) {
|
|
276
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
277
|
+
const child = execFile("git", [...command.args], {
|
|
278
|
+
cwd: command.cwd,
|
|
279
|
+
encoding: "buffer",
|
|
280
|
+
windowsHide: true,
|
|
281
|
+
maxBuffer: MAX_GIT_OUTPUT_BYTES
|
|
282
|
+
}, (error, stdout, stderr) => {
|
|
283
|
+
if (error) {
|
|
284
|
+
rejectPromise(Object.assign(error, { stdout, stderr }));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
resolvePromise({
|
|
288
|
+
stdout: Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout),
|
|
289
|
+
stderr: Buffer.isBuffer(stderr) ? stderr : Buffer.from(stderr)
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
if (command.stdin)
|
|
293
|
+
child.stdin?.end(command.stdin);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { SwarmCleanupRecord, SwarmWorkspaceRecord } from "../../contracts/index.js";
|
|
2
|
+
export interface SwarmGitCommand {
|
|
3
|
+
readonly cwd: string;
|
|
4
|
+
readonly args: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export interface SwarmGitResult {
|
|
7
|
+
readonly stdout: string;
|
|
8
|
+
readonly stderr: string;
|
|
9
|
+
}
|
|
10
|
+
export type SwarmGitRunner = (command: SwarmGitCommand) => Promise<SwarmGitResult>;
|
|
11
|
+
export interface SwarmWorkspaceFs {
|
|
12
|
+
mkdir(path: string): Promise<void>;
|
|
13
|
+
realpath(path: string): Promise<string>;
|
|
14
|
+
pathExists(path: string): Promise<boolean>;
|
|
15
|
+
rm?(path: string): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export type SwarmWorkspaceSpec = {
|
|
18
|
+
readonly kind: "child";
|
|
19
|
+
readonly workspaceId: string;
|
|
20
|
+
readonly childRunId: string;
|
|
21
|
+
readonly agentId: string;
|
|
22
|
+
readonly taskIds: readonly string[];
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: "integration";
|
|
25
|
+
readonly workspaceId: string;
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: "verifier";
|
|
28
|
+
readonly workspaceId: string;
|
|
29
|
+
};
|
|
30
|
+
/** Runtime-only capability. Its absolute path must never be copied into shareable evidence. */
|
|
31
|
+
export interface SwarmWorkspaceRuntimeHandle {
|
|
32
|
+
readonly path: string;
|
|
33
|
+
readonly record: SwarmWorkspaceRecord;
|
|
34
|
+
readonly isolationMode?: "worktree" | "independent_clone";
|
|
35
|
+
}
|
|
36
|
+
export interface SwarmWorkspaceCleanupGate {
|
|
37
|
+
readonly evidencePersisted: boolean;
|
|
38
|
+
readonly processTreeClosed: boolean;
|
|
39
|
+
readonly force?: boolean;
|
|
40
|
+
readonly ownedProcessId?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface SwarmWorkspaceManager {
|
|
43
|
+
readonly baselineCommit: string;
|
|
44
|
+
readonly canonicalRoot: string;
|
|
45
|
+
readonly ownedRoot: string;
|
|
46
|
+
createWorkspace(spec: SwarmWorkspaceSpec): Promise<SwarmWorkspaceRuntimeHandle>;
|
|
47
|
+
removeWorkspace(handle: SwarmWorkspaceRuntimeHandle, gate: SwarmWorkspaceCleanupGate): Promise<SwarmCleanupRecord>;
|
|
48
|
+
}
|
|
49
|
+
export interface CreateSwarmWorkspaceManagerInput {
|
|
50
|
+
readonly canonicalRoot: string;
|
|
51
|
+
readonly ownedRoot: string;
|
|
52
|
+
readonly swarmId: string;
|
|
53
|
+
readonly isolationMode?: "worktree" | "independent_clone";
|
|
54
|
+
readonly gitRunner?: SwarmGitRunner;
|
|
55
|
+
readonly fs?: SwarmWorkspaceFs;
|
|
56
|
+
readonly now?: () => string;
|
|
57
|
+
readonly createCleanupId?: () => string;
|
|
58
|
+
}
|
|
59
|
+
export declare class SwarmWorkspaceError extends Error {
|
|
60
|
+
readonly code: string;
|
|
61
|
+
constructor(code: string, message: string, options?: {
|
|
62
|
+
cause?: unknown;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export declare class SwarmWorkspaceCreationError extends SwarmWorkspaceError {
|
|
66
|
+
readonly originalCode: string;
|
|
67
|
+
readonly cleanup: SwarmCleanupRecord;
|
|
68
|
+
readonly recoveryHandle: SwarmWorkspaceRuntimeHandle;
|
|
69
|
+
constructor(input: {
|
|
70
|
+
original: SwarmWorkspaceError;
|
|
71
|
+
cleanup: SwarmCleanupRecord;
|
|
72
|
+
recoveryHandle: SwarmWorkspaceRuntimeHandle;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
export declare function createSwarmWorkspaceManager(input: CreateSwarmWorkspaceManagerInput): Promise<SwarmWorkspaceManager>;
|
|
76
|
+
/** Non-authority path guard shared by production composition callers before any ID-derived join. */
|
|
77
|
+
export declare function assertSwarmPathIdentifier(value: string, label?: string): void;
|