rea-agents 1.7.0 → 2.0.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.
Files changed (156) hide show
  1. package/README.md +38 -31
  2. package/dist/application/CapabilityInventory.js +18 -0
  3. package/dist/application/CrossVersionInvestigation.js +10 -6
  4. package/dist/application/Doctor.js +56 -6
  5. package/dist/application/ElectronBoundaryAnalysis.js +96 -0
  6. package/dist/application/ElectronBoundaryGraph.js +9 -0
  7. package/dist/application/ElectronBoundaryGraphContext.js +62 -0
  8. package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
  9. package/dist/application/ElectronBoundaryGraphNative.js +86 -0
  10. package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
  11. package/dist/application/EnhancedTools.js +38 -13
  12. package/dist/application/InvestigationProviders.js +30 -0
  13. package/dist/application/JavaScriptApplicationEvidence.js +26 -0
  14. package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
  15. package/dist/application/JavaScriptApplicationService.js +74 -0
  16. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
  17. package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
  18. package/dist/application/JavaScriptArtifactAnalysis.js +7 -1
  19. package/dist/application/JavaScriptArtifactGraphBuilder.js +59 -16
  20. package/dist/application/JavaScriptArtifactGraphEvidence.js +24 -14
  21. package/dist/application/JavaScriptArtifactReconstruction.js +3 -0
  22. package/dist/application/JavaScriptArtifactReconstructionInput.js +2 -83
  23. package/dist/application/JavaScriptReplayPlanning.js +201 -0
  24. package/dist/application/JavaScriptReplayService.js +266 -0
  25. package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
  26. package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
  27. package/dist/application/ManagedApplicationGraphService.js +55 -0
  28. package/dist/application/ManagedMemberComparisonService.js +122 -0
  29. package/dist/application/ManagedNativeVerificationService.js +39 -0
  30. package/dist/application/ManagedReconstructionService.js +58 -0
  31. package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
  32. package/dist/application/PermissionAuthority.js +1 -1
  33. package/dist/application/ProcessSampling.js +15 -11
  34. package/dist/application/ProjectPermissionStore.js +1 -0
  35. package/dist/application/runtime.js +2 -0
  36. package/dist/browser/CdpCaptureEvents.js +49 -0
  37. package/dist/browser/CdpElectronInspection.js +106 -162
  38. package/dist/browser/CdpElectronProvider.js +5 -1
  39. package/dist/browser/CdpElectronScriptEvents.js +69 -0
  40. package/dist/browser/CdpElectronScripts.js +94 -0
  41. package/dist/browser/CdpElectronWorkers.js +56 -0
  42. package/dist/browser/CdpPageCapture.js +8 -4
  43. package/dist/catalogIdentity.js +4 -1
  44. package/dist/cli.js +388 -0
  45. package/dist/cliApplicationCommands.js +62 -0
  46. package/dist/cliCommandNames.js +13 -0
  47. package/dist/cliElectronCommands.js +103 -1
  48. package/dist/cliJsonInput.js +54 -0
  49. package/dist/config.js +83 -0
  50. package/dist/contracts/applicationToolContracts.js +91 -0
  51. package/dist/contracts/artifactToolContracts.js +5 -9
  52. package/dist/contracts/browserToolContracts.js +14 -62
  53. package/dist/contracts/electronToolContracts.js +45 -19
  54. package/dist/contracts/errorSchemas.js +1 -0
  55. package/dist/contracts/investigationExamples.js +0 -6
  56. package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
  57. package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
  58. package/dist/contracts/managedToolContracts.js +153 -0
  59. package/dist/contracts/managedWorkflowExamples.js +346 -0
  60. package/dist/contracts/managedWorkflowToolContracts.js +203 -0
  61. package/dist/contracts/nativeToolContracts.js +2 -6
  62. package/dist/contracts/promptContracts.js +18 -2
  63. package/dist/contracts/toolContractExamples.js +10 -25
  64. package/dist/contracts/toolContracts.js +47 -45
  65. package/dist/contracts/toolEffects.js +160 -0
  66. package/dist/contracts/toolOutputSchemas.js +46 -31
  67. package/dist/doctorRuntime.js +42 -1
  68. package/dist/domain/artifactComparison.js +3 -4
  69. package/dist/domain/browserObservation.js +3 -0
  70. package/dist/domain/bundleComparison.js +4 -4
  71. package/dist/domain/electronObservation.js +13 -0
  72. package/dist/domain/electronStaticAnalysis.js +9 -0
  73. package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
  74. package/dist/domain/electronStaticAnalysisIpc.js +171 -0
  75. package/dist/domain/electronStaticAnalysisNative.js +164 -0
  76. package/dist/domain/electronStaticAnalysisTypes.js +1 -0
  77. package/dist/domain/electronStaticAnalysisValues.js +103 -0
  78. package/dist/domain/errors.js +67 -1
  79. package/dist/domain/functionComparisonSchemas.js +3 -8
  80. package/dist/domain/inputIssueProjection.js +52 -0
  81. package/dist/domain/javascriptApplicationAnalysis.js +155 -0
  82. package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
  83. package/dist/domain/javascriptApplicationEvidenceSchemas.js +18 -0
  84. package/dist/domain/javascriptApplicationGraph.js +2 -1
  85. package/dist/domain/javascriptApplicationGraphSchemas.js +7 -0
  86. package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
  87. package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
  88. package/dist/domain/javascriptApplicationVersionItems.js +221 -0
  89. package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
  90. package/dist/domain/javascriptFeatureSeed.js +140 -0
  91. package/dist/domain/javascriptFeatureTrace.js +232 -0
  92. package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
  93. package/dist/domain/javascriptFeatureTraversal.js +88 -0
  94. package/dist/domain/javascriptNativeHandoff.js +85 -0
  95. package/dist/domain/javascriptReplay.js +361 -0
  96. package/dist/domain/javascriptRuntimeLoadState.js +106 -0
  97. package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
  98. package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
  99. package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
  100. package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
  101. package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
  102. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
  103. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
  104. package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
  105. package/dist/domain/javascriptStaticAnalysis.js +31 -43
  106. package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
  107. package/dist/domain/javascriptStaticAnalysisHelpers.js +10 -0
  108. package/dist/domain/javascriptStaticAnalysisState.js +6 -0
  109. package/dist/domain/managedApplicationGraph.js +556 -0
  110. package/dist/domain/managedArtifact.js +444 -0
  111. package/dist/domain/managedMemberComparison.js +565 -0
  112. package/dist/domain/managedNativeVerification.js +408 -0
  113. package/dist/domain/managedReconstruction.js +183 -0
  114. package/dist/domain/managedRuntimeCorrelation.js +175 -0
  115. package/dist/domain/reconstructionVerificationSchemas.js +1 -3
  116. package/dist/dotnet/ManagedArtifactInspector.js +318 -0
  117. package/dist/dotnet/ManagedMemberInspector.js +887 -0
  118. package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
  119. package/dist/dotnet/ManagedMetadataInventory.js +368 -0
  120. package/dist/dotnet/ManagedMetadataLayout.js +322 -0
  121. package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
  122. package/dist/dotnet/ManagedPeReader.js +157 -0
  123. package/dist/dotnet/ManagedReaderFailure.js +10 -0
  124. package/dist/dotnet/ManagedStaticProvider.js +203 -0
  125. package/dist/generatedPackageMetadata.js +2 -2
  126. package/dist/ghidra/GhidraProvider.js +6 -0
  127. package/dist/main.js +14 -0
  128. package/dist/replay/JavaScriptReplayWorker.js +286 -0
  129. package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
  130. package/dist/replay/LinuxRuntimeClosure.js +46 -0
  131. package/dist/replay/LinuxSeccompPolicy.js +40 -0
  132. package/dist/replay/ReplayWorkerProtocol.js +55 -0
  133. package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
  134. package/dist/server/createServer.js +52 -0
  135. package/dist/server/registerApplicationTools.js +115 -0
  136. package/dist/server/registerArtifactComparisonTool.js +16 -9
  137. package/dist/server/registerBrowserTools.js +33 -8
  138. package/dist/server/registerBundleComparisonTool.js +17 -20
  139. package/dist/server/registerElectronTools.js +45 -3
  140. package/dist/server/registerEnhancedTools.js +58 -13
  141. package/dist/server/registerEvidenceTools.js +5 -1
  142. package/dist/server/registerFunctionComparisonTool.js +14 -7
  143. package/dist/server/registerInvestigationTools.js +21 -17
  144. package/dist/server/registerManagedTools.js +6 -0
  145. package/dist/server/registerManagedWorkflowTools.js +277 -0
  146. package/dist/server/registerOfficialTools.js +8 -8
  147. package/dist/server/registerProcessComparisonTool.js +25 -47
  148. package/dist/server/registerSessionStatusTool.js +7 -7
  149. package/dist/server/registerSessionTools.js +51 -86
  150. package/dist/server/sessionEvidence.js +14 -19
  151. package/dist/server/toolInputValidation.js +77 -0
  152. package/dist/server/toolRegistrationOptions.js +79 -2
  153. package/dist/server/toolResult.js +25 -17
  154. package/package.json +12 -4
  155. package/scripts/rea.mjs +1 -1
  156. package/skills/rea-analysis/SKILL.md +59 -3
@@ -0,0 +1,182 @@
1
+ import { constants } from "node:fs";
2
+ import { mkdtemp, open, readFile, realpath, rm, stat, writeFile, } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { spawn } from "node:child_process";
7
+ import { digestBytes } from "../application/JavaScriptReplayPlanning.js";
8
+ import { buildLinuxX64ReplaySeccomp, linuxX64ReplaySeccompDigest, } from "./LinuxSeccompPolicy.js";
9
+ import { resolveLinuxRuntimeClosure } from "./LinuxRuntimeClosure.js";
10
+ const VERSION_OUTPUT_LIMIT = 16 * 1024;
11
+ /** Production planning host for descriptor-backed reads and fail-closed probes. */
12
+ export class SystemJavaScriptReplayHost {
13
+ async readSource(path, maximumBytes) {
14
+ const canonicalPath = await realpath(path);
15
+ const handle = await open(canonicalPath, constants.O_RDONLY | constants.O_NOFOLLOW);
16
+ try {
17
+ const metadata = await handle.stat();
18
+ if (!metadata.isFile() || metadata.size > maximumBytes)
19
+ throw new RangeError(`Replay source exceeds its declared limit: ${canonicalPath}`);
20
+ const bytes = await handle.readFile();
21
+ return { canonicalPath, bytes };
22
+ }
23
+ finally {
24
+ await handle.close();
25
+ }
26
+ }
27
+ async identifyExecutable(path, versionArguments) {
28
+ const canonicalPath = await realpath(path);
29
+ const metadata = await stat(canonicalPath);
30
+ if (!metadata.isFile())
31
+ throw new TypeError(`Replay executable is not a file: ${canonicalPath}`);
32
+ const [bytes, version] = await Promise.all([
33
+ readFile(canonicalPath),
34
+ capture(canonicalPath, versionArguments, VERSION_OUTPUT_LIMIT),
35
+ ]);
36
+ return {
37
+ path: canonicalPath,
38
+ version: version.trim(),
39
+ sha256: digestBytes(bytes),
40
+ };
41
+ }
42
+ identifyWorker() {
43
+ return this.identifyExecutable(fileURLToPath(new URL("./JavaScriptReplayWorker.js", import.meta.url)), []);
44
+ }
45
+ identifyRuntimeClosure(nodePath) {
46
+ return resolveLinuxRuntimeClosure(nodePath);
47
+ }
48
+ seccompDigest() {
49
+ return linuxX64ReplaySeccompDigest();
50
+ }
51
+ async probe(policy) {
52
+ if (process.platform !== "linux" || process.arch !== "x64")
53
+ throw new TypeError("Controlled replay requires Linux x86_64 in v1");
54
+ const bwrapMetadata = await stat(policy.bubblewrapPath);
55
+ if ((bwrapMetadata.mode & 0o4000) !== 0)
56
+ throw new TypeError("Setuid Bubblewrap is not admitted by replay policy");
57
+ if ((await readFile("/sys/fs/cgroup/cgroup.controllers", "utf8")).trim()
58
+ .length === 0)
59
+ throw new TypeError("Controlled replay requires delegated cgroup v2");
60
+ const directory = await mkdtemp(join(tmpdir(), "rea-replay-probe-"));
61
+ const filterPath = join(directory, "seccomp.bpf");
62
+ await writeFile(filterPath, buildLinuxX64ReplaySeccomp(), { mode: 0o600 });
63
+ const filter = await open(filterPath, "r");
64
+ try {
65
+ await run(policy.bubblewrapPath, [
66
+ "--unshare-all",
67
+ "--unshare-user",
68
+ "--disable-userns",
69
+ "--new-session",
70
+ "--die-with-parent",
71
+ "--cap-drop",
72
+ "ALL",
73
+ "--ro-bind",
74
+ "/usr",
75
+ "/usr",
76
+ "--ro-bind",
77
+ "/lib",
78
+ "/lib",
79
+ "--ro-bind-try",
80
+ "/lib64",
81
+ "/lib64",
82
+ "--proc",
83
+ "/proc",
84
+ "--dev",
85
+ "/dev",
86
+ "--seccomp",
87
+ "3",
88
+ "--",
89
+ "/usr/bin/true",
90
+ ], ["ignore", "pipe", "pipe", filter.fd]);
91
+ await runExpectFailure(policy.bubblewrapPath, [
92
+ "--unshare-all",
93
+ "--unshare-user",
94
+ "--disable-userns",
95
+ "--new-session",
96
+ "--die-with-parent",
97
+ "--cap-drop",
98
+ "ALL",
99
+ "--ro-bind",
100
+ "/usr",
101
+ "/usr",
102
+ "--ro-bind",
103
+ "/lib",
104
+ "/lib",
105
+ "--ro-bind-try",
106
+ "/lib64",
107
+ "/lib64",
108
+ "--proc",
109
+ "/proc",
110
+ "--dev",
111
+ "/dev",
112
+ "--seccomp",
113
+ "3",
114
+ "--",
115
+ "/usr/bin/unshare",
116
+ "--mount",
117
+ "/usr/bin/true",
118
+ ], ["ignore", "pipe", "pipe", filter.fd]);
119
+ await run(policy.systemdRunPath, [
120
+ "--user",
121
+ "--pipe",
122
+ "--wait",
123
+ "--service-type=exec",
124
+ "--quiet",
125
+ "--property=MemoryMax=16777216",
126
+ "--property=MemorySwapMax=0",
127
+ "--property=TasksMax=2",
128
+ "--",
129
+ "/usr/bin/true",
130
+ ]);
131
+ }
132
+ finally {
133
+ await filter.close();
134
+ await rm(directory, { recursive: true, force: true });
135
+ }
136
+ }
137
+ }
138
+ const capture = async (executable, arguments_, maximumBytes) => {
139
+ if (arguments_.length === 0)
140
+ return "content-addressed-worker-v1";
141
+ const result = await collect(executable, arguments_, maximumBytes);
142
+ if (result.code !== 0)
143
+ throw new TypeError(`Replay executable probe failed: ${executable}`);
144
+ return result.stdout.length > 0 ? result.stdout : result.stderr;
145
+ };
146
+ const run = async (executable, arguments_, stdio) => {
147
+ const result = await collect(executable, arguments_, VERSION_OUTPUT_LIMIT, stdio);
148
+ if (result.code !== 0)
149
+ throw new TypeError(`Replay feature probe failed (${String(result.code)}): ${executable}: ${result.stderr.trim()}`);
150
+ };
151
+ const runExpectFailure = async (executable, arguments_, stdio) => {
152
+ const result = await collect(executable, arguments_, VERSION_OUTPUT_LIMIT, stdio);
153
+ if (result.code === 0)
154
+ throw new TypeError(`Replay denial probe unexpectedly succeeded: ${executable}`);
155
+ };
156
+ const collect = async (executable, arguments_, maximumBytes, stdio = ["ignore", "pipe", "pipe"]) => new Promise((resolve, reject) => {
157
+ const child = spawn(executable, [...arguments_], { stdio });
158
+ const timeout = setTimeout(() => child.kill("SIGKILL"), 5_000);
159
+ timeout.unref();
160
+ let stdout = "";
161
+ let stderr = "";
162
+ const retain = (current, chunk) => {
163
+ const remaining = Math.max(0, maximumBytes - Buffer.byteLength(current));
164
+ return current + chunk.subarray(0, remaining).toString("utf8");
165
+ };
166
+ if (child.stdout !== null)
167
+ child.stdout.on("data", (chunk) => {
168
+ stdout = retain(stdout, chunk);
169
+ });
170
+ if (child.stderr !== null)
171
+ child.stderr.on("data", (chunk) => {
172
+ stderr = retain(stderr, chunk);
173
+ });
174
+ child.once("error", (error) => {
175
+ clearTimeout(timeout);
176
+ reject(error);
177
+ });
178
+ child.once("close", (code) => {
179
+ clearTimeout(timeout);
180
+ resolve({ code, stdout, stderr });
181
+ });
182
+ });
@@ -5,12 +5,17 @@ import { registerOfficialTools } from "./registerOfficialTools.js";
5
5
  import { registerSessionTools } from "./registerSessionTools.js";
6
6
  import { registerNativeTools } from "./registerNativeTools.js";
7
7
  import { registerArtifactTools } from "./registerArtifactTools.js";
8
+ import { registerManagedTools } from "./registerManagedTools.js";
9
+ import { registerManagedWorkflowTools } from "./registerManagedWorkflowTools.js";
8
10
  import { silentLogger } from "../logger.js";
9
11
  import { registerGuidedPrompts } from "./registerPrompts.js";
10
12
  import { registerEvidenceResources } from "./registerEvidenceResources.js";
11
13
  import { createServerIdentity } from "../serverIdentity.js";
12
14
  import { registerBrowserTools } from "./registerBrowserTools.js";
13
15
  import { registerElectronTools } from "./registerElectronTools.js";
16
+ import { registerApplicationTools } from "./registerApplicationTools.js";
17
+ import { LinuxJavaScriptReplayRunner } from "../replay/LinuxJavaScriptReplayRunner.js";
18
+ import { SystemJavaScriptReplayHost } from "../replay/SystemJavaScriptReplayHost.js";
14
19
  /**
15
20
  * Construct one MCP server without acquiring subprocess resources.
16
21
  * Supplying a session adds target lifecycle tools; omitting it retains the
@@ -66,6 +71,14 @@ export const createServer = (analysis, session, options = {}) => {
66
71
  server.sendResourceListChanged();
67
72
  return recorded;
68
73
  };
74
+ const recordEvidenceWithUnknown = session === undefined
75
+ ? undefined
76
+ : (evidence, input) => {
77
+ const recorded = session.recordEvidenceWithUnknown(evidence, input);
78
+ if (recorded.ok)
79
+ server.sendResourceListChanged();
80
+ return recorded;
81
+ };
69
82
  registerOfficialTools(server, analysis, {
70
83
  logger: toolLogger,
71
84
  activeTarget,
@@ -96,6 +109,26 @@ export const createServer = (analysis, session, options = {}) => {
96
109
  ? {}
97
110
  : { permissionAuthority: options.permissionAuthority }),
98
111
  });
112
+ registerManagedTools(server, analysis, {
113
+ logger: toolLogger,
114
+ activeTarget,
115
+ recordEvidence,
116
+ });
117
+ if (session !== undefined)
118
+ registerManagedWorkflowTools(server, {
119
+ logger: toolLogger,
120
+ recordEvidence,
121
+ recordEvidenceWithUnknown,
122
+ session,
123
+ runtime: {
124
+ policy: options.managedRuntimePolicy ?? {
125
+ enabled: false,
126
+ roots: [],
127
+ executablePath: "/usr/bin/dotnet",
128
+ },
129
+ authority: options.permissionAuthority,
130
+ },
131
+ });
99
132
  registerBrowserTools(server, {
100
133
  logger: toolLogger,
101
134
  browser: options.browserObservation,
@@ -108,6 +141,25 @@ export const createServer = (analysis, session, options = {}) => {
108
141
  permissionAuthority: options.permissionAuthority,
109
142
  recordEvidence,
110
143
  });
144
+ registerApplicationTools(server, {
145
+ logger: toolLogger,
146
+ recordEvidence,
147
+ recordEvidenceWithUnknown,
148
+ replay: {
149
+ policy: options.javascriptReplayPolicy ?? {
150
+ enabled: false,
151
+ roots: [],
152
+ nodePath: process.execPath,
153
+ bubblewrapPath: "/usr/bin/bwrap",
154
+ systemdRunPath: "/usr/bin/systemd-run",
155
+ systemctlPath: "/usr/bin/systemctl",
156
+ shellPath: "/usr/bin/bash",
157
+ },
158
+ host: options.javascriptReplayHost ?? new SystemJavaScriptReplayHost(),
159
+ runner: options.javascriptReplayRunner ?? new LinuxJavaScriptReplayRunner(),
160
+ authority: options.permissionAuthority,
161
+ },
162
+ });
111
163
  registerGuidedPrompts(server, analysis, session);
112
164
  if (session !== undefined) {
113
165
  registerEvidenceResources(server, session);
@@ -0,0 +1,115 @@
1
+ import { compareApplicationVersionsEvidenceValidated, traceApplicationFeatureEvidenceValidated, } from "../application/JavaScriptApplicationWorkflowService.js";
2
+ import { runControlledReplayValidated, } from "../application/JavaScriptReplayService.js";
3
+ import { APPLICATION_TOOL_CONTRACTS } from "../contracts/applicationToolContracts.js";
4
+ import { compareApplicationVersionsInputSchema } from "../domain/javascriptApplicationVersionComparisonSchemas.js";
5
+ import { applicationVersionComparisonResultSchema } from "../domain/javascriptApplicationVersionComparisonSchemas.js";
6
+ import { traceApplicationFeatureInputSchema } from "../domain/javascriptFeatureTraceSchemas.js";
7
+ import { parseEvidence } from "../domain/evidence.js";
8
+ import { controlledReplayInputSchema, controlledReplayOutputSchema, } from "../domain/javascriptReplay.js";
9
+ import { logToolExecution } from "./toolLogging.js";
10
+ import { toCallToolResult } from "./toolResult.js";
11
+ import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
12
+ import { safeParseToolInput } from "./toolInputValidation.js";
13
+ import { mcpProgressReporter } from "./mcpProgress.js";
14
+ /** Register provider-neutral JavaScript application graph workflows. */
15
+ export const registerApplicationTools = (server, options) => {
16
+ const [traceContract, compareContract, replayContract] = APPLICATION_TOOL_CONTRACTS;
17
+ server.registerTool(traceContract.name, toolRegistrationOptions(traceContract), async (input) => {
18
+ const parsedInput = safeParseToolInput(traceApplicationFeatureInputSchema, input, traceContract.name);
19
+ if (!parsedInput.ok)
20
+ return toCallToolResult(parsedInput, traceContract);
21
+ const parsed = parsedInput.value;
22
+ const result = await logToolExecution(options.logger, traceContract.name, () => Promise.resolve(traceApplicationFeatureEvidenceValidated(parsed)));
23
+ if (!result.ok)
24
+ return toCallToolResult(result, traceContract);
25
+ const sources = [parsed.application, ...parsed.native_observations];
26
+ const recorded = recordSources(options.recordEvidence, sources);
27
+ if (!recorded.ok)
28
+ return toCallToolResult(recorded, traceContract);
29
+ return recordResult(options, traceContract, result.value);
30
+ });
31
+ server.registerTool(compareContract.name, toolRegistrationOptions(compareContract), async (input) => {
32
+ const parsedInput = safeParseToolInput(compareApplicationVersionsInputSchema, input, compareContract.name);
33
+ if (!parsedInput.ok)
34
+ return toCallToolResult(parsedInput, compareContract);
35
+ const parsed = parsedInput.value;
36
+ const result = await logToolExecution(options.logger, compareContract.name, () => Promise.resolve(compareApplicationVersionsEvidenceValidated(parsed)));
37
+ if (!result.ok)
38
+ return toCallToolResult(result, compareContract);
39
+ const sources = [
40
+ parsed.left,
41
+ parsed.right,
42
+ ...parsed.left_native_observations,
43
+ ...parsed.right_native_observations,
44
+ ];
45
+ const recorded = recordSources(options.recordEvidence, sources);
46
+ if (!recorded.ok)
47
+ return toCallToolResult(recorded, compareContract);
48
+ const comparison = applicationVersionComparisonResultSchema.parse(result.value.normalized_result);
49
+ const unknown = comparison.summary.unknown > 0;
50
+ return recordResult(options, compareContract, result.value, parsed.unknown_registry_approved === true && unknown);
51
+ });
52
+ server.registerTool(replayContract.name, toolRegistrationOptions(replayContract), async (input, context) => {
53
+ const parsedInput = safeParseToolInput(controlledReplayInputSchema, input, replayContract.name);
54
+ if (!parsedInput.ok)
55
+ return toCallToolResult(parsedInput, replayContract);
56
+ const parsed = parsedInput.value;
57
+ const result = await logToolExecution(options.logger, replayContract.name, () => runControlledReplayValidated(options.replay, parsed, {
58
+ signal: context.mcpReq.signal,
59
+ progress: mcpProgressReporter(context),
60
+ }));
61
+ if (!result.ok)
62
+ return toCallToolResult(result, replayContract);
63
+ const output = controlledReplayOutputSchema.parse(result.value);
64
+ if (output.evidence !== null) {
65
+ const sourcesRecorded = recordSources(options.recordEvidence, output.source_evidence.map((item) => parseEvidence(item)));
66
+ if (!sourcesRecorded.ok)
67
+ return toCallToolResult(sourcesRecorded, replayContract);
68
+ const recorded = options.recordEvidence?.(parseEvidence(output.evidence));
69
+ if (recorded !== undefined && !recorded.ok)
70
+ return toCallToolResult(recorded, replayContract);
71
+ }
72
+ return toCallToolResult(result, replayContract, {
73
+ evidenceResourcesAvailable: output.evidence !== null && options.recordEvidence !== undefined,
74
+ });
75
+ });
76
+ };
77
+ const recordSources = (recordEvidence, sources) => {
78
+ for (const source of sources) {
79
+ const recorded = recordEvidence?.(source);
80
+ if (recorded !== undefined && !recorded.ok)
81
+ return recorded;
82
+ }
83
+ return { ok: true, value: null };
84
+ };
85
+ const recordResult = (options, contract, evidence, recordUnknown = false) => {
86
+ const recorded = recordUnknown
87
+ ? options.recordEvidenceWithUnknown?.(evidence, {
88
+ approved: true,
89
+ question: "Which application entities remain unmatched or ambiguous across these versions?",
90
+ severity: "medium",
91
+ domain: "application-version-comparison",
92
+ supporting_evidence_ids: [evidence.evidence_id],
93
+ contradicting_evidence_ids: [],
94
+ required_authority: "shipped-artifact",
95
+ required_confidence: "observed",
96
+ required_environment: null,
97
+ recommended_probes: [
98
+ {
99
+ operation: "analyze_javascript_application",
100
+ rationale: "Repeat static reconstruction with complete artifacts and source maps when available.",
101
+ },
102
+ {
103
+ operation: "reconcile_javascript_runtime",
104
+ rationale: "Add approved passive runtime Evidence without promoting it to static fact.",
105
+ },
106
+ ],
107
+ relationships: [],
108
+ })
109
+ : options.recordEvidence?.(evidence);
110
+ if (recorded !== undefined && !recorded.ok)
111
+ return toCallToolResult(recorded, contract);
112
+ return toCallToolResult({ ok: true, value: evidence }, contract, {
113
+ evidenceResourcesAvailable: recorded !== undefined,
114
+ });
115
+ };
@@ -2,27 +2,35 @@ import { artifactComparisonInputSchema, compareArtifacts, } from "../domain/arti
2
2
  import { createEvidence } from "../domain/evidence.js";
3
3
  import { jsonValueSchema } from "../domain/jsonValue.js";
4
4
  import { recordDerivedEvidence } from "./recordDerivedEvidence.js";
5
- import { resolveSessionEvidence } from "./sessionEvidence.js";
5
+ import { resolveSessionEvidenceIds } from "./sessionEvidence.js";
6
6
  import { ARTIFACT_COMPARISON_PROVIDER } from "./sessionToolPolicies.js";
7
7
  import { toCallToolResult } from "./toolResult.js";
8
8
  import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
9
9
  import { runDerivedOperation } from "./runDerivedOperation.js";
10
+ import { safeParseToolInput } from "./toolInputValidation.js";
10
11
  /** Register Evidence-backed deterministic artifact comparison. */
11
12
  export const registerArtifactComparisonTool = (server, session, contract) => {
12
13
  server.registerTool(contract.name, toolRegistrationOptions(contract), async (input, context) => {
13
- const parsed = artifactComparisonInputSchema.parse(input);
14
- const left = resolveSessionEvidence(session, parsed.left);
14
+ const parsedInput = safeParseToolInput(artifactComparisonInputSchema, input, contract.name);
15
+ if (!parsedInput.ok)
16
+ return toCallToolResult(parsedInput, contract);
17
+ const parsed = parsedInput.value;
18
+ const expected = {
19
+ operation: "inventory_artifact",
20
+ predicate: "rea.analysis/v2",
21
+ };
22
+ const left = resolveSessionEvidenceIds(session, parsed.left_evidence_ids, expected);
15
23
  if (!left.ok)
16
24
  return toCallToolResult(left, contract);
17
- const right = resolveSessionEvidence(session, parsed.right);
25
+ const right = resolveSessionEvidenceIds(session, parsed.right_evidence_ids, expected);
18
26
  if (!right.ok)
19
27
  return toCallToolResult(right, contract);
20
28
  const computed = await runDerivedOperation(context, contract.name, () => compareArtifacts(left.value, right.value, parsed.offset, parsed.limit));
21
29
  if (!computed.ok)
22
30
  return toCallToolResult(computed, contract);
23
31
  const comparison = computed.value;
24
- const leftEvidenceIds = evidenceIds(parsed.left);
25
- const rightEvidenceIds = evidenceIds(parsed.right);
32
+ const leftEvidenceIds = parsed.left_evidence_ids;
33
+ const rightEvidenceIds = parsed.right_evidence_ids;
26
34
  const evidence = createEvidence(undefined, ARTIFACT_COMPARISON_PROVIDER, {
27
35
  predicateType: "rea.artifact-comparison/v1",
28
36
  operation: contract.name,
@@ -49,8 +57,8 @@ const artifactUnknownInput = (input, status) => {
49
57
  question: `Artifact comparison is ${status}`,
50
58
  severity: status === "unknown" || status === "truncated" ? "high" : "medium",
51
59
  domain: "artifact-comparison",
52
- supporting_evidence_ids: evidenceIds(input.left),
53
- contradicting_evidence_ids: evidenceIds(input.right),
60
+ supporting_evidence_ids: [...input.left_evidence_ids],
61
+ contradicting_evidence_ids: [...input.right_evidence_ids],
54
62
  required_authority: "shipped-artifact",
55
63
  required_confidence: "observed",
56
64
  required_environment: null,
@@ -63,4 +71,3 @@ const artifactUnknownInput = (input, status) => {
63
71
  relationships: [],
64
72
  };
65
73
  };
66
- const evidenceIds = (input) => (Array.isArray(input) ? input : [input]).map(({ evidence_id: id }) => id);
@@ -9,19 +9,26 @@ import { captureWebScreenshotInputSchema, compareWebScreenshotsInputSchema, } fr
9
9
  import { logToolExecution } from "./toolLogging.js";
10
10
  import { toCallToolResult } from "./toolResult.js";
11
11
  import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
12
+ import { safeParseToolInput } from "./toolInputValidation.js";
12
13
  import { mcpProgressReporter } from "./mcpProgress.js";
13
14
  /** Register browser tools even when policy/provider availability denies execution. */
14
15
  export const registerBrowserTools = (server, options) => {
15
16
  const [listContract, inspectContract, analyzeContract, sessionContract, webMcpContract, captureDiffContract, screenshotContract, screenshotDiffContract,] = BROWSER_TOOL_CONTRACTS;
16
17
  server.registerTool(listContract.name, toolRegistrationOptions(listContract), async (input, context) => {
17
- const parsed = listBrowserTargetsInputSchema.parse(input);
18
+ const parsedInput = safeParseToolInput(listBrowserTargetsInputSchema, input, listContract.name);
19
+ if (!parsedInput.ok)
20
+ return toCallToolResult(parsedInput, listContract);
21
+ const parsed = parsedInput.value;
18
22
  const result = await logToolExecution(options.logger, listContract.name, () => listBrowserTargets(options.browser, options.permissionAuthority, parsed, { signal: context.mcpReq.signal }));
19
23
  if (!result.ok)
20
24
  return toCallToolResult(result, listContract);
21
25
  return evidenceResult(options, listContract, result.value);
22
26
  });
23
27
  server.registerTool(inspectContract.name, toolRegistrationOptions(inspectContract), async (input, context) => {
24
- const parsed = inspectWebPageInputSchema.parse(input);
28
+ const parsedInput = safeParseToolInput(inspectWebPageInputSchema, input, inspectContract.name);
29
+ if (!parsedInput.ok)
30
+ return toCallToolResult(parsedInput, inspectContract);
31
+ const parsed = parsedInput.value;
25
32
  const result = await logToolExecution(options.logger, inspectContract.name, () => inspectWebPage(options.browser, options.permissionAuthority, parsed, {
26
33
  signal: context.mcpReq.signal,
27
34
  progress: mcpProgressReporter(context),
@@ -31,7 +38,10 @@ export const registerBrowserTools = (server, options) => {
31
38
  return evidenceResult(options, inspectContract, result.value);
32
39
  });
33
40
  server.registerTool(analyzeContract.name, toolRegistrationOptions(analyzeContract), async (input, context) => {
34
- const parsed = analyzeWebBundleInputSchema.parse(input);
41
+ const parsedInput = safeParseToolInput(analyzeWebBundleInputSchema, input, analyzeContract.name);
42
+ if (!parsedInput.ok)
43
+ return toCallToolResult(parsedInput, analyzeContract);
44
+ const parsed = parsedInput.value;
35
45
  const result = await logToolExecution(options.logger, analyzeContract.name, () => analyzeWebBundle(options.browser, options.permissionAuthority, parsed, {
36
46
  signal: context.mcpReq.signal,
37
47
  progress: mcpProgressReporter(context),
@@ -41,7 +51,10 @@ export const registerBrowserTools = (server, options) => {
41
51
  return evidenceResult(options, analyzeContract, result.value);
42
52
  });
43
53
  server.registerTool(sessionContract.name, toolRegistrationOptions(sessionContract), async (input, context) => {
44
- const parsed = observeWebSessionInputSchema.parse(input);
54
+ const parsedInput = safeParseToolInput(observeWebSessionInputSchema, input, sessionContract.name);
55
+ if (!parsedInput.ok)
56
+ return toCallToolResult(parsedInput, sessionContract);
57
+ const parsed = parsedInput.value;
45
58
  const result = await logToolExecution(options.logger, sessionContract.name, () => observeWebSession(options.browser, options.permissionAuthority, parsed, {
46
59
  signal: context.mcpReq.signal,
47
60
  progress: mcpProgressReporter(context),
@@ -51,7 +64,10 @@ export const registerBrowserTools = (server, options) => {
51
64
  return evidenceResult(options, sessionContract, result.value);
52
65
  });
53
66
  server.registerTool(webMcpContract.name, toolRegistrationOptions(webMcpContract), async (input, context) => {
54
- const parsed = discoverWebMcpToolsInputSchema.parse(input);
67
+ const parsedInput = safeParseToolInput(discoverWebMcpToolsInputSchema, input, webMcpContract.name);
68
+ if (!parsedInput.ok)
69
+ return toCallToolResult(parsedInput, webMcpContract);
70
+ const parsed = parsedInput.value;
55
71
  const result = await logToolExecution(options.logger, webMcpContract.name, () => discoverWebMcpTools(options.browser, options.permissionAuthority, parsed, {
56
72
  signal: context.mcpReq.signal,
57
73
  progress: mcpProgressReporter(context),
@@ -61,14 +77,20 @@ export const registerBrowserTools = (server, options) => {
61
77
  return evidenceResult(options, webMcpContract, result.value);
62
78
  });
63
79
  server.registerTool(captureDiffContract.name, toolRegistrationOptions(captureDiffContract), async (input) => {
64
- const parsed = compareWebCapturesInputSchema.parse(input);
80
+ const parsedInput = safeParseToolInput(compareWebCapturesInputSchema, input, captureDiffContract.name);
81
+ if (!parsedInput.ok)
82
+ return toCallToolResult(parsedInput, captureDiffContract);
83
+ const parsed = parsedInput.value;
65
84
  const result = await logToolExecution(options.logger, captureDiffContract.name, () => compareWebCaptureEvidence(options.browser, parsed));
66
85
  if (!result.ok)
67
86
  return toCallToolResult(result, captureDiffContract);
68
87
  return evidenceResult(options, captureDiffContract, result.value);
69
88
  });
70
89
  server.registerTool(screenshotContract.name, toolRegistrationOptions(screenshotContract), async (input, context) => {
71
- const parsed = captureWebScreenshotInputSchema.parse(input);
90
+ const parsedInput = safeParseToolInput(captureWebScreenshotInputSchema, input, screenshotContract.name);
91
+ if (!parsedInput.ok)
92
+ return toCallToolResult(parsedInput, screenshotContract);
93
+ const parsed = parsedInput.value;
72
94
  const result = await logToolExecution(options.logger, screenshotContract.name, () => captureWebScreenshot(options.browser, options.permissionAuthority, parsed, {
73
95
  signal: context.mcpReq.signal,
74
96
  progress: mcpProgressReporter(context),
@@ -78,7 +100,10 @@ export const registerBrowserTools = (server, options) => {
78
100
  return evidenceResult(options, screenshotContract, result.value);
79
101
  });
80
102
  server.registerTool(screenshotDiffContract.name, toolRegistrationOptions(screenshotDiffContract), async (input) => {
81
- const parsed = compareWebScreenshotsInputSchema.parse(input);
103
+ const parsedInput = safeParseToolInput(compareWebScreenshotsInputSchema, input, screenshotDiffContract.name);
104
+ if (!parsedInput.ok)
105
+ return toCallToolResult(parsedInput, screenshotDiffContract);
106
+ const parsed = parsedInput.value;
82
107
  const result = await logToolExecution(options.logger, screenshotDiffContract.name, () => compareWebScreenshotEvidence(options.browser, parsed));
83
108
  if (!result.ok)
84
109
  return toCallToolResult(result, screenshotDiffContract);
@@ -1,31 +1,29 @@
1
+ import { readEvidenceBundle } from "../application/EvidenceBundleFiles.js";
1
2
  import { bundleComparisonInputSchema, compareBundles, } from "../domain/bundleComparison.js";
2
3
  import { createEvidence } from "../domain/evidence.js";
3
- import { EvidenceIntegrityError } from "../domain/errors.js";
4
4
  import { jsonValueSchema } from "../domain/jsonValue.js";
5
- import { err, ok } from "../domain/result.js";
5
+ import { ok } from "../domain/result.js";
6
6
  import { BUNDLE_COMPARISON_PROVIDER } from "./sessionToolPolicies.js";
7
7
  import { toCallToolResult } from "./toolResult.js";
8
- import { isSessionEvidence } from "./sessionEvidence.js";
9
8
  import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
10
9
  import { runDerivedOperation } from "./runDerivedOperation.js";
10
+ import { safeParseToolInput } from "./toolInputValidation.js";
11
11
  /** Register canonical Evidence bundle comparison. */
12
- export const registerBundleComparisonTool = (server, session, contract) => {
12
+ export const registerBundleComparisonTool = (server, session, contract, evidenceFilePolicy) => {
13
13
  server.registerTool(contract.name, toolRegistrationOptions(contract), async (input, context) => {
14
- const parsed = bundleComparisonInputSchema.parse(input);
15
- const sourceIds = [
16
- ...new Set([
17
- ...parsed.left.records.map(({ evidence_id: id }) => id),
18
- ...parsed.right.records.map(({ evidence_id: id }) => id),
19
- ]),
20
- ].sort();
21
- if ([...parsed.left.records, ...parsed.right.records].some((evidence) => !isSessionEvidence(session, evidence)))
22
- return toCallToolResult(err(new EvidenceIntegrityError("Bundle comparison input Evidence is not present in this session")), contract);
23
- const ownedUnknownDigests = new Set(session
24
- .exportEvidenceBundle()
25
- .unknowns.map(({ revision_digest: digest }) => digest));
26
- if ([...parsed.left.unknowns, ...parsed.right.unknowns].some(({ revision_digest: digest }) => !ownedUnknownDigests.has(digest)))
27
- return toCallToolResult(err(new EvidenceIntegrityError("Bundle comparison input unknown history is not present in this session")), contract);
28
- const computed = await runDerivedOperation(context, contract.name, () => compareBundles(parsed.left, parsed.right, parsed.record_pairs, parsed.offset, parsed.limit));
14
+ const parsedInput = safeParseToolInput(bundleComparisonInputSchema, input, contract.name);
15
+ if (!parsedInput.ok)
16
+ return toCallToolResult(parsedInput, contract);
17
+ const parsed = parsedInput.value;
18
+ const [left, right] = await Promise.all([
19
+ readEvidenceBundle(parsed.left_bundle_path, evidenceFilePolicy),
20
+ readEvidenceBundle(parsed.right_bundle_path, evidenceFilePolicy),
21
+ ]);
22
+ if (!left.ok)
23
+ return toCallToolResult(left, contract);
24
+ if (!right.ok)
25
+ return toCallToolResult(right, contract);
26
+ const computed = await runDerivedOperation(context, contract.name, () => compareBundles(left.value, right.value, parsed.record_pairs, parsed.offset, parsed.limit));
29
27
  if (!computed.ok)
30
28
  return toCallToolResult(computed, contract);
31
29
  const comparison = computed.value;
@@ -43,7 +41,6 @@ export const registerBundleComparisonTool = (server, session, contract) => {
43
41
  confidence: "derived",
44
42
  authority: "analyst-inference",
45
43
  limitations: comparison.limitations,
46
- evidenceLinks: sourceIds,
47
44
  });
48
45
  const recorded = session.recordEvidence(evidence);
49
46
  return toCallToolResult(recorded.ok ? ok(evidence) : recorded, contract);