rea-agents 0.3.0 → 0.5.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 (125) hide show
  1. package/README.md +139 -21
  2. package/bridge/hopper_bridge.py +156 -14
  3. package/dist/application/AnalysisProvider.js +12 -1
  4. package/dist/application/ArtifactExtraction.js +166 -0
  5. package/dist/application/ArtifactGraphConstruction.js +257 -0
  6. package/dist/application/ArtifactInventory.js +253 -0
  7. package/dist/application/BinarySession.js +174 -14
  8. package/dist/application/CompositeProvider.js +73 -0
  9. package/dist/application/DirectAnalysis.js +44 -3
  10. package/dist/application/Doctor.js +35 -6
  11. package/dist/application/EnhancedTools.js +10 -7
  12. package/dist/application/EvidenceBundleCommands.js +51 -0
  13. package/dist/application/EvidenceBundleFiles.js +127 -0
  14. package/dist/application/EvidenceLedger.js +225 -18
  15. package/dist/application/FilesystemSnapshot.js +124 -0
  16. package/dist/application/LinuxHopper.js +186 -0
  17. package/dist/application/LoopbackReplay.js +195 -37
  18. package/dist/application/ProcessHarness.js +200 -191
  19. package/dist/application/ProcessNormalization.js +44 -0
  20. package/dist/application/ProcessOwnership.js +105 -0
  21. package/dist/application/ProcessSampling.js +284 -0
  22. package/dist/application/RealHopperAssertions.js +116 -0
  23. package/dist/application/ReferenceSourceImport.js +182 -0
  24. package/dist/application/ReferenceSourceImportEntries.js +122 -0
  25. package/dist/application/ReferenceSourceImportPolicy.js +73 -0
  26. package/dist/application/ReferenceSourceImportTypes.js +18 -0
  27. package/dist/application/ReferenceSourceVcsAdapter.js +34 -0
  28. package/dist/application/Setup.js +192 -41
  29. package/dist/application/Uninstall.js +130 -0
  30. package/dist/application/runtime.js +8 -1
  31. package/dist/artifacts/ArtifactPaths.js +51 -0
  32. package/dist/artifacts/ArtifactProvider.js +130 -0
  33. package/dist/artifacts/ArtifactReader.js +9 -0
  34. package/dist/artifacts/AsarArtifactReader.js +62 -0
  35. package/dist/artifacts/DirectoryArtifactReader.js +107 -0
  36. package/dist/artifacts/MachOSliceArtifactReader.js +66 -0
  37. package/dist/artifacts/SafeOutputTree.js +199 -0
  38. package/dist/artifacts/StreamBytes.js +10 -0
  39. package/dist/artifacts/ZipArtifactReader.js +109 -0
  40. package/dist/cli.js +229 -21
  41. package/dist/cliEvidenceCommands.js +68 -0
  42. package/dist/cliLogging.js +21 -0
  43. package/dist/config.js +35 -1
  44. package/dist/contracts/artifactComparisonExample.js +95 -0
  45. package/dist/contracts/artifactToolContracts.js +84 -0
  46. package/dist/contracts/enhancedInputs.js +4 -0
  47. package/dist/contracts/functionComparisonExample.js +57 -0
  48. package/dist/contracts/investigationExamples.js +118 -0
  49. package/dist/contracts/nativeToolContracts.js +52 -0
  50. package/dist/contracts/processCaptureExample.js +25 -0
  51. package/dist/contracts/toolContractExamples.js +69 -0
  52. package/dist/contracts/toolContracts.js +99 -36
  53. package/dist/contracts/toolOutputSchemas.js +156 -82
  54. package/dist/contracts/unknownContractExamples.js +33 -0
  55. package/dist/domain/artifactComparison.js +273 -0
  56. package/dist/domain/artifactGraph.js +194 -0
  57. package/dist/domain/artifactInventoryEvidence.js +150 -0
  58. package/dist/domain/binaryTarget.js +55 -0
  59. package/dist/domain/bundleComparison.js +266 -0
  60. package/dist/domain/callPath.js +346 -0
  61. package/dist/domain/changedBehavior.js +294 -0
  62. package/dist/domain/errors.js +199 -5
  63. package/dist/domain/evidence.js +41 -10
  64. package/dist/domain/evidenceBundle.js +187 -6
  65. package/dist/domain/functionComparison.js +201 -0
  66. package/dist/domain/functionComparisonNormalization.js +112 -0
  67. package/dist/domain/functionComparisonResults.js +54 -0
  68. package/dist/domain/functionComparisonSchemas.js +82 -0
  69. package/dist/domain/functionDossierEvidence.js +171 -0
  70. package/dist/domain/hopperValues.js +35 -8
  71. package/dist/domain/nativeInspection.js +142 -0
  72. package/dist/domain/processCapture.js +152 -54
  73. package/dist/domain/processComparison.js +106 -0
  74. package/dist/domain/reconstructionUnknowns.js +90 -0
  75. package/dist/domain/reconstructionVerification.js +285 -0
  76. package/dist/domain/reconstructionVerificationSchemas.js +126 -0
  77. package/dist/domain/referenceSourceClassification.js +496 -0
  78. package/dist/domain/referenceSourceGraph.js +376 -0
  79. package/dist/domain/referenceSourceImportParsing.js +235 -0
  80. package/dist/domain/referenceSourcePolicy.js +1 -0
  81. package/dist/domain/residualUnknown.js +239 -0
  82. package/dist/domain/staticRuntimeCorrelation.js +375 -0
  83. package/dist/hopper/BridgeLauncher.js +39 -3
  84. package/dist/hopper/HopperClient.js +14 -5
  85. package/dist/hopper/HopperProvider.js +57 -22
  86. package/dist/hopper/protocol.js +13 -2
  87. package/dist/identity.js +1 -0
  88. package/dist/main.js +5 -1
  89. package/dist/native/CommandRunner.js +156 -0
  90. package/dist/native/NativeMacOSProvider.js +306 -0
  91. package/dist/native/NativeMachoInspection.js +135 -0
  92. package/dist/native/parsers/codesign.js +55 -0
  93. package/dist/native/parsers/demangle.js +26 -0
  94. package/dist/native/parsers/dyldInfo.js +25 -0
  95. package/dist/native/parsers/lipo.js +67 -0
  96. package/dist/native/parsers/otool.js +193 -0
  97. package/dist/native/parsers/plist.js +23 -0
  98. package/dist/reference/ReferenceSourceReader.js +73 -0
  99. package/dist/reference/ReferenceSourceReaderEntries.js +206 -0
  100. package/dist/reference/ReferenceSourceReaderErrors.js +19 -0
  101. package/dist/reference/ReferenceSourceReaderFile.js +119 -0
  102. package/dist/reference/ReferenceSourceReaderPaths.js +23 -0
  103. package/dist/reference/ReferenceSourceReaderTypes.js +2 -0
  104. package/dist/reference/ReferenceSourceReaderValidate.js +71 -0
  105. package/dist/server/createServer.js +31 -5
  106. package/dist/server/recordDerivedEvidence.js +10 -0
  107. package/dist/server/registerArtifactComparisonTool.js +62 -0
  108. package/dist/server/registerArtifactTools.js +6 -0
  109. package/dist/server/registerBundleComparisonTool.js +47 -0
  110. package/dist/server/registerEnhancedTools.js +64 -12
  111. package/dist/server/registerEvidenceTools.js +36 -0
  112. package/dist/server/registerFunctionComparisonTool.js +68 -0
  113. package/dist/server/registerInvestigationTools.js +224 -0
  114. package/dist/server/registerNativeTools.js +6 -0
  115. package/dist/server/registerOfficialTools.js +47 -14
  116. package/dist/server/registerProcessComparisonTool.js +106 -0
  117. package/dist/server/registerSessionTools.js +179 -70
  118. package/dist/server/sessionEvidence.js +28 -0
  119. package/dist/server/sessionToolPolicies.js +64 -0
  120. package/dist/server/toolRegistrationOptions.js +7 -0
  121. package/dist/server/toolResult.js +8 -5
  122. package/install.sh +198 -0
  123. package/package.json +18 -1
  124. package/scripts/rea.mjs +5 -1
  125. package/skills/rea-analysis/SKILL.md +77 -2
@@ -0,0 +1,71 @@
1
+ import { constants } from "node:fs";
2
+ import { lstat, realpath } from "node:fs/promises";
3
+ import { err, ok } from "../domain/result.js";
4
+ import { cancelled, failure } from "./ReferenceSourceReaderErrors.js";
5
+ import { withinRoot } from "./ReferenceSourceReaderPaths.js";
6
+ import {} from "./ReferenceSourceReaderTypes.js";
7
+ export const bigLstat = (path) => lstat(path, { bigint: true });
8
+ export const sameFile = (left, right) => left.dev === right.dev &&
9
+ left.ino === right.ino &&
10
+ left.size === right.size &&
11
+ left.mtimeNs === right.mtimeNs &&
12
+ left.ctimeNs === right.ctimeNs;
13
+ export const isAborted = (signal) => signal?.aborted === true;
14
+ const validLimits = (limits) => Object.values(limits).every((value) => Number.isSafeInteger(value) && value > 0);
15
+ export const noFollowOpenSupported = () => Number.isSafeInteger(constants.O_NOFOLLOW) && constants.O_NOFOLLOW !== 0;
16
+ export const validateDirectory = async (root, rootIdentity, path) => {
17
+ try {
18
+ const rootNow = await bigLstat(root);
19
+ if (!sameFile(rootIdentity, rootNow))
20
+ return { ok: false, code: "changed", message: "Reference root changed" };
21
+ const stats = await bigLstat(path);
22
+ if (stats.isSymbolicLink())
23
+ return {
24
+ ok: false,
25
+ code: "symlink",
26
+ message: "Symbolic links are not followed",
27
+ };
28
+ if (!stats.isDirectory())
29
+ return {
30
+ ok: false,
31
+ code: "changed",
32
+ message: "Directory identity changed",
33
+ };
34
+ const canonical = await realpath(path);
35
+ if (canonical !== path || !withinRoot(root, canonical))
36
+ return {
37
+ ok: false,
38
+ code: "changed",
39
+ message: "Directory escaped the reference root",
40
+ };
41
+ return { ok: true, stats };
42
+ }
43
+ catch {
44
+ return {
45
+ ok: false,
46
+ code: "io",
47
+ message: "Directory identity could not be verified",
48
+ };
49
+ }
50
+ };
51
+ export const prepareRoot = async (root, limits, signal) => {
52
+ if (!validLimits(limits))
53
+ return err(failure("invalid-limits", "Reader limits must be positive integers"));
54
+ if (isAborted(signal))
55
+ return err(cancelled());
56
+ try {
57
+ const metadata = await bigLstat(root);
58
+ if (metadata.isSymbolicLink() || !metadata.isDirectory())
59
+ return err(failure("invalid-root", "Reference source root must be a directory"));
60
+ const canonicalRoot = await realpath(root);
61
+ if (isAborted(signal))
62
+ return err(cancelled());
63
+ const canonicalMetadata = await bigLstat(canonicalRoot);
64
+ if (!sameFile(metadata, canonicalMetadata))
65
+ return err(failure("invalid-root", "Reference source root changed during resolution"));
66
+ return ok({ canonicalRoot, rootIdentity: canonicalMetadata });
67
+ }
68
+ catch {
69
+ return err(failure("invalid-root", "Reference source root could not be resolved"));
70
+ }
71
+ };
@@ -3,13 +3,16 @@ import { PRODUCT_IDENTITY } from "../identity.js";
3
3
  import { registerEnhancedTools } from "./registerEnhancedTools.js";
4
4
  import { registerOfficialTools } from "./registerOfficialTools.js";
5
5
  import { registerSessionTools } from "./registerSessionTools.js";
6
+ import { registerNativeTools } from "./registerNativeTools.js";
7
+ import { registerArtifactTools } from "./registerArtifactTools.js";
6
8
  import { silentLogger } from "../logger.js";
7
9
  /**
8
10
  * Construct one MCP server without acquiring subprocess resources.
9
11
  * Supplying a session adds target lifecycle tools; omitting it retains the
10
12
  * fixed-target seam used by focused tests and embedders.
11
13
  */
12
- export const createServer = (analysis, session, logger = silentLogger, processPolicy) => {
14
+ export const createServer = (analysis, session, options = {}) => {
15
+ const logger = options.logger ?? silentLogger;
13
16
  const server = new McpServer({ name: PRODUCT_IDENTITY.mcpServerKey, version: "0.1.0" }, {
14
17
  capabilities: { tools: {} },
15
18
  instructions: session === undefined
@@ -18,13 +21,36 @@ export const createServer = (analysis, session, logger = silentLogger, processPo
18
21
  });
19
22
  const toolLogger = logger.child({ layer: "server" });
20
23
  const activeTarget = session === undefined ? undefined : () => session.activeTarget();
21
- const provider = session?.providerIdentity();
22
24
  const recordEvidence = session === undefined
23
25
  ? undefined
24
26
  : (evidence) => session.recordEvidence(evidence);
25
- registerOfficialTools(server, analysis, toolLogger, activeTarget, provider, recordEvidence);
26
- registerEnhancedTools(server, analysis, toolLogger, activeTarget, provider, recordEvidence);
27
+ registerOfficialTools(server, analysis, {
28
+ logger: toolLogger,
29
+ activeTarget,
30
+ recordEvidence,
31
+ recordUnknown: session === undefined
32
+ ? undefined
33
+ : (input) => session.recordUnknown(input),
34
+ });
35
+ registerEnhancedTools(server, analysis, {
36
+ logger: toolLogger,
37
+ activeTarget,
38
+ recordEvidence,
39
+ recordUnknown: session === undefined
40
+ ? undefined
41
+ : (input) => session.recordUnknown(input),
42
+ });
43
+ registerNativeTools(server, analysis, {
44
+ logger: toolLogger,
45
+ activeTarget,
46
+ recordEvidence,
47
+ });
48
+ registerArtifactTools(server, analysis, {
49
+ logger: toolLogger,
50
+ activeTarget,
51
+ recordEvidence,
52
+ });
27
53
  if (session !== undefined)
28
- registerSessionTools(server, session, toolLogger, processPolicy);
54
+ registerSessionTools(server, session, toolLogger, options);
29
55
  return server;
30
56
  };
@@ -0,0 +1,10 @@
1
+ import { ok } from "../domain/result.js";
2
+ /** Atomically record derived Evidence with an optional approved unknown. */
3
+ export const recordDerivedEvidence = (session, evidence, unknown) => {
4
+ if (unknown !== undefined) {
5
+ const recorded = session.recordEvidenceWithUnknown(evidence, unknown);
6
+ return recorded.ok ? ok(evidence) : recorded;
7
+ }
8
+ const recorded = session.recordEvidence(evidence);
9
+ return recorded.ok ? ok(evidence) : recorded;
10
+ };
@@ -0,0 +1,62 @@
1
+ import { artifactComparisonInputSchema, compareArtifacts, } from "../domain/artifactComparison.js";
2
+ import { createEvidence } from "../domain/evidence.js";
3
+ import { jsonValueSchema } from "../domain/jsonValue.js";
4
+ import { recordDerivedEvidence } from "./recordDerivedEvidence.js";
5
+ import { resolveSessionEvidence } from "./sessionEvidence.js";
6
+ import { ARTIFACT_COMPARISON_PROVIDER } from "./sessionToolPolicies.js";
7
+ import { toCallToolResult } from "./toolResult.js";
8
+ import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
9
+ /** Register Evidence-backed deterministic artifact comparison. */
10
+ export const registerArtifactComparisonTool = (server, session, contract) => {
11
+ server.registerTool(contract.name, toolRegistrationOptions(contract), (input) => {
12
+ const parsed = artifactComparisonInputSchema.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 comparison = compareArtifacts(left.value, right.value, parsed.offset, parsed.limit);
20
+ const leftEvidenceIds = evidenceIds(parsed.left);
21
+ const rightEvidenceIds = evidenceIds(parsed.right);
22
+ const evidence = createEvidence(undefined, ARTIFACT_COMPARISON_PROVIDER, {
23
+ predicateType: "rea.artifact-comparison/v1",
24
+ operation: contract.name,
25
+ parameters: {
26
+ left_evidence_ids: leftEvidenceIds,
27
+ right_evidence_ids: rightEvidenceIds,
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: [...leftEvidenceIds, ...rightEvidenceIds],
36
+ });
37
+ return toCallToolResult(recordDerivedEvidence(session, evidence, artifactUnknownInput(parsed, comparison.status)), contract);
38
+ });
39
+ };
40
+ const artifactUnknownInput = (input, status) => {
41
+ if (input.unknown_registry_approved !== true || status === "unchanged")
42
+ return undefined;
43
+ return {
44
+ approved: true,
45
+ question: `Artifact comparison is ${status}`,
46
+ severity: status === "unknown" || status === "truncated" ? "high" : "medium",
47
+ domain: "artifact-comparison",
48
+ supporting_evidence_ids: evidenceIds(input.left),
49
+ contradicting_evidence_ids: evidenceIds(input.right),
50
+ required_authority: "shipped-artifact",
51
+ required_confidence: "observed",
52
+ required_environment: null,
53
+ recommended_probes: [
54
+ {
55
+ operation: "inventory_artifact",
56
+ rationale: "Capture both complete artifact graphs under equal limits.",
57
+ },
58
+ ],
59
+ relationships: [],
60
+ };
61
+ };
62
+ const evidenceIds = (input) => (Array.isArray(input) ? input : [input]).map(({ evidence_id: id }) => id);
@@ -0,0 +1,6 @@
1
+ import { ARTIFACT_TOOL_CONTRACTS } from "../contracts/artifactToolContracts.js";
2
+ import { registerEvidenceTools } from "./registerEvidenceTools.js";
3
+ /** Register deterministic artifact inventory and safe extraction operations. */
4
+ export const registerArtifactTools = (server, analysis, options) => {
5
+ registerEvidenceTools(server, analysis, ARTIFACT_TOOL_CONTRACTS, options);
6
+ };
@@ -0,0 +1,47 @@
1
+ import { bundleComparisonInputSchema, compareBundles, } from "../domain/bundleComparison.js";
2
+ import { createEvidence } from "../domain/evidence.js";
3
+ import { EvidenceIntegrityError } from "../domain/errors.js";
4
+ import { jsonValueSchema } from "../domain/jsonValue.js";
5
+ import { err, ok } from "../domain/result.js";
6
+ import { BUNDLE_COMPARISON_PROVIDER } from "./sessionToolPolicies.js";
7
+ import { toCallToolResult } from "./toolResult.js";
8
+ import { isSessionEvidence } from "./sessionEvidence.js";
9
+ import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
10
+ /** Register canonical Evidence bundle comparison. */
11
+ export const registerBundleComparisonTool = (server, session, contract) => {
12
+ server.registerTool(contract.name, toolRegistrationOptions(contract), (input) => {
13
+ const parsed = bundleComparisonInputSchema.parse(input);
14
+ const sourceIds = [
15
+ ...new Set([
16
+ ...parsed.left.records.map(({ evidence_id: id }) => id),
17
+ ...parsed.right.records.map(({ evidence_id: id }) => id),
18
+ ]),
19
+ ].sort();
20
+ if ([...parsed.left.records, ...parsed.right.records].some((evidence) => !isSessionEvidence(session, evidence)))
21
+ return toCallToolResult(err(new EvidenceIntegrityError("Bundle comparison input Evidence is not present in this session")), contract);
22
+ const ownedUnknownDigests = new Set(session
23
+ .exportEvidenceBundle()
24
+ .unknowns.map(({ revision_digest: digest }) => digest));
25
+ if ([...parsed.left.unknowns, ...parsed.right.unknowns].some(({ revision_digest: digest }) => !ownedUnknownDigests.has(digest)))
26
+ return toCallToolResult(err(new EvidenceIntegrityError("Bundle comparison input unknown history is not present in this session")), contract);
27
+ const comparison = compareBundles(parsed.left, parsed.right, parsed.record_pairs, parsed.offset, parsed.limit);
28
+ const evidence = createEvidence(undefined, BUNDLE_COMPARISON_PROVIDER, {
29
+ predicateType: "rea.bundle-comparison/v1",
30
+ operation: contract.name,
31
+ parameters: {
32
+ left_bundle_sha256: comparison.left_bundle_sha256,
33
+ right_bundle_sha256: comparison.right_bundle_sha256,
34
+ record_pairs: parsed.record_pairs,
35
+ offset: parsed.offset,
36
+ limit: parsed.limit,
37
+ },
38
+ result: jsonValueSchema.parse(comparison),
39
+ confidence: "derived",
40
+ authority: "analyst-inference",
41
+ limitations: comparison.limitations,
42
+ evidenceLinks: sourceIds,
43
+ });
44
+ const recorded = session.recordEvidence(evidence);
45
+ return toCallToolResult(recorded.ok ? ok(evidence) : recorded, contract);
46
+ });
47
+ };
@@ -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, logger, activeTarget, provider = {
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
- provider,
21
- recordEvidence,
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?.(), registration.provider, {
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
- redactedRawPayload: result.value,
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
- registration.recordEvidence?.(evidence);
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);