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
@@ -0,0 +1,106 @@
1
+ import { compareCodePoints, } from "./javascriptApplicationGraph.js";
2
+ import { createStaticRuntimeScope, mappedRuntimePaths, staticNodeWithinRuntimeScope, } from "./javascriptRuntimeStaticCandidates.js";
3
+ /** Classify static code presence without claiming bundled module execution. */
4
+ export const classifyStaticLoadStates = (layers, runtimeEntities, matches, options) => {
5
+ const states = [];
6
+ let totalStates = 0;
7
+ for (const layer of layers) {
8
+ const projection = classifyLayer(layer, runtimeEntities, matches, {
9
+ reconciliationComplete: options.reconciliationComplete,
10
+ maximumStates: Math.max(0, options.maximumStates - states.length),
11
+ });
12
+ totalStates += projection.total;
13
+ states.push(...projection.states);
14
+ }
15
+ return { states, omittedStates: totalStates - states.length };
16
+ };
17
+ const classifyLayer = (layer, runtimeEntities, matches, options) => {
18
+ const nodes = layer.graph.nodes.filter(isLoadStateNode);
19
+ if (options.maximumStates === 0)
20
+ return { states: [], total: nodes.length };
21
+ const direct = new Map();
22
+ for (const match of matches)
23
+ if (match.status === "matched" &&
24
+ match.static_layer_id === layer.layerId &&
25
+ match.static_node_id !== null)
26
+ append(direct, match.static_node_id, match.runtime_node_id);
27
+ const resident = residentNodes(layer, new Set(direct.keys()));
28
+ const runtimeScope = createStaticRuntimeScope(layer, runtimeEntities);
29
+ const scopedEntities = runtimeEntities.filter((entity) => mappedRuntimePaths(layer, entity).length > 0);
30
+ const layerComplete = options.reconciliationComplete &&
31
+ layer.graph.coverage.status === "complete" &&
32
+ scopedEntities.length > 0 &&
33
+ scopedEntities.every(({ capture }) => captureScriptsComplete(capture.evidence.evidence_id, runtimeEntities));
34
+ const states = nodes.slice(0, options.maximumStates).map((node) => {
35
+ const runtimeNodeIds = uniqueSorted(direct.get(node.node_id) ?? []);
36
+ if (runtimeNodeIds.length > 0)
37
+ return state(layer, node, {
38
+ status: "loaded",
39
+ runtimeNodeIds,
40
+ reason: "runtime-script-correspondence",
41
+ });
42
+ if (resident.has(node.node_id))
43
+ return state(layer, node, {
44
+ status: "resident-in-loaded-asset",
45
+ runtimeNodeIds: [],
46
+ reason: "containing-asset-was-loaded",
47
+ });
48
+ const nodeWithinScope = staticNodeWithinRuntimeScope(node, runtimeScope);
49
+ if (layerComplete && nodeWithinScope)
50
+ return state(layer, node, {
51
+ status: "not-observed-in-capture",
52
+ runtimeNodeIds: [],
53
+ reason: "not-observed-in-bounded-capture",
54
+ });
55
+ return state(layer, node, {
56
+ status: "unknown",
57
+ runtimeNodeIds: [],
58
+ reason: !nodeWithinScope
59
+ ? "layer-outside-runtime-scope"
60
+ : "static-or-runtime-coverage-incomplete",
61
+ });
62
+ });
63
+ return { states, total: nodes.length };
64
+ };
65
+ const residentNodes = (layer, directlyLoaded) => {
66
+ const nodeKinds = new Map(layer.graph.nodes.map(({ node_id: id, kind }) => [id, kind]));
67
+ const children = new Map();
68
+ for (const edge of layer.graph.edges)
69
+ if (edge.relation === "contains")
70
+ append(children, edge.source_node_id, edge.target_node_id);
71
+ const pending = [...directlyLoaded].filter((nodeId) => nodeKinds.get(nodeId) === "javascript-asset");
72
+ const resident = new Set();
73
+ for (let index = 0; index < pending.length; index += 1) {
74
+ const current = pending[index];
75
+ if (current === undefined)
76
+ break;
77
+ for (const child of children.get(current) ?? []) {
78
+ if (resident.has(child))
79
+ continue;
80
+ resident.add(child);
81
+ pending.push(child);
82
+ }
83
+ }
84
+ return resident;
85
+ };
86
+ const append = (values, key, value) => {
87
+ const current = values.get(key);
88
+ if (current === undefined)
89
+ values.set(key, [value]);
90
+ else
91
+ current.push(value);
92
+ };
93
+ const captureScriptsComplete = (evidenceId, entities) => entities.find(({ capture }) => capture.evidence.evidence_id === evidenceId)
94
+ ?.capture.scriptsCompleteWithinScope === true;
95
+ const isLoadStateNode = (node) => node.kind === "javascript-asset" ||
96
+ node.kind === "javascript-chunk" ||
97
+ node.kind === "javascript-module";
98
+ const state = (layer, node, input) => ({
99
+ static_layer_id: layer.layerId,
100
+ static_node_id: node.node_id,
101
+ kind: node.kind,
102
+ status: input.status,
103
+ runtime_node_ids: [...input.runtimeNodeIds],
104
+ reason: input.reason,
105
+ });
106
+ const uniqueSorted = (values) => [...new Set(values)].sort(compareCodePoints);
@@ -0,0 +1,42 @@
1
+ import { classifyStaticLoadStates } from "./javascriptRuntimeLoadState.js";
2
+ import { buildReconciledApplicationGraph } from "./javascriptRuntimeReconciliationGraph.js";
3
+ import { reconcileRuntimeEntities } from "./javascriptRuntimeReconciliationMatching.js";
4
+ import { parseRuntimeCaptures, parseStaticLayers, } from "./javascriptRuntimeReconciliationParsing.js";
5
+ import { createJavaScriptRuntimeReconciliationResult } from "./javascriptRuntimeReconciliationResult.js";
6
+ import { reconcileJavaScriptRuntimeInputSchema } from "./javascriptRuntimeReconciliationSchemas.js";
7
+ import { projectRuntimeCaptures } from "./javascriptRuntimeReconciliationRuntime.js";
8
+ import { collectStaticRuntimeCandidates } from "./javascriptRuntimeStaticCandidates.js";
9
+ /** Reconcile static JAG layers with authorized passive CDP Evidence. */
10
+ export const reconcileJavaScriptRuntime = (input) => {
11
+ const parsed = reconcileJavaScriptRuntimeInputSchema.parse(input);
12
+ const layers = parseStaticLayers(parsed.static_layers);
13
+ const captures = parseRuntimeCaptures(parsed.runtime_observations);
14
+ const runtime = projectRuntimeCaptures(captures, parsed.limits.max_runtime_entities);
15
+ const candidates = collectStaticRuntimeCandidates(layers);
16
+ const matching = reconcileRuntimeEntities({
17
+ entities: runtime.entities,
18
+ candidates,
19
+ layers,
20
+ maximumItems: parsed.limits.max_reconciliation_items,
21
+ });
22
+ const loadStates = classifyStaticLoadStates(layers, runtime.entities, matching.items, {
23
+ maximumStates: parsed.limits.max_static_load_states,
24
+ reconciliationComplete: runtime.omittedEntities === 0 && matching.omittedItems === 0,
25
+ });
26
+ const graph = buildReconciledApplicationGraph({
27
+ layers,
28
+ captures,
29
+ runtime,
30
+ reconciliationEdges: matching.edges,
31
+ omittedReconciliationItems: matching.omittedItems,
32
+ });
33
+ return createJavaScriptRuntimeReconciliationResult({
34
+ layers,
35
+ captures,
36
+ runtime,
37
+ matching,
38
+ loadStates,
39
+ graph: graph.graph,
40
+ omittedGraphItems: graph.omittedGraphItems,
41
+ });
42
+ };
@@ -0,0 +1,169 @@
1
+ import { compareCodePoints, createJavaScriptApplicationGraph, createJavaScriptApplicationNode, } from "./javascriptApplicationGraph.js";
2
+ /** Merge source graphs, runtime nodes, and inferred mappings into one valid JAG. */
3
+ export const buildReconciledApplicationGraph = (input) => {
4
+ const merged = mergeNodes([
5
+ ...input.layers.flatMap(({ graph }) => graph.nodes),
6
+ ...input.runtime.nodes,
7
+ ]);
8
+ const allEdges = uniqueEdges([
9
+ ...input.layers.flatMap(({ graph }) => graph.edges),
10
+ ...input.runtime.edges,
11
+ ...input.reconciliationEdges,
12
+ ]);
13
+ const bounded = boundGraphContent(input, merged.nodes, allEdges);
14
+ const facts = graphCoverageFacts(input, merged.omittedObservations + bounded.omittedItems);
15
+ const graph = createJavaScriptApplicationGraph({
16
+ schema: "JavaScriptApplicationGraph",
17
+ schema_version: 1,
18
+ root_node_ids: bounded.rootNodeIds,
19
+ nodes: bounded.nodes,
20
+ edges: bounded.edges,
21
+ coverage: graphCoverage(input, facts, bounded.nodes.length),
22
+ limitations: uniqueSorted([
23
+ ...input.layers.flatMap(({ graph }) => graph.limitations),
24
+ "Runtime presence is bounded to passive capture windows and does not prove feature execution.",
25
+ "Cross-layer observed_as edges are inferences; digest equality proves byte identity only.",
26
+ "Source-map relationships retain their original static authority and are not promoted into runtime matches.",
27
+ ...(facts.inputTruncated
28
+ ? [
29
+ "One or more source graphs or passive captures were incomplete or truncated.",
30
+ ]
31
+ : []),
32
+ ...(facts.outputTruncated
33
+ ? [
34
+ "The reconciled graph omitted entities or classifications at caller limits.",
35
+ ]
36
+ : []),
37
+ ]),
38
+ });
39
+ return {
40
+ graph,
41
+ omittedGraphItems: merged.omittedObservations + bounded.omittedItems,
42
+ };
43
+ };
44
+ const boundGraphContent = (input, nodes, edges) => {
45
+ const allRoots = preferredRootIds(input);
46
+ const rootNodeIds = allRoots.slice(0, 1_000);
47
+ const roots = new Set(rootNodeIds);
48
+ const sortedNodes = [...nodes].sort((left, right) => compareCodePoints(left.node_id, right.node_id));
49
+ const retainedNodes = [
50
+ ...sortedNodes.filter(({ node_id: id }) => roots.has(id)),
51
+ ...sortedNodes.filter(({ node_id: id }) => !roots.has(id)),
52
+ ].slice(0, 100_000);
53
+ const retainedIds = new Set(retainedNodes.map(({ node_id: id }) => id));
54
+ const retainedEdges = [...edges]
55
+ .sort((left, right) => compareCodePoints(left.edge_id, right.edge_id))
56
+ .filter(({ source_node_id: source, target_node_id: target }) => retainedIds.has(source) && retainedIds.has(target))
57
+ .slice(0, 200_000);
58
+ return {
59
+ rootNodeIds,
60
+ nodes: retainedNodes,
61
+ edges: retainedEdges,
62
+ omittedItems: allRoots.length -
63
+ rootNodeIds.length +
64
+ (nodes.length - retainedNodes.length) +
65
+ (edges.length - retainedEdges.length),
66
+ };
67
+ };
68
+ const preferredRootIds = (input) => {
69
+ const runtime = [...input.runtime.targetNodeByEvidenceId.values()].map(({ node_id: id }) => id);
70
+ const application = input.layers
71
+ .filter(({ role }) => role === "application")
72
+ .flatMap(({ graph }) => graph.root_node_ids);
73
+ const supplemental = input.layers
74
+ .filter(({ role }) => role !== "application")
75
+ .flatMap(({ graph }) => graph.root_node_ids);
76
+ return [...new Set([...runtime, ...application, ...supplemental])];
77
+ };
78
+ const graphCoverageFacts = (input, omittedNodeObservations) => {
79
+ const projectionOmissions = input.runtime.omittedEntities +
80
+ input.omittedReconciliationItems +
81
+ omittedNodeObservations;
82
+ const captureTruncated = input.captures.some(({ inspection }) => inspection.completeness.conditions.includes("truncated"));
83
+ const captureHasUnknownOmissions = input.captures.some(({ inspection }) => inspection.completeness.unavailable_sections.length > 0);
84
+ const inputTruncated = input.layers.some(({ graph }) => graph.coverage.truncated) ||
85
+ captureTruncated;
86
+ const outputTruncated = projectionOmissions > 0;
87
+ const sourceOmissions = input.layers.reduce((total, { graph }) => total + (graph.coverage.omitted_count ?? 0), 0);
88
+ const omissionsKnown = !captureTruncated &&
89
+ !captureHasUnknownOmissions &&
90
+ input.layers.every(({ graph }) => graph.coverage.omitted_count !== null);
91
+ return {
92
+ projectionOmissions,
93
+ knownOmissions: projectionOmissions + sourceOmissions,
94
+ omissionsKnown,
95
+ inputTruncated,
96
+ outputTruncated,
97
+ truncated: inputTruncated || outputTruncated,
98
+ complete: !inputTruncated &&
99
+ !outputTruncated &&
100
+ input.layers.every(({ graph }) => graph.coverage.status === "complete") &&
101
+ input.captures.every(({ inspection }) => inspection.completeness.status === "complete_within_window"),
102
+ };
103
+ };
104
+ const graphCoverage = (input, facts, nodeCount) => facts.complete
105
+ ? { status: "complete", truncated: false, omitted_count: 0, limits: [] }
106
+ : facts.truncated
107
+ ? {
108
+ status: "partial",
109
+ truncated: true,
110
+ omitted_count: facts.omissionsKnown ? facts.knownOmissions : null,
111
+ limits: [
112
+ ...(facts.inputTruncated
113
+ ? [
114
+ {
115
+ name: "runtime-reconciliation-input-coverage",
116
+ value: input.layers.length + input.captures.length,
117
+ unit: "items",
118
+ },
119
+ ]
120
+ : []),
121
+ ...(facts.outputTruncated
122
+ ? [
123
+ {
124
+ name: "runtime-reconciliation-output-limits",
125
+ value: nodeCount,
126
+ unit: "items",
127
+ },
128
+ ]
129
+ : []),
130
+ ],
131
+ }
132
+ : {
133
+ status: "partial",
134
+ truncated: false,
135
+ omitted_count: facts.omissionsKnown ? facts.knownOmissions : null,
136
+ limits: [],
137
+ };
138
+ const mergeNodes = (nodes) => {
139
+ const grouped = new Map();
140
+ for (const node of nodes)
141
+ grouped.set(node.node_id, [...(grouped.get(node.node_id) ?? []), node]);
142
+ let omittedObservations = 0;
143
+ const merged = [...grouped.values()].map((group) => {
144
+ const first = group[0];
145
+ if (first === undefined)
146
+ throw new TypeError("Empty JAG node group");
147
+ if (group.some(({ kind, identity }) => kind !== first.kind ||
148
+ JSON.stringify(identity) !== JSON.stringify(first.identity)))
149
+ throw new TypeError("Colliding JAG node identifiers disagree");
150
+ const observations = [
151
+ ...new Map(group
152
+ .flatMap(({ observations: values }) => values)
153
+ .map((observation) => [observation.observation_id, observation])).values(),
154
+ ].sort((left, right) => compareCodePoints(left.observation_id, right.observation_id));
155
+ omittedObservations += Math.max(0, observations.length - 64);
156
+ return createJavaScriptApplicationNode({
157
+ kind: first.kind,
158
+ identity: first.identity,
159
+ observations: observations
160
+ .slice(0, 64)
161
+ .map(({ observation_id: _id, identifier_strategy: _strategy, ...value }) => value),
162
+ });
163
+ });
164
+ return { nodes: merged, omittedObservations };
165
+ };
166
+ const uniqueEdges = (edges) => [
167
+ ...new Map(edges.map((edge) => [edge.edge_id, edge])).values(),
168
+ ];
169
+ const uniqueSorted = (values) => [...new Set(values)].sort(compareCodePoints);
@@ -0,0 +1,247 @@
1
+ import { compareCodePoints, createJavaScriptApplicationEdge, } from "./javascriptApplicationGraph.js";
2
+ import { digestCanonical, } from "./javascriptRuntimeReconciliationParsing.js";
3
+ import { mappedRuntimePaths, } from "./javascriptRuntimeStaticCandidates.js";
4
+ /** Match runtime entities conservatively and emit only inferred observed_as edges. */
5
+ export const reconcileRuntimeEntities = (input) => {
6
+ const index = indexCandidates(input.candidates);
7
+ const evaluated = input.entities.map((entity) => reconcileEntity(entity, index, input.layers));
8
+ const retained = evaluated
9
+ .sort((left, right) => compareCodePoints(left.item.reconciliation_id, right.item.reconciliation_id))
10
+ .slice(0, input.maximumItems);
11
+ return {
12
+ items: retained.map(({ item }) => item),
13
+ edges: retained.flatMap(({ edge }) => (edge === null ? [] : [edge])),
14
+ omittedItems: evaluated.length - retained.length,
15
+ };
16
+ };
17
+ const reconcileEntity = (entity, index, layers) => {
18
+ const locations = candidatesWithLocation(entity, index, layers);
19
+ if (entity.sourceSha256 !== null) {
20
+ const content = index.byCategoryDigest.get(categoryDigestKey(entity.staticKind, entity.sourceSha256)) ?? [];
21
+ const locatedKeys = new Set(locations.map(({ candidate }) => candidateKey(candidate)));
22
+ const combined = content.filter((candidate) => locatedKeys.has(candidateKey(candidate)));
23
+ if (combined.length === 1)
24
+ return matched(entity, combined[0], {
25
+ basis: "content-and-location",
26
+ confidence: "high",
27
+ reason: "content-and-location-match",
28
+ });
29
+ if (content.length === 1) {
30
+ const candidate = content[0];
31
+ const digestBasis = candidate.digests.find(({ sha256 }) => sha256 === entity.sourceSha256)?.basis;
32
+ return matched(entity, candidate, {
33
+ basis: digestBasis ?? "content-sha256",
34
+ confidence: "high",
35
+ reason: "unique-content-match",
36
+ });
37
+ }
38
+ if (content.length > 1)
39
+ return unresolved(entity, {
40
+ status: "ambiguous",
41
+ basis: "none",
42
+ confidence: "unknown",
43
+ reason: "ambiguous-static-candidates",
44
+ candidates: content,
45
+ });
46
+ if (locations.length > 0)
47
+ return unresolved(entity, {
48
+ status: "unmatched",
49
+ basis: strongestLocationBasis(locations),
50
+ confidence: "high",
51
+ reason: "captured-content-disagrees-with-static-location",
52
+ candidates: locations.map(({ candidate }) => candidate),
53
+ });
54
+ return unresolved(entity, {
55
+ status: "unmatched",
56
+ basis: "none",
57
+ confidence: "high",
58
+ reason: "no-static-content-match",
59
+ candidates: [],
60
+ });
61
+ }
62
+ if (locations.length === 1) {
63
+ const located = locations[0];
64
+ return matched(entity, located.candidate, {
65
+ basis: located.mapping.basis,
66
+ confidence: "medium",
67
+ reason: "unique-location-match",
68
+ });
69
+ }
70
+ if (locations.length > 1)
71
+ return unresolved(entity, {
72
+ status: "ambiguous",
73
+ basis: strongestLocationBasis(locations),
74
+ confidence: "unknown",
75
+ reason: "ambiguous-static-candidates",
76
+ candidates: locations.map(({ candidate }) => candidate),
77
+ });
78
+ const mapped = layers.some((layer) => mappedRuntimePaths(layer, entity).length > 0);
79
+ return unresolved(entity, {
80
+ status: mapped ? "unmatched" : "unknown",
81
+ basis: "none",
82
+ confidence: "unknown",
83
+ reason: mapped
84
+ ? "no-static-content-match"
85
+ : "no-authorized-location-mapping",
86
+ candidates: [],
87
+ });
88
+ };
89
+ const candidatesWithLocation = (entity, index, layers) => {
90
+ const located = new Map();
91
+ for (const layer of layers)
92
+ for (const mapping of mappedRuntimePaths(layer, entity))
93
+ for (const candidate of index.byLayerCategoryPath.get(layerCategoryPathKey(layer.layerId, entity.staticKind, mapping.path)) ?? []) {
94
+ const key = candidateKey(candidate);
95
+ if (!located.has(key))
96
+ located.set(key, { candidate, mapping });
97
+ }
98
+ return [...located.values()].sort((left, right) => compareCodePoints(candidateKey(left.candidate), candidateKey(right.candidate)));
99
+ };
100
+ const indexCandidates = (candidates) => {
101
+ const byCategoryDigest = new Map();
102
+ const byLayerCategoryPath = new Map();
103
+ for (const candidate of candidates) {
104
+ for (const { sha256 } of candidate.digests)
105
+ appendIndex(byCategoryDigest, categoryDigestKey(candidate.category, sha256), candidate);
106
+ for (const path of candidate.paths)
107
+ appendIndex(byLayerCategoryPath, layerCategoryPathKey(candidate.layer.layerId, candidate.category, path), candidate);
108
+ }
109
+ return { byCategoryDigest, byLayerCategoryPath };
110
+ };
111
+ const appendIndex = (index, key, candidate) => {
112
+ const values = index.get(key);
113
+ if (values === undefined)
114
+ index.set(key, [candidate]);
115
+ else
116
+ values.push(candidate);
117
+ };
118
+ const categoryDigestKey = (category, digest) => `${category}\0${digest}`;
119
+ const layerCategoryPathKey = (layerId, category, path) => `${layerId}\0${category}\0${path}`;
120
+ const matched = (entity, candidate, input) => {
121
+ const item = createItem(entity, {
122
+ layer: candidate.layer,
123
+ staticNodeId: candidate.node.node_id,
124
+ status: "matched",
125
+ basis: input.basis,
126
+ confidence: input.confidence,
127
+ reason: input.reason,
128
+ candidates: [candidate],
129
+ });
130
+ return {
131
+ item,
132
+ edge: createJavaScriptApplicationEdge({
133
+ source_node_id: candidate.node.node_id,
134
+ target_node_id: entity.node.node_id,
135
+ relation: "observed_as",
136
+ properties: {
137
+ reconciliation_id: item.reconciliation_id,
138
+ basis: input.basis,
139
+ static_layer_id: candidate.layer.layerId,
140
+ },
141
+ evidence: reconciliationEvidence(entity, candidate.layer, input.basis, input.confidence),
142
+ }),
143
+ };
144
+ };
145
+ const unresolved = (entity, input) => ({
146
+ item: createItem(entity, {
147
+ layer: null,
148
+ staticNodeId: null,
149
+ status: input.status,
150
+ basis: input.basis,
151
+ confidence: input.confidence,
152
+ reason: input.reason,
153
+ candidates: input.candidates,
154
+ }),
155
+ edge: null,
156
+ });
157
+ const createItem = (entity, input) => {
158
+ const candidateReferences = uniqueCandidateReferences(input.candidates);
159
+ const semantic = {
160
+ entity_kind: entity.kind,
161
+ runtime_evidence_id: entity.capture.evidence.evidence_id,
162
+ runtime_node_id: entity.node.node_id,
163
+ static_layer_id: input.layer?.layerId ?? null,
164
+ static_node_id: input.staticNodeId,
165
+ status: input.status,
166
+ basis: input.basis,
167
+ confidence: input.confidence,
168
+ reason: input.reason,
169
+ candidate_static_count: candidateReferences.count,
170
+ candidate_static_nodes: candidateReferences.items,
171
+ };
172
+ return {
173
+ reconciliation_id: `jrr_item_${digestCanonical(semantic)}`,
174
+ ...semantic,
175
+ };
176
+ };
177
+ const reconciliationEvidence = (entity, layer, basis, confidence) => ({
178
+ authority: "cross-layer-reconciliation",
179
+ state: "inferred",
180
+ confidence: confidence === "medium" ? "medium" : "high",
181
+ artifact: entity.sourceSha256 === null
182
+ ? {
183
+ available: false,
184
+ reason: "not-observed",
185
+ detail: "Runtime source bytes were not captured for this mapping.",
186
+ }
187
+ : {
188
+ available: true,
189
+ artifact_id: `art_${entity.sourceSha256}`,
190
+ sha256: entity.sourceSha256,
191
+ },
192
+ location: entity.node.observations[0].evidence.location,
193
+ extractor: {
194
+ name: "rea-javascript-runtime-reconciliation",
195
+ version: "1",
196
+ operation: "reconcile_javascript_runtime",
197
+ executable_sha256: null,
198
+ },
199
+ coverage: layer.graph.coverage.status === "complete" &&
200
+ entity.capture.scriptsCompleteWithinScope
201
+ ? { status: "complete", truncated: false, omitted_count: 0, limits: [] }
202
+ : {
203
+ status: "partial",
204
+ truncated: false,
205
+ omitted_count: null,
206
+ limits: [],
207
+ },
208
+ limitations: [
209
+ basis === "content-sha256" ||
210
+ basis === "module-source-sha256" ||
211
+ basis === "content-and-location"
212
+ ? "Equal content digests establish byte identity, not execution causality."
213
+ : "Location correspondence is inferred from an artifact root or caller-declared mapping.",
214
+ "Passive script parsing or target presence does not prove feature execution.",
215
+ ],
216
+ evidence_ids: [
217
+ layer.evidence.evidence_id,
218
+ entity.capture.evidence.evidence_id,
219
+ ].sort(),
220
+ });
221
+ const strongestLocationBasis = (values) => {
222
+ const bases = new Set(values.map(({ mapping }) => mapping.basis));
223
+ if (bases.has("artifact-path"))
224
+ return "artifact-path";
225
+ if (bases.has("operator-file-mapping"))
226
+ return "operator-file-mapping";
227
+ return "operator-url-mapping";
228
+ };
229
+ const candidateKey = (candidate) => `${candidate.layer.layerId}:${candidate.node.node_id}`;
230
+ const uniqueCandidateReferences = (candidates) => {
231
+ const seen = new Set();
232
+ let count = 0;
233
+ const items = [];
234
+ for (const { layer, node } of candidates) {
235
+ const key = `${layer.layerId}:${node.node_id}`;
236
+ if (seen.has(key))
237
+ continue;
238
+ seen.add(key);
239
+ count += 1;
240
+ if (items.length < 1_000)
241
+ items.push({
242
+ static_layer_id: layer.layerId,
243
+ static_node_id: node.node_id,
244
+ });
245
+ }
246
+ return { count, items };
247
+ };