rea-agents 1.3.0 → 1.4.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/README.md +21 -0
- package/bridge/hopper_bridge.py +12 -3
- package/dist/application/ArtifactGraphConstruction.js +1 -1
- package/dist/application/ArtifactInventory.js +98 -26
- package/dist/application/AuthorizedArtifactInventory.js +2 -2
- package/dist/application/BinarySession.js +83 -0
- package/dist/application/CapabilityInventory.js +127 -0
- package/dist/application/ClientRegistrationStatus.js +69 -0
- package/dist/application/CrossVersionInventory.js +19 -8
- package/dist/application/CrossVersionInvestigation.js +68 -5
- package/dist/application/DirectAnalysis.js +97 -9
- package/dist/application/Doctor.js +74 -2
- package/dist/application/PermissionAuthority.js +183 -0
- package/dist/application/PermissionConfiguration.js +26 -0
- package/dist/application/ProcessCaptureError.js +13 -0
- package/dist/application/ProcessCaptureLifecycle.js +3 -1
- package/dist/application/ProcessCli.js +23 -10
- package/dist/application/ProcessHarness.js +4 -1
- package/dist/application/ProgressReporter.js +30 -0
- package/dist/application/ProjectPermissionStore.js +110 -0
- package/dist/application/Setup.js +2 -41
- package/dist/application/SupportedClients.js +41 -0
- package/dist/application/Uninstall.js +1 -1
- package/dist/application/Upgrade.js +2 -0
- package/dist/application/runtime.js +1 -1
- package/dist/artifacts/ArtifactProvider.js +13 -3
- package/dist/catalogIdentity.js +121 -0
- package/dist/cli.js +46 -3
- package/dist/cliEvidenceCommands.js +42 -1
- package/dist/cliInvestigationCommands.js +57 -6
- package/dist/cliLogging.js +20 -3
- package/dist/cliPolicyCommands.js +115 -0
- package/dist/cliProcessCommands.js +4 -14
- package/dist/config.js +63 -3
- package/dist/contracts/artifactToolContracts.js +14 -1
- package/dist/contracts/errorSchemas.js +98 -0
- package/dist/contracts/toolContracts.js +10 -1
- package/dist/contracts/toolOutputSchemas.js +62 -30
- package/dist/domain/artifactComparison.js +38 -11
- package/dist/domain/artifactGraph.js +25 -1
- package/dist/domain/artifactInventoryEvidence.js +18 -0
- package/dist/domain/changedBehavior.js +5 -3
- package/dist/domain/errors.js +216 -3
- package/dist/domain/investigationWorkspace.js +131 -4
- package/dist/domain/permissionPolicy.js +157 -0
- package/dist/domain/processComparison.js +1 -0
- package/dist/domain/reconstructionVerification.js +1 -1
- package/dist/domain/reconstructionVerificationSchemas.js +1 -0
- package/dist/domain/staticRuntimeCorrelation.js +5 -1
- package/dist/generatedPackageMetadata.js +9 -0
- package/dist/hopper/BridgeLauncher.js +7 -3
- package/dist/hopper/HopperClient.js +32 -5
- package/dist/identity.js +10 -3
- package/dist/logger.js +2 -2
- package/dist/main.js +95 -7
- package/dist/server/createServer.js +52 -5
- package/dist/server/mcpProgress.js +23 -0
- package/dist/server/registerArtifactComparisonTool.js +6 -2
- package/dist/server/registerBundleComparisonTool.js +6 -2
- package/dist/server/registerEnhancedTools.js +18 -1
- package/dist/server/registerEvidenceResources.js +302 -0
- package/dist/server/registerEvidenceTools.js +58 -1
- package/dist/server/registerFunctionComparisonTool.js +6 -2
- package/dist/server/registerInvestigationTools.js +83 -10
- package/dist/server/registerOfficialTools.js +19 -1
- package/dist/server/registerProcessComparisonTool.js +7 -3
- package/dist/server/registerSessionStatusTool.js +44 -3
- package/dist/server/registerSessionTools.js +129 -7
- package/dist/server/runDerivedOperation.js +35 -0
- package/dist/server/toolResult.js +27 -3
- package/dist/serverIdentity.js +70 -0
- package/package.json +6 -4
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { ResourceNotFoundError, ResourceTemplate, } from "@modelcontextprotocol/server";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import canonicalize from "canonicalize";
|
|
4
|
+
const evidenceUri = (evidenceId) => `rea://evidence/${evidenceId}`;
|
|
5
|
+
/** Expose immutable session-owned Evidence v2 records as MCP resources. */
|
|
6
|
+
export const registerEvidenceResources = (server, session) => {
|
|
7
|
+
server.registerResource("investigation-workspace-revision", new ResourceTemplate("rea://workspace/{workspaceId}/revision/{revision}", {
|
|
8
|
+
list: () => ({
|
|
9
|
+
resources: session.investigationWorkspaces().map((workspace) => ({
|
|
10
|
+
uri: workspaceUri(workspace.workspace_id, workspace.revision),
|
|
11
|
+
name: `${workspace.workspace_id} revision ${String(workspace.revision)}`,
|
|
12
|
+
title: `${workspace.name} revision ${String(workspace.revision)}`,
|
|
13
|
+
description: "Immutable CAS-linked investigation workspace revision.",
|
|
14
|
+
mimeType: "application/json",
|
|
15
|
+
})),
|
|
16
|
+
}),
|
|
17
|
+
complete: {
|
|
18
|
+
workspaceId: (prefix) => [
|
|
19
|
+
...new Set(session
|
|
20
|
+
.investigationWorkspaces()
|
|
21
|
+
.map(({ workspace_id }) => workspace_id)),
|
|
22
|
+
].filter((workspaceId) => workspaceId.startsWith(prefix)),
|
|
23
|
+
revision: (prefix, context) => session
|
|
24
|
+
.investigationWorkspaces()
|
|
25
|
+
.filter(({ workspace_id }) => context?.arguments?.workspaceId === workspace_id)
|
|
26
|
+
.map(({ revision }) => String(revision))
|
|
27
|
+
.filter((revision) => revision.startsWith(prefix)),
|
|
28
|
+
},
|
|
29
|
+
}), {
|
|
30
|
+
title: "Investigation workspace revision",
|
|
31
|
+
description: "Immutable session-retained workspace with CAS linkage.",
|
|
32
|
+
mimeType: "application/json",
|
|
33
|
+
}, (uri, variables) => {
|
|
34
|
+
const workspaceId = stringVariable(variables.workspaceId, uri.href);
|
|
35
|
+
const revisionText = stringVariable(variables.revision, uri.href);
|
|
36
|
+
if (!/^\d+$/u.test(revisionText))
|
|
37
|
+
throw new ResourceNotFoundError(uri.href);
|
|
38
|
+
const workspace = session.investigationWorkspace(workspaceId, Number(revisionText));
|
|
39
|
+
if (workspace === undefined)
|
|
40
|
+
throw new ResourceNotFoundError(uri.href);
|
|
41
|
+
return jsonResource(uri.href, workspace);
|
|
42
|
+
});
|
|
43
|
+
server.registerResource("session-evidence", new ResourceTemplate("rea://evidence/{evidenceId}", {
|
|
44
|
+
list: () => ({
|
|
45
|
+
resources: session.exportEvidenceBundle().records.map((evidence) => ({
|
|
46
|
+
uri: evidenceUri(evidence.evidence_id),
|
|
47
|
+
name: evidence.evidence_id,
|
|
48
|
+
title: `${evidence.operation} evidence`,
|
|
49
|
+
description: `Session-owned Evidence v2 record for ${evidence.operation}.`,
|
|
50
|
+
mimeType: "application/json",
|
|
51
|
+
})),
|
|
52
|
+
}),
|
|
53
|
+
complete: {
|
|
54
|
+
evidenceId: (prefix) => session
|
|
55
|
+
.exportEvidenceBundle()
|
|
56
|
+
.records.map(({ evidence_id }) => evidence_id)
|
|
57
|
+
.filter((evidenceId) => evidenceId.startsWith(prefix)),
|
|
58
|
+
},
|
|
59
|
+
}), {
|
|
60
|
+
title: "Session evidence",
|
|
61
|
+
description: "Immutable Evidence v2 records owned by this REA session.",
|
|
62
|
+
mimeType: "application/json",
|
|
63
|
+
}, (uri, variables) => {
|
|
64
|
+
const evidenceId = variables.evidenceId;
|
|
65
|
+
if (typeof evidenceId !== "string")
|
|
66
|
+
throw new ResourceNotFoundError(uri.href);
|
|
67
|
+
const evidence = session.evidenceById(evidenceId);
|
|
68
|
+
if (evidence === undefined)
|
|
69
|
+
throw new ResourceNotFoundError(uri.href);
|
|
70
|
+
return {
|
|
71
|
+
contents: [
|
|
72
|
+
{
|
|
73
|
+
uri: uri.href,
|
|
74
|
+
mimeType: "application/json",
|
|
75
|
+
text: JSON.stringify(evidence, null, 2),
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
server.registerResource("active-residual-unknowns", "rea://unknowns/active", {
|
|
81
|
+
title: "Active residual unknowns",
|
|
82
|
+
description: "Current unresolved session-owned residual unknown heads.",
|
|
83
|
+
mimeType: "application/json",
|
|
84
|
+
}, (uri) => jsonResource(uri.href, session.listUnknowns().filter(({ status }) => status !== "resolved")));
|
|
85
|
+
server.registerResource("analysis-snapshot", new ResourceTemplate("rea://snapshot/{snapshotDigest}", {
|
|
86
|
+
list: () => {
|
|
87
|
+
const snapshot = session.exportAnalysisSnapshot();
|
|
88
|
+
if (!snapshot.ok)
|
|
89
|
+
return { resources: [] };
|
|
90
|
+
const digest = contentDigest(snapshot.value);
|
|
91
|
+
return {
|
|
92
|
+
resources: [
|
|
93
|
+
{
|
|
94
|
+
uri: `rea://snapshot/${digest}`,
|
|
95
|
+
name: digest,
|
|
96
|
+
title: "Current analysis snapshot",
|
|
97
|
+
description: "Immutable projection of the current target cache.",
|
|
98
|
+
mimeType: "application/json",
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
}), {
|
|
104
|
+
title: "Analysis snapshot",
|
|
105
|
+
description: "Current provider-neutral analysis snapshot by content digest.",
|
|
106
|
+
mimeType: "application/json",
|
|
107
|
+
}, (uri, variables) => {
|
|
108
|
+
const requested = stringVariable(variables.snapshotDigest, uri.href);
|
|
109
|
+
const snapshot = session.exportAnalysisSnapshot();
|
|
110
|
+
if (!snapshot.ok || contentDigest(snapshot.value) !== requested)
|
|
111
|
+
throw new ResourceNotFoundError(uri.href);
|
|
112
|
+
return jsonResource(uri.href, snapshot.value);
|
|
113
|
+
});
|
|
114
|
+
server.registerResource("artifact-page", new ResourceTemplate("rea://artifact/{manifestId}/{collection}", {
|
|
115
|
+
list: undefined,
|
|
116
|
+
complete: {
|
|
117
|
+
collection: (prefix) => ["nodes", "occurrences", "edges"].filter((value) => value.startsWith(prefix)),
|
|
118
|
+
},
|
|
119
|
+
}), {
|
|
120
|
+
title: "Artifact graph page",
|
|
121
|
+
description: "Deterministically paged artifact graph collection.",
|
|
122
|
+
mimeType: "application/json",
|
|
123
|
+
}, (uri, variables) => {
|
|
124
|
+
const manifestId = stringVariable(variables.manifestId, uri.href);
|
|
125
|
+
const collection = stringVariable(variables.collection, uri.href);
|
|
126
|
+
if (!ARTIFACT_COLLECTIONS.has(collection))
|
|
127
|
+
throw new ResourceNotFoundError(uri.href);
|
|
128
|
+
const evidence = session
|
|
129
|
+
.exportEvidenceBundle()
|
|
130
|
+
.records.find((record) => artifactManifestId(record.normalized_result) === manifestId);
|
|
131
|
+
if (evidence === undefined)
|
|
132
|
+
throw new ResourceNotFoundError(uri.href);
|
|
133
|
+
const result = objectValue(evidence.normalized_result);
|
|
134
|
+
const page = result?.[collection];
|
|
135
|
+
if (page === undefined)
|
|
136
|
+
throw new ResourceNotFoundError(uri.href);
|
|
137
|
+
return jsonResource(uri.href, {
|
|
138
|
+
evidence_id: evidence.evidence_id,
|
|
139
|
+
manifest_id: manifestId,
|
|
140
|
+
collection,
|
|
141
|
+
page,
|
|
142
|
+
provider: evidence.provider,
|
|
143
|
+
limitations: evidence.limitations,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
server.registerResource("function-dossier", new ResourceTemplate("rea://function/{targetSha256}/{address}", {
|
|
147
|
+
list: undefined,
|
|
148
|
+
}), {
|
|
149
|
+
title: "Function dossier",
|
|
150
|
+
description: "Session-owned analyze_function dossier with provenance.",
|
|
151
|
+
mimeType: "application/json",
|
|
152
|
+
}, (uri, variables) => {
|
|
153
|
+
const targetSha256 = stringVariable(variables.targetSha256, uri.href);
|
|
154
|
+
const address = stringVariable(variables.address, uri.href);
|
|
155
|
+
const evidence = session
|
|
156
|
+
.exportEvidenceBundle()
|
|
157
|
+
.records.find((record) => record.operation === "analyze_function" &&
|
|
158
|
+
record.subject?.digest.sha256 === targetSha256 &&
|
|
159
|
+
functionAddress(record.normalized_result) === address);
|
|
160
|
+
if (evidence === undefined)
|
|
161
|
+
throw new ResourceNotFoundError(uri.href);
|
|
162
|
+
return jsonResource(uri.href, evidence);
|
|
163
|
+
});
|
|
164
|
+
server.registerResource("evidence-section", new ResourceTemplate("rea://evidence/{evidenceId}/section/{section}", {
|
|
165
|
+
list: undefined,
|
|
166
|
+
complete: {
|
|
167
|
+
evidenceId: (prefix) => evidenceIds(session, prefix),
|
|
168
|
+
section: (prefix) => EVIDENCE_SECTIONS.filter((section) => section.startsWith(prefix)),
|
|
169
|
+
},
|
|
170
|
+
}), {
|
|
171
|
+
title: "Evidence section",
|
|
172
|
+
description: "One independently useful bounded section of session Evidence v2.",
|
|
173
|
+
mimeType: "application/json",
|
|
174
|
+
}, (uri, variables) => {
|
|
175
|
+
const evidenceId = stringVariable(variables.evidenceId, uri.href);
|
|
176
|
+
const section = stringVariable(variables.section, uri.href);
|
|
177
|
+
const evidence = session.evidenceById(evidenceId);
|
|
178
|
+
if (evidence === undefined)
|
|
179
|
+
throw new ResourceNotFoundError(uri.href);
|
|
180
|
+
const value = evidenceSection(evidence.normalized_result, section);
|
|
181
|
+
if (value === undefined)
|
|
182
|
+
throw new ResourceNotFoundError(uri.href);
|
|
183
|
+
return jsonResource(uri.href, {
|
|
184
|
+
evidence_id: evidence.evidence_id,
|
|
185
|
+
operation: evidence.operation,
|
|
186
|
+
authority: evidence.authority,
|
|
187
|
+
confidence: evidence.confidence,
|
|
188
|
+
provider: evidence.provider,
|
|
189
|
+
limitations: evidence.limitations,
|
|
190
|
+
section,
|
|
191
|
+
value,
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
server.registerResource("residual-unknown", new ResourceTemplate("rea://unknown/{unknownId}", {
|
|
195
|
+
list: () => ({
|
|
196
|
+
resources: session.listUnknowns().map((unknown) => ({
|
|
197
|
+
uri: `rea://unknown/${unknown.unknown_id}`,
|
|
198
|
+
name: unknown.unknown_id,
|
|
199
|
+
title: unknown.question,
|
|
200
|
+
description: `Current ${unknown.status} residual unknown revision.`,
|
|
201
|
+
mimeType: "application/json",
|
|
202
|
+
})),
|
|
203
|
+
}),
|
|
204
|
+
complete: {
|
|
205
|
+
unknownId: (prefix) => session
|
|
206
|
+
.listUnknowns()
|
|
207
|
+
.map(({ unknown_id }) => unknown_id)
|
|
208
|
+
.filter((unknownId) => unknownId.startsWith(prefix)),
|
|
209
|
+
},
|
|
210
|
+
}), {
|
|
211
|
+
title: "Residual unknown",
|
|
212
|
+
description: "Current session-owned residual unknown head.",
|
|
213
|
+
mimeType: "application/json",
|
|
214
|
+
}, (uri, variables) => {
|
|
215
|
+
const unknownId = stringVariable(variables.unknownId, uri.href);
|
|
216
|
+
const unknown = session
|
|
217
|
+
.listUnknowns()
|
|
218
|
+
.find(({ unknown_id }) => unknown_id === unknownId);
|
|
219
|
+
if (unknown === undefined)
|
|
220
|
+
throw new ResourceNotFoundError(uri.href);
|
|
221
|
+
const history = session
|
|
222
|
+
.exportEvidenceBundle()
|
|
223
|
+
.unknowns.filter(({ unknown_id }) => unknown_id === unknownId);
|
|
224
|
+
return jsonResource(uri.href, { current: unknown, history });
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
const workspaceUri = (workspaceId, revision) => `rea://workspace/${workspaceId}/revision/${String(revision)}`;
|
|
228
|
+
const EVIDENCE_SECTIONS = [
|
|
229
|
+
"result",
|
|
230
|
+
"terminal",
|
|
231
|
+
"filesystem",
|
|
232
|
+
"process",
|
|
233
|
+
"protocol",
|
|
234
|
+
"nodes",
|
|
235
|
+
"occurrences",
|
|
236
|
+
"edges",
|
|
237
|
+
];
|
|
238
|
+
const evidenceIds = (session, prefix) => session
|
|
239
|
+
.exportEvidenceBundle()
|
|
240
|
+
.records.map(({ evidence_id }) => evidence_id)
|
|
241
|
+
.filter((evidenceId) => evidenceId.startsWith(prefix));
|
|
242
|
+
const stringVariable = (value, uri) => {
|
|
243
|
+
if (typeof value !== "string")
|
|
244
|
+
throw new ResourceNotFoundError(uri);
|
|
245
|
+
return value;
|
|
246
|
+
};
|
|
247
|
+
const evidenceSection = (result, section) => {
|
|
248
|
+
if (section === "result")
|
|
249
|
+
return result;
|
|
250
|
+
if (typeof result !== "object" || result === null || Array.isArray(result))
|
|
251
|
+
return undefined;
|
|
252
|
+
if (section === "terminal")
|
|
253
|
+
return pick(result, ["frames", "rendered_frames", "interaction_events"]);
|
|
254
|
+
if (section === "filesystem")
|
|
255
|
+
return pick(result, [
|
|
256
|
+
"filesystem_checkpoints",
|
|
257
|
+
"filesystem_effects",
|
|
258
|
+
"files_before",
|
|
259
|
+
"files_after",
|
|
260
|
+
]);
|
|
261
|
+
if (section === "process")
|
|
262
|
+
return pick(result, ["process_samples", "exit", "settlement", "cleanup"]);
|
|
263
|
+
if (section === "protocol")
|
|
264
|
+
return pick(result, ["protocol_events", "shim_events"]);
|
|
265
|
+
const value = result[section];
|
|
266
|
+
return value === undefined ? undefined : value;
|
|
267
|
+
};
|
|
268
|
+
const pick = (result, names) => {
|
|
269
|
+
const entries = names.flatMap((name) => result[name] === undefined ? [] : [[name, result[name]]]);
|
|
270
|
+
return entries.length === 0 ? undefined : Object.fromEntries(entries);
|
|
271
|
+
};
|
|
272
|
+
const jsonResource = (uri, value) => ({
|
|
273
|
+
contents: [
|
|
274
|
+
{
|
|
275
|
+
uri,
|
|
276
|
+
mimeType: "application/json",
|
|
277
|
+
text: JSON.stringify(value, null, 2),
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
});
|
|
281
|
+
const ARTIFACT_COLLECTIONS = new Set(["nodes", "occurrences", "edges"]);
|
|
282
|
+
const objectValue = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
|
|
283
|
+
? value
|
|
284
|
+
: undefined;
|
|
285
|
+
const artifactManifestId = (value) => {
|
|
286
|
+
const manifest = objectValue(objectValue(value)?.manifest ?? null);
|
|
287
|
+
return typeof manifest?.manifest_id === "string"
|
|
288
|
+
? manifest.manifest_id
|
|
289
|
+
: undefined;
|
|
290
|
+
};
|
|
291
|
+
const functionAddress = (value) => {
|
|
292
|
+
const result = objectValue(value);
|
|
293
|
+
const procedure = objectValue(result?.procedure ?? null);
|
|
294
|
+
const candidate = procedure?.address ?? result?.address;
|
|
295
|
+
return typeof candidate === "string" ? candidate : undefined;
|
|
296
|
+
};
|
|
297
|
+
const contentDigest = (value) => {
|
|
298
|
+
const encoded = canonicalize(value);
|
|
299
|
+
if (encoded === undefined)
|
|
300
|
+
throw new TypeError("Resource is not canonical JSON");
|
|
301
|
+
return createHash("sha256").update(encoded).digest("hex");
|
|
302
|
+
};
|
|
@@ -3,14 +3,44 @@ import { jsonObjectSchema } from "../domain/jsonValue.js";
|
|
|
3
3
|
import { logToolExecution } from "./toolLogging.js";
|
|
4
4
|
import { toCallToolResult } from "./toolResult.js";
|
|
5
5
|
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
|
6
|
+
import { mcpProgressReporter } from "./mcpProgress.js";
|
|
7
|
+
import { AnalysisProtocolError, PermissionRequiredError, } from "../domain/errors.js";
|
|
8
|
+
import { err } from "../domain/result.js";
|
|
6
9
|
/** Register provider-backed contracts that return atomic Evidence v2 observations. */
|
|
7
10
|
export const registerEvidenceTools = (server, analysis, contracts, options) => {
|
|
8
11
|
for (const contract of contracts) {
|
|
9
12
|
server.registerTool(contract.name, toolRegistrationOptions(contract), async (input, context) => {
|
|
13
|
+
const progress = mcpProgressReporter(context);
|
|
14
|
+
await progress.report({
|
|
15
|
+
phase: contract.name,
|
|
16
|
+
completed: 0,
|
|
17
|
+
total: 1,
|
|
18
|
+
message: "started",
|
|
19
|
+
});
|
|
10
20
|
const parameters = jsonObjectSchema.parse(contract.inputSchema.parse(input));
|
|
21
|
+
if (options.permissionAuthority !== undefined) {
|
|
22
|
+
const request = permissionRequest(contract.name, parameters);
|
|
23
|
+
if (request !== undefined) {
|
|
24
|
+
const authorized = await options.permissionAuthority.authorize(request, contract.name === "extract_artifact" ? "write" : "read");
|
|
25
|
+
if (!authorized.ok)
|
|
26
|
+
return toCallToolResult(err(authorized.error instanceof PermissionRequiredError
|
|
27
|
+
? authorized.error
|
|
28
|
+
: new AnalysisProtocolError(authorized.error.message, {
|
|
29
|
+
cause: authorized.error,
|
|
30
|
+
})), contract);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
11
33
|
const execution = await logToolExecution(options.logger, contract.name, () => analysis.execute(contract.name, parameters, {
|
|
12
34
|
signal: context.mcpReq.signal,
|
|
35
|
+
progress,
|
|
13
36
|
}));
|
|
37
|
+
await progress.report({
|
|
38
|
+
phase: contract.name,
|
|
39
|
+
completed: 1,
|
|
40
|
+
total: 1,
|
|
41
|
+
message: execution.ok ? "completed" : "failed",
|
|
42
|
+
terminal: true,
|
|
43
|
+
});
|
|
14
44
|
if (!execution.ok)
|
|
15
45
|
return toCallToolResult(execution, contract);
|
|
16
46
|
const evidence = createEvidence(execution.value.subject ?? options.activeTarget?.(), execution.value.provider, {
|
|
@@ -24,7 +54,34 @@ export const registerEvidenceTools = (server, analysis, contracts, options) => {
|
|
|
24
54
|
const recorded = options.recordEvidence?.(evidence);
|
|
25
55
|
return recorded !== undefined && !recorded.ok
|
|
26
56
|
? toCallToolResult(recorded, contract)
|
|
27
|
-
: toCallToolResult({ ok: true, value: evidence }, contract
|
|
57
|
+
: toCallToolResult({ ok: true, value: evidence }, contract, {
|
|
58
|
+
evidenceResourcesAvailable: recorded !== undefined,
|
|
59
|
+
});
|
|
28
60
|
});
|
|
29
61
|
}
|
|
30
62
|
};
|
|
63
|
+
const permissionRequest = (operation, parameters) => {
|
|
64
|
+
if (operation === "extract_artifact" &&
|
|
65
|
+
typeof parameters.output_root === "string")
|
|
66
|
+
return {
|
|
67
|
+
capability: "artifact_extract",
|
|
68
|
+
roots: [parameters.output_root],
|
|
69
|
+
executables: [],
|
|
70
|
+
environment_names: [],
|
|
71
|
+
network: "none",
|
|
72
|
+
mount: false,
|
|
73
|
+
operation_identity: `extract_artifact:${parameters.output_root}`,
|
|
74
|
+
};
|
|
75
|
+
if (operation === "inventory_artifact" &&
|
|
76
|
+
parameters.native_mount_approved === true)
|
|
77
|
+
return {
|
|
78
|
+
capability: "native_mount",
|
|
79
|
+
roots: [],
|
|
80
|
+
executables: [],
|
|
81
|
+
environment_names: [],
|
|
82
|
+
network: "none",
|
|
83
|
+
mount: true,
|
|
84
|
+
operation_identity: "inventory_artifact:native_mount",
|
|
85
|
+
};
|
|
86
|
+
return undefined;
|
|
87
|
+
};
|
|
@@ -6,9 +6,10 @@ import { toCallToolResult } from "./toolResult.js";
|
|
|
6
6
|
import { recordDerivedEvidence } from "./recordDerivedEvidence.js";
|
|
7
7
|
import { resolveSessionEvidence } from "./sessionEvidence.js";
|
|
8
8
|
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
|
9
|
+
import { runDerivedOperation } from "./runDerivedOperation.js";
|
|
9
10
|
/** Register explicit Evidence-backed function comparison. */
|
|
10
11
|
export const registerFunctionComparisonTool = (server, session, contract) => {
|
|
11
|
-
server.registerTool(contract.name, toolRegistrationOptions(contract), (input) => {
|
|
12
|
+
server.registerTool(contract.name, toolRegistrationOptions(contract), async (input, context) => {
|
|
12
13
|
const parsed = functionComparisonInputSchema.parse(input);
|
|
13
14
|
const left = resolveSessionEvidence(session, parsed.left);
|
|
14
15
|
if (!left.ok)
|
|
@@ -18,7 +19,10 @@ export const registerFunctionComparisonTool = (server, session, contract) => {
|
|
|
18
19
|
return toCallToolResult(right, contract);
|
|
19
20
|
const leftIds = evidenceIds(left.value);
|
|
20
21
|
const rightIds = evidenceIds(right.value);
|
|
21
|
-
const
|
|
22
|
+
const computed = await runDerivedOperation(context, contract.name, () => compareFunctions(left.value, right.value, parsed.offset, parsed.limit));
|
|
23
|
+
if (!computed.ok)
|
|
24
|
+
return toCallToolResult(computed, contract);
|
|
25
|
+
const comparison = computed.value;
|
|
22
26
|
const evidence = createEvidence(undefined, FUNCTION_COMPARISON_PROVIDER, {
|
|
23
27
|
predicateType: "rea.function-comparison/v1",
|
|
24
28
|
operation: contract.name,
|
|
@@ -10,6 +10,9 @@ import { err, ok } from "../domain/result.js";
|
|
|
10
10
|
import { CALL_PATH_PROVIDER, CHANGED_BEHAVIOR_PROVIDER, RECONSTRUCTION_PROVIDER, STATIC_RUNTIME_PROVIDER, } from "./sessionToolPolicies.js";
|
|
11
11
|
import { toCallToolResult } from "./toolResult.js";
|
|
12
12
|
import { recordDerivedEvidence } from "./recordDerivedEvidence.js";
|
|
13
|
+
import { AnalysisProtocolError, PermissionRequiredError, } from "../domain/errors.js";
|
|
14
|
+
import { mcpProgressReporter } from "./mcpProgress.js";
|
|
15
|
+
import { runDerivedOperation } from "./runDerivedOperation.js";
|
|
13
16
|
/** Register Evidence-composed differential investigation workflows. */
|
|
14
17
|
export const registerInvestigationTools = (server, session, contracts, policies) => {
|
|
15
18
|
registerChangedBehavior(server, session, contracts[0], policies);
|
|
@@ -17,17 +20,62 @@ export const registerInvestigationTools = (server, session, contracts, policies)
|
|
|
17
20
|
registerStaticRuntime(server, session, contracts[2]);
|
|
18
21
|
registerReconstruction(server, session, contracts[3]);
|
|
19
22
|
};
|
|
20
|
-
const investigationExecution = (session, signal, inputRoots) => ({
|
|
23
|
+
const investigationExecution = (session, signal, inputRoots, integrityContinueEnabled, progress) => ({
|
|
24
|
+
session,
|
|
25
|
+
signal,
|
|
26
|
+
inputRoots,
|
|
27
|
+
integrityContinueEnabled,
|
|
28
|
+
...(progress === undefined ? {} : { progress }),
|
|
29
|
+
});
|
|
21
30
|
const isIncomplete = (status) => status === "unknown" || status === "truncated";
|
|
22
31
|
const registerChangedBehavior = (server, session, contract, policies) => {
|
|
23
32
|
server.registerTool(contract.name, contractOptions(contract), async (input, context) => {
|
|
24
33
|
const parsed = changedBehaviorInputSchema.parse(input);
|
|
25
34
|
if (parsed.investigation_run !== undefined) {
|
|
26
|
-
const
|
|
35
|
+
const progress = mcpProgressReporter(context);
|
|
36
|
+
if (policies.permissionAuthority !== undefined) {
|
|
37
|
+
const scopes = [
|
|
38
|
+
{
|
|
39
|
+
capability: "investigation_workspace_read",
|
|
40
|
+
roots: [parsed.investigation_run.workspace_path],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
capability: "investigation_workspace_write",
|
|
44
|
+
roots: [parsed.investigation_run.workspace_path],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
capability: "investigation_input",
|
|
48
|
+
roots: [
|
|
49
|
+
parsed.investigation_run.left_path,
|
|
50
|
+
parsed.investigation_run.right_path,
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
for (const scope of scopes) {
|
|
55
|
+
const authorized = await policies.permissionAuthority.authorize({
|
|
56
|
+
...scope,
|
|
57
|
+
executables: [],
|
|
58
|
+
environment_names: [],
|
|
59
|
+
network: "none",
|
|
60
|
+
mount: false,
|
|
61
|
+
operation_identity: `${contract.name}:${scope.capability}:${scope.roots.join(":")}`,
|
|
62
|
+
}, scope.capability === "investigation_input" ? "read" : "write");
|
|
63
|
+
if (!authorized.ok)
|
|
64
|
+
return toCallToolResult(err(authorized.error instanceof PermissionRequiredError
|
|
65
|
+
? authorized.error
|
|
66
|
+
: new AnalysisProtocolError(authorized.error.message, {
|
|
67
|
+
cause: authorized.error,
|
|
68
|
+
})), contract);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const investigated = await runCrossVersionInvestigation(parsed.investigation_run, policies.evidenceFiles, investigationExecution(session, context.mcpReq.signal, policies.inputRoots, policies.integrityContinueEnabled?.() ?? false, progress));
|
|
27
72
|
if (!investigated.ok)
|
|
28
73
|
return toCallToolResult(investigated, contract);
|
|
74
|
+
const workspace = investigated.value.workspace;
|
|
75
|
+
if (session.retainInvestigationWorkspace(workspace) === "added")
|
|
76
|
+
server.sendResourceListChanged();
|
|
29
77
|
const result = changedBehaviorResultSchema.parse(investigated.value.evidence.normalized_result);
|
|
30
|
-
|
|
78
|
+
const toolResult = toCallToolResult(recordWorkflowEvidence(session, investigated.value.evidence, parsed.unknown_registry_approved, isIncomplete(result.behavior_status), {
|
|
31
79
|
question: `Automatic changed behavior run ${result.investigation_run?.run_id ?? "unknown"} remains ${result.behavior_status}`,
|
|
32
80
|
domain: "changed-behavior",
|
|
33
81
|
requiredAuthority: "controlled-replay",
|
|
@@ -39,12 +87,28 @@ const registerChangedBehavior = (server, session, contract, policies) => {
|
|
|
39
87
|
},
|
|
40
88
|
],
|
|
41
89
|
}), contract);
|
|
90
|
+
return {
|
|
91
|
+
...toolResult,
|
|
92
|
+
content: [
|
|
93
|
+
...toolResult.content,
|
|
94
|
+
{
|
|
95
|
+
type: "resource_link",
|
|
96
|
+
uri: `rea://workspace/${workspace.workspace_id}/revision/${String(workspace.revision)}`,
|
|
97
|
+
name: `${workspace.workspace_id} revision ${String(workspace.revision)}`,
|
|
98
|
+
description: "Immutable CAS-linked investigation workspace revision",
|
|
99
|
+
mimeType: "application/json",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
42
103
|
}
|
|
43
104
|
const closure = evidenceClosure(session, comparisonClosure(parsed.comparisons));
|
|
44
105
|
if (!closure.ok)
|
|
45
106
|
return toCallToolResult(closure, contract);
|
|
46
107
|
const links = closure.value;
|
|
47
|
-
const
|
|
108
|
+
const computed = await runDerivedOperation(context, contract.name, () => findChangedBehavior(parsed.comparisons, parsed.offset, parsed.limit));
|
|
109
|
+
if (!computed.ok)
|
|
110
|
+
return toCallToolResult(computed, contract);
|
|
111
|
+
const result = computed.value;
|
|
48
112
|
const evidence = createEvidence(undefined, CHANGED_BEHAVIOR_PROVIDER, {
|
|
49
113
|
predicateType: "rea.changed-behavior/v1",
|
|
50
114
|
operation: contract.name,
|
|
@@ -75,13 +139,16 @@ const registerChangedBehavior = (server, session, contract, policies) => {
|
|
|
75
139
|
});
|
|
76
140
|
};
|
|
77
141
|
const registerCallPath = (server, session, contract) => {
|
|
78
|
-
server.registerTool(contract.name, contractOptions(contract), (input) => {
|
|
142
|
+
server.registerTool(contract.name, contractOptions(contract), async (input, context) => {
|
|
79
143
|
const parsed = callPathInputSchema.parse(input);
|
|
80
144
|
const closure = evidenceClosure(session, functionEvidenceIds(parsed.functions));
|
|
81
145
|
if (!closure.ok)
|
|
82
146
|
return toCallToolResult(closure, contract);
|
|
83
147
|
const links = closure.value;
|
|
84
|
-
const
|
|
148
|
+
const computed = await runDerivedOperation(context, contract.name, () => buildCallPath(parsed));
|
|
149
|
+
if (!computed.ok)
|
|
150
|
+
return toCallToolResult(computed, contract);
|
|
151
|
+
const result = computed.value;
|
|
85
152
|
const evidence = createEvidence(undefined, CALL_PATH_PROVIDER, {
|
|
86
153
|
predicateType: "rea.call-path/v1",
|
|
87
154
|
operation: contract.name,
|
|
@@ -115,7 +182,7 @@ const registerCallPath = (server, session, contract) => {
|
|
|
115
182
|
});
|
|
116
183
|
};
|
|
117
184
|
const registerStaticRuntime = (server, session, contract) => {
|
|
118
|
-
server.registerTool(contract.name, contractOptions(contract), (input) => {
|
|
185
|
+
server.registerTool(contract.name, contractOptions(contract), async (input, context) => {
|
|
119
186
|
const parsed = staticRuntimeCorrelationInputSchema.parse(input);
|
|
120
187
|
const closure = evidenceClosure(session, comparisonClosure([
|
|
121
188
|
...parsed.static_comparisons,
|
|
@@ -124,7 +191,10 @@ const registerStaticRuntime = (server, session, contract) => {
|
|
|
124
191
|
if (!closure.ok)
|
|
125
192
|
return toCallToolResult(closure, contract);
|
|
126
193
|
const links = closure.value;
|
|
127
|
-
const
|
|
194
|
+
const computed = await runDerivedOperation(context, contract.name, () => correlateStaticAndRuntime(parsed));
|
|
195
|
+
if (!computed.ok)
|
|
196
|
+
return toCallToolResult(computed, contract);
|
|
197
|
+
const result = computed.value;
|
|
128
198
|
const evidence = createEvidence(undefined, STATIC_RUNTIME_PROVIDER, {
|
|
129
199
|
predicateType: "rea.static-runtime-correlation/v1",
|
|
130
200
|
operation: contract.name,
|
|
@@ -154,7 +224,7 @@ const registerStaticRuntime = (server, session, contract) => {
|
|
|
154
224
|
});
|
|
155
225
|
};
|
|
156
226
|
const registerReconstruction = (server, session, contract) => {
|
|
157
|
-
server.registerTool(contract.name, contractOptions(contract), (input) => {
|
|
227
|
+
server.registerTool(contract.name, contractOptions(contract), async (input, context) => {
|
|
158
228
|
const parsed = reconstructionVerificationInputSchema.parse(input);
|
|
159
229
|
const owned = session.exportEvidenceBundle();
|
|
160
230
|
const ownedUnknowns = new Set(owned.unknowns.map(({ revision_digest: digest }) => digest));
|
|
@@ -162,7 +232,10 @@ const registerReconstruction = (server, session, contract) => {
|
|
|
162
232
|
if (parsed.evidence_bundle.records.some(({ evidence_id: id }) => !ownedEvidenceIds.has(id)) ||
|
|
163
233
|
parsed.evidence_bundle.unknowns.some(({ revision_digest: digest }) => !ownedUnknowns.has(digest)))
|
|
164
234
|
return toCallToolResult(err(new EvidenceIntegrityError("Reconstruction input unknown history is not present in this session")), contract);
|
|
165
|
-
const
|
|
235
|
+
const computed = await runDerivedOperation(context, contract.name, () => verifyReconstruction(parsed.specification, owned, parsed.offset, parsed.limit));
|
|
236
|
+
if (!computed.ok)
|
|
237
|
+
return toCallToolResult(computed, contract);
|
|
238
|
+
const result = computed.value;
|
|
166
239
|
const closure = evidenceClosure(session, result.evidence_links);
|
|
167
240
|
if (!closure.ok)
|
|
168
241
|
return toCallToolResult(closure, contract);
|
|
@@ -4,6 +4,7 @@ import { toCallToolResult } from "./toolResult.js";
|
|
|
4
4
|
import { logToolExecution } from "./toolLogging.js";
|
|
5
5
|
import { createEvidence } from "../domain/evidence.js";
|
|
6
6
|
import { AnalysisCapabilityUnavailableError, UnknownRegistryError, } from "../domain/errors.js";
|
|
7
|
+
import { mcpProgressReporter } from "./mcpProgress.js";
|
|
7
8
|
/** Register direct bridge proxies, preserving MCP cancellation and typed errors. */
|
|
8
9
|
export const registerOfficialTools = (server, analysis, options) => {
|
|
9
10
|
for (const contract of OFFICIAL_TOOL_CONTRACTS) {
|
|
@@ -23,9 +24,24 @@ const registerOfficialTool = (server, analysis, contract, registration) => {
|
|
|
23
24
|
annotations: contract.annotations,
|
|
24
25
|
}, async (input, context) => {
|
|
25
26
|
const arguments_ = projectOfficialArguments(contract, input);
|
|
27
|
+
const progress = mcpProgressReporter(context);
|
|
28
|
+
await progress.report({
|
|
29
|
+
phase: contract.name,
|
|
30
|
+
completed: 0,
|
|
31
|
+
total: 1,
|
|
32
|
+
message: "started",
|
|
33
|
+
});
|
|
26
34
|
const result = await logToolExecution(registration.logger, contract.name, () => analysis.execute(contract.name, arguments_, {
|
|
27
35
|
signal: context.mcpReq.signal,
|
|
36
|
+
progress,
|
|
28
37
|
}));
|
|
38
|
+
await progress.report({
|
|
39
|
+
phase: contract.name,
|
|
40
|
+
completed: 1,
|
|
41
|
+
total: 1,
|
|
42
|
+
message: result.ok ? "completed" : "failed",
|
|
43
|
+
terminal: true,
|
|
44
|
+
});
|
|
29
45
|
if (result.ok) {
|
|
30
46
|
const evidence = createEvidence(result.value.subject ?? registration.activeTarget?.(), result.value.provider, {
|
|
31
47
|
operation: contract.name,
|
|
@@ -38,7 +54,9 @@ const registerOfficialTool = (server, analysis, contract, registration) => {
|
|
|
38
54
|
const recorded = registration.recordEvidence?.(evidence);
|
|
39
55
|
if (recorded !== undefined && !recorded.ok)
|
|
40
56
|
return toCallToolResult(recorded, contract);
|
|
41
|
-
return toCallToolResult({ ok: true, value: evidence }, contract
|
|
57
|
+
return toCallToolResult({ ok: true, value: evidence }, contract, {
|
|
58
|
+
evidenceResourcesAvailable: recorded !== undefined,
|
|
59
|
+
});
|
|
42
60
|
}
|
|
43
61
|
if (result.error instanceof AnalysisCapabilityUnavailableError &&
|
|
44
62
|
approvedUnknownTracking(input) &&
|
|
@@ -10,9 +10,10 @@ import { err } from "../domain/result.js";
|
|
|
10
10
|
import { PROCESS_PROVIDER } from "./sessionToolPolicies.js";
|
|
11
11
|
import { toCallToolResult } from "./toolResult.js";
|
|
12
12
|
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
|
13
|
+
import { runDerivedOperation } from "./runDerivedOperation.js";
|
|
13
14
|
/** Register deterministic process-capture comparison and contradiction tracking. */
|
|
14
15
|
export const registerProcessComparisonTool = (server, session, contract, now = Date.now) => {
|
|
15
|
-
server.registerTool(contract.name, toolRegistrationOptions(contract), (input) => {
|
|
16
|
+
server.registerTool(contract.name, toolRegistrationOptions(contract), async (input, context) => {
|
|
16
17
|
const parsed = z
|
|
17
18
|
.object({
|
|
18
19
|
left_evidence_id: z.string(),
|
|
@@ -28,12 +29,15 @@ export const registerProcessComparisonTool = (server, session, contract, now = D
|
|
|
28
29
|
return toCallToolResult(validated, contract);
|
|
29
30
|
let comparison;
|
|
30
31
|
try {
|
|
31
|
-
|
|
32
|
+
const computed = await runDerivedOperation(context, contract.name, () => compareProcessCaptures(parsed.left, parsed.right, {
|
|
32
33
|
...(parsed.max_capture_age_ms === undefined
|
|
33
34
|
? {}
|
|
34
35
|
: { maxCaptureAgeMs: parsed.max_capture_age_ms }),
|
|
35
36
|
now,
|
|
36
|
-
});
|
|
37
|
+
}));
|
|
38
|
+
if (!computed.ok)
|
|
39
|
+
return toCallToolResult(computed, contract);
|
|
40
|
+
comparison = computed.value;
|
|
37
41
|
}
|
|
38
42
|
catch (cause) {
|
|
39
43
|
return toCallToolResult(err(new EvidenceIntegrityError(cause instanceof Error
|