rea-agents 1.2.0 → 1.3.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 +74 -27
- package/bridge/hopper_bridge.py +161 -12
- package/dist/application/AnalysisSnapshotCache.js +131 -0
- package/dist/application/AnalysisSnapshotFiles.js +31 -0
- package/dist/application/ArtifactInventory.js +4 -0
- package/dist/application/AuthorizedArtifactInventory.js +16 -0
- package/dist/application/BinarySession.js +66 -36
- package/dist/application/BinarySessionPort.js +1 -0
- package/dist/application/BoundedJsonFiles.js +109 -0
- package/dist/application/CrossVersionInventory.js +98 -0
- package/dist/application/CrossVersionInvestigation.js +295 -0
- package/dist/application/DirectAnalysis.js +115 -22
- package/dist/application/Doctor.js +30 -1
- package/dist/application/EvidenceBundleFiles.js +15 -108
- package/dist/application/EvidenceLedger.js +3 -2
- package/dist/application/InvestigationProviders.js +48 -0
- package/dist/application/InvestigationWorkspaceStore.js +214 -0
- package/dist/application/LinuxHopper.js +72 -48
- package/dist/application/ProcessCaptureAuthority.js +2 -2
- package/dist/application/ProcessCaptureError.js +12 -0
- package/dist/application/ProcessCaptureLifecycle.js +19 -1
- package/dist/application/ProcessCli.js +98 -31
- package/dist/application/ProcessHarness.js +2 -2
- package/dist/application/ReferenceSourceImportEntries.js +18 -3
- package/dist/application/ReferenceSourceImportTypes.js +32 -0
- package/dist/application/Setup.js +33 -60
- package/dist/application/SetupClients.js +19 -0
- package/dist/application/SetupInstallFailure.js +53 -0
- package/dist/application/SetupPlan.js +31 -0
- package/dist/application/Uninstall.js +33 -23
- package/dist/application/UnknownEvidence.js +33 -0
- package/dist/application/Upgrade.js +1 -1
- package/dist/application/runtime.js +1 -1
- package/dist/artifacts/ArtifactProvider.js +2 -5
- package/dist/cli.js +31 -11
- package/dist/cliEvidenceCommands.js +11 -12
- package/dist/cliInvestigationCommands.js +86 -0
- package/dist/cliOutput.js +41 -0
- package/dist/cliProcessCommands.js +14 -4
- package/dist/config.js +39 -24
- package/dist/contracts/promptContracts.js +256 -0
- package/dist/contracts/sessionLifecycleInputs.js +11 -0
- package/dist/contracts/toolContractTypes.js +1 -0
- package/dist/contracts/toolContracts.js +10 -6
- package/dist/contracts/toolOutputSchemas.js +33 -4
- package/dist/domain/analysisSnapshot.js +149 -0
- package/dist/domain/changedBehavior.js +30 -7
- package/dist/domain/errors.js +180 -41
- package/dist/domain/evidence.js +4 -2
- package/dist/domain/evidenceBundle.js +28 -0
- package/dist/domain/hopperStartupFailure.js +55 -0
- package/dist/domain/investigationWorkspace.js +214 -0
- package/dist/domain/jsonValue.js +2 -0
- package/dist/domain/nativeInspection.js +3 -2
- package/dist/domain/processCapture.js +5 -4
- package/dist/domain/processComparison.js +5 -4
- package/dist/hopper/BridgeLauncher.js +47 -20
- package/dist/hopper/HopperClient.js +17 -2
- package/dist/hopper/HopperProvider.js +1 -0
- package/dist/main.js +47 -24
- package/dist/server/createServer.js +2 -0
- package/dist/server/promptCompletion.js +144 -0
- package/dist/server/registerEnhancedTools.js +2 -6
- package/dist/server/registerEvidenceTools.js +2 -8
- package/dist/server/registerFunctionComparisonTool.js +1 -1
- package/dist/server/registerInvestigationTools.js +31 -11
- package/dist/server/registerOfficialTools.js +6 -13
- package/dist/server/registerProcessComparisonTool.js +1 -7
- package/dist/server/registerPrompts.js +67 -0
- package/dist/server/registerSessionStatusTool.js +11 -0
- package/dist/server/registerSessionTools.js +52 -18
- package/dist/server/sessionEvidence.js +2 -8
- package/dist/server/sessionToolPolicies.js +1 -42
- package/dist/server/toolResult.js +7 -3
- package/install.sh +11 -11
- package/package.json +5 -3
- package/scripts/hopper-demo-x11.py +349 -0
- package/scripts/rea.mjs +6 -3
- package/skills/rea-analysis/SKILL.md +9 -2
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { artifactInventoryResultSchema } from "../domain/artifactGraph.js";
|
|
3
|
+
import { parseAddressedPage } from "../domain/hopperValues.js";
|
|
4
|
+
import { processCaptureSchema, validateProcessCapture, } from "../domain/processCapture.js";
|
|
5
|
+
const COMPLETION_SCAN_LIMIT = 10_000;
|
|
6
|
+
const PROCEDURE_PAGE_SIZE = 500;
|
|
7
|
+
const MAX_COMPLETION_VALUE_LENGTH = 4_096;
|
|
8
|
+
const documentListSchema = z.array(z.string().min(1));
|
|
9
|
+
const providerStatusSchema = z.object({
|
|
10
|
+
providers: z.array(z.object({ id: z.string().min(1) })),
|
|
11
|
+
});
|
|
12
|
+
/** Build bounded live completion over the active provider and session ledger. */
|
|
13
|
+
export const createPromptCompletionSource = (analysis, session) => ({
|
|
14
|
+
async complete(kind, value, context) {
|
|
15
|
+
if (value.length > MAX_COMPLETION_VALUE_LENGTH)
|
|
16
|
+
return [];
|
|
17
|
+
const candidates = await completionCandidates(kind, analysis, session, context);
|
|
18
|
+
const prefix = normalize(value);
|
|
19
|
+
return uniqueSorted(candidates)
|
|
20
|
+
.filter((candidate) => normalize(candidate).startsWith(prefix))
|
|
21
|
+
.slice(0, COMPLETION_SCAN_LIMIT);
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
const completionCandidates = async (kind, analysis, session, context) => {
|
|
25
|
+
switch (kind) {
|
|
26
|
+
case "document":
|
|
27
|
+
return documentCandidates(analysis);
|
|
28
|
+
case "procedure":
|
|
29
|
+
return procedureCandidates(analysis, context?.arguments?.document);
|
|
30
|
+
case "provider":
|
|
31
|
+
return providerCandidates(session);
|
|
32
|
+
case "evidence":
|
|
33
|
+
case "capture":
|
|
34
|
+
case "manifest":
|
|
35
|
+
case "occurrence":
|
|
36
|
+
return evidenceCandidates(kind, session);
|
|
37
|
+
case "unknown":
|
|
38
|
+
return unknownCandidates(session);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const documentCandidates = async (analysis) => {
|
|
42
|
+
const result = await analysis.execute("list_documents", {});
|
|
43
|
+
if (!result.ok)
|
|
44
|
+
return [];
|
|
45
|
+
const parsed = documentListSchema.safeParse(result.value.result);
|
|
46
|
+
return parsed.success ? parsed.data.slice(0, COMPLETION_SCAN_LIMIT) : [];
|
|
47
|
+
};
|
|
48
|
+
const procedureCandidates = async (analysis, document) => {
|
|
49
|
+
if (document !== undefined && document.length > MAX_COMPLETION_VALUE_LENGTH)
|
|
50
|
+
return [];
|
|
51
|
+
const byAddress = new Map();
|
|
52
|
+
let offset = 0;
|
|
53
|
+
let exhaustive = false;
|
|
54
|
+
while (byAddress.size < COMPLETION_SCAN_LIMIT / 2) {
|
|
55
|
+
const result = await analysis.execute("list_procedures", {
|
|
56
|
+
offset,
|
|
57
|
+
limit: PROCEDURE_PAGE_SIZE,
|
|
58
|
+
...(document === undefined || document.length === 0 ? {} : { document }),
|
|
59
|
+
});
|
|
60
|
+
if (!result.ok)
|
|
61
|
+
break;
|
|
62
|
+
const parsed = parseAddressedPage(result.value.result);
|
|
63
|
+
if (!parsed.ok)
|
|
64
|
+
break;
|
|
65
|
+
for (const item of parsed.value.items) {
|
|
66
|
+
if (byAddress.size >= COMPLETION_SCAN_LIMIT / 2)
|
|
67
|
+
break;
|
|
68
|
+
if (!byAddress.has(item.address))
|
|
69
|
+
byAddress.set(item.address, item.name);
|
|
70
|
+
}
|
|
71
|
+
const nextOffset = parsed.value.nextOffset;
|
|
72
|
+
if (!parsed.value.hasMore) {
|
|
73
|
+
exhaustive = true;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
if (nextOffset === null || nextOffset <= offset)
|
|
77
|
+
break;
|
|
78
|
+
offset = nextOffset;
|
|
79
|
+
}
|
|
80
|
+
const addressesByName = new Map();
|
|
81
|
+
for (const [address, name] of byAddress) {
|
|
82
|
+
const addresses = addressesByName.get(name) ?? new Set();
|
|
83
|
+
addresses.add(address);
|
|
84
|
+
addressesByName.set(name, addresses);
|
|
85
|
+
}
|
|
86
|
+
const candidates = [...byAddress.keys()];
|
|
87
|
+
if (exhaustive)
|
|
88
|
+
for (const [name, addresses] of addressesByName)
|
|
89
|
+
if (addresses.size === 1 && name.length > 0)
|
|
90
|
+
candidates.push(name);
|
|
91
|
+
return candidates;
|
|
92
|
+
};
|
|
93
|
+
const providerCandidates = (session) => {
|
|
94
|
+
if (session === undefined)
|
|
95
|
+
return [];
|
|
96
|
+
const parsed = providerStatusSchema.safeParse(session.status());
|
|
97
|
+
return parsed.success
|
|
98
|
+
? parsed.data.providers.map(({ id }) => id).slice(0, COMPLETION_SCAN_LIMIT)
|
|
99
|
+
: [];
|
|
100
|
+
};
|
|
101
|
+
const evidenceCandidates = (kind, session) => {
|
|
102
|
+
if (session === undefined)
|
|
103
|
+
return [];
|
|
104
|
+
const candidates = [];
|
|
105
|
+
for (const evidence of session.exportEvidenceBundle().records) {
|
|
106
|
+
const remaining = COMPLETION_SCAN_LIMIT - candidates.length;
|
|
107
|
+
candidates.push(...evidenceValues(kind, evidence).slice(0, remaining));
|
|
108
|
+
if (candidates.length >= COMPLETION_SCAN_LIMIT)
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
return candidates;
|
|
112
|
+
};
|
|
113
|
+
const evidenceValues = (kind, evidence) => {
|
|
114
|
+
if (kind === "evidence")
|
|
115
|
+
return [evidence.evidence_id];
|
|
116
|
+
if (kind === "capture")
|
|
117
|
+
return isProcessCaptureEvidence(evidence) ? [evidence.evidence_id] : [];
|
|
118
|
+
if (evidence.operation !== "inventory_artifact")
|
|
119
|
+
return [];
|
|
120
|
+
const inventory = artifactInventoryResultSchema.safeParse(evidence.normalized_result);
|
|
121
|
+
if (!inventory.success)
|
|
122
|
+
return [];
|
|
123
|
+
if (kind === "manifest")
|
|
124
|
+
return [inventory.data.manifest.manifest_id];
|
|
125
|
+
return inventory.data.occurrences.items.map(({ occurrence_id: id }) => id);
|
|
126
|
+
};
|
|
127
|
+
const isProcessCaptureEvidence = (evidence) => {
|
|
128
|
+
if (evidence.operation !== "capture_process_scenario")
|
|
129
|
+
return false;
|
|
130
|
+
const capture = processCaptureSchema.safeParse(evidence.normalized_result);
|
|
131
|
+
return capture.success && validateProcessCapture(capture.data).length === 0;
|
|
132
|
+
};
|
|
133
|
+
const unknownCandidates = (session) => session === undefined
|
|
134
|
+
? []
|
|
135
|
+
: session
|
|
136
|
+
.listUnknowns()
|
|
137
|
+
.filter(({ status }) => status !== "resolved")
|
|
138
|
+
.map(({ unknown_id: id }) => id)
|
|
139
|
+
.slice(0, COMPLETION_SCAN_LIMIT);
|
|
140
|
+
const uniqueSorted = (values) => [
|
|
141
|
+
...new Set(values.filter((value) => value.length > 0 && value.length <= MAX_COMPLETION_VALUE_LENGTH)),
|
|
142
|
+
].sort(compareText);
|
|
143
|
+
const compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
144
|
+
const normalize = (value) => value.normalize("NFKC").toLowerCase();
|
|
@@ -4,7 +4,7 @@ import { ENHANCED_TOOL_CONTRACTS, } from "../contracts/toolContracts.js";
|
|
|
4
4
|
import { toCallToolResult } from "./toolResult.js";
|
|
5
5
|
import { logToolExecution } from "./toolLogging.js";
|
|
6
6
|
import { createEvidence } from "../domain/evidence.js";
|
|
7
|
-
import {
|
|
7
|
+
import { jsonObjectSchema } from "../domain/jsonValue.js";
|
|
8
8
|
import { enhancedInputSchemas } from "../contracts/enhancedInputs.js";
|
|
9
9
|
import { UnknownRegistryError } from "../domain/errors.js";
|
|
10
10
|
const WORKFLOW_PROVIDER = {
|
|
@@ -32,11 +32,7 @@ const registerEnhancedTool = (server, services, contract, registration) => {
|
|
|
32
32
|
outputSchema: contract.outputSchema,
|
|
33
33
|
annotations: contract.annotations,
|
|
34
34
|
}, async (input, context) => {
|
|
35
|
-
const parsedInput =
|
|
36
|
-
if (typeof parsedInput !== "object" ||
|
|
37
|
-
parsedInput === null ||
|
|
38
|
-
Array.isArray(parsedInput))
|
|
39
|
-
throw new Error("Validated enhanced input was not a JSON object");
|
|
35
|
+
const parsedInput = jsonObjectSchema.parse(enhancedInputSchemas[name].parse(input));
|
|
40
36
|
const parameters = { ...parsedInput };
|
|
41
37
|
const result = await logToolExecution(registration.logger, name, () => services.execute(name, input, context.mcpReq.signal));
|
|
42
38
|
if (result.ok) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createEvidence } from "../domain/evidence.js";
|
|
2
|
-
import {
|
|
2
|
+
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";
|
|
@@ -7,7 +7,7 @@ import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
|
|
7
7
|
export const registerEvidenceTools = (server, analysis, contracts, options) => {
|
|
8
8
|
for (const contract of contracts) {
|
|
9
9
|
server.registerTool(contract.name, toolRegistrationOptions(contract), async (input, context) => {
|
|
10
|
-
const parameters =
|
|
10
|
+
const parameters = jsonObjectSchema.parse(contract.inputSchema.parse(input));
|
|
11
11
|
const execution = await logToolExecution(options.logger, contract.name, () => analysis.execute(contract.name, parameters, {
|
|
12
12
|
signal: context.mcpReq.signal,
|
|
13
13
|
}));
|
|
@@ -28,9 +28,3 @@ export const registerEvidenceTools = (server, analysis, contracts, options) => {
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
-
const jsonObject = (input) => {
|
|
32
|
-
const parsed = jsonValueSchema.parse(input);
|
|
33
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
34
|
-
throw new TypeError("Provider tool input was not an object");
|
|
35
|
-
return parsed;
|
|
36
|
-
};
|
|
@@ -59,7 +59,7 @@ const functionUnknownInput = ({ approved, status, leftIds, rightIds, }) => {
|
|
|
59
59
|
recommended_probes: [
|
|
60
60
|
{
|
|
61
61
|
operation: "analyze_function",
|
|
62
|
-
rationale: "Capture complete dossiers
|
|
62
|
+
rationale: "Capture complete dossiers for both functions under the same target context and analysis limits.",
|
|
63
63
|
},
|
|
64
64
|
],
|
|
65
65
|
relationships: [],
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { runCrossVersionInvestigation } from "../application/CrossVersionInvestigation.js";
|
|
1
2
|
import { buildCallPath, callPathInputSchema } from "../domain/callPath.js";
|
|
2
3
|
import { correlateStaticAndRuntime, staticRuntimeCorrelationInputSchema, } from "../domain/staticRuntimeCorrelation.js";
|
|
3
4
|
import { reconstructionVerificationInputSchema, verifyReconstruction, } from "../domain/reconstructionVerification.js";
|
|
4
|
-
import { changedBehaviorInputSchema, findChangedBehavior, } from "../domain/changedBehavior.js";
|
|
5
|
+
import { changedBehaviorResultSchema, changedBehaviorInputSchema, findChangedBehavior, } from "../domain/changedBehavior.js";
|
|
5
6
|
import { createEvidence } from "../domain/evidence.js";
|
|
6
7
|
import { EvidenceIntegrityError } from "../domain/errors.js";
|
|
7
8
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
@@ -10,15 +11,35 @@ import { CALL_PATH_PROVIDER, CHANGED_BEHAVIOR_PROVIDER, RECONSTRUCTION_PROVIDER,
|
|
|
10
11
|
import { toCallToolResult } from "./toolResult.js";
|
|
11
12
|
import { recordDerivedEvidence } from "./recordDerivedEvidence.js";
|
|
12
13
|
/** Register Evidence-composed differential investigation workflows. */
|
|
13
|
-
export const registerInvestigationTools = (server, session, contracts) => {
|
|
14
|
-
registerChangedBehavior(server, session, contracts[0]);
|
|
14
|
+
export const registerInvestigationTools = (server, session, contracts, policies) => {
|
|
15
|
+
registerChangedBehavior(server, session, contracts[0], policies);
|
|
15
16
|
registerCallPath(server, session, contracts[1]);
|
|
16
17
|
registerStaticRuntime(server, session, contracts[2]);
|
|
17
18
|
registerReconstruction(server, session, contracts[3]);
|
|
18
19
|
};
|
|
19
|
-
const
|
|
20
|
-
|
|
20
|
+
const investigationExecution = (session, signal, inputRoots) => ({ session, signal, inputRoots });
|
|
21
|
+
const isIncomplete = (status) => status === "unknown" || status === "truncated";
|
|
22
|
+
const registerChangedBehavior = (server, session, contract, policies) => {
|
|
23
|
+
server.registerTool(contract.name, contractOptions(contract), async (input, context) => {
|
|
21
24
|
const parsed = changedBehaviorInputSchema.parse(input);
|
|
25
|
+
if (parsed.investigation_run !== undefined) {
|
|
26
|
+
const investigated = await runCrossVersionInvestigation(parsed.investigation_run, policies.evidenceFiles, investigationExecution(session, context.mcpReq.signal, policies.inputRoots));
|
|
27
|
+
if (!investigated.ok)
|
|
28
|
+
return toCallToolResult(investigated, contract);
|
|
29
|
+
const result = changedBehaviorResultSchema.parse(investigated.value.evidence.normalized_result);
|
|
30
|
+
return toCallToolResult(recordWorkflowEvidence(session, investigated.value.evidence, parsed.unknown_registry_approved, isIncomplete(result.behavior_status), {
|
|
31
|
+
question: `Automatic changed behavior run ${result.investigation_run?.run_id ?? "unknown"} remains ${result.behavior_status}`,
|
|
32
|
+
domain: "changed-behavior",
|
|
33
|
+
requiredAuthority: "controlled-replay",
|
|
34
|
+
requiredConfidence: "observed",
|
|
35
|
+
probes: [
|
|
36
|
+
{
|
|
37
|
+
operation: "capture_process_scenario",
|
|
38
|
+
rationale: "Capture both versions under the same bounded scenario and environment.",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
}), contract);
|
|
42
|
+
}
|
|
22
43
|
const closure = evidenceClosure(session, comparisonClosure(parsed.comparisons));
|
|
23
44
|
if (!closure.ok)
|
|
24
45
|
return toCallToolResult(closure, contract);
|
|
@@ -38,9 +59,8 @@ const registerChangedBehavior = (server, session, contract) => {
|
|
|
38
59
|
limitations: result.limitations,
|
|
39
60
|
evidenceLinks: links,
|
|
40
61
|
});
|
|
41
|
-
const recorded = recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, result.behavior_status
|
|
42
|
-
|
|
43
|
-
question: `Changed behavior result ${evidence.evidence_id} remains ${result.behavior_status}`,
|
|
62
|
+
const recorded = recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, isIncomplete(result.behavior_status), {
|
|
63
|
+
question: "Did both versions behave the same under a complete controlled replay?",
|
|
44
64
|
domain: "changed-behavior",
|
|
45
65
|
requiredAuthority: "controlled-replay",
|
|
46
66
|
requiredConfidence: "observed",
|
|
@@ -80,7 +100,7 @@ const registerCallPath = (server, session, contract) => {
|
|
|
80
100
|
evidenceLinks: links,
|
|
81
101
|
});
|
|
82
102
|
const recorded = recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, result.status === "unknown" || result.status === "truncated", {
|
|
83
|
-
question:
|
|
103
|
+
question: "Can the requested call path be established from complete analysis?",
|
|
84
104
|
domain: "call-path",
|
|
85
105
|
requiredAuthority: "shipped-artifact",
|
|
86
106
|
requiredConfidence: "derived",
|
|
@@ -120,7 +140,7 @@ const registerStaticRuntime = (server, session, contract) => {
|
|
|
120
140
|
evidenceLinks: links,
|
|
121
141
|
});
|
|
122
142
|
return toCallToolResult(recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, result.status === "unknown" || result.status === "truncated", {
|
|
123
|
-
question:
|
|
143
|
+
question: "Does runtime behavior match the available static analysis?",
|
|
124
144
|
domain: "static-runtime-correlation",
|
|
125
145
|
requiredAuthority: null,
|
|
126
146
|
requiredConfidence: "derived",
|
|
@@ -163,7 +183,7 @@ const registerReconstruction = (server, session, contract) => {
|
|
|
163
183
|
evidenceLinks: links,
|
|
164
184
|
});
|
|
165
185
|
return toCallToolResult(recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, result.status === "unknown", {
|
|
166
|
-
question:
|
|
186
|
+
question: "Does the reconstruction satisfy every declared claim?",
|
|
167
187
|
domain: "reconstruction-verification",
|
|
168
188
|
requiredAuthority: null,
|
|
169
189
|
requiredConfidence: "derived",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OFFICIAL_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
|
|
2
|
-
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
2
|
+
import { jsonObjectSchema, jsonValueSchema, } from "../domain/jsonValue.js";
|
|
3
3
|
import { toCallToolResult } from "./toolResult.js";
|
|
4
4
|
import { logToolExecution } from "./toolLogging.js";
|
|
5
5
|
import { createEvidence } from "../domain/evidence.js";
|
|
@@ -45,9 +45,9 @@ const registerOfficialTool = (server, analysis, contract, registration) => {
|
|
|
45
45
|
registration.recordUnknown !== undefined) {
|
|
46
46
|
const unknown = registration.recordUnknown({
|
|
47
47
|
approved: true,
|
|
48
|
-
question:
|
|
48
|
+
question: "The requested analysis is unavailable for the current target.",
|
|
49
49
|
severity: "medium",
|
|
50
|
-
domain: "
|
|
50
|
+
domain: "analysis-capability",
|
|
51
51
|
supporting_evidence_ids: [],
|
|
52
52
|
contradicting_evidence_ids: [],
|
|
53
53
|
required_authority: "shipped-artifact",
|
|
@@ -56,7 +56,7 @@ const registerOfficialTool = (server, analysis, contract, registration) => {
|
|
|
56
56
|
recommended_probes: [
|
|
57
57
|
{
|
|
58
58
|
operation: contract.name,
|
|
59
|
-
rationale: "
|
|
59
|
+
rationale: "Choose another analysis or target that can answer this question.",
|
|
60
60
|
},
|
|
61
61
|
],
|
|
62
62
|
relationships: [],
|
|
@@ -70,19 +70,12 @@ const registerOfficialTool = (server, analysis, contract, registration) => {
|
|
|
70
70
|
});
|
|
71
71
|
};
|
|
72
72
|
const projectOfficialArguments = (contract, input) => {
|
|
73
|
-
const parsed =
|
|
74
|
-
if (!parsed.success ||
|
|
75
|
-
typeof parsed.data !== "object" ||
|
|
76
|
-
parsed.data === null ||
|
|
77
|
-
Array.isArray(parsed.data)) {
|
|
78
|
-
// The SDK validates this with the same schema before invoking the callback.
|
|
79
|
-
throw new Error("Validated MCP tool input was not a JSON object");
|
|
80
|
-
}
|
|
73
|
+
const parsed = jsonObjectSchema.parse(contract.inputSchema.parse(input));
|
|
81
74
|
const projected = {};
|
|
82
75
|
for (const key of Object.keys(contract.inputSchema.shape)) {
|
|
83
76
|
if (key === "unknown_registry_approved")
|
|
84
77
|
continue;
|
|
85
|
-
projected[key] = parsed
|
|
78
|
+
projected[key] = jsonValueSchema.parse(parsed[key] ?? null);
|
|
86
79
|
}
|
|
87
80
|
return projected;
|
|
88
81
|
};
|
|
@@ -80,17 +80,11 @@ const validateCaptureSources = (session, parsed) => {
|
|
|
80
80
|
evidence.confidence !== "observed" ||
|
|
81
81
|
evidence.authority !== "controlled-replay" ||
|
|
82
82
|
evidenceCapture === undefined ||
|
|
83
|
-
|
|
83
|
+
canonicalize(evidenceCapture) !== canonicalize(capture))
|
|
84
84
|
return err(new EvidenceIntegrityError("Process comparison payload does not match its source Evidence"));
|
|
85
85
|
}
|
|
86
86
|
return { ok: true, value: null };
|
|
87
87
|
};
|
|
88
|
-
const canonicalJson = (value) => {
|
|
89
|
-
const encoded = canonicalize(value);
|
|
90
|
-
if (encoded === undefined)
|
|
91
|
-
throw new TypeError("Process capture could not be canonicalized");
|
|
92
|
-
return encoded;
|
|
93
|
-
};
|
|
94
88
|
const comparisonUnknownInput = (parsed, comparison) => {
|
|
95
89
|
if (parsed.unknown_registry_approved !== true ||
|
|
96
90
|
comparison.status === "unchanged")
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { completable, } from "@modelcontextprotocol/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { PROMPT_CONTRACTS, renderGuidedPrompt, } from "../contracts/promptContracts.js";
|
|
4
|
+
import { createPromptCompletionSource, } from "./promptCompletion.js";
|
|
5
|
+
const promptValuesSchema = z.record(z.string(), z.string());
|
|
6
|
+
/** Register every guided workflow and its session-scoped argument completers. */
|
|
7
|
+
export const registerGuidedPrompts = (server, analysis, session) => {
|
|
8
|
+
const completion = createPromptCompletionSource(analysis, session);
|
|
9
|
+
const prompts = new Map();
|
|
10
|
+
for (const contract of PROMPT_CONTRACTS)
|
|
11
|
+
prompts.set(contract.name, registerPrompt(server, contract, completion));
|
|
12
|
+
return {
|
|
13
|
+
update(contract) {
|
|
14
|
+
const prompt = prompts.get(contract.name);
|
|
15
|
+
if (prompt === undefined)
|
|
16
|
+
throw new RangeError(`Unknown guided prompt ${contract.name}`);
|
|
17
|
+
const argsSchema = promptArgumentsSchema(contract, completion);
|
|
18
|
+
prompt.update({
|
|
19
|
+
title: contract.title,
|
|
20
|
+
description: contract.description,
|
|
21
|
+
argsSchema,
|
|
22
|
+
callback: (arguments_) => promptResult(contract, arguments_),
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const registerPrompt = (server, contract, completion) => {
|
|
28
|
+
const argsSchema = promptArgumentsSchema(contract, completion);
|
|
29
|
+
return server.registerPrompt(contract.name, {
|
|
30
|
+
title: contract.title,
|
|
31
|
+
description: contract.description,
|
|
32
|
+
argsSchema,
|
|
33
|
+
}, (arguments_) => promptResult(contract, arguments_));
|
|
34
|
+
};
|
|
35
|
+
const promptArgumentsSchema = (contract, completion) => {
|
|
36
|
+
const shape = {};
|
|
37
|
+
for (const [name, argument] of Object.entries(contract.arguments))
|
|
38
|
+
shape[name] = argumentSchema(argument, completion);
|
|
39
|
+
return z.object(shape);
|
|
40
|
+
};
|
|
41
|
+
const argumentSchema = (argument, completion) => {
|
|
42
|
+
const value = z
|
|
43
|
+
.string()
|
|
44
|
+
.trim()
|
|
45
|
+
.min(1)
|
|
46
|
+
.max(4_096)
|
|
47
|
+
.describe(argument.description);
|
|
48
|
+
const kind = argument.completion;
|
|
49
|
+
const completed = kind === undefined
|
|
50
|
+
? value
|
|
51
|
+
: completable(value, async (partial, context) => [
|
|
52
|
+
...(await completion.complete(kind, partial, context)),
|
|
53
|
+
]);
|
|
54
|
+
return argument.required ? completed : completed.optional();
|
|
55
|
+
};
|
|
56
|
+
const promptResult = (contract, arguments_) => ({
|
|
57
|
+
description: contract.description,
|
|
58
|
+
messages: [
|
|
59
|
+
{
|
|
60
|
+
role: "user",
|
|
61
|
+
content: {
|
|
62
|
+
type: "text",
|
|
63
|
+
text: renderGuidedPrompt(contract, promptValuesSchema.parse(arguments_)),
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SESSION_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
|
|
2
|
+
import { toCallToolResult } from "./toolResult.js";
|
|
3
|
+
/** Register the read-only provider and target status operation. */
|
|
4
|
+
export const registerSessionStatusTool = (server, session, contract) => {
|
|
5
|
+
server.registerTool(contract.name, {
|
|
6
|
+
description: contract.description,
|
|
7
|
+
inputSchema: contract.inputSchema,
|
|
8
|
+
outputSchema: contract.outputSchema,
|
|
9
|
+
annotations: contract.annotations,
|
|
10
|
+
}, () => toCallToolResult({ ok: true, value: session.status() }, contract));
|
|
11
|
+
};
|
|
@@ -7,6 +7,7 @@ import { processScenarioSchema } from "../domain/processCapture.js";
|
|
|
7
7
|
import { captureProcessScenario } from "../application/ProcessHarness.js";
|
|
8
8
|
import { recordUnknownInputSchema, updateUnknownInputSchema, } from "../domain/residualUnknown.js";
|
|
9
9
|
import { readEvidenceBundle, writeEvidenceBundle, } from "../application/EvidenceBundleFiles.js";
|
|
10
|
+
import { readAnalysisSnapshot, writeAnalysisSnapshot, } from "../application/AnalysisSnapshotFiles.js";
|
|
10
11
|
import { UnknownRegistryError } from "../domain/errors.js";
|
|
11
12
|
import { DENY_EVIDENCE_FILE_POLICY, DENY_PROCESS_POLICY, } from "./sessionToolPolicies.js";
|
|
12
13
|
import { createProcessCaptureEvidence } from "../application/ProcessEvidence.js";
|
|
@@ -15,13 +16,15 @@ import { registerArtifactComparisonTool } from "./registerArtifactComparisonTool
|
|
|
15
16
|
import { registerFunctionComparisonTool } from "./registerFunctionComparisonTool.js";
|
|
16
17
|
import { registerBundleComparisonTool } from "./registerBundleComparisonTool.js";
|
|
17
18
|
import { registerInvestigationTools } from "./registerInvestigationTools.js";
|
|
19
|
+
import { closeBinaryInputSchema, openBinaryInputSchema, } from "../contracts/sessionLifecycleInputs.js";
|
|
20
|
+
import { registerSessionStatusTool } from "./registerSessionStatusTool.js";
|
|
18
21
|
const recordProcessResidualUnknowns = (session, scenario, evidence, residuals) => {
|
|
19
22
|
if (scenario.unknown_registry_approved !== true)
|
|
20
23
|
return ok(null);
|
|
21
24
|
for (const residual of residuals) {
|
|
22
25
|
const unknown = session.recordUnknown({
|
|
23
26
|
approved: true,
|
|
24
|
-
question:
|
|
27
|
+
question: `Was ${residual.scope} behavior fully observed during capture?`,
|
|
25
28
|
severity: "medium",
|
|
26
29
|
domain: `process-${residual.scope}`,
|
|
27
30
|
supporting_evidence_ids: [evidence.evidence_id],
|
|
@@ -163,7 +166,7 @@ const registerUnknownTools = ({ server, session, contracts: [listContract, recor
|
|
|
163
166
|
return toCallToolResult(session.verifyUnknownResolution(unknownId), verifyContract);
|
|
164
167
|
});
|
|
165
168
|
};
|
|
166
|
-
const registerLifecycleTools = (server, session, logger, contracts) => {
|
|
169
|
+
const registerLifecycleTools = ({ server, session, logger, contracts, snapshotFilePolicy, }) => {
|
|
167
170
|
const [openContract, closeContract, statusContract] = contracts;
|
|
168
171
|
server.registerTool(openContract.name, {
|
|
169
172
|
description: openContract.description,
|
|
@@ -171,8 +174,18 @@ const registerLifecycleTools = (server, session, logger, contracts) => {
|
|
|
171
174
|
outputSchema: openContract.outputSchema,
|
|
172
175
|
annotations: openContract.annotations,
|
|
173
176
|
}, async (input, context) => {
|
|
174
|
-
const parsed =
|
|
175
|
-
|
|
177
|
+
const parsed = openBinaryInputSchema.parse(input);
|
|
178
|
+
let snapshot;
|
|
179
|
+
if (parsed.snapshot_path !== undefined) {
|
|
180
|
+
const loaded = await readAnalysisSnapshot(parsed.snapshot_path, snapshotFilePolicy);
|
|
181
|
+
if (!loaded.ok)
|
|
182
|
+
return toCallToolResult(loaded, openContract);
|
|
183
|
+
snapshot = loaded.value;
|
|
184
|
+
}
|
|
185
|
+
const opened = await logToolExecution(logger, openContract.name, () => session.open(parsed.path, {
|
|
186
|
+
signal: context.mcpReq.signal,
|
|
187
|
+
...(snapshot === undefined ? {} : { snapshot }),
|
|
188
|
+
}));
|
|
176
189
|
return opened.ok
|
|
177
190
|
? toCallToolResult({
|
|
178
191
|
ok: true,
|
|
@@ -192,23 +205,40 @@ const registerLifecycleTools = (server, session, logger, contracts) => {
|
|
|
192
205
|
inputSchema: closeContract.inputSchema,
|
|
193
206
|
outputSchema: closeContract.outputSchema,
|
|
194
207
|
annotations: closeContract.annotations,
|
|
195
|
-
}, async () =>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
208
|
+
}, async (input) => {
|
|
209
|
+
const parsed = closeBinaryInputSchema.parse(input);
|
|
210
|
+
if (parsed.snapshot_path !== undefined) {
|
|
211
|
+
const snapshot = session.exportAnalysisSnapshot();
|
|
212
|
+
if (!snapshot.ok)
|
|
213
|
+
return toCallToolResult(snapshot, closeContract);
|
|
214
|
+
const written = await writeAnalysisSnapshot(snapshot.value, parsed.snapshot_path, parsed.overwrite, snapshotFilePolicy);
|
|
215
|
+
if (!written.ok)
|
|
216
|
+
return toCallToolResult(written, closeContract);
|
|
217
|
+
const closed = await logToolExecution(logger, closeContract.name, () => session.close());
|
|
218
|
+
return closed.ok
|
|
219
|
+
? toCallToolResult(ok({
|
|
220
|
+
path: written.value.path,
|
|
221
|
+
bytes: written.value.bytes,
|
|
222
|
+
entries: snapshot.value.entries.length,
|
|
223
|
+
}), closeContract)
|
|
224
|
+
: toCallToolResult(closed, closeContract);
|
|
225
|
+
}
|
|
226
|
+
return toCallToolResult(await logToolExecution(logger, closeContract.name, () => session.close()), closeContract);
|
|
227
|
+
});
|
|
228
|
+
registerSessionStatusTool(server, session, statusContract);
|
|
202
229
|
};
|
|
203
230
|
export const registerSessionTools = (server, session, logger, options = {}) => {
|
|
204
231
|
const processPolicy = options.processPolicy ?? DENY_PROCESS_POLICY;
|
|
205
232
|
const evidenceFilePolicy = options.evidenceFilePolicy ?? DENY_EVIDENCE_FILE_POLICY;
|
|
233
|
+
const analysisSnapshotFilePolicy = options.analysisSnapshotFilePolicy ?? DENY_EVIDENCE_FILE_POLICY;
|
|
206
234
|
const [openContract, closeContract, statusContract, exportContract, importContract, captureContract, compareContract, compareArtifactsContract, compareFunctionsContract, compareBundlesContract, changedBehaviorContract, callPathContract, staticRuntimeContract, reconstructionContract, listUnknownContract, recordUnknownContract, updateUnknownContract, verifyUnknownContract,] = SESSION_TOOL_CONTRACTS;
|
|
207
|
-
registerLifecycleTools(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
235
|
+
registerLifecycleTools({
|
|
236
|
+
server,
|
|
237
|
+
session,
|
|
238
|
+
logger,
|
|
239
|
+
contracts: [openContract, closeContract, statusContract],
|
|
240
|
+
snapshotFilePolicy: analysisSnapshotFilePolicy,
|
|
241
|
+
});
|
|
212
242
|
registerEvidenceTools({
|
|
213
243
|
server,
|
|
214
244
|
session,
|
|
@@ -227,12 +257,16 @@ export const registerSessionTools = (server, session, logger, options = {}) => {
|
|
|
227
257
|
registerArtifactComparisonTool(server, session, compareArtifactsContract);
|
|
228
258
|
registerFunctionComparisonTool(server, session, compareFunctionsContract);
|
|
229
259
|
registerBundleComparisonTool(server, session, compareBundlesContract);
|
|
230
|
-
|
|
260
|
+
const investigationContracts = [
|
|
231
261
|
changedBehaviorContract,
|
|
232
262
|
callPathContract,
|
|
233
263
|
staticRuntimeContract,
|
|
234
264
|
reconstructionContract,
|
|
235
|
-
]
|
|
265
|
+
];
|
|
266
|
+
registerInvestigationTools(server, session, investigationContracts, {
|
|
267
|
+
evidenceFiles: evidenceFilePolicy,
|
|
268
|
+
inputRoots: options.investigationInputRoots ?? [],
|
|
269
|
+
});
|
|
236
270
|
registerUnknownTools({
|
|
237
271
|
server,
|
|
238
272
|
session,
|
|
@@ -9,7 +9,7 @@ export const resolveSessionEvidence = (session, input) => {
|
|
|
9
9
|
for (const item of supplied) {
|
|
10
10
|
const parsed = parseEvidence(item);
|
|
11
11
|
const owned = session.evidenceById(parsed.evidence_id);
|
|
12
|
-
if (owned === undefined ||
|
|
12
|
+
if (owned === undefined || canonicalize(owned) !== canonicalize(parsed))
|
|
13
13
|
return err(new EvidenceIntegrityError("Comparison input does not match its session-owned Evidence"));
|
|
14
14
|
authoritative.push(owned);
|
|
15
15
|
}
|
|
@@ -18,11 +18,5 @@ export const resolveSessionEvidence = (session, input) => {
|
|
|
18
18
|
/** Check exact ownership for a single Evidence record. */
|
|
19
19
|
export const isSessionEvidence = (session, input) => {
|
|
20
20
|
const owned = session.evidenceById(input.evidence_id);
|
|
21
|
-
return owned !== undefined &&
|
|
22
|
-
};
|
|
23
|
-
const canonicalJson = (value) => {
|
|
24
|
-
const encoded = canonicalize(value);
|
|
25
|
-
if (encoded === undefined)
|
|
26
|
-
throw new TypeError("Evidence could not be canonicalized");
|
|
27
|
-
return encoded;
|
|
21
|
+
return owned !== undefined && canonicalize(owned) === canonicalize(input);
|
|
28
22
|
};
|
|
@@ -15,45 +15,4 @@ export const DENY_EVIDENCE_FILE_POLICY = {
|
|
|
15
15
|
maxNodes: 1_000_000,
|
|
16
16
|
};
|
|
17
17
|
export { PROCESS_PROVIDER } from "../application/ProcessEvidence.js";
|
|
18
|
-
|
|
19
|
-
export const ARTIFACT_COMPARISON_PROVIDER = {
|
|
20
|
-
id: "rea-artifact-comparison",
|
|
21
|
-
name: "REA artifact comparison",
|
|
22
|
-
version: "1",
|
|
23
|
-
};
|
|
24
|
-
/** Provider identity for deterministic cross-function comparisons. */
|
|
25
|
-
export const FUNCTION_COMPARISON_PROVIDER = {
|
|
26
|
-
id: "rea-function-comparison",
|
|
27
|
-
name: "REA function comparison",
|
|
28
|
-
version: "1",
|
|
29
|
-
};
|
|
30
|
-
/** Provider identity for deterministic Evidence bundle comparisons. */
|
|
31
|
-
export const BUNDLE_COMPARISON_PROVIDER = {
|
|
32
|
-
id: "rea-bundle-comparison",
|
|
33
|
-
name: "REA Evidence bundle comparison",
|
|
34
|
-
version: "1",
|
|
35
|
-
};
|
|
36
|
-
/** Provider identity for comparison-Evidence behavior aggregation. */
|
|
37
|
-
export const CHANGED_BEHAVIOR_PROVIDER = {
|
|
38
|
-
id: "rea-changed-behavior",
|
|
39
|
-
name: "REA changed behavior investigation",
|
|
40
|
-
version: "1",
|
|
41
|
-
};
|
|
42
|
-
/** Provider identity for Evidence-backed call-path reconstruction. */
|
|
43
|
-
export const CALL_PATH_PROVIDER = {
|
|
44
|
-
id: "rea-call-path",
|
|
45
|
-
name: "REA call path reconstruction",
|
|
46
|
-
version: "1",
|
|
47
|
-
};
|
|
48
|
-
/** Provider identity for explicit static/runtime hypothesis correlation. */
|
|
49
|
-
export const STATIC_RUNTIME_PROVIDER = {
|
|
50
|
-
id: "rea-static-runtime-correlation",
|
|
51
|
-
name: "REA static/runtime correlation",
|
|
52
|
-
version: "1",
|
|
53
|
-
};
|
|
54
|
-
/** Provider identity for finite reconstruction specification verification. */
|
|
55
|
-
export const RECONSTRUCTION_PROVIDER = {
|
|
56
|
-
id: "rea-reconstruction-verifier",
|
|
57
|
-
name: "REA reconstruction verifier",
|
|
58
|
-
version: "1",
|
|
59
|
-
};
|
|
18
|
+
export { ARTIFACT_COMPARISON_PROVIDER, BUNDLE_COMPARISON_PROVIDER, CALL_PATH_PROVIDER, CHANGED_BEHAVIOR_PROVIDER, FUNCTION_COMPARISON_PROVIDER, RECONSTRUCTION_PROVIDER, STATIC_RUNTIME_PROVIDER, } from "../application/InvestigationProviders.js";
|