rea-agents 0.3.0 → 0.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 +124 -20
- package/bridge/hopper_bridge.py +141 -13
- package/dist/application/AnalysisProvider.js +12 -1
- package/dist/application/ArtifactExtraction.js +166 -0
- package/dist/application/ArtifactGraphConstruction.js +257 -0
- package/dist/application/ArtifactInventory.js +253 -0
- package/dist/application/BinarySession.js +164 -14
- package/dist/application/CompositeProvider.js +73 -0
- package/dist/application/DirectAnalysis.js +27 -3
- package/dist/application/Doctor.js +35 -6
- package/dist/application/EnhancedTools.js +8 -7
- package/dist/application/EvidenceBundleCommands.js +29 -0
- package/dist/application/EvidenceBundleFiles.js +127 -0
- package/dist/application/EvidenceLedger.js +225 -18
- package/dist/application/FilesystemSnapshot.js +124 -0
- package/dist/application/LinuxHopper.js +186 -0
- package/dist/application/LoopbackReplay.js +195 -37
- package/dist/application/ProcessHarness.js +200 -191
- package/dist/application/ProcessNormalization.js +44 -0
- package/dist/application/ProcessOwnership.js +84 -0
- package/dist/application/ProcessSampling.js +284 -0
- package/dist/application/RealHopperAssertions.js +105 -0
- package/dist/application/ReferenceSourceImport.js +182 -0
- package/dist/application/ReferenceSourceImportEntries.js +122 -0
- package/dist/application/ReferenceSourceImportPolicy.js +73 -0
- package/dist/application/ReferenceSourceImportTypes.js +18 -0
- package/dist/application/ReferenceSourceVcsAdapter.js +34 -0
- package/dist/application/Setup.js +192 -41
- package/dist/application/Uninstall.js +130 -0
- package/dist/application/runtime.js +8 -1
- package/dist/artifacts/ArtifactPaths.js +51 -0
- package/dist/artifacts/ArtifactProvider.js +130 -0
- package/dist/artifacts/ArtifactReader.js +9 -0
- package/dist/artifacts/AsarArtifactReader.js +62 -0
- package/dist/artifacts/DirectoryArtifactReader.js +107 -0
- package/dist/artifacts/MachOSliceArtifactReader.js +66 -0
- package/dist/artifacts/SafeOutputTree.js +199 -0
- package/dist/artifacts/StreamBytes.js +10 -0
- package/dist/artifacts/ZipArtifactReader.js +109 -0
- package/dist/cli.js +149 -2
- package/dist/config.js +35 -1
- package/dist/contracts/artifactComparisonExample.js +95 -0
- package/dist/contracts/artifactToolContracts.js +84 -0
- package/dist/contracts/enhancedInputs.js +4 -0
- package/dist/contracts/functionComparisonExample.js +57 -0
- package/dist/contracts/investigationExamples.js +118 -0
- package/dist/contracts/nativeToolContracts.js +52 -0
- package/dist/contracts/processCaptureExample.js +25 -0
- package/dist/contracts/toolContractExamples.js +69 -0
- package/dist/contracts/toolContracts.js +99 -36
- package/dist/contracts/toolOutputSchemas.js +154 -82
- package/dist/contracts/unknownContractExamples.js +33 -0
- package/dist/domain/artifactComparison.js +273 -0
- package/dist/domain/artifactGraph.js +194 -0
- package/dist/domain/artifactInventoryEvidence.js +150 -0
- package/dist/domain/binaryTarget.js +55 -0
- package/dist/domain/bundleComparison.js +266 -0
- package/dist/domain/callPath.js +346 -0
- package/dist/domain/changedBehavior.js +294 -0
- package/dist/domain/errors.js +192 -4
- package/dist/domain/evidence.js +41 -10
- package/dist/domain/evidenceBundle.js +187 -6
- package/dist/domain/functionComparison.js +201 -0
- package/dist/domain/functionComparisonNormalization.js +112 -0
- package/dist/domain/functionComparisonResults.js +54 -0
- package/dist/domain/functionComparisonSchemas.js +82 -0
- package/dist/domain/functionDossierEvidence.js +171 -0
- package/dist/domain/hopperValues.js +14 -7
- package/dist/domain/nativeInspection.js +142 -0
- package/dist/domain/processCapture.js +152 -54
- package/dist/domain/processComparison.js +106 -0
- package/dist/domain/reconstructionUnknowns.js +90 -0
- package/dist/domain/reconstructionVerification.js +285 -0
- package/dist/domain/reconstructionVerificationSchemas.js +126 -0
- package/dist/domain/referenceSourceClassification.js +496 -0
- package/dist/domain/referenceSourceGraph.js +376 -0
- package/dist/domain/referenceSourceImportParsing.js +235 -0
- package/dist/domain/referenceSourcePolicy.js +1 -0
- package/dist/domain/residualUnknown.js +239 -0
- package/dist/domain/staticRuntimeCorrelation.js +375 -0
- package/dist/hopper/BridgeLauncher.js +40 -3
- package/dist/hopper/HopperClient.js +14 -5
- package/dist/hopper/HopperProvider.js +57 -22
- package/dist/identity.js +1 -0
- package/dist/main.js +5 -1
- package/dist/native/CommandRunner.js +156 -0
- package/dist/native/NativeMacOSProvider.js +306 -0
- package/dist/native/NativeMachoInspection.js +135 -0
- package/dist/native/parsers/codesign.js +55 -0
- package/dist/native/parsers/demangle.js +26 -0
- package/dist/native/parsers/dyldInfo.js +25 -0
- package/dist/native/parsers/lipo.js +67 -0
- package/dist/native/parsers/otool.js +193 -0
- package/dist/native/parsers/plist.js +23 -0
- package/dist/reference/ReferenceSourceReader.js +73 -0
- package/dist/reference/ReferenceSourceReaderEntries.js +206 -0
- package/dist/reference/ReferenceSourceReaderErrors.js +19 -0
- package/dist/reference/ReferenceSourceReaderFile.js +119 -0
- package/dist/reference/ReferenceSourceReaderPaths.js +23 -0
- package/dist/reference/ReferenceSourceReaderTypes.js +2 -0
- package/dist/reference/ReferenceSourceReaderValidate.js +71 -0
- package/dist/server/createServer.js +31 -5
- package/dist/server/recordDerivedEvidence.js +10 -0
- package/dist/server/registerArtifactComparisonTool.js +62 -0
- package/dist/server/registerArtifactTools.js +6 -0
- package/dist/server/registerBundleComparisonTool.js +47 -0
- package/dist/server/registerEnhancedTools.js +64 -12
- package/dist/server/registerEvidenceTools.js +36 -0
- package/dist/server/registerFunctionComparisonTool.js +68 -0
- package/dist/server/registerInvestigationTools.js +224 -0
- package/dist/server/registerNativeTools.js +6 -0
- package/dist/server/registerOfficialTools.js +47 -14
- package/dist/server/registerProcessComparisonTool.js +106 -0
- package/dist/server/registerSessionTools.js +179 -70
- package/dist/server/sessionEvidence.js +28 -0
- package/dist/server/sessionToolPolicies.js +64 -0
- package/dist/server/toolRegistrationOptions.js +7 -0
- package/dist/server/toolResult.js +8 -5
- package/install.sh +198 -0
- package/package.json +18 -1
- package/scripts/rea.mjs +5 -1
- package/skills/rea-analysis/SKILL.md +77 -2
|
@@ -6,19 +6,21 @@ import { logToolExecution } from "./toolLogging.js";
|
|
|
6
6
|
import { createEvidence } from "../domain/evidence.js";
|
|
7
7
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
8
8
|
import { enhancedInputSchemas } from "../contracts/enhancedInputs.js";
|
|
9
|
+
import { UnknownRegistryError } from "../domain/errors.js";
|
|
10
|
+
const WORKFLOW_PROVIDER = {
|
|
11
|
+
id: "rea-workflow",
|
|
12
|
+
name: "REA composed investigation workflow",
|
|
13
|
+
version: "1",
|
|
14
|
+
};
|
|
9
15
|
/** Register composed workflows against the same port as direct bridge tools. */
|
|
10
|
-
export const registerEnhancedTools = (server, analysis,
|
|
11
|
-
id: "unidentified",
|
|
12
|
-
name: "Unidentified provider",
|
|
13
|
-
version: null,
|
|
14
|
-
}, recordEvidence) => {
|
|
16
|
+
export const registerEnhancedTools = (server, analysis, options) => {
|
|
15
17
|
const services = new EnhancedTools(analysis);
|
|
16
18
|
for (const contract of ENHANCED_TOOL_CONTRACTS) {
|
|
17
19
|
registerEnhancedTool(server, services, contract, {
|
|
18
|
-
logger,
|
|
19
|
-
activeTarget,
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
logger: options.logger,
|
|
21
|
+
activeTarget: options.activeTarget,
|
|
22
|
+
recordEvidence: options.recordEvidence,
|
|
23
|
+
recordUnknown: options.recordUnknown,
|
|
22
24
|
});
|
|
23
25
|
}
|
|
24
26
|
};
|
|
@@ -38,16 +40,66 @@ const registerEnhancedTool = (server, services, contract, registration) => {
|
|
|
38
40
|
const parameters = { ...parsedInput };
|
|
39
41
|
const result = await logToolExecution(registration.logger, name, () => services.execute(name, input, context.mcpReq.signal));
|
|
40
42
|
if (result.ok) {
|
|
41
|
-
const evidence = createEvidence(registration.activeTarget?.(),
|
|
43
|
+
const evidence = createEvidence(registration.activeTarget?.(), WORKFLOW_PROVIDER, {
|
|
42
44
|
operation: name,
|
|
43
45
|
parameters,
|
|
44
46
|
result: result.value,
|
|
45
47
|
confidence: "derived",
|
|
46
|
-
|
|
48
|
+
limitations: [
|
|
49
|
+
"Derived by an REA workflow from one or more provider observations.",
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
const recorded = registration.recordEvidence?.(evidence);
|
|
53
|
+
if (recorded !== undefined && !recorded.ok)
|
|
54
|
+
return toCallToolResult(recorded, contract);
|
|
55
|
+
const unknowns = recordWorkflowUnknowns({
|
|
56
|
+
name,
|
|
57
|
+
input: parsedInput,
|
|
58
|
+
result: result.value,
|
|
59
|
+
evidenceId: evidence.evidence_id,
|
|
60
|
+
recordUnknown: registration.recordUnknown,
|
|
47
61
|
});
|
|
48
|
-
|
|
62
|
+
if (!unknowns.ok)
|
|
63
|
+
return toCallToolResult(unknowns, contract);
|
|
49
64
|
return toCallToolResult({ ok: true, value: evidence }, contract);
|
|
50
65
|
}
|
|
51
66
|
return toCallToolResult(result, contract);
|
|
52
67
|
});
|
|
53
68
|
};
|
|
69
|
+
const recordWorkflowUnknowns = ({ name, input, result, evidenceId, recordUnknown, }) => {
|
|
70
|
+
if (name !== "trace_feature" ||
|
|
71
|
+
input.unknown_registry_approved !== true ||
|
|
72
|
+
recordUnknown === undefined ||
|
|
73
|
+
typeof result !== "object" ||
|
|
74
|
+
result === null ||
|
|
75
|
+
Array.isArray(result) ||
|
|
76
|
+
!Array.isArray(result.residual_unknowns))
|
|
77
|
+
return { ok: true, value: null };
|
|
78
|
+
for (const question of result.residual_unknowns) {
|
|
79
|
+
if (typeof question !== "string")
|
|
80
|
+
continue;
|
|
81
|
+
const recorded = recordUnknown({
|
|
82
|
+
approved: true,
|
|
83
|
+
question,
|
|
84
|
+
severity: "medium",
|
|
85
|
+
domain: "control-flow",
|
|
86
|
+
supporting_evidence_ids: [evidenceId],
|
|
87
|
+
contradicting_evidence_ids: [],
|
|
88
|
+
required_authority: "shipped-artifact",
|
|
89
|
+
required_confidence: "observed",
|
|
90
|
+
required_environment: null,
|
|
91
|
+
recommended_probes: [
|
|
92
|
+
{
|
|
93
|
+
operation: "trace_feature",
|
|
94
|
+
rationale: "Continue with a larger bounded budget.",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
relationships: [],
|
|
98
|
+
});
|
|
99
|
+
if (!recorded.ok &&
|
|
100
|
+
!(recorded.error instanceof UnknownRegistryError &&
|
|
101
|
+
recorded.error.reason === "already-exists"))
|
|
102
|
+
return recorded;
|
|
103
|
+
}
|
|
104
|
+
return { ok: true, value: null };
|
|
105
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createEvidence } from "../domain/evidence.js";
|
|
2
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
3
|
+
import { logToolExecution } from "./toolLogging.js";
|
|
4
|
+
import { toCallToolResult } from "./toolResult.js";
|
|
5
|
+
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
|
6
|
+
/** Register provider-backed contracts that return atomic Evidence v2 observations. */
|
|
7
|
+
export const registerEvidenceTools = (server, analysis, contracts, options) => {
|
|
8
|
+
for (const contract of contracts) {
|
|
9
|
+
server.registerTool(contract.name, toolRegistrationOptions(contract), async (input, context) => {
|
|
10
|
+
const parameters = jsonObject(contract.inputSchema.parse(input));
|
|
11
|
+
const execution = await logToolExecution(options.logger, contract.name, () => analysis.execute(contract.name, parameters, {
|
|
12
|
+
signal: context.mcpReq.signal,
|
|
13
|
+
}));
|
|
14
|
+
if (!execution.ok)
|
|
15
|
+
return toCallToolResult(execution, contract);
|
|
16
|
+
const evidence = createEvidence(execution.value.subject ?? options.activeTarget?.(), execution.value.provider, {
|
|
17
|
+
operation: contract.name,
|
|
18
|
+
parameters,
|
|
19
|
+
result: execution.value.result,
|
|
20
|
+
rawResult: execution.value.rawResult,
|
|
21
|
+
limitations: execution.value.limitations,
|
|
22
|
+
locations: execution.value.locations,
|
|
23
|
+
});
|
|
24
|
+
const recorded = options.recordEvidence?.(evidence);
|
|
25
|
+
return recorded !== undefined && !recorded.ok
|
|
26
|
+
? toCallToolResult(recorded, contract)
|
|
27
|
+
: toCallToolResult({ ok: true, value: evidence }, contract);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
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
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { compareFunctions, functionComparisonInputSchema, } from "../domain/functionComparison.js";
|
|
2
|
+
import { createEvidence } from "../domain/evidence.js";
|
|
3
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
4
|
+
import { FUNCTION_COMPARISON_PROVIDER } from "./sessionToolPolicies.js";
|
|
5
|
+
import { toCallToolResult } from "./toolResult.js";
|
|
6
|
+
import { recordDerivedEvidence } from "./recordDerivedEvidence.js";
|
|
7
|
+
import { resolveSessionEvidence } from "./sessionEvidence.js";
|
|
8
|
+
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
|
9
|
+
/** Register explicit Evidence-backed function comparison. */
|
|
10
|
+
export const registerFunctionComparisonTool = (server, session, contract) => {
|
|
11
|
+
server.registerTool(contract.name, toolRegistrationOptions(contract), (input) => {
|
|
12
|
+
const parsed = functionComparisonInputSchema.parse(input);
|
|
13
|
+
const left = resolveSessionEvidence(session, parsed.left);
|
|
14
|
+
if (!left.ok)
|
|
15
|
+
return toCallToolResult(left, contract);
|
|
16
|
+
const right = resolveSessionEvidence(session, parsed.right);
|
|
17
|
+
if (!right.ok)
|
|
18
|
+
return toCallToolResult(right, contract);
|
|
19
|
+
const leftIds = evidenceIds(left.value);
|
|
20
|
+
const rightIds = evidenceIds(right.value);
|
|
21
|
+
const comparison = compareFunctions(left.value, right.value, parsed.offset, parsed.limit);
|
|
22
|
+
const evidence = createEvidence(undefined, FUNCTION_COMPARISON_PROVIDER, {
|
|
23
|
+
predicateType: "rea.function-comparison/v1",
|
|
24
|
+
operation: contract.name,
|
|
25
|
+
parameters: {
|
|
26
|
+
left_evidence_ids: leftIds,
|
|
27
|
+
right_evidence_ids: rightIds,
|
|
28
|
+
offset: parsed.offset,
|
|
29
|
+
limit: parsed.limit,
|
|
30
|
+
},
|
|
31
|
+
result: jsonValueSchema.parse(comparison),
|
|
32
|
+
confidence: "derived",
|
|
33
|
+
authority: "analyst-inference",
|
|
34
|
+
limitations: comparison.limitations,
|
|
35
|
+
evidenceLinks: [...leftIds, ...rightIds],
|
|
36
|
+
});
|
|
37
|
+
const recorded = recordDerivedEvidence(session, evidence, functionUnknownInput({
|
|
38
|
+
approved: parsed.unknown_registry_approved,
|
|
39
|
+
status: comparison.status,
|
|
40
|
+
leftIds,
|
|
41
|
+
rightIds,
|
|
42
|
+
}));
|
|
43
|
+
return toCallToolResult(recorded, contract);
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
const functionUnknownInput = ({ approved, status, leftIds, rightIds, }) => {
|
|
47
|
+
if (approved !== true || status === "unchanged")
|
|
48
|
+
return undefined;
|
|
49
|
+
return {
|
|
50
|
+
approved: true,
|
|
51
|
+
question: `Function comparison is ${status}`,
|
|
52
|
+
severity: status === "changed" ? "medium" : "high",
|
|
53
|
+
domain: "function-comparison",
|
|
54
|
+
supporting_evidence_ids: [...leftIds],
|
|
55
|
+
contradicting_evidence_ids: [...rightIds],
|
|
56
|
+
required_authority: "shipped-artifact",
|
|
57
|
+
required_confidence: "observed",
|
|
58
|
+
required_environment: null,
|
|
59
|
+
recommended_probes: [
|
|
60
|
+
{
|
|
61
|
+
operation: "analyze_function",
|
|
62
|
+
rationale: "Capture complete dossiers with equal providers and analysis limits.",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
relationships: [],
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
const evidenceIds = (input) => (Array.isArray(input) ? input : [input]).map(({ evidence_id: id }) => id);
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { buildCallPath, callPathInputSchema } from "../domain/callPath.js";
|
|
2
|
+
import { correlateStaticAndRuntime, staticRuntimeCorrelationInputSchema, } from "../domain/staticRuntimeCorrelation.js";
|
|
3
|
+
import { reconstructionVerificationInputSchema, verifyReconstruction, } from "../domain/reconstructionVerification.js";
|
|
4
|
+
import { changedBehaviorInputSchema, findChangedBehavior, } from "../domain/changedBehavior.js";
|
|
5
|
+
import { createEvidence } from "../domain/evidence.js";
|
|
6
|
+
import { EvidenceIntegrityError } from "../domain/errors.js";
|
|
7
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
8
|
+
import { err, ok } from "../domain/result.js";
|
|
9
|
+
import { CALL_PATH_PROVIDER, CHANGED_BEHAVIOR_PROVIDER, RECONSTRUCTION_PROVIDER, STATIC_RUNTIME_PROVIDER, } from "./sessionToolPolicies.js";
|
|
10
|
+
import { toCallToolResult } from "./toolResult.js";
|
|
11
|
+
import { recordDerivedEvidence } from "./recordDerivedEvidence.js";
|
|
12
|
+
/** Register Evidence-composed differential investigation workflows. */
|
|
13
|
+
export const registerInvestigationTools = (server, session, contracts) => {
|
|
14
|
+
registerChangedBehavior(server, session, contracts[0]);
|
|
15
|
+
registerCallPath(server, session, contracts[1]);
|
|
16
|
+
registerStaticRuntime(server, session, contracts[2]);
|
|
17
|
+
registerReconstruction(server, session, contracts[3]);
|
|
18
|
+
};
|
|
19
|
+
const registerChangedBehavior = (server, session, contract) => {
|
|
20
|
+
server.registerTool(contract.name, contractOptions(contract), (input) => {
|
|
21
|
+
const parsed = changedBehaviorInputSchema.parse(input);
|
|
22
|
+
const closure = evidenceClosure(session, comparisonClosure(parsed.comparisons));
|
|
23
|
+
if (!closure.ok)
|
|
24
|
+
return toCallToolResult(closure, contract);
|
|
25
|
+
const links = closure.value;
|
|
26
|
+
const result = findChangedBehavior(parsed.comparisons, parsed.offset, parsed.limit);
|
|
27
|
+
const evidence = createEvidence(undefined, CHANGED_BEHAVIOR_PROVIDER, {
|
|
28
|
+
predicateType: "rea.changed-behavior/v1",
|
|
29
|
+
operation: contract.name,
|
|
30
|
+
parameters: {
|
|
31
|
+
comparison_evidence_ids: parsed.comparisons.map(({ evidence_id: id }) => id),
|
|
32
|
+
offset: parsed.offset,
|
|
33
|
+
limit: parsed.limit,
|
|
34
|
+
},
|
|
35
|
+
result: jsonValueSchema.parse(result),
|
|
36
|
+
confidence: "derived",
|
|
37
|
+
authority: "analyst-inference",
|
|
38
|
+
limitations: result.limitations,
|
|
39
|
+
evidenceLinks: links,
|
|
40
|
+
});
|
|
41
|
+
const recorded = recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, result.behavior_status === "unknown" ||
|
|
42
|
+
result.behavior_status === "truncated", {
|
|
43
|
+
question: `Changed behavior result ${evidence.evidence_id} remains ${result.behavior_status}`,
|
|
44
|
+
domain: "changed-behavior",
|
|
45
|
+
requiredAuthority: "controlled-replay",
|
|
46
|
+
requiredConfidence: "observed",
|
|
47
|
+
probes: [
|
|
48
|
+
{
|
|
49
|
+
operation: "capture_process_scenario",
|
|
50
|
+
rationale: "Capture both versions under the same bounded scenario and environment.",
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
return toCallToolResult(recorded, contract);
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
const registerCallPath = (server, session, contract) => {
|
|
58
|
+
server.registerTool(contract.name, contractOptions(contract), (input) => {
|
|
59
|
+
const parsed = callPathInputSchema.parse(input);
|
|
60
|
+
const closure = evidenceClosure(session, functionEvidenceIds(parsed.functions));
|
|
61
|
+
if (!closure.ok)
|
|
62
|
+
return toCallToolResult(closure, contract);
|
|
63
|
+
const links = closure.value;
|
|
64
|
+
const result = buildCallPath(parsed);
|
|
65
|
+
const evidence = createEvidence(undefined, CALL_PATH_PROVIDER, {
|
|
66
|
+
predicateType: "rea.call-path/v1",
|
|
67
|
+
operation: contract.name,
|
|
68
|
+
parameters: {
|
|
69
|
+
start: parsed.start.address,
|
|
70
|
+
goal: parsed.goal.address,
|
|
71
|
+
max_depth: parsed.max_depth,
|
|
72
|
+
max_paths: parsed.max_paths,
|
|
73
|
+
offset: parsed.offset,
|
|
74
|
+
limit: parsed.limit,
|
|
75
|
+
},
|
|
76
|
+
result: jsonValueSchema.parse(result),
|
|
77
|
+
confidence: "derived",
|
|
78
|
+
authority: "analyst-inference",
|
|
79
|
+
limitations: result.limitations,
|
|
80
|
+
evidenceLinks: links,
|
|
81
|
+
});
|
|
82
|
+
const recorded = recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, result.status === "unknown" || result.status === "truncated", {
|
|
83
|
+
question: `Call path ${parsed.start.address} → ${parsed.goal.address} is ${result.status}`,
|
|
84
|
+
domain: "call-path",
|
|
85
|
+
requiredAuthority: "shipped-artifact",
|
|
86
|
+
requiredConfidence: "derived",
|
|
87
|
+
probes: [
|
|
88
|
+
{
|
|
89
|
+
operation: "analyze_function",
|
|
90
|
+
rationale: "Collect complete callee dossiers for the reported frontier addresses.",
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
return toCallToolResult(recorded, contract);
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
const registerStaticRuntime = (server, session, contract) => {
|
|
98
|
+
server.registerTool(contract.name, contractOptions(contract), (input) => {
|
|
99
|
+
const parsed = staticRuntimeCorrelationInputSchema.parse(input);
|
|
100
|
+
const closure = evidenceClosure(session, comparisonClosure([
|
|
101
|
+
...parsed.static_comparisons,
|
|
102
|
+
...parsed.runtime_comparisons,
|
|
103
|
+
]));
|
|
104
|
+
if (!closure.ok)
|
|
105
|
+
return toCallToolResult(closure, contract);
|
|
106
|
+
const links = closure.value;
|
|
107
|
+
const result = correlateStaticAndRuntime(parsed);
|
|
108
|
+
const evidence = createEvidence(undefined, STATIC_RUNTIME_PROVIDER, {
|
|
109
|
+
predicateType: "rea.static-runtime-correlation/v1",
|
|
110
|
+
operation: contract.name,
|
|
111
|
+
parameters: {
|
|
112
|
+
mapping_count: parsed.mappings.length,
|
|
113
|
+
offset: parsed.offset,
|
|
114
|
+
limit: parsed.limit,
|
|
115
|
+
},
|
|
116
|
+
result: jsonValueSchema.parse(result),
|
|
117
|
+
confidence: "inferred",
|
|
118
|
+
authority: "analyst-inference",
|
|
119
|
+
limitations: result.limitations,
|
|
120
|
+
evidenceLinks: links,
|
|
121
|
+
});
|
|
122
|
+
return toCallToolResult(recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, result.status === "unknown" || result.status === "truncated", {
|
|
123
|
+
question: `Static/runtime correlation result ${evidence.evidence_id} is ${result.status}`,
|
|
124
|
+
domain: "static-runtime-correlation",
|
|
125
|
+
requiredAuthority: null,
|
|
126
|
+
requiredConfidence: "derived",
|
|
127
|
+
probes: [
|
|
128
|
+
{
|
|
129
|
+
operation: "capture_process_scenario",
|
|
130
|
+
rationale: "Repeat runtime observations and complete the mapped static comparison Evidence.",
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
}), contract);
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
const registerReconstruction = (server, session, contract) => {
|
|
137
|
+
server.registerTool(contract.name, contractOptions(contract), (input) => {
|
|
138
|
+
const parsed = reconstructionVerificationInputSchema.parse(input);
|
|
139
|
+
const owned = session.exportEvidenceBundle();
|
|
140
|
+
const ownedUnknowns = new Set(owned.unknowns.map(({ revision_digest: digest }) => digest));
|
|
141
|
+
const ownedEvidenceIds = new Set(owned.records.map(({ evidence_id: id }) => id));
|
|
142
|
+
if (parsed.evidence_bundle.records.some(({ evidence_id: id }) => !ownedEvidenceIds.has(id)) ||
|
|
143
|
+
parsed.evidence_bundle.unknowns.some(({ revision_digest: digest }) => !ownedUnknowns.has(digest)))
|
|
144
|
+
return toCallToolResult(err(new EvidenceIntegrityError("Reconstruction input unknown history is not present in this session")), contract);
|
|
145
|
+
const result = verifyReconstruction(parsed.specification, owned, parsed.offset, parsed.limit);
|
|
146
|
+
const closure = evidenceClosure(session, result.evidence_links);
|
|
147
|
+
if (!closure.ok)
|
|
148
|
+
return toCallToolResult(closure, contract);
|
|
149
|
+
const links = closure.value;
|
|
150
|
+
const evidence = createEvidence(undefined, RECONSTRUCTION_PROVIDER, {
|
|
151
|
+
predicateType: "rea.reconstruction-verification/v1",
|
|
152
|
+
operation: contract.name,
|
|
153
|
+
parameters: {
|
|
154
|
+
specification_sha256: result.specification_sha256,
|
|
155
|
+
claim_ids: parsed.specification.claims.map(({ claim_id: id }) => id),
|
|
156
|
+
offset: parsed.offset,
|
|
157
|
+
limit: parsed.limit,
|
|
158
|
+
},
|
|
159
|
+
result: jsonValueSchema.parse(result),
|
|
160
|
+
confidence: "derived",
|
|
161
|
+
authority: "analyst-inference",
|
|
162
|
+
limitations: result.limitations,
|
|
163
|
+
evidenceLinks: links,
|
|
164
|
+
});
|
|
165
|
+
return toCallToolResult(recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, result.status === "unknown", {
|
|
166
|
+
question: `Reconstruction ${result.specification_sha256} has unknown claims`,
|
|
167
|
+
domain: "reconstruction-verification",
|
|
168
|
+
requiredAuthority: null,
|
|
169
|
+
requiredConfidence: "derived",
|
|
170
|
+
probes: result.recommended_probes.map(({ operation, rationale }) => ({
|
|
171
|
+
operation,
|
|
172
|
+
rationale,
|
|
173
|
+
})),
|
|
174
|
+
}), contract);
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
const contractOptions = (contract) => ({
|
|
178
|
+
description: contract.description,
|
|
179
|
+
inputSchema: contract.inputSchema,
|
|
180
|
+
outputSchema: contract.outputSchema,
|
|
181
|
+
annotations: contract.annotations,
|
|
182
|
+
});
|
|
183
|
+
const comparisonClosure = (comparisons) => uniqueIds(comparisons.flatMap((evidence) => [
|
|
184
|
+
evidence.evidence_id,
|
|
185
|
+
...evidence.evidence_links,
|
|
186
|
+
]));
|
|
187
|
+
const functionEvidenceIds = (groups) => uniqueIds(groups.flatMap((group) => (Array.isArray(group) ? group : [group]).map(({ evidence_id: id }) => id)));
|
|
188
|
+
const uniqueIds = (ids) => [...new Set(ids)].sort((left, right) => left.localeCompare(right));
|
|
189
|
+
const evidenceClosure = (session, seedIds) => {
|
|
190
|
+
const records = new Map(session
|
|
191
|
+
.exportEvidenceBundle()
|
|
192
|
+
.records.map((evidence) => [evidence.evidence_id, evidence]));
|
|
193
|
+
const visited = new Set();
|
|
194
|
+
const pending = [...seedIds];
|
|
195
|
+
while (pending.length > 0) {
|
|
196
|
+
const evidenceId = pending.pop();
|
|
197
|
+
if (evidenceId === undefined || visited.has(evidenceId))
|
|
198
|
+
continue;
|
|
199
|
+
const evidence = records.get(evidenceId);
|
|
200
|
+
if (evidence === undefined)
|
|
201
|
+
return err(new EvidenceIntegrityError("Investigation input has a dangling Evidence link"));
|
|
202
|
+
visited.add(evidenceId);
|
|
203
|
+
pending.push(...evidence.evidence_links);
|
|
204
|
+
}
|
|
205
|
+
return ok(uniqueIds([...visited]));
|
|
206
|
+
};
|
|
207
|
+
const recordWorkflowEvidence = (...[session, evidence, approved, unresolved, input]) => {
|
|
208
|
+
if (approved !== true || !unresolved) {
|
|
209
|
+
return recordDerivedEvidence(session, evidence, undefined);
|
|
210
|
+
}
|
|
211
|
+
return recordDerivedEvidence(session, evidence, {
|
|
212
|
+
approved: true,
|
|
213
|
+
question: input.question,
|
|
214
|
+
severity: "high",
|
|
215
|
+
domain: input.domain,
|
|
216
|
+
supporting_evidence_ids: [evidence.evidence_id],
|
|
217
|
+
contradicting_evidence_ids: [],
|
|
218
|
+
required_authority: input.requiredAuthority,
|
|
219
|
+
required_confidence: input.requiredConfidence,
|
|
220
|
+
required_environment: null,
|
|
221
|
+
recommended_probes: [...input.probes],
|
|
222
|
+
relationships: [],
|
|
223
|
+
});
|
|
224
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { NATIVE_TOOL_CONTRACTS } from "../contracts/nativeToolContracts.js";
|
|
2
|
+
import { registerEvidenceTools } from "./registerEvidenceTools.js";
|
|
3
|
+
/** Register provider-neutral static inspection operations. */
|
|
4
|
+
export const registerNativeTools = (server, analysis, options) => {
|
|
5
|
+
registerEvidenceTools(server, analysis, NATIVE_TOOL_CONTRACTS, options);
|
|
6
|
+
};
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import { OFFICIAL_TOOL_CONTRACTS
|
|
1
|
+
import { OFFICIAL_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
|
|
2
2
|
import { 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";
|
|
6
|
+
import { AnalysisCapabilityUnavailableError, UnknownRegistryError, } from "../domain/errors.js";
|
|
6
7
|
/** Register direct bridge proxies, preserving MCP cancellation and typed errors. */
|
|
7
|
-
export const registerOfficialTools = (server, analysis,
|
|
8
|
-
id: "unidentified",
|
|
9
|
-
name: "Unidentified provider",
|
|
10
|
-
version: null,
|
|
11
|
-
}, recordEvidence) => {
|
|
8
|
+
export const registerOfficialTools = (server, analysis, options) => {
|
|
12
9
|
for (const contract of OFFICIAL_TOOL_CONTRACTS) {
|
|
13
10
|
registerOfficialTool(server, analysis, contract, {
|
|
14
|
-
logger,
|
|
15
|
-
activeTarget,
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
logger: options.logger,
|
|
12
|
+
activeTarget: options.activeTarget,
|
|
13
|
+
recordEvidence: options.recordEvidence,
|
|
14
|
+
recordUnknown: options.recordUnknown,
|
|
18
15
|
});
|
|
19
16
|
}
|
|
20
17
|
};
|
|
@@ -30,15 +27,45 @@ const registerOfficialTool = (server, analysis, contract, registration) => {
|
|
|
30
27
|
signal: context.mcpReq.signal,
|
|
31
28
|
}));
|
|
32
29
|
if (result.ok) {
|
|
33
|
-
const evidence = createEvidence(registration.activeTarget?.(),
|
|
30
|
+
const evidence = createEvidence(result.value.subject ?? registration.activeTarget?.(), result.value.provider, {
|
|
34
31
|
operation: contract.name,
|
|
35
32
|
parameters: arguments_,
|
|
36
|
-
result: result.value,
|
|
37
|
-
|
|
33
|
+
result: result.value.result,
|
|
34
|
+
rawResult: result.value.rawResult,
|
|
35
|
+
limitations: result.value.limitations,
|
|
36
|
+
locations: result.value.locations,
|
|
38
37
|
});
|
|
39
|
-
registration.recordEvidence?.(evidence);
|
|
38
|
+
const recorded = registration.recordEvidence?.(evidence);
|
|
39
|
+
if (recorded !== undefined && !recorded.ok)
|
|
40
|
+
return toCallToolResult(recorded, contract);
|
|
40
41
|
return toCallToolResult({ ok: true, value: evidence }, contract);
|
|
41
42
|
}
|
|
43
|
+
if (result.error instanceof AnalysisCapabilityUnavailableError &&
|
|
44
|
+
approvedUnknownTracking(input) &&
|
|
45
|
+
registration.recordUnknown !== undefined) {
|
|
46
|
+
const unknown = registration.recordUnknown({
|
|
47
|
+
approved: true,
|
|
48
|
+
question: `${contract.name} is unavailable: ${result.error.reason}`,
|
|
49
|
+
severity: "medium",
|
|
50
|
+
domain: "provider-capability",
|
|
51
|
+
supporting_evidence_ids: [],
|
|
52
|
+
contradicting_evidence_ids: [],
|
|
53
|
+
required_authority: "shipped-artifact",
|
|
54
|
+
required_confidence: "observed",
|
|
55
|
+
required_environment: null,
|
|
56
|
+
recommended_probes: [
|
|
57
|
+
{
|
|
58
|
+
operation: contract.name,
|
|
59
|
+
rationale: "Use a provider that declares this capability available.",
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
relationships: [],
|
|
63
|
+
});
|
|
64
|
+
if (!unknown.ok &&
|
|
65
|
+
!(unknown.error instanceof UnknownRegistryError &&
|
|
66
|
+
unknown.error.reason === "already-exists"))
|
|
67
|
+
return toCallToolResult(unknown, contract);
|
|
68
|
+
}
|
|
42
69
|
return toCallToolResult(result, contract);
|
|
43
70
|
});
|
|
44
71
|
};
|
|
@@ -53,7 +80,13 @@ const projectOfficialArguments = (contract, input) => {
|
|
|
53
80
|
}
|
|
54
81
|
const projected = {};
|
|
55
82
|
for (const key of Object.keys(contract.inputSchema.shape)) {
|
|
83
|
+
if (key === "unknown_registry_approved")
|
|
84
|
+
continue;
|
|
56
85
|
projected[key] = parsed.data[key] ?? null;
|
|
57
86
|
}
|
|
58
87
|
return projected;
|
|
59
88
|
};
|
|
89
|
+
const approvedUnknownTracking = (input) => typeof input === "object" &&
|
|
90
|
+
input !== null &&
|
|
91
|
+
"unknown_registry_approved" in input &&
|
|
92
|
+
input.unknown_registry_approved === true;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import canonicalize from "canonicalize";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { SESSION_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
|
|
4
|
+
import { createEvidence } from "../domain/evidence.js";
|
|
5
|
+
import { EvidenceIntegrityError } from "../domain/errors.js";
|
|
6
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
7
|
+
import { compareProcessCaptures, processCaptureSchema, } from "../domain/processCapture.js";
|
|
8
|
+
import { recordDerivedEvidence } from "./recordDerivedEvidence.js";
|
|
9
|
+
import { err } from "../domain/result.js";
|
|
10
|
+
import { PROCESS_PROVIDER } from "./sessionToolPolicies.js";
|
|
11
|
+
import { toCallToolResult } from "./toolResult.js";
|
|
12
|
+
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
|
13
|
+
/** Register deterministic process-capture comparison and contradiction tracking. */
|
|
14
|
+
export const registerProcessComparisonTool = (server, session, contract) => {
|
|
15
|
+
server.registerTool(contract.name, toolRegistrationOptions(contract), (input) => {
|
|
16
|
+
const parsed = z
|
|
17
|
+
.object({
|
|
18
|
+
left_evidence_id: z.string(),
|
|
19
|
+
left: processCaptureSchema,
|
|
20
|
+
right_evidence_id: z.string(),
|
|
21
|
+
right: processCaptureSchema,
|
|
22
|
+
unknown_registry_approved: z.literal(true).optional(),
|
|
23
|
+
})
|
|
24
|
+
.parse(input);
|
|
25
|
+
const validated = validateCaptureSources(session, parsed);
|
|
26
|
+
if (!validated.ok)
|
|
27
|
+
return toCallToolResult(validated, contract);
|
|
28
|
+
const comparison = compareProcessCaptures(parsed.left, parsed.right);
|
|
29
|
+
const evidence = createEvidence(undefined, PROCESS_PROVIDER, {
|
|
30
|
+
predicateType: "rea.process-comparison/v1",
|
|
31
|
+
operation: contract.name,
|
|
32
|
+
parameters: {
|
|
33
|
+
left_evidence_id: parsed.left_evidence_id,
|
|
34
|
+
right_evidence_id: parsed.right_evidence_id,
|
|
35
|
+
left_normalization: parsed.left.normalization,
|
|
36
|
+
right_normalization: parsed.right.normalization,
|
|
37
|
+
},
|
|
38
|
+
result: jsonValueSchema.parse(comparison),
|
|
39
|
+
confidence: "derived",
|
|
40
|
+
authority: "analyst-inference",
|
|
41
|
+
limitations: comparison.limitations,
|
|
42
|
+
evidenceLinks: [parsed.left_evidence_id, parsed.right_evidence_id],
|
|
43
|
+
});
|
|
44
|
+
return toCallToolResult(recordDerivedEvidence(session, evidence, comparisonUnknownInput(parsed, comparison)), contract);
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const validateCaptureSources = (session, parsed) => {
|
|
48
|
+
for (const [evidenceId, capture] of [
|
|
49
|
+
[parsed.left_evidence_id, parsed.left],
|
|
50
|
+
[parsed.right_evidence_id, parsed.right],
|
|
51
|
+
]) {
|
|
52
|
+
const evidence = session.evidenceById(evidenceId);
|
|
53
|
+
const result = processCaptureSchema.safeParse(evidence?.normalized_result);
|
|
54
|
+
if (evidence === undefined ||
|
|
55
|
+
evidence.operation !== "capture_process_scenario" ||
|
|
56
|
+
evidence.predicate_type !== "rea.process-capture/v2" ||
|
|
57
|
+
evidence.provider.id !== PROCESS_PROVIDER.id ||
|
|
58
|
+
evidence.provider.name !== PROCESS_PROVIDER.name ||
|
|
59
|
+
evidence.provider.version !== PROCESS_PROVIDER.version ||
|
|
60
|
+
evidence.confidence !== "observed" ||
|
|
61
|
+
evidence.authority !== "controlled-replay" ||
|
|
62
|
+
!result.success ||
|
|
63
|
+
canonicalJson(result.data) !== canonicalJson(capture))
|
|
64
|
+
return err(new EvidenceIntegrityError("Process comparison payload does not match its source Evidence"));
|
|
65
|
+
}
|
|
66
|
+
return { ok: true, value: null };
|
|
67
|
+
};
|
|
68
|
+
const canonicalJson = (value) => {
|
|
69
|
+
const encoded = canonicalize(value);
|
|
70
|
+
if (encoded === undefined)
|
|
71
|
+
throw new TypeError("Process capture could not be canonicalized");
|
|
72
|
+
return encoded;
|
|
73
|
+
};
|
|
74
|
+
const comparisonUnknownInput = (parsed, comparison) => {
|
|
75
|
+
if (parsed.unknown_registry_approved !== true ||
|
|
76
|
+
comparison.status === "unchanged")
|
|
77
|
+
return undefined;
|
|
78
|
+
const differingScopes = [
|
|
79
|
+
["terminal", comparison.terminal],
|
|
80
|
+
["exit", comparison.exit],
|
|
81
|
+
["filesystem", comparison.filesystem],
|
|
82
|
+
["protocol", comparison.protocol],
|
|
83
|
+
["process", comparison.process],
|
|
84
|
+
]
|
|
85
|
+
.filter(([, status]) => status !== "unchanged")
|
|
86
|
+
.map(([scope]) => scope)
|
|
87
|
+
.join(", ");
|
|
88
|
+
return {
|
|
89
|
+
approved: true,
|
|
90
|
+
question: `Process captures disagree across: ${differingScopes}`,
|
|
91
|
+
severity: "high",
|
|
92
|
+
domain: "process-comparison",
|
|
93
|
+
supporting_evidence_ids: [parsed.left_evidence_id],
|
|
94
|
+
contradicting_evidence_ids: [parsed.right_evidence_id],
|
|
95
|
+
required_authority: "controlled-replay",
|
|
96
|
+
required_confidence: "observed",
|
|
97
|
+
required_environment: null,
|
|
98
|
+
recommended_probes: [
|
|
99
|
+
{
|
|
100
|
+
operation: "capture_process_scenario",
|
|
101
|
+
rationale: "Repeat both scenarios under the same controlled environment.",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
relationships: [],
|
|
105
|
+
};
|
|
106
|
+
};
|