rea-agents 1.7.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +38 -31
  2. package/dist/application/CapabilityInventory.js +18 -0
  3. package/dist/application/CrossVersionInvestigation.js +10 -6
  4. package/dist/application/Doctor.js +56 -6
  5. package/dist/application/ElectronBoundaryAnalysis.js +96 -0
  6. package/dist/application/ElectronBoundaryGraph.js +9 -0
  7. package/dist/application/ElectronBoundaryGraphContext.js +62 -0
  8. package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
  9. package/dist/application/ElectronBoundaryGraphNative.js +86 -0
  10. package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
  11. package/dist/application/EnhancedTools.js +38 -13
  12. package/dist/application/InvestigationProviders.js +30 -0
  13. package/dist/application/JavaScriptApplicationEvidence.js +26 -0
  14. package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
  15. package/dist/application/JavaScriptApplicationService.js +74 -0
  16. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
  17. package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
  18. package/dist/application/JavaScriptArtifactAnalysis.js +7 -1
  19. package/dist/application/JavaScriptArtifactGraphBuilder.js +59 -16
  20. package/dist/application/JavaScriptArtifactGraphEvidence.js +24 -14
  21. package/dist/application/JavaScriptArtifactReconstruction.js +3 -0
  22. package/dist/application/JavaScriptArtifactReconstructionInput.js +2 -83
  23. package/dist/application/JavaScriptReplayPlanning.js +201 -0
  24. package/dist/application/JavaScriptReplayService.js +266 -0
  25. package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
  26. package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
  27. package/dist/application/ManagedApplicationGraphService.js +55 -0
  28. package/dist/application/ManagedMemberComparisonService.js +122 -0
  29. package/dist/application/ManagedNativeVerificationService.js +39 -0
  30. package/dist/application/ManagedReconstructionService.js +58 -0
  31. package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
  32. package/dist/application/PermissionAuthority.js +1 -1
  33. package/dist/application/ProcessSampling.js +15 -11
  34. package/dist/application/ProjectPermissionStore.js +1 -0
  35. package/dist/application/runtime.js +2 -0
  36. package/dist/browser/CdpCaptureEvents.js +49 -0
  37. package/dist/browser/CdpElectronInspection.js +106 -162
  38. package/dist/browser/CdpElectronProvider.js +5 -1
  39. package/dist/browser/CdpElectronScriptEvents.js +69 -0
  40. package/dist/browser/CdpElectronScripts.js +94 -0
  41. package/dist/browser/CdpElectronWorkers.js +56 -0
  42. package/dist/browser/CdpPageCapture.js +8 -4
  43. package/dist/catalogIdentity.js +4 -1
  44. package/dist/cli.js +388 -0
  45. package/dist/cliApplicationCommands.js +62 -0
  46. package/dist/cliCommandNames.js +13 -0
  47. package/dist/cliElectronCommands.js +103 -1
  48. package/dist/cliJsonInput.js +54 -0
  49. package/dist/config.js +83 -0
  50. package/dist/contracts/applicationToolContracts.js +91 -0
  51. package/dist/contracts/artifactToolContracts.js +5 -9
  52. package/dist/contracts/browserToolContracts.js +14 -62
  53. package/dist/contracts/electronToolContracts.js +45 -19
  54. package/dist/contracts/errorSchemas.js +1 -0
  55. package/dist/contracts/investigationExamples.js +0 -6
  56. package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
  57. package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
  58. package/dist/contracts/managedToolContracts.js +153 -0
  59. package/dist/contracts/managedWorkflowExamples.js +346 -0
  60. package/dist/contracts/managedWorkflowToolContracts.js +203 -0
  61. package/dist/contracts/nativeToolContracts.js +2 -6
  62. package/dist/contracts/promptContracts.js +18 -2
  63. package/dist/contracts/toolContractExamples.js +10 -25
  64. package/dist/contracts/toolContracts.js +47 -45
  65. package/dist/contracts/toolEffects.js +160 -0
  66. package/dist/contracts/toolOutputSchemas.js +46 -31
  67. package/dist/doctorRuntime.js +42 -1
  68. package/dist/domain/artifactComparison.js +3 -4
  69. package/dist/domain/browserObservation.js +3 -0
  70. package/dist/domain/bundleComparison.js +4 -4
  71. package/dist/domain/electronObservation.js +13 -0
  72. package/dist/domain/electronStaticAnalysis.js +9 -0
  73. package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
  74. package/dist/domain/electronStaticAnalysisIpc.js +171 -0
  75. package/dist/domain/electronStaticAnalysisNative.js +164 -0
  76. package/dist/domain/electronStaticAnalysisTypes.js +1 -0
  77. package/dist/domain/electronStaticAnalysisValues.js +103 -0
  78. package/dist/domain/errors.js +67 -1
  79. package/dist/domain/functionComparisonSchemas.js +3 -8
  80. package/dist/domain/inputIssueProjection.js +52 -0
  81. package/dist/domain/javascriptApplicationAnalysis.js +155 -0
  82. package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
  83. package/dist/domain/javascriptApplicationEvidenceSchemas.js +18 -0
  84. package/dist/domain/javascriptApplicationGraph.js +2 -1
  85. package/dist/domain/javascriptApplicationGraphSchemas.js +7 -0
  86. package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
  87. package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
  88. package/dist/domain/javascriptApplicationVersionItems.js +221 -0
  89. package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
  90. package/dist/domain/javascriptFeatureSeed.js +140 -0
  91. package/dist/domain/javascriptFeatureTrace.js +232 -0
  92. package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
  93. package/dist/domain/javascriptFeatureTraversal.js +88 -0
  94. package/dist/domain/javascriptNativeHandoff.js +85 -0
  95. package/dist/domain/javascriptReplay.js +361 -0
  96. package/dist/domain/javascriptRuntimeLoadState.js +106 -0
  97. package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
  98. package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
  99. package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
  100. package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
  101. package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
  102. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
  103. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
  104. package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
  105. package/dist/domain/javascriptStaticAnalysis.js +31 -43
  106. package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
  107. package/dist/domain/javascriptStaticAnalysisHelpers.js +10 -0
  108. package/dist/domain/javascriptStaticAnalysisState.js +6 -0
  109. package/dist/domain/managedApplicationGraph.js +556 -0
  110. package/dist/domain/managedArtifact.js +444 -0
  111. package/dist/domain/managedMemberComparison.js +565 -0
  112. package/dist/domain/managedNativeVerification.js +408 -0
  113. package/dist/domain/managedReconstruction.js +183 -0
  114. package/dist/domain/managedRuntimeCorrelation.js +175 -0
  115. package/dist/domain/reconstructionVerificationSchemas.js +1 -3
  116. package/dist/dotnet/ManagedArtifactInspector.js +318 -0
  117. package/dist/dotnet/ManagedMemberInspector.js +887 -0
  118. package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
  119. package/dist/dotnet/ManagedMetadataInventory.js +368 -0
  120. package/dist/dotnet/ManagedMetadataLayout.js +322 -0
  121. package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
  122. package/dist/dotnet/ManagedPeReader.js +157 -0
  123. package/dist/dotnet/ManagedReaderFailure.js +10 -0
  124. package/dist/dotnet/ManagedStaticProvider.js +203 -0
  125. package/dist/generatedPackageMetadata.js +2 -2
  126. package/dist/ghidra/GhidraProvider.js +6 -0
  127. package/dist/main.js +14 -0
  128. package/dist/replay/JavaScriptReplayWorker.js +286 -0
  129. package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
  130. package/dist/replay/LinuxRuntimeClosure.js +46 -0
  131. package/dist/replay/LinuxSeccompPolicy.js +40 -0
  132. package/dist/replay/ReplayWorkerProtocol.js +55 -0
  133. package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
  134. package/dist/server/createServer.js +52 -0
  135. package/dist/server/registerApplicationTools.js +115 -0
  136. package/dist/server/registerArtifactComparisonTool.js +16 -9
  137. package/dist/server/registerBrowserTools.js +33 -8
  138. package/dist/server/registerBundleComparisonTool.js +17 -20
  139. package/dist/server/registerElectronTools.js +45 -3
  140. package/dist/server/registerEnhancedTools.js +58 -13
  141. package/dist/server/registerEvidenceTools.js +5 -1
  142. package/dist/server/registerFunctionComparisonTool.js +14 -7
  143. package/dist/server/registerInvestigationTools.js +21 -17
  144. package/dist/server/registerManagedTools.js +6 -0
  145. package/dist/server/registerManagedWorkflowTools.js +277 -0
  146. package/dist/server/registerOfficialTools.js +8 -8
  147. package/dist/server/registerProcessComparisonTool.js +25 -47
  148. package/dist/server/registerSessionStatusTool.js +7 -7
  149. package/dist/server/registerSessionTools.js +51 -86
  150. package/dist/server/sessionEvidence.js +14 -19
  151. package/dist/server/toolInputValidation.js +77 -0
  152. package/dist/server/toolRegistrationOptions.js +79 -2
  153. package/dist/server/toolResult.js +25 -17
  154. package/package.json +12 -4
  155. package/scripts/rea.mjs +1 -1
  156. package/skills/rea-analysis/SKILL.md +59 -3
@@ -0,0 +1,232 @@
1
+ import { createHash } from "node:crypto";
2
+ import canonicalize from "canonicalize";
3
+ import { compareCodePoints, createJavaScriptApplicationGraph, } from "./javascriptApplicationGraph.js";
4
+ import { applicationFeatureTraceResultSchema, } from "./javascriptFeatureTraceSchemas.js";
5
+ import { findApplicationFeatureSeeds, } from "./javascriptFeatureSeed.js";
6
+ import { traverseApplicationFeature, } from "./javascriptFeatureTraversal.js";
7
+ import { buildJavaScriptNativeHandoffs } from "./javascriptNativeHandoff.js";
8
+ /** Trace a literal feature through a bounded, authority-preserving JAG. */
9
+ export const traceApplicationFeature = (input) => {
10
+ const allSeedMatches = findApplicationFeatureSeeds(input.graph.nodes, input.seed);
11
+ const seedMatches = allSeedMatches.slice(0, input.limits.max_seed_matches);
12
+ if (seedMatches.length === 0)
13
+ return noMatchResult(input);
14
+ const traversal = traverseApplicationFeature(input.graph, seedMatches.map(({ node_id: id }) => id), input.direction, input.limits);
15
+ const graph = traceGraph(input, seedMatches, traversal, allSeedMatches.length);
16
+ const allPaths = terminalPaths(traversal, seedMatches);
17
+ const paths = allPaths.slice(0, input.limits.max_paths);
18
+ const nativeHandoffs = buildJavaScriptNativeHandoffs(traversal.nodes, traversal.edges, input.nativeEvidence);
19
+ const evidenceLinks = uniqueSorted([
20
+ input.sourceEvidenceId,
21
+ ...nativeHandoffs.flatMap(({ evidence_ids: ids }) => ids),
22
+ ]);
23
+ const omissions = traceOmissions(input, traversal, allSeedMatches.length, allPaths.length);
24
+ const semantic = {
25
+ schema_version: 1,
26
+ source_evidence_id: input.sourceEvidenceId,
27
+ source_graph_id: input.graph.graph_id,
28
+ seed: input.seed,
29
+ direction: input.direction,
30
+ seed_matches: seedMatches,
31
+ graph,
32
+ paths,
33
+ native_handoffs: nativeHandoffs,
34
+ summary: {
35
+ matched_seeds: seedMatches.length,
36
+ traced_nodes: traversal.nodes.length,
37
+ traced_edges: traversal.edges.length,
38
+ terminal_paths: paths.length,
39
+ native_handoffs: nativeHandoffs.length,
40
+ ...factSummary(traversal.nodes, traversal.edges),
41
+ },
42
+ coverage: {
43
+ status: traceCoverageStatus(input.graph, omissions),
44
+ source_graph_status: input.graph.coverage.status,
45
+ scanned_nodes: input.graph.nodes.length,
46
+ total_seed_matches: allSeedMatches.length,
47
+ ...omissions,
48
+ frontier_node_ids: traversal.frontierNodeIds,
49
+ },
50
+ evidence_links: evidenceLinks,
51
+ limitations: traceLimitations(input.graph, omissions),
52
+ };
53
+ return applicationFeatureTraceResultSchema.parse({
54
+ ...semantic,
55
+ trace_id: `jatr_${digestCanonical(semantic)}`,
56
+ });
57
+ };
58
+ const noMatchResult = (input) => {
59
+ const semantic = {
60
+ schema_version: 1,
61
+ source_evidence_id: input.sourceEvidenceId,
62
+ source_graph_id: input.graph.graph_id,
63
+ seed: input.seed,
64
+ direction: input.direction,
65
+ seed_matches: [],
66
+ graph: null,
67
+ paths: [],
68
+ native_handoffs: [],
69
+ summary: {
70
+ matched_seeds: 0,
71
+ traced_nodes: 0,
72
+ traced_edges: 0,
73
+ terminal_paths: 0,
74
+ native_handoffs: 0,
75
+ observed_facts: 0,
76
+ inferred_facts: 0,
77
+ unknown_facts: 0,
78
+ unavailable_facts: 0,
79
+ },
80
+ coverage: {
81
+ status: "no-match",
82
+ source_graph_status: input.graph.coverage.status,
83
+ scanned_nodes: input.graph.nodes.length,
84
+ total_seed_matches: 0,
85
+ omitted_seed_matches: 0,
86
+ omitted_nodes: 0,
87
+ omitted_edges: 0,
88
+ omitted_paths: 0,
89
+ frontier_node_ids: [],
90
+ },
91
+ evidence_links: [input.sourceEvidenceId],
92
+ limitations: uniqueSorted([
93
+ "No graph entity matched the literal seed; this is not evidence that the feature is absent.",
94
+ ...sourceCoverageLimitations(input.graph),
95
+ ]),
96
+ };
97
+ return applicationFeatureTraceResultSchema.parse({
98
+ ...semantic,
99
+ trace_id: `jatr_${digestCanonical(semantic)}`,
100
+ });
101
+ };
102
+ const traceGraph = (input, seedMatches, traversal, totalSeedMatches) => {
103
+ const omitted = traversal.omittedNodeCount +
104
+ traversal.omittedEdgeCount +
105
+ (totalSeedMatches - seedMatches.length);
106
+ return createJavaScriptApplicationGraph({
107
+ schema: "JavaScriptApplicationGraph",
108
+ schema_version: 1,
109
+ root_node_ids: uniqueSorted(seedMatches.map(({ node_id: id }) => id)),
110
+ nodes: traversal.nodes,
111
+ edges: traversal.edges,
112
+ coverage: traceGraphCoverage(input.graph, input.limits, omitted),
113
+ limitations: uniqueSorted([
114
+ ...input.graph.limitations,
115
+ "Trace edges retain their source authority; graph connectivity does not prove runtime execution or reachability.",
116
+ ...(omitted === 0
117
+ ? []
118
+ : ["Trace output stopped at explicit caller bounds."]),
119
+ ]),
120
+ });
121
+ };
122
+ const traceGraphCoverage = (source, limits, omitted) => {
123
+ if (omitted > 0)
124
+ return {
125
+ status: "partial",
126
+ truncated: true,
127
+ omitted_count: omitted,
128
+ limits: [
129
+ {
130
+ name: "max-seed-matches",
131
+ value: limits.max_seed_matches,
132
+ unit: "items",
133
+ },
134
+ { name: "max-depth", value: limits.max_depth, unit: "depth" },
135
+ { name: "max-nodes", value: limits.max_nodes, unit: "items" },
136
+ { name: "max-edges", value: limits.max_edges, unit: "items" },
137
+ ],
138
+ };
139
+ return source.coverage.status === "complete"
140
+ ? { status: "complete", truncated: false, omitted_count: 0, limits: [] }
141
+ : {
142
+ status: source.coverage.status,
143
+ truncated: source.coverage.truncated,
144
+ omitted_count: source.coverage.omitted_count,
145
+ limits: source.coverage.limits,
146
+ };
147
+ };
148
+ const terminalPaths = (traversal, seeds) => {
149
+ const seedIds = new Set(seeds.map(({ node_id: id }) => id));
150
+ const edgeById = new Map(traversal.edges.map((edge) => [edge.edge_id, edge]));
151
+ return traversal.nodes
152
+ .filter((node) => isTerminal(node) &&
153
+ (!seedIds.has(node.node_id) || traversal.nodes.length === 1))
154
+ .map((node) => pathTo(node, traversal.predecessors, edgeById))
155
+ .filter((path) => path !== null)
156
+ .sort((left, right) => compareCodePoints(left.path_id, right.path_id));
157
+ };
158
+ const pathTo = (node, predecessors, edgeById) => {
159
+ const nodeIds = [node.node_id];
160
+ const edgeIds = [];
161
+ let current = node.node_id;
162
+ while (predecessors.has(current)) {
163
+ const predecessor = predecessors.get(current);
164
+ if (predecessor === undefined)
165
+ break;
166
+ nodeIds.push(predecessor.previousNodeId);
167
+ edgeIds.push(predecessor.edgeId);
168
+ current = predecessor.previousNodeId;
169
+ }
170
+ nodeIds.reverse();
171
+ edgeIds.reverse();
172
+ const edges = edgeIds
173
+ .map((edgeId) => edgeById.get(edgeId))
174
+ .filter((edge) => edge !== undefined);
175
+ if (edges.length !== edgeIds.length)
176
+ return null;
177
+ const semantic = {
178
+ start_node_id: nodeIds[0] ?? node.node_id,
179
+ end_node_id: node.node_id,
180
+ end_kind: node.kind,
181
+ node_ids: nodeIds,
182
+ edge_ids: edgeIds,
183
+ authorities: uniqueSorted(edges.map(({ evidence }) => evidence.authority)),
184
+ contains_inference: edges.some(({ evidence }) => evidence.state === "inferred"),
185
+ };
186
+ return { ...semantic, path_id: `jatp_${digestCanonical(semantic)}` };
187
+ };
188
+ const isTerminal = (node) => ["endpoint", "storage", "native-addon", "native-export", "unknown"].includes(node.kind);
189
+ const factSummary = (nodes, edges) => {
190
+ const states = [
191
+ ...nodes.flatMap(({ observations }) => observations.map(({ evidence }) => evidence.state)),
192
+ ...edges.map(({ evidence }) => evidence.state),
193
+ ];
194
+ return {
195
+ observed_facts: states.filter((state) => state === "observed").length,
196
+ inferred_facts: states.filter((state) => state === "inferred").length,
197
+ unknown_facts: states.filter((state) => state === "unknown").length,
198
+ unavailable_facts: states.filter((state) => state === "unavailable").length,
199
+ };
200
+ };
201
+ const traceOmissions = (input, traversal, seedMatches, paths) => ({
202
+ omitted_seed_matches: Math.max(0, seedMatches - input.limits.max_seed_matches),
203
+ omitted_nodes: traversal.omittedNodeCount,
204
+ omitted_edges: traversal.omittedEdgeCount,
205
+ omitted_paths: Math.max(0, paths - input.limits.max_paths),
206
+ });
207
+ const traceCoverageStatus = (graph, omissions) => Object.values(omissions).some((value) => value > 0)
208
+ ? "truncated"
209
+ : graph.coverage.status === "complete"
210
+ ? "complete-within-source"
211
+ : "partial";
212
+ const traceLimitations = (graph, omissions) => uniqueSorted([
213
+ "A trace reports graph relationships, not proof that code executed or that a feature is reachable in every state.",
214
+ "Static, native, passive-runtime, inferred, and unknown facts retain their original authority in the returned graph.",
215
+ "Native handoffs never open a binary or invoke a provider automatically; snapshot reuse remains provider/profile/target exact.",
216
+ ...sourceCoverageLimitations(graph),
217
+ ...(Object.values(omissions).some((value) => value > 0)
218
+ ? ["Trace results were truncated at explicit caller limits."]
219
+ : []),
220
+ ]);
221
+ const sourceCoverageLimitations = (graph) => graph.coverage.status === "complete"
222
+ ? []
223
+ : [
224
+ "The source application graph is incomplete; unmatched seeds and frontiers remain unknown.",
225
+ ];
226
+ const digestCanonical = (value) => {
227
+ const encoded = canonicalize(value);
228
+ if (encoded === undefined)
229
+ throw new TypeError("Feature trace could not canonicalize data");
230
+ return createHash("sha256").update(encoded).digest("hex");
231
+ };
232
+ const uniqueSorted = (values) => [...new Set(values)].sort(compareCodePoints);
@@ -0,0 +1,136 @@
1
+ import { z } from "zod";
2
+ import { evidenceSchema, providerSchema } from "./evidence.js";
3
+ import { javascriptApplicationGraphSchema } from "./javascriptApplicationGraph.js";
4
+ const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
5
+ const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
6
+ const nodeIdSchema = z.string().regex(/^jag_node_[a-f0-9]{64}$/u);
7
+ const edgeIdSchema = z.string().regex(/^jag_edge_[a-f0-9]{64}$/u);
8
+ const boundedTextSchema = z.string().min(1).max(4_096);
9
+ /** Literal starting point for one bounded application trace. */
10
+ const applicationFeatureSeedSchema = z.strictObject({
11
+ kind: z.enum([
12
+ "node-id",
13
+ "route",
14
+ "string",
15
+ "api",
16
+ "channel",
17
+ "module",
18
+ "native-export",
19
+ ]),
20
+ value: boundedTextSchema,
21
+ match: z.enum(["exact", "contains"]).nullable().default(null),
22
+ case_sensitive: z.boolean().default(false),
23
+ });
24
+ const featureTraceLimitsSchema = z.strictObject({
25
+ max_seed_matches: z.number().int().min(1).max(1_000).default(25),
26
+ max_depth: z.number().int().min(0).max(64).default(12),
27
+ max_nodes: z.number().int().min(1).max(50_000).default(2_000),
28
+ max_edges: z.number().int().min(1).max(100_000).default(4_000),
29
+ max_paths: z.number().int().min(1).max(1_000).default(100),
30
+ });
31
+ /** Evidence-backed application graph and explicit trace bounds. */
32
+ export const traceApplicationFeatureInputSchema = z
33
+ .strictObject({
34
+ application: evidenceSchema,
35
+ native_observations: z.array(evidenceSchema).max(64).default([]),
36
+ seed: applicationFeatureSeedSchema,
37
+ direction: z.enum(["outgoing", "incoming", "both"]).default("both"),
38
+ limits: featureTraceLimitsSchema.default({
39
+ max_seed_matches: 25,
40
+ max_depth: 12,
41
+ max_nodes: 2_000,
42
+ max_edges: 4_000,
43
+ max_paths: 100,
44
+ }),
45
+ })
46
+ .superRefine((input, context) => {
47
+ const ids = input.native_observations.map(({ evidence_id: id }) => id);
48
+ if (new Set(ids).size !== ids.length)
49
+ context.addIssue({
50
+ code: "custom",
51
+ path: ["native_observations"],
52
+ message: "Native observations must be unique",
53
+ });
54
+ });
55
+ const seedMatchSchema = z.strictObject({
56
+ node_id: nodeIdSchema,
57
+ kind: z.string().min(1).max(128),
58
+ basis: z.enum(["node-id", "label", "identity", "property"]),
59
+ field: z.string().min(1).max(256),
60
+ });
61
+ const tracePathSchema = z.strictObject({
62
+ path_id: z.string().regex(/^jatp_[a-f0-9]{64}$/u),
63
+ start_node_id: nodeIdSchema,
64
+ end_node_id: nodeIdSchema,
65
+ end_kind: z.string().min(1).max(128),
66
+ node_ids: z.array(nodeIdSchema).min(1).max(65),
67
+ edge_ids: z.array(edgeIdSchema).max(64),
68
+ authorities: z.array(z.string().min(1).max(128)).max(16),
69
+ contains_inference: z.boolean(),
70
+ });
71
+ const nativeHandoffSchema = z.strictObject({
72
+ native_node_id: nodeIdSchema,
73
+ artifact_sha256: digestSchema,
74
+ artifact_path: z.string().min(1).max(4_096).nullable(),
75
+ export_node_ids: z.array(nodeIdSchema).max(1_000),
76
+ requested_exports: z.array(boundedTextSchema).max(1_000),
77
+ status: z.enum(["evidence-linked", "requires-provider-analysis"]),
78
+ providers: z.array(providerSchema).max(64),
79
+ evidence_ids: z.array(evidenceIdSchema).max(64),
80
+ recommended_tools: z
81
+ .array(z.enum([
82
+ "open_binary",
83
+ "binary_overview",
84
+ "search_procedures",
85
+ "analyze_function",
86
+ "xrefs",
87
+ ]))
88
+ .max(5),
89
+ });
90
+ /** Evidence-preserving bounded subgraph and native-analysis frontiers. */
91
+ export const applicationFeatureTraceResultSchema = z.strictObject({
92
+ schema_version: z.literal(1),
93
+ trace_id: z.string().regex(/^jatr_[a-f0-9]{64}$/u),
94
+ source_evidence_id: evidenceIdSchema,
95
+ source_graph_id: z.string().regex(/^jag_[a-f0-9]{64}$/u),
96
+ seed: applicationFeatureSeedSchema,
97
+ direction: z.enum(["outgoing", "incoming", "both"]),
98
+ seed_matches: z.array(seedMatchSchema).max(1_000),
99
+ graph: javascriptApplicationGraphSchema.nullable(),
100
+ paths: z.array(tracePathSchema).max(1_000),
101
+ native_handoffs: z.array(nativeHandoffSchema).max(10_000),
102
+ summary: z.strictObject({
103
+ matched_seeds: z.number().int().min(0),
104
+ traced_nodes: z.number().int().min(0),
105
+ traced_edges: z.number().int().min(0),
106
+ terminal_paths: z.number().int().min(0),
107
+ native_handoffs: z.number().int().min(0),
108
+ observed_facts: z.number().int().min(0),
109
+ inferred_facts: z.number().int().min(0),
110
+ unknown_facts: z.number().int().min(0),
111
+ unavailable_facts: z.number().int().min(0),
112
+ }),
113
+ coverage: z.strictObject({
114
+ status: z.enum([
115
+ "complete-within-source",
116
+ "partial",
117
+ "truncated",
118
+ "no-match",
119
+ ]),
120
+ source_graph_status: z.enum([
121
+ "complete",
122
+ "partial",
123
+ "unknown",
124
+ "unavailable",
125
+ ]),
126
+ scanned_nodes: z.number().int().min(0),
127
+ total_seed_matches: z.number().int().min(0),
128
+ omitted_seed_matches: z.number().int().min(0),
129
+ omitted_nodes: z.number().int().min(0),
130
+ omitted_edges: z.number().int().min(0),
131
+ omitted_paths: z.number().int().min(0),
132
+ frontier_node_ids: z.array(nodeIdSchema).max(50_000),
133
+ }),
134
+ evidence_links: z.array(evidenceIdSchema).min(1).max(65),
135
+ limitations: z.array(boundedTextSchema).max(1_000),
136
+ });
@@ -0,0 +1,88 @@
1
+ import { compareCodePoints, } from "./javascriptApplicationGraph.js";
2
+ /** Traverse a JAG without treating edge inference as observed reachability. */
3
+ export const traverseApplicationFeature = (graph, seedNodeIds, direction, limits) => {
4
+ const nodeById = new Map(graph.nodes.map((node) => [node.node_id, node]));
5
+ const adjacency = buildAdjacency(graph.edges, direction);
6
+ const visited = new Set(seedNodeIds);
7
+ const retainedEdges = new Map();
8
+ const omittedNodes = new Set();
9
+ const omittedEdges = new Set();
10
+ const frontier = new Set();
11
+ const depths = new Map(seedNodeIds.map((nodeId) => [nodeId, 0]));
12
+ const predecessors = new Map();
13
+ const queue = [...seedNodeIds];
14
+ for (let offset = 0; offset < queue.length; offset += 1) {
15
+ const current = queue[offset];
16
+ if (current === undefined)
17
+ continue;
18
+ const depth = depths.get(current) ?? 0;
19
+ for (const entry of adjacency.get(current) ?? []) {
20
+ if (depth >= limits.max_depth) {
21
+ recordOmission(entry, current, omittedNodes, omittedEdges, frontier);
22
+ continue;
23
+ }
24
+ if (!visited.has(entry.nextNodeId) && visited.size >= limits.max_nodes) {
25
+ recordOmission(entry, current, omittedNodes, omittedEdges, frontier);
26
+ continue;
27
+ }
28
+ if (!retainedEdges.has(entry.edge.edge_id) &&
29
+ retainedEdges.size >= limits.max_edges) {
30
+ omittedEdges.add(entry.edge.edge_id);
31
+ frontier.add(current);
32
+ frontier.add(entry.nextNodeId);
33
+ continue;
34
+ }
35
+ retainedEdges.set(entry.edge.edge_id, entry.edge);
36
+ if (visited.has(entry.nextNodeId))
37
+ continue;
38
+ if (!nodeById.has(entry.nextNodeId))
39
+ continue;
40
+ visited.add(entry.nextNodeId);
41
+ depths.set(entry.nextNodeId, depth + 1);
42
+ predecessors.set(entry.nextNodeId, {
43
+ previousNodeId: current,
44
+ edgeId: entry.edge.edge_id,
45
+ });
46
+ queue.push(entry.nextNodeId);
47
+ }
48
+ }
49
+ return {
50
+ nodes: [...visited]
51
+ .map((nodeId) => nodeById.get(nodeId))
52
+ .filter((node) => node !== undefined)
53
+ .sort((left, right) => compareCodePoints(left.node_id, right.node_id)),
54
+ edges: [...retainedEdges.values()].sort((left, right) => compareCodePoints(left.edge_id, right.edge_id)),
55
+ depths,
56
+ predecessors,
57
+ frontierNodeIds: [...frontier].sort(compareCodePoints),
58
+ omittedNodeCount: omittedNodes.size,
59
+ omittedEdgeCount: omittedEdges.size,
60
+ };
61
+ };
62
+ const buildAdjacency = (edges, direction) => {
63
+ const adjacency = new Map();
64
+ for (const edge of edges) {
65
+ if (direction !== "incoming")
66
+ addAdjacency(adjacency, edge.source_node_id, {
67
+ edge,
68
+ nextNodeId: edge.target_node_id,
69
+ });
70
+ if (direction !== "outgoing")
71
+ addAdjacency(adjacency, edge.target_node_id, {
72
+ edge,
73
+ nextNodeId: edge.source_node_id,
74
+ });
75
+ }
76
+ for (const entries of adjacency.values())
77
+ entries.sort((left, right) => compareCodePoints(`${left.edge.edge_id}\0${left.nextNodeId}`, `${right.edge.edge_id}\0${right.nextNodeId}`));
78
+ return adjacency;
79
+ };
80
+ const addAdjacency = (adjacency, nodeId, entry) => {
81
+ adjacency.set(nodeId, [...(adjacency.get(nodeId) ?? []), entry]);
82
+ };
83
+ const recordOmission = (entry, current, omittedNodes, omittedEdges, frontier) => {
84
+ omittedNodes.add(entry.nextNodeId);
85
+ omittedEdges.add(entry.edge.edge_id);
86
+ frontier.add(current);
87
+ frontier.add(entry.nextNodeId);
88
+ };
@@ -0,0 +1,85 @@
1
+ import { compareCodePoints, } from "./javascriptApplicationGraph.js";
2
+ /** Build provider-neutral addon targets and link exact-subject native Evidence. */
3
+ export const buildJavaScriptNativeHandoffs = (nodes, edges, nativeEvidence) => {
4
+ const nodeById = new Map(nodes.map((node) => [node.node_id, node]));
5
+ return nodes
6
+ .filter(({ kind }) => kind === "native-addon")
7
+ .flatMap((node) => {
8
+ const digest = artifactDigest(node);
9
+ if (digest === null)
10
+ return [];
11
+ const linked = nativeEvidence.filter(({ subject }) => subject?.digest.sha256 === digest);
12
+ const exportNodes = nativeExportNodes(node, edges, nodeById);
13
+ return [
14
+ {
15
+ native_node_id: node.node_id,
16
+ artifact_sha256: digest,
17
+ artifact_path: artifactPath(node),
18
+ export_node_ids: exportNodes.map(({ node_id: id }) => id),
19
+ requested_exports: requestedExports(exportNodes),
20
+ status: linked.length === 0
21
+ ? "requires-provider-analysis"
22
+ : "evidence-linked",
23
+ providers: uniqueProviders(linked),
24
+ evidence_ids: linked
25
+ .map(({ evidence_id: id }) => id)
26
+ .sort(compareCodePoints),
27
+ recommended_tools: [
28
+ "open_binary",
29
+ "binary_overview",
30
+ "search_procedures",
31
+ "analyze_function",
32
+ "xrefs",
33
+ ],
34
+ },
35
+ ];
36
+ })
37
+ .sort((left, right) => compareCodePoints(left.native_node_id, right.native_node_id));
38
+ };
39
+ const artifactDigest = (node) => {
40
+ if (node.identity.strategy === "content-digest")
41
+ return node.identity.sha256;
42
+ const observation = node.observations.find(({ evidence }) => evidence.artifact.available);
43
+ return observation?.evidence.artifact.available === true
44
+ ? observation.evidence.artifact.sha256
45
+ : null;
46
+ };
47
+ const artifactPath = (node) => {
48
+ for (const { evidence } of node.observations)
49
+ if (evidence.location.available &&
50
+ evidence.location.value.kind === "artifact-path")
51
+ return evidence.location.value.path;
52
+ return null;
53
+ };
54
+ const nativeExportNodes = (addon, edges, nodeById) => {
55
+ const ids = edges.flatMap((edge) => {
56
+ if (edge.source_node_id === addon.node_id &&
57
+ ["contains", "exposes"].includes(edge.relation))
58
+ return [edge.target_node_id];
59
+ if (edge.target_node_id === addon.node_id &&
60
+ ["imports", "loads"].includes(edge.relation))
61
+ return [edge.source_node_id];
62
+ return [];
63
+ });
64
+ return [...new Set(ids)]
65
+ .map((id) => nodeById.get(id))
66
+ .filter((node) => node?.kind === "native-export")
67
+ .sort((left, right) => compareCodePoints(left.node_id, right.node_id));
68
+ };
69
+ const requestedExports = (nodes) => {
70
+ const values = nodes.flatMap((node) => node.observations.flatMap((observation) => [
71
+ ...(observation.label === null ? [] : [observation.label]),
72
+ ...stringArray(observation.properties.requested_members),
73
+ ...stringArray(observation.properties.members),
74
+ ]));
75
+ return [...new Set(values)].sort(compareCodePoints).slice(0, 1_000);
76
+ };
77
+ const stringArray = (value) => Array.isArray(value)
78
+ ? value.filter((item) => typeof item === "string")
79
+ : [];
80
+ const uniqueProviders = (evidence) => [
81
+ ...new Map(evidence.map(({ provider }) => [
82
+ `${provider.id}\0${provider.name}\0${provider.version ?? ""}`,
83
+ provider,
84
+ ])).values(),
85
+ ].sort((left, right) => compareCodePoints(`${left.id}\0${left.name}\0${left.version ?? ""}`, `${right.id}\0${right.name}\0${right.version ?? ""}`));