rea-agents 1.6.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 (238) hide show
  1. package/README.md +157 -66
  2. package/bridge/ghidra/ReaGhidraBridge.java +2075 -0
  3. package/bridge/hopper_bridge.py +13 -3
  4. package/dist/application/AbortablePromise.js +25 -0
  5. package/dist/application/AnalysisProvider.js +3 -0
  6. package/dist/application/AnalysisProviderEvaluation.js +60 -0
  7. package/dist/application/AnalysisProviderRegistry.js +228 -0
  8. package/dist/application/AnalysisSnapshotCache.js +41 -21
  9. package/dist/application/AnalysisSnapshotFiles.js +5 -6
  10. package/dist/application/BinarySession.js +218 -101
  11. package/dist/application/CapabilityInventory.js +18 -0
  12. package/dist/application/CompositeProvider.js +20 -8
  13. package/dist/application/CrossVersionInvestigation.js +10 -6
  14. package/dist/application/DirectAnalysis.js +49 -35
  15. package/dist/application/Doctor.js +110 -8
  16. package/dist/application/ElectronBoundaryAnalysis.js +96 -0
  17. package/dist/application/ElectronBoundaryGraph.js +9 -0
  18. package/dist/application/ElectronBoundaryGraphContext.js +62 -0
  19. package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
  20. package/dist/application/ElectronBoundaryGraphNative.js +86 -0
  21. package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
  22. package/dist/application/EnhancedTools.js +39 -14
  23. package/dist/application/InvestigationProviders.js +41 -0
  24. package/dist/application/JavaScriptApplicationEvidence.js +26 -0
  25. package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
  26. package/dist/application/JavaScriptApplicationService.js +74 -0
  27. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
  28. package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
  29. package/dist/application/JavaScriptArtifactAnalysis.js +305 -0
  30. package/dist/application/JavaScriptArtifactAnalysisTypes.js +1 -0
  31. package/dist/application/JavaScriptArtifactFiles.js +231 -0
  32. package/dist/application/JavaScriptArtifactGraphAccumulator.js +54 -0
  33. package/dist/application/JavaScriptArtifactGraphBuilder.js +144 -0
  34. package/dist/application/JavaScriptArtifactGraphContext.js +205 -0
  35. package/dist/application/JavaScriptArtifactGraphDocuments.js +108 -0
  36. package/dist/application/JavaScriptArtifactGraphEvidence.js +99 -0
  37. package/dist/application/JavaScriptArtifactGraphFindings.js +271 -0
  38. package/dist/application/JavaScriptArtifactGraphStructure.js +409 -0
  39. package/dist/application/JavaScriptArtifactReconstruction.js +77 -0
  40. package/dist/application/JavaScriptArtifactReconstructionInput.js +18 -0
  41. package/dist/application/JavaScriptReplayPlanning.js +201 -0
  42. package/dist/application/JavaScriptReplayService.js +266 -0
  43. package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
  44. package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
  45. package/dist/application/MacHopper.js +20 -4
  46. package/dist/application/ManagedApplicationGraphService.js +55 -0
  47. package/dist/application/ManagedMemberComparisonService.js +122 -0
  48. package/dist/application/ManagedNativeVerificationService.js +39 -0
  49. package/dist/application/ManagedReconstructionService.js +58 -0
  50. package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
  51. package/dist/application/PermissionAuthority.js +1 -1
  52. package/dist/application/ProcessCaptureLifecycle.js +1 -1
  53. package/dist/application/ProcessHarness.js +1 -1
  54. package/dist/application/ProcessSampling.js +16 -12
  55. package/dist/application/ProjectPermissionStore.js +1 -0
  56. package/dist/application/SessionProviderRouter.js +219 -0
  57. package/dist/application/Setup.js +84 -60
  58. package/dist/application/SetupClients.js +1 -1
  59. package/dist/application/SetupPlan.js +9 -2
  60. package/dist/application/SupportedClients.js +40 -16
  61. package/dist/application/Upgrade.js +1 -1
  62. package/dist/application/runtime.js +11 -6
  63. package/dist/artifacts/AsarArtifactReader.js +23 -3
  64. package/dist/browser/CdpBrowserProvider.js +4 -2
  65. package/dist/browser/CdpCaptureEvents.js +49 -0
  66. package/dist/browser/CdpElectronInspection.js +106 -162
  67. package/dist/browser/CdpElectronProvider.js +9 -3
  68. package/dist/browser/CdpElectronScriptEvents.js +69 -0
  69. package/dist/browser/CdpElectronScripts.js +94 -0
  70. package/dist/browser/CdpElectronWorkers.js +56 -0
  71. package/dist/browser/CdpEndpoint.js +14 -1
  72. package/dist/browser/CdpPageCapture.js +8 -4
  73. package/dist/catalogIdentity.js +6 -42
  74. package/dist/cli.js +446 -34
  75. package/dist/cliApplicationCommands.js +62 -0
  76. package/dist/cliBrowserAdvancedCommands.js +5 -4
  77. package/dist/cliBrowserCommands.js +5 -4
  78. package/dist/cliCommandNames.js +56 -0
  79. package/dist/cliElectronCommands.js +106 -3
  80. package/dist/cliEvidenceCommands.js +4 -3
  81. package/dist/cliInvestigationCommands.js +2 -1
  82. package/dist/cliJsonInput.js +54 -0
  83. package/dist/cliPolicyCommands.js +2 -1
  84. package/dist/cliProcessCommands.js +3 -2
  85. package/dist/config.js +101 -2
  86. package/dist/contracts/applicationToolContracts.js +91 -0
  87. package/dist/contracts/artifactToolContracts.js +5 -9
  88. package/dist/contracts/browserToolContracts.js +14 -62
  89. package/dist/contracts/electronToolContracts.js +45 -19
  90. package/dist/contracts/enhancedInputs.js +2 -2
  91. package/dist/contracts/errorSchemas.js +1 -0
  92. package/dist/contracts/investigationExamples.js +0 -6
  93. package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
  94. package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
  95. package/dist/contracts/managedToolContracts.js +153 -0
  96. package/dist/contracts/managedWorkflowExamples.js +346 -0
  97. package/dist/contracts/managedWorkflowToolContracts.js +203 -0
  98. package/dist/contracts/nativeToolContracts.js +2 -6
  99. package/dist/contracts/promptContracts.js +18 -2
  100. package/dist/contracts/providerSelection.js +28 -0
  101. package/dist/contracts/sessionLifecycleInputs.js +2 -0
  102. package/dist/contracts/toolContractExamples.js +10 -25
  103. package/dist/contracts/toolContracts.js +69 -65
  104. package/dist/contracts/toolEffects.js +160 -0
  105. package/dist/contracts/toolOutputSchemas.js +152 -42
  106. package/dist/doctorRuntime.js +47 -0
  107. package/dist/domain/analysisProfile.js +78 -0
  108. package/dist/domain/analysisSnapshot.js +92 -46
  109. package/dist/domain/artifactComparison.js +3 -4
  110. package/dist/domain/artifactGraph.js +1 -0
  111. package/dist/domain/binaryTarget.js +4 -29
  112. package/dist/domain/browserObservation.js +3 -0
  113. package/dist/domain/bundleComparison.js +4 -4
  114. package/dist/domain/electronObservation.js +13 -0
  115. package/dist/domain/electronStaticAnalysis.js +9 -0
  116. package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
  117. package/dist/domain/electronStaticAnalysisIpc.js +171 -0
  118. package/dist/domain/electronStaticAnalysisNative.js +164 -0
  119. package/dist/domain/electronStaticAnalysisTypes.js +1 -0
  120. package/dist/domain/electronStaticAnalysisValues.js +103 -0
  121. package/dist/domain/errors.js +148 -7
  122. package/dist/domain/evidence.js +44 -5
  123. package/dist/domain/evidenceBundle.js +9 -7
  124. package/dist/domain/functionComparison.js +9 -3
  125. package/dist/domain/functionComparisonNormalization.js +37 -0
  126. package/dist/domain/functionComparisonSchemas.js +3 -8
  127. package/dist/domain/functionDossierEvidence.js +4 -1
  128. package/dist/domain/hopperValues.js +66 -9
  129. package/dist/domain/inputIssueProjection.js +52 -0
  130. package/dist/domain/investigationWorkspace.js +2 -1
  131. package/dist/domain/javascriptApplicationAnalysis.js +155 -0
  132. package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
  133. package/dist/domain/javascriptApplicationEvidenceSchemas.js +379 -0
  134. package/dist/domain/javascriptApplicationGraph.js +282 -0
  135. package/dist/domain/javascriptApplicationGraphSchemas.js +139 -0
  136. package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
  137. package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
  138. package/dist/domain/javascriptApplicationVersionItems.js +221 -0
  139. package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
  140. package/dist/domain/javascriptAstFingerprint.js +143 -0
  141. package/dist/domain/javascriptFeatureSeed.js +140 -0
  142. package/dist/domain/javascriptFeatureTrace.js +232 -0
  143. package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
  144. package/dist/domain/javascriptFeatureTraversal.js +88 -0
  145. package/dist/domain/javascriptNativeHandoff.js +85 -0
  146. package/dist/domain/javascriptReplay.js +361 -0
  147. package/dist/domain/javascriptRuntimeLoadState.js +106 -0
  148. package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
  149. package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
  150. package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
  151. package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
  152. package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
  153. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
  154. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
  155. package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
  156. package/dist/domain/javascriptStaticAnalysis.js +346 -0
  157. package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
  158. package/dist/domain/javascriptStaticAnalysisHelpers.js +274 -0
  159. package/dist/domain/javascriptStaticAnalysisState.js +23 -0
  160. package/dist/domain/javascriptStaticAnalysisTypes.js +1 -0
  161. package/dist/domain/managedApplicationGraph.js +556 -0
  162. package/dist/domain/managedArtifact.js +444 -0
  163. package/dist/domain/managedMemberComparison.js +565 -0
  164. package/dist/domain/managedNativeVerification.js +408 -0
  165. package/dist/domain/managedReconstruction.js +183 -0
  166. package/dist/domain/managedRuntimeCorrelation.js +175 -0
  167. package/dist/domain/reconstructionVerificationSchemas.js +1 -3
  168. package/dist/dotnet/ManagedArtifactInspector.js +318 -0
  169. package/dist/dotnet/ManagedMemberInspector.js +887 -0
  170. package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
  171. package/dist/dotnet/ManagedMetadataInventory.js +368 -0
  172. package/dist/dotnet/ManagedMetadataLayout.js +322 -0
  173. package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
  174. package/dist/dotnet/ManagedPeReader.js +157 -0
  175. package/dist/dotnet/ManagedReaderFailure.js +10 -0
  176. package/dist/dotnet/ManagedStaticProvider.js +203 -0
  177. package/dist/generatedPackageMetadata.js +2 -2
  178. package/dist/ghidra/GhidraAnalysisProfile.js +37 -0
  179. package/dist/ghidra/GhidraClient.js +400 -0
  180. package/dist/ghidra/GhidraClientTypes.js +1 -0
  181. package/dist/ghidra/GhidraDefaults.js +20 -0
  182. package/dist/ghidra/GhidraDiagnostics.js +43 -0
  183. package/dist/ghidra/GhidraDoctor.js +59 -0
  184. package/dist/ghidra/GhidraFunctionValues.js +264 -0
  185. package/dist/ghidra/GhidraInstallation.js +239 -0
  186. package/dist/ghidra/GhidraInventoryValues.js +217 -0
  187. package/dist/ghidra/GhidraLauncher.js +139 -0
  188. package/dist/ghidra/GhidraProvider.js +366 -0
  189. package/dist/ghidra/GhidraRequestQueue.js +98 -0
  190. package/dist/ghidra/GhidraResponseBuffer.js +31 -0
  191. package/dist/ghidra/GhidraResponseRouter.js +24 -0
  192. package/dist/ghidra/GhidraSessionError.js +39 -0
  193. package/dist/ghidra/GhidraSessionValues.js +62 -0
  194. package/dist/ghidra/GhidraSocketConnection.js +46 -0
  195. package/dist/ghidra/protocol.js +69 -0
  196. package/dist/hopper/BridgeLauncher.js +19 -54
  197. package/dist/hopper/HopperAnalysisProfile.js +106 -0
  198. package/dist/hopper/HopperClient.js +154 -162
  199. package/dist/hopper/HopperProvider.js +99 -7
  200. package/dist/hopper/HopperSessionValues.js +27 -0
  201. package/dist/hopper/HopperSocketConnection.js +31 -0
  202. package/dist/main.js +14 -0
  203. package/dist/native/NativeMacOSProvider.js +3 -1
  204. package/dist/process/PendingOperations.js +59 -0
  205. package/dist/process/PrivateRuntimeRoot.js +33 -0
  206. package/dist/process/ProviderDeadline.js +95 -0
  207. package/dist/process/ProviderProcess.js +275 -0
  208. package/dist/replay/JavaScriptReplayWorker.js +286 -0
  209. package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
  210. package/dist/replay/LinuxRuntimeClosure.js +46 -0
  211. package/dist/replay/LinuxSeccompPolicy.js +40 -0
  212. package/dist/replay/ReplayWorkerProtocol.js +55 -0
  213. package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
  214. package/dist/server/createServer.js +53 -0
  215. package/dist/server/promptCompletion.js +9 -3
  216. package/dist/server/registerApplicationTools.js +115 -0
  217. package/dist/server/registerArtifactComparisonTool.js +16 -9
  218. package/dist/server/registerBrowserTools.js +33 -8
  219. package/dist/server/registerBundleComparisonTool.js +17 -20
  220. package/dist/server/registerElectronTools.js +45 -3
  221. package/dist/server/registerEnhancedTools.js +67 -19
  222. package/dist/server/registerEvidenceTools.js +5 -1
  223. package/dist/server/registerFunctionComparisonTool.js +14 -7
  224. package/dist/server/registerInvestigationTools.js +21 -17
  225. package/dist/server/registerManagedTools.js +6 -0
  226. package/dist/server/registerManagedWorkflowTools.js +277 -0
  227. package/dist/server/registerOfficialTools.js +11 -8
  228. package/dist/server/registerProcessComparisonTool.js +25 -47
  229. package/dist/server/registerSessionStatusTool.js +7 -7
  230. package/dist/server/registerSessionTools.js +57 -87
  231. package/dist/server/sessionEvidence.js +14 -19
  232. package/dist/server/toolInputValidation.js +77 -0
  233. package/dist/server/toolRegistrationOptions.js +79 -2
  234. package/dist/server/toolResult.js +25 -17
  235. package/package.json +17 -7
  236. package/scripts/rea.mjs +36 -2
  237. package/skills/rea-analysis/SKILL.md +59 -3
  238. /package/dist/{application → process}/ProcessOwnership.js +0 -0
@@ -1,5 +1,6 @@
1
1
  import { parseBinaryTarget } from "../domain/binaryTarget.js";
2
- import { AnalysisCapabilityUnavailableError, EvidenceIntegrityError, EvidenceLimitError, AnalysisCancelledError, NoBinaryOpenError, } from "../domain/errors.js";
2
+ import { analysisProfilesEqual, } from "../domain/analysisProfile.js";
3
+ import { AnalysisCapabilityUnavailableError, EvidenceIntegrityError, EvidenceLimitError, AnalysisCancelledError, NoBinaryOpenError, ProviderAdapterError, } from "../domain/errors.js";
3
4
  import { err, ok } from "../domain/result.js";
4
5
  import { createEvidence } from "../domain/evidence.js";
5
6
  import { evidenceBundleForTarget } from "../domain/evidenceBundle.js";
@@ -7,27 +8,25 @@ import { EvidenceLedger } from "./EvidenceLedger.js";
7
8
  import { UnknownRegistryError } from "../domain/errors.js";
8
9
  import { enhancedToolNameSchema } from "../contracts/enhancedInputs.js";
9
10
  import { OFFICIAL_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
10
- import { snapshotMatchesTarget, snapshotTarget, } from "../domain/analysisSnapshot.js";
11
+ import { snapshotMatchesBinding, snapshotMatchesTarget, snapshotTarget, } from "../domain/analysisSnapshot.js";
11
12
  import { AnalysisSnapshotCache, isSnapshotCacheable, } from "./AnalysisSnapshotCache.js";
12
13
  import { UNKNOWN_REGISTRY_PROVIDER, unknownEvidenceLinks, unknownMutationEvidence, } from "./UnknownEvidence.js";
13
14
  import { parseInvestigationWorkspace, } from "../domain/investigationWorkspace.js";
15
+ import { SessionProviderRouter, } from "./SessionProviderRouter.js";
14
16
  const OFFICIAL_OPERATIONS = new Set(OFFICIAL_TOOL_CONTRACTS.map(({ name }) => name));
15
17
  /**
16
18
  * Owns the single active target shared by CLI and MCP adapters.
17
19
  *
18
- * Target transitions are serialized because each client dispatches Hopper API
19
- * work on its dedicated Python thread, and switching targets tears that bridge
20
- * down. A failed switch recreates the previous target instead of retaining a
21
- * client whose bridge was already shut down.
20
+ * Target transitions are serialized because switching targets tears down the
21
+ * active provider client. A failed switch recreates the previous target instead
22
+ * of retaining a client whose resources were already shut down.
22
23
  */
23
24
  export class BinarySession {
24
25
  provider;
25
26
  #active;
26
27
  #transition = Promise.resolve();
27
28
  #calls = new Set();
28
- #createClient;
29
- #providerIdentity;
30
- #capabilities;
29
+ #providerRouter;
31
30
  #evidence = new EvidenceLedger({
32
31
  maxRecords: 10_000,
33
32
  maxBytes: 64 * 1024 * 1024,
@@ -36,46 +35,55 @@ export class BinarySession {
36
35
  #investigationWorkspaces = new Map();
37
36
  #runtimeAvailability = new Map();
38
37
  #availabilityListeners = new Set();
39
- #snapshotsEnabled;
40
38
  #snapshotInvalidated = false;
41
39
  constructor(provider, options = {}) {
42
40
  this.provider = provider;
43
- this.#snapshotsEnabled = options.snapshotsEnabled ?? true;
44
- this.#createClient =
45
- typeof provider === "function"
41
+ this.#providerRouter =
42
+ provider instanceof SessionProviderRouter
46
43
  ? provider
47
- : (target) => provider.createClient(target);
48
- this.#providerIdentity =
49
- typeof provider === "function"
50
- ? { id: "unidentified", name: "Unidentified provider", version: null }
51
- : provider.identity();
52
- this.#capabilities =
53
- typeof provider === "function"
54
- ? undefined
55
- : new Map(provider
56
- .capabilities()
57
- .map((descriptor) => [descriptor.operation, descriptor]));
44
+ : SessionProviderRouter.legacy(provider, options);
58
45
  }
59
46
  /** Identify the provider producing evidence for this session. */
60
47
  providerIdentity(operation) {
48
+ const route = this.#currentRoute();
49
+ let selected = route.identity;
61
50
  if (operation !== undefined) {
62
- const exact = this.#capabilities?.get(operation)?.provider;
51
+ const exact = route.capabilities?.get(operation)?.provider;
63
52
  if (exact !== undefined)
64
- return structuredClone(exact);
65
- if (enhancedToolNameSchema.safeParse(operation).success) {
53
+ selected = exact;
54
+ else if (enhancedToolNameSchema.safeParse(operation).success) {
66
55
  const providers = new Map();
67
- for (const descriptor of this.#capabilities?.values() ?? [])
56
+ for (const descriptor of route.capabilities?.values() ?? [])
68
57
  if (descriptor.available &&
69
58
  OFFICIAL_OPERATIONS.has(descriptor.operation))
70
59
  providers.set(descriptor.provider.id, descriptor.provider);
71
60
  if (providers.size === 1) {
72
61
  const provider = providers.values().next().value;
73
62
  if (provider !== undefined)
74
- return structuredClone(provider);
63
+ selected = provider;
75
64
  }
76
65
  }
77
66
  }
78
- return structuredClone(this.#providerIdentity);
67
+ const profile = this.#active?.profile;
68
+ return structuredClone(profile !== null &&
69
+ profile !== undefined &&
70
+ profile.provider.id === selected.id
71
+ ? profile.provider
72
+ : selected);
73
+ }
74
+ /** Return the selected immutable profile, optionally scoped to an operation. */
75
+ analysisProfile(operation) {
76
+ const profile = this.#active?.profile;
77
+ if (profile === null || profile === undefined)
78
+ return undefined;
79
+ if (operation !== undefined &&
80
+ this.providerIdentity(operation).id !== profile.provider.id)
81
+ return undefined;
82
+ return structuredClone(profile);
83
+ }
84
+ /** Return opaque adapter metadata retained for legacy open_binary output. */
85
+ openCompatibility() {
86
+ return structuredClone(this.#active?.compatibility ?? {});
79
87
  }
80
88
  /** Observe runtime provider-health changes that affect discovery metadata. */
81
89
  onAvailabilityChanged(listener) {
@@ -104,20 +112,24 @@ export class BinarySession {
104
112
  }
105
113
  /** Export immutable cached calls and session evidence for the active target. */
106
114
  exportAnalysisSnapshot() {
107
- if (!this.#snapshotsEnabled)
108
- return err(new EvidenceIntegrityError("Analysis snapshots are unavailable with custom Hopper loader arguments"));
109
115
  if (this.#snapshotInvalidated)
110
116
  return err(new EvidenceIntegrityError("Analysis snapshots are unavailable after analysis metadata mutations"));
111
117
  const target = this.#active?.target;
112
- return this.#snapshot.export(target, target === undefined
118
+ const profile = this.#active?.profile ?? undefined;
119
+ if (target !== undefined && profile === undefined)
120
+ return err(new EvidenceIntegrityError("Analysis snapshots require a concrete provider analysis profile"));
121
+ return this.#snapshot.export(target, profile, target === undefined
113
122
  ? this.#evidence.export()
114
123
  : evidenceBundleForTarget(this.#evidence.export(), target.sha256));
115
124
  }
116
125
  /** Stage validated cached calls for an identical binary and merge its evidence. */
117
126
  importAnalysisSnapshot(snapshot) {
118
- if (!this.#snapshotsEnabled)
119
- return err(new EvidenceIntegrityError("Analysis snapshots are unavailable with custom Hopper loader arguments"));
120
- return this.#snapshot.import(snapshot, this.#active?.target, (bundle) => this.#evidence.import(bundle));
127
+ const active = this.#active;
128
+ if (active?.profile === null)
129
+ return err(new EvidenceIntegrityError("Analysis snapshot profile_mismatch: the active target has no concrete analysis profile"));
130
+ return this.#snapshot.import(snapshot, active === undefined
131
+ ? undefined
132
+ : { target: active.target, profile: active.profile }, (bundle) => this.#evidence.import(bundle));
121
133
  }
122
134
  /** Retain one validated immutable workspace revision for session resources. */
123
135
  retainInvestigationWorkspace(workspace) {
@@ -187,15 +199,31 @@ export class BinarySession {
187
199
  const parsed = await parseBinaryTarget(path, process.cwd(), process.arch, options.targetKind);
188
200
  if (!parsed.ok)
189
201
  return parsed;
202
+ const sameTarget = this.#active?.target.path === parsed.value.path &&
203
+ snapshotMatchesTarget(snapshotTarget(this.#active.target), parsed.value);
204
+ const resolvedRoute = sameTarget &&
205
+ options.providerId === undefined &&
206
+ this.#active !== undefined
207
+ ? ok(this.#active.route)
208
+ : await this.#providerRouter.resolve(parsed.value, options.providerId, options.signal);
209
+ if (!resolvedRoute.ok)
210
+ return resolvedRoute;
211
+ const route = resolvedRoute.value;
212
+ const { profile, compatibility } = route;
190
213
  if (options.snapshot !== undefined &&
191
- !snapshotMatchesTarget(options.snapshot.target, parsed.value))
192
- return err(new EvidenceIntegrityError("Analysis snapshot target does not match the requested binary"));
193
- if (this.#active === undefined && !this.#snapshot.matches(parsed.value))
194
- return err(new EvidenceIntegrityError("Analysis snapshot target does not match the requested binary"));
214
+ (profile === null ||
215
+ !snapshotMatchesBinding(options.snapshot, parsed.value, profile)))
216
+ return err(new EvidenceIntegrityError("Analysis snapshot profile_mismatch: target, provider, or analysis profile does not match the requested binary"));
217
+ if (this.#active === undefined &&
218
+ !this.#snapshot.matches(parsed.value, profile ?? undefined))
219
+ return err(new EvidenceIntegrityError("Analysis snapshot profile_mismatch: staged target, provider, or analysis profile does not match"));
195
220
  if (isAborted(options.signal))
196
221
  return err(new AnalysisCancelledError("open_binary"));
197
- if (this.#active?.target.path === parsed.value.path &&
198
- snapshotMatchesTarget(snapshotTarget(this.#active.target), parsed.value)) {
222
+ const activeProfile = this.#active?.profile;
223
+ const sameProfile = activeProfile === null || activeProfile === undefined
224
+ ? profile === null
225
+ : profile !== null && analysisProfilesEqual(activeProfile, profile);
226
+ if (sameTarget && sameProfile) {
199
227
  if (options.snapshot !== undefined) {
200
228
  const imported = this.importAnalysisSnapshot(options.snapshot);
201
229
  if (!imported.ok)
@@ -207,7 +235,7 @@ export class BinarySession {
207
235
  const previous = this.#active;
208
236
  this.#active = undefined;
209
237
  await previous?.client.close();
210
- const client = this.#createClient(parsed.value);
238
+ const client = route.createClient(parsed.value);
211
239
  const started = await client.execute("health", {}, options);
212
240
  if (!started.ok) {
213
241
  await client.close();
@@ -219,9 +247,18 @@ export class BinarySession {
219
247
  await this.#restore(previous);
220
248
  return err(new AnalysisCancelledError("open_binary"));
221
249
  }
222
- this.#active = { target: parsed.value, client };
250
+ this.#active = {
251
+ target: parsed.value,
252
+ client,
253
+ profile,
254
+ compatibility: structuredClone(compatibility),
255
+ route,
256
+ };
223
257
  this.#clearRuntimeAvailability();
224
- this.#snapshot.select(parsed.value);
258
+ if (profile === null)
259
+ this.#snapshot.clear();
260
+ else
261
+ this.#snapshot.select(parsed.value, profile);
225
262
  if (options.snapshot !== undefined) {
226
263
  const imported = this.importAnalysisSnapshot(options.snapshot);
227
264
  if (!imported.ok) {
@@ -252,59 +289,74 @@ export class BinarySession {
252
289
  /** Describe the active binary session. */
253
290
  status() {
254
291
  const target = this.#active?.target;
292
+ const route = this.#currentRoute();
293
+ const configuredProvider = this.#providerRouter.configuredIdentity();
255
294
  const provider = {
256
- id: this.#providerIdentity.id,
257
- name: this.#providerIdentity.name,
258
- version: this.#providerIdentity.version,
295
+ id: configuredProvider.id,
296
+ name: configuredProvider.name,
297
+ version: configuredProvider.version,
259
298
  };
260
- const providers = new Map();
261
- if (this.#capabilities === undefined)
262
- providers.set(provider.id, provider);
263
- else
264
- for (const descriptor of this.#capabilities.values())
265
- providers.set(descriptor.provider.id, descriptor.provider);
266
- const providerList = [...providers.values()]
267
- .sort((left, right) => left.id.localeCompare(right.id))
299
+ const providerList = this.#providerRouter
300
+ .providerIdentities(route)
268
301
  .map(({ id, name, version }) => ({ id, name, version }));
269
- const capabilities = this.#capabilities === undefined
302
+ const capabilities = route.capabilities === undefined
270
303
  ? []
271
- : [...this.#capabilities.values()]
304
+ : [...route.capabilities.values()]
272
305
  .sort((left, right) => left.operation.localeCompare(right.operation))
273
306
  .map((descriptor) => ({
274
307
  ...descriptor,
275
308
  ...this.#runtimeAvailability.get(descriptor.operation),
276
309
  }))
277
- .map((descriptor) => ({
278
- operation: descriptor.operation,
279
- available: descriptor.available,
280
- reason: descriptor.reason,
281
- input_contract_version: descriptor.inputContractVersion,
282
- output_contract_version: descriptor.outputContractVersion,
283
- pagination: descriptor.pagination,
284
- exhaustive: descriptor.exhaustive,
285
- effects: {
286
- mutates_artifact: descriptor.effects.mutatesArtifact,
287
- launches_process: descriptor.effects.launchesProcess,
288
- may_show_ui: descriptor.effects.mayShowUi,
289
- may_access_network: descriptor.effects.mayAccessNetwork,
290
- may_write_filesystem: descriptor.effects.mayWriteFilesystem,
291
- changes_permissions: descriptor.effects.changesPermissions,
292
- requires_root: descriptor.effects.requiresRoot,
293
- },
294
- limits: {
295
- max_results: descriptor.limits.maxResults,
296
- max_payload_bytes: descriptor.limits.maxPayloadBytes,
297
- timeout_ms: descriptor.limits.timeoutMs,
310
+ .map((descriptor) => capabilityStatus(descriptor));
311
+ const analysisProviderBinding = route.binding === null
312
+ ? null
313
+ : {
314
+ provider: {
315
+ id: route.binding.identity.id,
316
+ name: route.binding.identity.name,
317
+ version: route.binding.identity.version,
298
318
  },
299
- limitations: [...descriptor.limitations],
300
- }));
319
+ selection_source: route.binding.selectionSource,
320
+ analysis_profile: structuredClone(route.binding.profile),
321
+ };
322
+ const analysisProviderCandidates = this.#providerRouter
323
+ .candidateStatuses(route)
324
+ .map((candidate) => ({
325
+ provider: {
326
+ id: candidate.provider.id,
327
+ name: candidate.provider.name,
328
+ version: candidate.provider.version,
329
+ },
330
+ availability: {
331
+ status: candidate.availability.status,
332
+ code: candidate.availability.code,
333
+ reason: candidate.availability.reason,
334
+ diagnostics: structuredClone(candidate.availability.diagnostics),
335
+ },
336
+ target_support: {
337
+ status: candidate.targetSupport.status,
338
+ code: candidate.targetSupport.code,
339
+ reason: candidate.targetSupport.reason,
340
+ diagnostics: structuredClone(candidate.targetSupport.diagnostics),
341
+ },
342
+ selected: candidate.selected,
343
+ capabilities: candidate.capabilities
344
+ .slice()
345
+ .sort((left, right) => left.operation.localeCompare(right.operation))
346
+ .map((descriptor) => capabilityStatus(descriptor)),
347
+ }));
348
+ const common = {
349
+ provider,
350
+ providers: providerList,
351
+ capabilities,
352
+ analysis_provider_binding: analysisProviderBinding,
353
+ analysis_provider_candidates: analysisProviderCandidates,
354
+ };
301
355
  return target === undefined
302
- ? { open: false, provider, providers: providerList, capabilities }
356
+ ? { open: false, ...common }
303
357
  : {
304
358
  open: true,
305
- provider,
306
- providers: providerList,
307
- capabilities,
359
+ ...common,
308
360
  path: target.path,
309
361
  sha256: target.sha256,
310
362
  format: target.format,
@@ -312,14 +364,14 @@ export class BinarySession {
312
364
  architecture: target.architecture ?? null,
313
365
  };
314
366
  }
315
- /** Return the immutable artifact identity captured before Hopper launched. */
367
+ /** Return the immutable artifact identity captured before its provider started. */
316
368
  activeTarget() {
317
369
  return this.#active === undefined
318
370
  ? undefined
319
371
  : structuredClone(this.#active.target);
320
372
  }
321
373
  /**
322
- * Invoke a Hopper tool against the active target.
374
+ * Invoke a provider operation against the active target.
323
375
  * Calls may overlap, but a pending target transition prevents new calls from
324
376
  * entering until the transition has settled.
325
377
  */
@@ -327,15 +379,26 @@ export class BinarySession {
327
379
  const transitioned = await this.#waitForTransition(name, options?.signal);
328
380
  if (!transitioned.ok)
329
381
  return transitioned;
330
- const capability = this.#capabilities?.get(name);
331
- if (this.#capabilities !== undefined && capability?.available !== true)
332
- return err(new AnalysisCapabilityUnavailableError(this.#providerIdentity.id, name, capability?.reason ?? "operation is not declared by this provider"));
333
382
  const active = this.#active;
334
383
  if (active === undefined)
335
384
  return err(new NoBinaryOpenError());
336
- const cacheable = isSnapshotCacheable(name, capability, arguments_);
385
+ const capability = active.route.capabilities?.get(name);
386
+ if (active.route.capabilities !== undefined &&
387
+ capability?.available !== true) {
388
+ const selectionError = this.#providerRouter.unboundOperationError(name, active.route);
389
+ if (selectionError !== undefined)
390
+ return err(selectionError);
391
+ return err(new AnalysisCapabilityUnavailableError(active.route.binding?.identity.id ?? active.route.identity.id, name, capability?.reason ?? "operation is not declared by this provider"));
392
+ }
393
+ const profile = active.profile !== null &&
394
+ (capability === undefined ||
395
+ capability.provider.id === active.profile.provider.id)
396
+ ? active.profile
397
+ : undefined;
398
+ const cacheable = profile !== undefined &&
399
+ isSnapshotCacheable(name, capability, arguments_);
337
400
  const cached = cacheable
338
- ? this.#snapshot.lookup(active.target, name, arguments_, capability.provider)
401
+ ? this.#snapshot.lookup(active.target, profile, name, arguments_)
339
402
  : undefined;
340
403
  if (cached !== undefined)
341
404
  return ok(cached);
@@ -343,14 +406,21 @@ export class BinarySession {
343
406
  this.#calls.add(call);
344
407
  try {
345
408
  const result = await call;
346
- this.#observeRuntimeAvailability(name, result);
347
- if (result.ok && cacheable)
348
- this.#snapshot.record(active.target, name, arguments_, result.value);
349
- else if (result.ok && capability?.effects.mutatesArtifact === true) {
409
+ const profiled = commitExecutionProfile(name, result, profile);
410
+ this.#observeRuntimeAvailability(name, profiled);
411
+ if (profiled.ok && cacheable)
412
+ this.#snapshot.record({
413
+ target: active.target,
414
+ profile,
415
+ operation: name,
416
+ parameters: arguments_,
417
+ execution: profiled.value,
418
+ });
419
+ else if (profiled.ok && capability?.effects.mutatesArtifact === true) {
350
420
  this.#snapshot.clear();
351
421
  this.#snapshotInvalidated = true;
352
422
  }
353
- return result;
423
+ return profiled;
354
424
  }
355
425
  finally {
356
426
  this.#calls.delete(call);
@@ -373,9 +443,10 @@ export class BinarySession {
373
443
  "HopperStartError",
374
444
  "HopperRemoteError",
375
445
  ].includes(result.error._tag)) {
376
- const providerId = this.#capabilities?.get(operation)?.provider.id;
446
+ const capabilities = this.#active?.route.capabilities;
447
+ const providerId = capabilities?.get(operation)?.provider.id;
377
448
  let changed = false;
378
- for (const descriptor of this.#capabilities?.values() ?? [])
449
+ for (const descriptor of capabilities?.values() ?? [])
379
450
  if (providerId !== undefined && descriptor.provider.id === providerId)
380
451
  changed =
381
452
  this.#setRuntimeAvailability(descriptor.operation, false, "Provider became unavailable during this session.") || changed;
@@ -414,6 +485,9 @@ export class BinarySession {
414
485
  }
415
486
  }
416
487
  }
488
+ #currentRoute() {
489
+ return this.#active?.route ?? this.#providerRouter.initialRoute();
490
+ }
417
491
  #serialize(operation) {
418
492
  const result = this.#transition.then(operation, operation);
419
493
  this.#transition = result.then(() => undefined, () => undefined);
@@ -425,10 +499,16 @@ export class BinarySession {
425
499
  async #restore(previous) {
426
500
  if (previous === undefined)
427
501
  return;
428
- const client = this.#createClient(previous.target);
502
+ const client = previous.route.createClient(previous.target);
429
503
  const started = await client.execute("health", {});
430
504
  if (started.ok)
431
- this.#active = { target: previous.target, client };
505
+ this.#active = {
506
+ target: previous.target,
507
+ client,
508
+ profile: previous.profile,
509
+ compatibility: previous.compatibility,
510
+ route: previous.route,
511
+ };
432
512
  else
433
513
  await client.close();
434
514
  }
@@ -459,3 +539,40 @@ export class BinarySession {
459
539
  }
460
540
  }
461
541
  const isAborted = (signal) => signal?.aborted === true;
542
+ const commitExecutionProfile = (operation, result, profile) => {
543
+ if (!result.ok || profile === undefined)
544
+ return result;
545
+ const provider = result.value.provider;
546
+ if (provider.id !== profile.provider.id ||
547
+ provider.name !== profile.provider.name ||
548
+ provider.version !== profile.provider.version)
549
+ return err(new ProviderAdapterError(profile.provider.id, `${operation}:profile`));
550
+ return ok({
551
+ ...result.value,
552
+ analysisProfile: structuredClone(profile),
553
+ });
554
+ };
555
+ const capabilityStatus = (descriptor) => ({
556
+ operation: descriptor.operation,
557
+ available: descriptor.available,
558
+ reason: descriptor.reason,
559
+ input_contract_version: descriptor.inputContractVersion,
560
+ output_contract_version: descriptor.outputContractVersion,
561
+ pagination: descriptor.pagination,
562
+ exhaustive: descriptor.exhaustive,
563
+ effects: {
564
+ mutates_artifact: descriptor.effects.mutatesArtifact,
565
+ launches_process: descriptor.effects.launchesProcess,
566
+ may_show_ui: descriptor.effects.mayShowUi,
567
+ may_access_network: descriptor.effects.mayAccessNetwork,
568
+ may_write_filesystem: descriptor.effects.mayWriteFilesystem,
569
+ changes_permissions: descriptor.effects.changesPermissions,
570
+ requires_root: descriptor.effects.requiresRoot,
571
+ },
572
+ limits: {
573
+ max_results: descriptor.limits.maxResults,
574
+ max_payload_bytes: descriptor.limits.maxPayloadBytes,
575
+ timeout_ms: descriptor.limits.timeoutMs,
576
+ },
577
+ limitations: [...descriptor.limitations],
578
+ });
@@ -44,6 +44,7 @@ export const buildCapabilityInventory = (sessionStatus, policy) => {
44
44
  available: availability.reason === "available",
45
45
  reason: availability.reason,
46
46
  remediation: availability.remediation,
47
+ effects: { ...contract.effects },
47
48
  annotations: {
48
49
  read_only: contract.annotations.readOnlyHint ?? false,
49
50
  destructive: contract.annotations.destructiveHint ?? false,
@@ -59,6 +60,23 @@ const availabilityFor = (name, kind, targetOpen, targetKind, descriptors, policy
59
60
  reason: "policy_disabled",
60
61
  remediation: "Enable or grant process_capture within the administrator ceiling.",
61
62
  };
63
+ if (name === "run_controlled_replay" && !policy.javascriptReplayEnabled)
64
+ return {
65
+ reason: "policy_disabled",
66
+ remediation: "Enable javascript_replay with exact source roots and sandbox executables.",
67
+ };
68
+ if (name === "run_controlled_replay")
69
+ return { reason: "available", remediation: null };
70
+ if (name === "plan_managed_runtime_correlation" &&
71
+ !policy.managedRuntimeEnabled)
72
+ return {
73
+ reason: "policy_disabled",
74
+ remediation: "Enable managed_runtime with exact artifact roots and a runtime executable.",
75
+ };
76
+ if (name === "plan_managed_runtime_correlation")
77
+ return { reason: "available", remediation: null };
78
+ if (kind === "application")
79
+ return { reason: "available", remediation: null };
62
80
  if (name === "import_evidence_bundle" && policy.evidenceFileRoots === 0)
63
81
  return {
64
82
  reason: "policy_disabled",
@@ -1,8 +1,9 @@
1
- import { ProviderSelectionError } from "../domain/errors.js";
1
+ import { AnalysisCapabilityUnavailableError } from "../domain/errors.js";
2
2
  import { err, ok } from "../domain/result.js";
3
- const compositeIdentity = (providers) => ({
4
- id: `composite:${providers
5
- .map((provider) => provider.identity().id)
3
+ /** Synthetic compatibility identity for a deterministic provider set. */
4
+ export const compositeProviderIdentity = (identities) => ({
5
+ id: `composite:${identities
6
+ .map(({ id }) => id)
6
7
  .sort()
7
8
  .join("+")}`,
8
9
  name: "REA composite analysis provider",
@@ -14,11 +15,16 @@ export class CompositeProvider {
14
15
  #identity;
15
16
  #capabilities;
16
17
  #providerByOperation;
18
+ #profileProvider;
17
19
  constructor(providers) {
18
20
  this.providers = providers;
19
21
  if (providers.length === 0)
20
22
  throw new RangeError("CompositeProvider requires at least one provider");
21
- this.#identity = Object.freeze(compositeIdentity(providers));
23
+ const profileProviders = providers.filter(({ resolveAnalysisProfile }) => resolveAnalysisProfile !== undefined);
24
+ if (profileProviders.length > 1)
25
+ throw new TypeError("CompositeProvider supports at most one target-bound analysis profile");
26
+ this.#profileProvider = profileProviders[0];
27
+ this.#identity = Object.freeze(compositeProviderIdentity(providers.map((provider) => provider.identity())));
22
28
  const routes = new Map();
23
29
  const capabilities = [];
24
30
  for (const provider of providers) {
@@ -39,13 +45,19 @@ export class CompositeProvider {
39
45
  capabilities() {
40
46
  return this.#capabilities;
41
47
  }
42
- createClient(target) {
48
+ resolveAnalysisProfile(target, options) {
49
+ const resolve = this.#profileProvider?.resolveAnalysisProfile;
50
+ return resolve === undefined
51
+ ? Promise.resolve(ok({ profile: null, compatibility: {} }))
52
+ : resolve.call(this.#profileProvider, target, options);
53
+ }
54
+ createClient(target, profile) {
43
55
  const clients = new Map();
44
56
  const clientFor = (provider) => {
45
57
  const existing = clients.get(provider);
46
58
  if (existing !== undefined)
47
59
  return existing;
48
- const created = provider.createClient(target);
60
+ const created = provider.createClient(target, profile?.provider.id === provider.identity().id ? profile : undefined);
49
61
  clients.set(provider, created);
50
62
  return created;
51
63
  };
@@ -62,7 +74,7 @@ export class CompositeProvider {
62
74
  }));
63
75
  const provider = this.#providerByOperation.get(operation);
64
76
  return provider === undefined
65
- ? Promise.resolve(err(new ProviderSelectionError(operation)))
77
+ ? Promise.resolve(err(new AnalysisCapabilityUnavailableError(this.#identity.id, operation, "operation is not declared by this provider set")))
66
78
  : clientFor(provider).execute(operation, parameters, options);
67
79
  },
68
80
  close: async () => {
@@ -14,16 +14,20 @@ export const runCrossVersionInvestigation = async (input, policy, execution) =>
14
14
  return err(new AnalysisInputError("find_changed_behavior", {
15
15
  cause: parsed.error,
16
16
  }));
17
- const initial = await readInvestigationWorkspace(parsed.data.workspace_path, policy);
17
+ return runCrossVersionInvestigationValidated(parsed.data, policy, execution);
18
+ };
19
+ /** Run input already parsed by a trusted adapter boundary. */
20
+ export const runCrossVersionInvestigationValidated = async (input, policy, execution) => {
21
+ const initial = await readInvestigationWorkspace(input.workspace_path, policy);
18
22
  if (!initial.ok)
19
23
  return initial;
20
- const preflight = validateWorkspaceRequest(initial.value, parsed.data);
24
+ const preflight = validateWorkspaceRequest(initial.value, input);
21
25
  if (!preflight.ok)
22
26
  return preflight;
23
27
  if (isCancelled(execution.signal))
24
28
  return err(new AnalysisCancelledError("find_changed_behavior"));
25
- if (parsed.data.replay_run_id !== undefined) {
26
- const replay = selectCompletedInvestigationReplay(initial.value, parsed.data);
29
+ if (input.replay_run_id !== undefined) {
30
+ const replay = selectCompletedInvestigationReplay(initial.value, input);
27
31
  if (!replay.ok)
28
32
  return replay;
29
33
  return completeOutcome(replay.value.workspace, replay.value.run, true, execution.session);
@@ -39,7 +43,7 @@ export const runCrossVersionInvestigation = async (input, policy, execution) =>
39
43
  total: 4,
40
44
  message: "Scanning both version inputs",
41
45
  });
42
- const snapshots = await scanVersions(parsed.data, execution.inputRoots, execution.signal, execution.integrityContinueEnabled);
46
+ const snapshots = await scanVersions(input, execution.inputRoots, execution.signal, execution.integrityContinueEnabled);
43
47
  if (!snapshots.ok)
44
48
  return snapshots;
45
49
  await execution.progress?.report({
@@ -49,7 +53,7 @@ export const runCrossVersionInvestigation = async (input, policy, execution) =>
49
53
  message: "Version inputs scanned",
50
54
  });
51
55
  return continueInvestigation({
52
- input: parsed.data,
56
+ input,
53
57
  initial: initial.value,
54
58
  snapshots: snapshots.value,
55
59
  policy,