rea-agents 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/README.md +80 -18
  2. package/bridge/ghidra/ReaGhidraBridge.java +48 -1
  3. package/bridge/hopper_bridge.py +259 -24
  4. package/dist/application/AnalysisClientCleanup.js +14 -0
  5. package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
  6. package/dist/application/ArtifactExtraction.js +5 -1
  7. package/dist/application/ArtifactGraphConstruction.js +3 -15
  8. package/dist/application/ArtifactInventory/classify.js +5 -1
  9. package/dist/application/ArtifactInventory/reader.js +2 -0
  10. package/dist/application/BinarySession.js +15 -4
  11. package/dist/application/BinarySessionStatus.js +32 -1
  12. package/dist/application/BrowserEvidence.js +14 -0
  13. package/dist/application/BrowserScenarioCapturePort.js +1 -0
  14. package/dist/application/BrowserScenarioCaptureService.js +53 -0
  15. package/dist/application/BrowserScenarioEvidence.js +40 -0
  16. package/dist/application/CallPathTracing.js +160 -0
  17. package/dist/application/CapabilityInventory.js +79 -15
  18. package/dist/application/CompositeProvider.js +10 -1
  19. package/dist/application/DirectAnalysis.js +7 -2
  20. package/dist/application/EnhancedLiteralTracing.js +118 -0
  21. package/dist/application/EnhancedTools.js +35 -121
  22. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
  23. package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
  24. package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
  25. package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
  26. package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
  27. package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
  28. package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
  29. package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
  30. package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
  31. package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
  32. package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
  33. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
  34. package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
  35. package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
  36. package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
  37. package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
  38. package/dist/application/ProcessCaptureEnvironment.js +26 -0
  39. package/dist/application/ProcessCaptureLifecycle.js +1 -0
  40. package/dist/application/ProcessCheckpoints.js +5 -0
  41. package/dist/application/ProcessHarness.js +45 -34
  42. package/dist/application/ProcessNormalization.js +6 -0
  43. package/dist/application/ProcessReactiveCoordinator.js +10 -3
  44. package/dist/application/ProcessReactiveEffects.js +15 -1
  45. package/dist/application/ProcessReactiveHarness.js +100 -0
  46. package/dist/application/ProcessReactiveObservations.js +101 -16
  47. package/dist/application/ProjectPermissionStore.js +1 -0
  48. package/dist/application/ReconstructionObligationAuthority.js +72 -0
  49. package/dist/application/ReconstructionObligationCandidates.js +265 -0
  50. package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
  51. package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
  52. package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
  53. package/dist/application/ReconstructionObligationLedgerService.js +72 -0
  54. package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
  55. package/dist/application/ReconstructionObligationPolicies.js +132 -0
  56. package/dist/application/ReconstructionReadinessService.js +48 -0
  57. package/dist/artifacts/ArtifactProvider.js +87 -22
  58. package/dist/browser/BrowserScenarioSecrets.js +53 -0
  59. package/dist/browser/BrowserScenarioSessionPort.js +1 -0
  60. package/dist/browser/CdpBrowserProvider.js +2 -2
  61. package/dist/browser/CdpCaptureDocuments.js +57 -0
  62. package/dist/browser/CdpCaptureStorage.js +71 -29
  63. package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
  64. package/dist/browser/CdpEndpoint.js +8 -6
  65. package/dist/browser/CdpPageCapture.js +2 -0
  66. package/dist/browser/JavaScriptRuntimeScope.js +38 -0
  67. package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
  68. package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
  69. package/dist/browser/PlaywrightScenarioActions.js +62 -0
  70. package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
  71. package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
  72. package/dist/browser/PlaywrightScenarioEvents.js +301 -0
  73. package/dist/browser/PlaywrightScenarioSession.js +300 -0
  74. package/dist/browser/V8InspectorCaptureProjection.js +110 -0
  75. package/dist/browser/V8InspectorEndpoint.js +72 -0
  76. package/dist/browser/V8InspectorProvider.js +267 -0
  77. package/dist/catalogIdentity.js +8 -0
  78. package/dist/cli/artifactCommands.js +39 -0
  79. package/dist/cli/coreAnalysisCommands.js +37 -0
  80. package/dist/cli.js +4 -0
  81. package/dist/cliApplicationCommands.js +30 -2
  82. package/dist/cliBrowserAdvancedCommands.js +17 -5
  83. package/dist/cliBrowserCommands.js +1 -0
  84. package/dist/cliBrowserScenarioCommands.js +43 -0
  85. package/dist/cliCommandNames.js +8 -0
  86. package/dist/cliJavaScriptRuntimeCommands.js +164 -0
  87. package/dist/cliObservationOptions.js +4 -0
  88. package/dist/cliPolicyCommands.js +1 -0
  89. package/dist/config/environment.js +12 -0
  90. package/dist/config/parseConfig.js +88 -17
  91. package/dist/contracts/analysisSearchContract.js +14 -0
  92. package/dist/contracts/applicationToolContracts.js +86 -2
  93. package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
  94. package/dist/contracts/artifactToolContracts.js +22 -0
  95. package/dist/contracts/browserProviderToolContracts.js +7 -0
  96. package/dist/contracts/browserScenarioToolContracts.js +81 -0
  97. package/dist/contracts/browserToolContracts.js +88 -98
  98. package/dist/contracts/enhancedInputs.js +25 -6
  99. package/dist/contracts/functionInstructionContract.js +8 -0
  100. package/dist/contracts/hopperMemoryContracts.js +22 -0
  101. package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
  102. package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
  103. package/dist/contracts/reconstructionReadinessExample.js +436 -0
  104. package/dist/contracts/toolContractExamples.js +5 -0
  105. package/dist/contracts/toolContractTypes.js +1 -0
  106. package/dist/contracts/toolContracts.js +13 -21
  107. package/dist/contracts/toolEffects.js +21 -0
  108. package/dist/contracts/toolOutputSchemaGroups.js +70 -20
  109. package/dist/contracts/toolOutputSchemaPrimitives.js +40 -0
  110. package/dist/domain/analysisErrorProjection.js +8 -0
  111. package/dist/domain/analysisSnapshot.js +2 -0
  112. package/dist/domain/artifactGraph.js +2 -0
  113. package/dist/domain/artifactInspection.js +347 -0
  114. package/dist/domain/binaryTarget.js +3 -6
  115. package/dist/domain/browserCaptureComparison.js +82 -0
  116. package/dist/domain/browserObservation.js +8 -1
  117. package/dist/domain/browserObservationErrors.js +1 -0
  118. package/dist/domain/browserObservationSchemas.js +26 -0
  119. package/dist/domain/browserScenario.js +165 -0
  120. package/dist/domain/browserScenarioCapture.js +57 -0
  121. package/dist/domain/browserScenarioCaptureValues.js +204 -0
  122. package/dist/domain/browserScenarioDiff.js +324 -0
  123. package/dist/domain/browserScenarioDiffValues.js +157 -0
  124. package/dist/domain/browserScenarioNormalization.js +45 -0
  125. package/dist/domain/browserScenarioValues.js +372 -0
  126. package/dist/domain/evidence.js +2 -0
  127. package/dist/domain/hopperValues.js +10 -0
  128. package/dist/domain/javascriptRuntimeObservation.js +170 -0
  129. package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
  130. package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
  131. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
  132. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  133. package/dist/domain/javascriptSemanticAnalysis.js +5 -4
  134. package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
  135. package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
  136. package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
  137. package/dist/domain/javascriptSemanticDataEffects.js +284 -0
  138. package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
  139. package/dist/domain/javascriptSemanticFingerprints.js +174 -0
  140. package/dist/domain/javascriptSemanticIr.js +21 -1
  141. package/dist/domain/javascriptSemanticObjects.js +115 -0
  142. package/dist/domain/javascriptSemanticPromises.js +290 -0
  143. package/dist/domain/javascriptSemanticResources.js +136 -0
  144. package/dist/domain/javascriptStaticAnalysis.js +4 -3
  145. package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
  146. package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
  147. package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
  148. package/dist/domain/processCapture.js +71 -1
  149. package/dist/domain/processCaptureReactiveValidation.js +175 -0
  150. package/dist/domain/processCaptureValidation.js +2 -0
  151. package/dist/domain/processObservation.js +15 -6
  152. package/dist/domain/processReactiveRuntime.js +21 -10
  153. package/dist/domain/processReactiveScenario.js +1 -0
  154. package/dist/domain/processReactiveTransition.js +17 -10
  155. package/dist/domain/processScenario.js +21 -0
  156. package/dist/domain/providerCleanupError.js +11 -0
  157. package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
  158. package/dist/domain/reconstructionReadiness.js +121 -0
  159. package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
  160. package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
  161. package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
  162. package/dist/domain/reconstructionReadinessSchemas.js +240 -0
  163. package/dist/domain/sourceToBundleComparison.js +335 -0
  164. package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
  165. package/dist/domain/sourceToBundleSignals.js +202 -0
  166. package/dist/domain/webCaptureDiff.js +141 -27
  167. package/dist/domain/webScreenshot.js +1 -1
  168. package/dist/domain/zipPackageFormat.js +20 -0
  169. package/dist/generatedPackageMetadata.js +1 -1
  170. package/dist/ghidra/GhidraDefaults.js +1 -1
  171. package/dist/ghidra/GhidraFunctionValues.js +13 -1
  172. package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
  173. package/dist/hopper/HopperCleanup.js +158 -0
  174. package/dist/hopper/HopperClient.js +69 -99
  175. package/dist/hopper/HopperProvider.js +20 -1
  176. package/dist/hopper/HopperRequestQueue.js +187 -0
  177. package/dist/hopper/HopperResponseStream.js +46 -0
  178. package/dist/hopper/protocol.js +1 -0
  179. package/dist/main/transport.js +24 -24
  180. package/dist/main.js +6 -0
  181. package/dist/mcpDoctor.js +25 -2
  182. package/dist/native/NativeMacOSProvider.js +1 -0
  183. package/dist/process/ProcessCommandIdentity.js +19 -0
  184. package/dist/process/ProcessOwnership.js +11 -8
  185. package/dist/server/DynamicToolAvailability.js +70 -0
  186. package/dist/server/createServer.js +46 -1
  187. package/dist/server/lifecycleProgress.js +10 -0
  188. package/dist/server/permissionFailure.js +12 -0
  189. package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
  190. package/dist/server/registerApplicationTools/helpers.js +11 -7
  191. package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
  192. package/dist/server/registerApplicationTools/readiness.js +55 -0
  193. package/dist/server/registerApplicationTools.js +6 -0
  194. package/dist/server/registerArtifactTools.js +9 -1
  195. package/dist/server/registerBrowserScenarioTool.js +25 -0
  196. package/dist/server/registerBrowserTools.js +2 -2
  197. package/dist/server/registerCloseLifecycleTool.js +57 -0
  198. package/dist/server/registerEnhancedTools.js +16 -5
  199. package/dist/server/registerEvidenceResources.js +4 -0
  200. package/dist/server/registerEvidenceTools.js +9 -2
  201. package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
  202. package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
  203. package/dist/server/registerReconstructionReadinessResource.js +39 -0
  204. package/dist/server/registerSessionStatusTool.js +11 -8
  205. package/dist/server/registerSessionTools.js +11 -57
  206. package/dist/server/sessionAvailabilityPolicy.js +10 -8
  207. package/package.json +12 -6
  208. package/skills/reverse-engineer-anything/SKILL.md +4 -3
  209. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
@@ -0,0 +1,136 @@
1
+ import * as t from "@babel/types";
2
+ import { semanticStaticPropertyName } from "./javascriptSemanticProjection.js";
3
+ import { resolveSemanticBindingState, } from "./javascriptSemanticState.js";
4
+ import { dataEffectLimitReached, outerDataEffectBinding, traverseDataEffects, } from "./javascriptSemanticDataEffectHelpers.js";
5
+ import { range } from "./javascriptStaticAnalysisHelpers.js";
6
+ const ACQUIRE_METHODS = [
7
+ "open",
8
+ "openSync",
9
+ "createReadStream",
10
+ "createWriteStream",
11
+ "connect",
12
+ "createConnection",
13
+ ];
14
+ const RELEASE_METHODS = ["close", "destroy", "end"];
15
+ /** Recover built-in filesystem/network acquisition and local release handles. */
16
+ export const collectJavaScriptSemanticResources = (program, state, callables) => {
17
+ const context = {
18
+ state,
19
+ callableIds: new Set(callables.map(({ callableId }) => callableId)),
20
+ callableStack: [],
21
+ ancestors: [],
22
+ };
23
+ const acquisitions = collectAcquisitions(program, context);
24
+ return [
25
+ ...acquisitions.map(immutableAcquisition),
26
+ ...collectReleases(program, context, acquisitions),
27
+ ];
28
+ };
29
+ const collectAcquisitions = (program, context) => {
30
+ const output = [];
31
+ traverseDataEffects(program, context, (node) => {
32
+ if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
33
+ return;
34
+ const method = acquisitionMethod(node.callee, context.state);
35
+ if (method === null)
36
+ return;
37
+ if (resourceLimitReached(output.length, context.state))
38
+ return;
39
+ output.push({
40
+ node,
41
+ method,
42
+ ownerCallableId: context.callableStack.at(-1) ?? null,
43
+ resultBindingId: outerDataEffectBinding(node, context),
44
+ });
45
+ });
46
+ return output;
47
+ };
48
+ const collectReleases = (program, context, acquisitions) => {
49
+ const byBinding = new Map();
50
+ for (const acquisition of acquisitions) {
51
+ if (acquisition.resultBindingId === null)
52
+ continue;
53
+ const existing = byBinding.get(acquisition.resultBindingId) ?? [];
54
+ existing.push(acquisition);
55
+ byBinding.set(acquisition.resultBindingId, existing);
56
+ }
57
+ const output = [];
58
+ traverseDataEffects(program, context, (node) => {
59
+ if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
60
+ return;
61
+ const member = memberCallee(node);
62
+ if (member === null || !t.isIdentifier(member.object))
63
+ return;
64
+ const method = RELEASE_METHODS.find((candidate) => candidate ===
65
+ semanticStaticPropertyName(member.property, member.computed));
66
+ if (method === undefined)
67
+ return;
68
+ const binding = resolveSemanticBindingState(context.state, member.object, member.object.name);
69
+ const linked = binding === undefined ? [] : (byBinding.get(binding.bindingId) ?? []);
70
+ if (linked.length === 0)
71
+ return;
72
+ if (resourceLimitReached(acquisitions.length + output.length, context.state))
73
+ return;
74
+ output.push({
75
+ resourceId: `resource:release:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
76
+ kind: "release",
77
+ method,
78
+ location: range(node),
79
+ ownerCallableId: context.callableStack.at(-1) ?? null,
80
+ resultBindingId: binding?.bindingId ?? null,
81
+ linkedResourceIds: linked.map(resourceCandidateId).sort(),
82
+ resolution: linked.length === 1 ? "complete" : "partial",
83
+ });
84
+ });
85
+ return output;
86
+ };
87
+ const immutableAcquisition = (candidate) => ({
88
+ resourceId: resourceCandidateId(candidate),
89
+ kind: "acquire",
90
+ method: candidate.method,
91
+ location: range(candidate.node),
92
+ ownerCallableId: candidate.ownerCallableId,
93
+ resultBindingId: candidate.resultBindingId,
94
+ linkedResourceIds: [],
95
+ resolution: "complete",
96
+ });
97
+ const acquisitionMethod = (callee, state) => {
98
+ if (t.isIdentifier(callee)) {
99
+ const binding = resolveSemanticBindingState(state, callee, callee.name);
100
+ return methodFromBinding(binding);
101
+ }
102
+ if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
103
+ return null;
104
+ const method = ACQUIRE_METHODS.find((candidate) => candidate ===
105
+ semanticStaticPropertyName(callee.property, callee.computed));
106
+ if (method === undefined || !t.isIdentifier(callee.object))
107
+ return null;
108
+ const binding = resolveSemanticBindingState(state, callee.object, callee.object.name);
109
+ return resourceNamespace(binding) ? method : null;
110
+ };
111
+ const methodFromBinding = (binding) => {
112
+ for (const { specifier, importedPath } of binding?.directOrigins ?? []) {
113
+ if (!resourceModule(specifier))
114
+ continue;
115
+ const method = ACQUIRE_METHODS.find((candidate) => candidate === importedPath.at(-1));
116
+ if (method !== undefined)
117
+ return method;
118
+ }
119
+ return null;
120
+ };
121
+ const resourceNamespace = (binding) => binding?.directOrigins.some(({ specifier, importedPath }) => resourceModule(specifier) && importedPath.length === 0) ?? false;
122
+ const resourceModule = (specifier) => [
123
+ "fs",
124
+ "fs/promises",
125
+ "net",
126
+ "node:fs",
127
+ "node:fs/promises",
128
+ "node:net",
129
+ "node:tls",
130
+ "tls",
131
+ ].includes(specifier);
132
+ const resourceLimitReached = (retained, state) => dataEffectLimitReached(retained, state.limits.maxResourceOperations, "maxResourceOperations", state);
133
+ const resourceCandidateId = (candidate) => `resource:acquire:${String(candidate.node.start ?? -1)}:${String(candidate.node.end ?? -1)}`;
134
+ const memberCallee = (node) => t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)
135
+ ? node.callee
136
+ : null;
@@ -3,7 +3,7 @@ import * as t from "@babel/types";
3
3
  import { inspectElectronStaticNode } from "./electronStaticAnalysis.js";
4
4
  import { detectVendors, failedJavaScriptStaticAnalysis, registrationKey, sortedUnique, stringValue, } from "./javascriptStaticAnalysisHelpers.js";
5
5
  import { addReference, addSourceMapDirectives, inspectCall, inspectRouteProperty, inspectRoleProperty, } from "./javascriptStaticAnalysisCalls.js";
6
- import { inspectBundlerRegistration } from "./javascriptStaticAnalysisBundler.js";
6
+ import { inspectBundlerRegistration, inspectEsbuildWrapper, } from "./javascriptStaticAnalysisBundler.js";
7
7
  import { finalizeLocatedFindings } from "./javascriptStaticAnalysisFindings.js";
8
8
  import { createJavaScriptAnalysisAccumulator, } from "./javascriptStaticAnalysisState.js";
9
9
  /** Parse one bounded JavaScript artifact and recover static structure only. */
@@ -100,10 +100,11 @@ const inspectNode = (source, node, accumulator, limits) => {
100
100
  inspectElectronStaticNode(node, findings);
101
101
  if (t.isCallExpression(node)) {
102
102
  inspectBundlerRegistration(source, node, accumulator, limits);
103
- inspectCall(node, findings);
103
+ inspectEsbuildWrapper(source, node, accumulator, limits);
104
+ inspectCall(source, node, findings);
104
105
  }
105
106
  else if (t.isNewExpression(node))
106
- inspectCall(node, findings);
107
+ inspectCall(source, node, findings);
107
108
  if ((t.isImportDeclaration(node) || t.isExportAllDeclaration(node)) &&
108
109
  node.source !== undefined)
109
110
  addReference(findings, {
@@ -61,6 +61,44 @@ export const inspectBundlerRegistration = (source, call, accumulator, limits) =>
61
61
  };
62
62
  addBoundedFinding(accumulator, `registration\0${registrationKey(registration)}`, limits.maxFindings, () => accumulator.registrations.push(registration));
63
63
  };
64
+ /** Inspect esbuild helper wrappers and recover their literal module tables. */
65
+ export const inspectEsbuildWrapper = (source, call, accumulator, limits) => {
66
+ const name = calleeName(call.callee);
67
+ const wrapperKind = name === "__commonJS" || name.endsWith(".__commonJS")
68
+ ? "commonjs"
69
+ : name === "__esm" || name.endsWith(".__esm")
70
+ ? "esm"
71
+ : null;
72
+ const table = call.arguments[0];
73
+ if (wrapperKind === null || !t.isObjectExpression(table))
74
+ return;
75
+ const recovered = recoverBundlerModules(source, table, accumulator, limits);
76
+ if (recovered.modules.length === 0)
77
+ return;
78
+ if (recovered.omittedAsyncChunkKeys > 0) {
79
+ accumulator.droppedFindings += recovered.omittedAsyncChunkKeys;
80
+ accumulator.structuralTruncation = true;
81
+ }
82
+ accumulator.unknownFindings += recovered.unknownAsyncChunkKeys;
83
+ const registration = {
84
+ bundler: "esbuild",
85
+ runtime: `esbuild-${wrapperKind}`,
86
+ chunk_keys: [`${wrapperKind}@${String(call.start ?? 0)}`],
87
+ omitted_chunk_keys: 0,
88
+ unknown_chunk_keys: 0,
89
+ runtime_require_name: null,
90
+ runtime_module_cache_status: "not-observed",
91
+ entry_module_keys: [],
92
+ omitted_entry_module_keys: 0,
93
+ unknown_entry_module_keys: 0,
94
+ async_chunk_keys: recovered.asyncChunkKeys,
95
+ omitted_async_chunk_keys: recovered.omittedAsyncChunkKeys,
96
+ unknown_async_chunk_keys: recovered.unknownAsyncChunkKeys,
97
+ modules: recovered.modules.sort((left, right) => compareCodePoints(left.module_key, right.module_key)),
98
+ location: range(call),
99
+ };
100
+ addBoundedFinding(accumulator, `registration\0${registrationKey(registration)}`, limits.maxFindings, () => accumulator.registrations.push(registration));
101
+ };
64
102
  const recoverBundlerModules = (source, table, accumulator, limits) => {
65
103
  const modules = [];
66
104
  const asyncChunkKeys = [];
@@ -2,7 +2,7 @@ import * as t from "@babel/types";
2
2
  import { addBoundedFinding, addLocatedFinding, moduleAtOffset, } from "./javascriptStaticAnalysisFindings.js";
3
3
  import { STATIC_ROUTE_CALL_NAMES, argumentValue, calleeName, endpointArgument as endpointArgumentHelper, prefixedArgument, propertyName, range, rangeForOffsets, sanitizeCandidate, sourceSlice, staticPath, staticPathResolutionContext, storageKind, stringValue, } from "./javascriptStaticAnalysisHelpers.js";
4
4
  /** Inspect one call or new expression for references, endpoints, storage, and roles. */
5
- export const inspectCall = (node, context) => {
5
+ export const inspectCall = (source, node, context) => {
6
6
  const { accumulator } = context;
7
7
  const name = calleeName(node.callee);
8
8
  const first = stringValue(node.arguments[0]);
@@ -22,6 +22,8 @@ export const inspectCall = (node, context) => {
22
22
  kind: "dynamic-import",
23
23
  specifier: chunkLoad.specifier,
24
24
  });
25
+ for (const specifier of vitePreloadDependencySpecifiers(source, node, name))
26
+ addReference(context, { node, kind: "dynamic-import", specifier });
25
27
  if (name === "Worker" || name.endsWith(".Worker"))
26
28
  addReference(context, { node, kind: "worker", specifier: first });
27
29
  if (name.endsWith("serviceWorker.register"))
@@ -73,6 +75,26 @@ const chunkLoadSpecifier = (node, name, moduleRequireName) => {
73
75
  return null;
74
76
  return { specifier: prefixedArgument("chunk:", node.arguments[0]) };
75
77
  };
78
+ const vitePreloadDependencySpecifiers = (source, node, name) => {
79
+ if (name !== "__vite__mapDeps" || !t.isArrayExpression(node.arguments[0]))
80
+ return [];
81
+ const indexes = node.arguments[0].elements.flatMap((element) => t.isNumericLiteral(element) ? [element.value] : []);
82
+ if (indexes.length !== node.arguments[0].elements.length)
83
+ return [];
84
+ const table = vitePreloadDependencyTable(source);
85
+ return indexes.flatMap((index) => table[index] ?? []);
86
+ };
87
+ const vitePreloadDependencyTable = (source) => {
88
+ const match = /\.\s*f\s*\|\|\s*\(\s*[^)]*?\.f\s*=\s*\[([\s\S]*?)\]\s*\)/u.exec(source);
89
+ const body = match?.[1];
90
+ if (body === undefined)
91
+ return [];
92
+ return [
93
+ ...body.matchAll(/"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gu),
94
+ ]
95
+ .map((literal) => (literal[1] ?? literal[2] ?? "").slice(0, 4_096))
96
+ .slice(0, 256);
97
+ };
76
98
  const inspectEndpointCall = (node, name, first, context) => {
77
99
  const route = STATIC_ROUTE_CALL_NAMES.some((candidate) => name === candidate || name.endsWith(`.${candidate}`));
78
100
  if (route && first !== undefined)
@@ -294,6 +294,12 @@ export const rangeForOffsets = (source, start, end) => ({
294
294
  start: pointForOffset(source, start),
295
295
  end: pointForOffset(source, end),
296
296
  });
297
+ /** Compare two exact source ranges. */
298
+ export const sourceRangesEqual = (left, right) => left !== null &&
299
+ left.start.line === right.start.line &&
300
+ left.start.column === right.start.column &&
301
+ left.end.line === right.end.line &&
302
+ left.end.column === right.end.column;
297
303
  const pointForOffset = (source, offset) => {
298
304
  const before = source.slice(0, offset);
299
305
  const lines = before.split("\n");
@@ -335,6 +341,8 @@ const vendorPatterns = [
335
341
  { name: "webpack", patterns: ["__webpack_require__", "webpackChunk"] },
336
342
  { name: "Rspack", patterns: ["__webpack_require__.f", "rspackChunk"] },
337
343
  { name: "Vite", patterns: ["__vite__", "import.meta.hot"] },
344
+ { name: "Rollup", patterns: ["ROLLUP_FILE_URL", "import.meta.ROLLUP"] },
345
+ { name: "esbuild", patterns: ["__commonJS", "__esm"] },
338
346
  { name: "React", patterns: ["React.createElement", "react/jsx-runtime"] },
339
347
  { name: "Vue", patterns: ["__VUE__", "createApp("] },
340
348
  { name: "Next.js", patterns: ["__NEXT_DATA__", "/_next/"] },
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { jsonValueSchema } from "./jsonValue.js";
3
3
  import { LEGACY_PROCESS_CAPTURE_MESSAGE, normalizationSchema, } from "./processScenario.js";
4
+ import { PROCESS_REACTIVE_LIMITS } from "./processReactiveScenario.js";
4
5
  export * from "./processScenario.js";
5
6
  /** Capture collection whose members participate in global observation order. */
6
7
  export const PROCESS_CAPTURE_EVENT_COLLECTIONS = [
@@ -76,7 +77,7 @@ export const processCaptureSchema = z.object({
76
77
  sequence: z.number().int().nonnegative(),
77
78
  scheduled_at_ms: z.number().int().nonnegative(),
78
79
  dispatched_at_ms: z.number().int().nonnegative(),
79
- type: z.enum(["input", "resize", "signal"]),
80
+ type: z.enum(["input", "resize", "stdin_close", "signal"]),
80
81
  data: z.string(),
81
82
  outcome: z.enum(["dispatched", "target_exited", "failed"]),
82
83
  })),
@@ -145,6 +146,75 @@ export const processCaptureSchema = z.object({
145
146
  sensitive_aliases: z.array(z.string().min(1).max(64)).max(32),
146
147
  }))
147
148
  .default([]),
149
+ reactive_run: z
150
+ .object({
151
+ status: z.enum(["running", "finished"]),
152
+ outcome: z
153
+ .enum([
154
+ "passed",
155
+ "predicate_timeout",
156
+ "scenario_deadline",
157
+ "ambiguous_match",
158
+ "action_rejected",
159
+ "target_lost",
160
+ "capture_incomplete",
161
+ "cancelled",
162
+ "cleanup_failed",
163
+ ])
164
+ .nullable(),
165
+ active_state: z.string().min(1).max(64),
166
+ controls: z
167
+ .array(z.object({
168
+ sequence: z.number().int().nonnegative(),
169
+ kind: z.enum([
170
+ "state_deadline",
171
+ "scenario_deadline",
172
+ "target_lost",
173
+ "cancelled",
174
+ "cleanup_failed",
175
+ ]),
176
+ after_capture_order: z.number().int().min(-1),
177
+ }))
178
+ .default([]),
179
+ transitions: z
180
+ .array(z.object({
181
+ sequence: z.number().int().nonnegative(),
182
+ transition_id: z.string().min(1).max(64),
183
+ state_before: z.string().min(1).max(64),
184
+ state_after: z.string().min(1).max(64).nullable(),
185
+ outcome: z
186
+ .enum([
187
+ "passed",
188
+ "predicate_timeout",
189
+ "scenario_deadline",
190
+ "ambiguous_match",
191
+ "action_rejected",
192
+ "target_lost",
193
+ "capture_incomplete",
194
+ "cancelled",
195
+ "cleanup_failed",
196
+ ])
197
+ .nullable(),
198
+ trigger_event_ids: z
199
+ .array(z.string().min(1))
200
+ .max(PROCESS_REACTIVE_LIMITS.predicates),
201
+ action_event_ids: z
202
+ .array(z.string().min(1))
203
+ .max(PROCESS_REACTIVE_LIMITS.actionsPerTransition),
204
+ action_types: z
205
+ .array(z.enum([
206
+ "send_input",
207
+ "resize",
208
+ "close_stdin",
209
+ "send_signal",
210
+ "checkpoint",
211
+ ]))
212
+ .max(PROCESS_REACTIVE_LIMITS.actionsPerTransition),
213
+ }))
214
+ .max(PROCESS_REACTIVE_LIMITS.transitions),
215
+ })
216
+ .nullable()
217
+ .default(null),
148
218
  event_journal: z
149
219
  .array(z.object({
150
220
  capture_order: z.number().int().nonnegative(),
@@ -0,0 +1,175 @@
1
+ import canonicalize from "canonicalize";
2
+ import { projectProcessObservation, } from "./processObservation.js";
3
+ import { commitProcessReactiveProposal, createProcessReactiveSnapshot, reduceProcessReactiveScenario, } from "./processReactiveRuntime.js";
4
+ import { processReactiveScenarioSchema, } from "./processReactiveScenario.js";
5
+ const admittedCollection = (collection) => [
6
+ "frames",
7
+ "interaction_events",
8
+ "process_samples",
9
+ "filesystem_checkpoints",
10
+ "shim_events",
11
+ "protocol_events",
12
+ ].includes(collection);
13
+ const reactiveObservations = (capture) => (capture.event_journal ?? []).flatMap((location) => {
14
+ if (!admittedCollection(location.collection))
15
+ return [];
16
+ const observation = projectProcessObservation(capture, location);
17
+ return observation === null ? [] : [observation];
18
+ });
19
+ const validateTransitionDeclaration = (transition, next, context) => {
20
+ const { index, scenario, require } = context;
21
+ const path = `reactive_run.transitions[${String(index)}]`;
22
+ const declared = scenario.states
23
+ .find(({ id }) => id === transition.state_before)
24
+ ?.on.find(({ id }) => id === transition.transition_id);
25
+ require(declared !==
26
+ undefined, path, "reactive transition is not declared by its recorded source state");
27
+ if (declared !== undefined) {
28
+ const stateAfter = declared.target.kind === "goto" ? declared.target.state : null;
29
+ const outcome = declared.target.kind === "finish" ? declared.target.outcome : null;
30
+ require(transition.state_after === stateAfter &&
31
+ transition.outcome ===
32
+ outcome, path, "reactive transition target or outcome differs from the committed graph");
33
+ require(canonicalize(transition.action_types) ===
34
+ canonicalize(declared.actions.map(({ type }) => type)), `${path}.action_types`, "reactive transition actions differ from the committed graph");
35
+ }
36
+ require(transition.sequence ===
37
+ index, `${path}.sequence`, "reactive transition sequence must be contiguous from zero");
38
+ if (next !== undefined)
39
+ require(transition.state_after ===
40
+ next.state_before, `reactive_run.transitions[${String(index + 1)}].state_before`, "reactive transition states must be contiguous");
41
+ };
42
+ const validateTransitionDeclarations = (run, scenario, require) => {
43
+ require(run.transitions[0]?.state_before === scenario.initial_state ||
44
+ run.transitions.length ===
45
+ 0, "reactive_run.transitions[0].state_before", "reactive transition journal must start at the declared initial state");
46
+ for (const [index, transition] of run.transitions.entries())
47
+ validateTransitionDeclaration(transition, run.transitions[index + 1], {
48
+ index,
49
+ scenario,
50
+ require,
51
+ });
52
+ };
53
+ const commitRecordedEffects = (options) => commitProcessReactiveProposal(options.scenario, options.proposal, options.recordedFailure === undefined
54
+ ? options.proposal.effects.map((_, index) => {
55
+ const eventId = options.expected?.action_event_ids[index];
56
+ const observation = eventId === undefined
57
+ ? undefined
58
+ : options.observations.get(eventId);
59
+ return observation === undefined
60
+ ? { status: "rejected" }
61
+ : { status: "succeeded", observation };
62
+ })
63
+ : [
64
+ options.recordedFailure === "target_lost"
65
+ ? { status: "target_lost" }
66
+ : { status: "rejected" },
67
+ ], { committedSensitiveInputs: true });
68
+ const replayTransitions = (scenario, run, observations, require) => {
69
+ const observationsById = new Map(observations.map((observation) => [observation.event_id, observation]));
70
+ let snapshot = createProcessReactiveSnapshot(scenario);
71
+ let transitionIndex = 0;
72
+ const advance = (input) => {
73
+ let decision = reduceProcessReactiveScenario(scenario, snapshot, input);
74
+ if (decision.kind === "proposal") {
75
+ const proposal = decision;
76
+ decision = commitRecordedEffects({
77
+ scenario,
78
+ proposal: decision,
79
+ expected: run.transitions[transitionIndex],
80
+ observations: observationsById,
81
+ recordedFailure: run.transitions[transitionIndex] === undefined &&
82
+ decision.effects.length > 0 &&
83
+ (run.outcome === "action_rejected" || run.outcome === "target_lost")
84
+ ? run.outcome
85
+ : undefined,
86
+ });
87
+ require(decision.kind !== "finished" ||
88
+ decision.outcome !== "action_rejected" ||
89
+ run.outcome ===
90
+ "action_rejected", `reactive_run.transitions[${String(transitionIndex)}].action_event_ids`, `recorded action observations do not match proposed effects: ${canonicalize({
91
+ effects: proposal.effects,
92
+ observations: run.transitions[transitionIndex]?.action_event_ids.map((eventId) => observationsById.get(eventId)),
93
+ })}`);
94
+ }
95
+ if (decision.kind === "transition") {
96
+ const expected = run.transitions[transitionIndex];
97
+ require(expected !== undefined &&
98
+ canonicalize(decision.record) ===
99
+ canonicalize(expected), `reactive_run.transitions[${String(transitionIndex)}]`, "reactive transition differs from deterministic journal replay");
100
+ transitionIndex += 1;
101
+ }
102
+ snapshot = decision.snapshot;
103
+ };
104
+ const advanceControl = (control, index) => {
105
+ require(snapshot.status !== "finished" ||
106
+ control.kind ===
107
+ "cleanup_failed", `reactive_run.controls[${String(index)}]`, "reactive control cannot follow a finished run");
108
+ advance(control.kind === "state_deadline"
109
+ ? {
110
+ kind: control.kind,
111
+ state_id: snapshot.active_state,
112
+ state_entry_capture_order: snapshot.state_entry_capture_order,
113
+ }
114
+ : { kind: control.kind });
115
+ };
116
+ let controlIndex = 0;
117
+ for (const observation of observations) {
118
+ while ((run.controls[controlIndex]?.after_capture_order ??
119
+ Number.MAX_SAFE_INTEGER) < observation.capture_order) {
120
+ const control = run.controls[controlIndex];
121
+ advanceControl(control, controlIndex);
122
+ controlIndex += 1;
123
+ }
124
+ advance({ kind: "observation", observation });
125
+ }
126
+ for (; controlIndex < run.controls.length; controlIndex += 1) {
127
+ const control = run.controls[controlIndex];
128
+ advanceControl(control, controlIndex);
129
+ }
130
+ require(transitionIndex ===
131
+ run.transitions
132
+ .length, "reactive_run.transitions", `reactive transition journal is not reproduced by deterministic replay (replayed ${String(transitionIndex)} of ${String(run.transitions.length)}; outcome ${snapshot.outcome})`);
133
+ return snapshot;
134
+ };
135
+ const validateControls = (run, observations, require) => {
136
+ const admittedOrders = new Set([
137
+ -1,
138
+ ...observations.map(({ capture_order }) => capture_order),
139
+ ]);
140
+ for (const [index, control] of run.controls.entries()) {
141
+ require(control.sequence ===
142
+ index, `reactive_run.controls[${String(index)}].sequence`, "reactive control sequence must be contiguous");
143
+ require(admittedOrders.has(control.after_capture_order), `reactive_run.controls[${String(index)}].after_capture_order`, "reactive control cutoff must identify the last admitted observation");
144
+ require(index === 0 ||
145
+ control.after_capture_order >=
146
+ run.controls[index - 1]
147
+ .after_capture_order, `reactive_run.controls[${String(index)}].after_capture_order`, "reactive control cutoffs must be ordered");
148
+ }
149
+ };
150
+ /** Validate one capture's reactive journal by replaying the production reducer. */
151
+ export const validateProcessCaptureReactiveRun = (capture, require) => {
152
+ const declared = capture.manifest.scenario["reactive"];
153
+ const run = capture.reactive_run;
154
+ const declaredReactive = declared !== undefined && declared !== null;
155
+ require(declaredReactive ===
156
+ (run !==
157
+ null), "reactive_run", "reactive result presence must match the committed scenario");
158
+ if (run === null)
159
+ return;
160
+ const parsed = processReactiveScenarioSchema.safeParse(declared);
161
+ require(parsed.success, "manifest.scenario.reactive", "committed reactive scenario does not satisfy its declared schema");
162
+ if (!parsed.success)
163
+ return;
164
+ require(run.status === "finished" &&
165
+ run.outcome !==
166
+ null, "reactive_run.outcome", "completed capture requires a finished reactive outcome");
167
+ validateTransitionDeclarations(run, parsed.data, require);
168
+ const observations = reactiveObservations(capture);
169
+ validateControls(run, observations, require);
170
+ const snapshot = replayTransitions(parsed.data, run, observations, require);
171
+ require(snapshot.outcome ===
172
+ run.outcome, "reactive_run.outcome", "reactive outcome differs from deterministic journal replay");
173
+ require(snapshot.active_state ===
174
+ run.active_state, "reactive_run.active_state", "reactive active state differs from deterministic journal replay");
175
+ };
@@ -1,3 +1,4 @@
1
+ import { validateProcessCaptureReactiveRun } from "./processCaptureReactiveValidation.js";
1
2
  import { replayMachineSchema } from "./replayMachine.js";
2
3
  import { digestProcessCommitment } from "./processScenario.js";
3
4
  const orderedTimestamps = (values) => values.every((value, index) => index === 0 || value.at_ms >= values[index - 1].at_ms);
@@ -209,5 +210,6 @@ export const validateProcessCapture = (capture) => {
209
210
  validateLifecycle(capture, require);
210
211
  validateShimEvents(capture, require);
211
212
  validateReplayEvents(capture, require);
213
+ validateProcessCaptureReactiveRun(capture, require);
212
214
  return issues;
213
215
  };
@@ -60,13 +60,22 @@ const atMilliseconds = (value) => {
60
60
  }
61
61
  return null;
62
62
  };
63
- const subjectFor = (source, value) => {
63
+ /** Derive the stable capture-local identity for one normalized observation. */
64
+ export const processObservationSubject = (source, value) => {
64
65
  if (typeof value !== "object" || value === null || Array.isArray(value))
65
66
  return null;
66
- if (source === "process" && typeof value["pid"] === "number")
67
- return `process:${String(value["pid"])}`;
68
- if (source === "filesystem" && typeof value["name"] === "string")
69
- return `checkpoint:${value["name"]}`;
67
+ if (source === "process" && "pid" in value && typeof value.pid === "number")
68
+ return `process:${String(value.pid)}`;
69
+ if (source === "filesystem" &&
70
+ "name" in value &&
71
+ typeof value.name === "string")
72
+ return `checkpoint:${value.name}`;
73
+ if (source === "shim" &&
74
+ "command" in value &&
75
+ typeof value.command === "string")
76
+ return "route_index" in value && typeof value.route_index === "number"
77
+ ? `shim:${value.command}:route:${String(value.route_index)}`
78
+ : `shim:${value.command}:unmatched`;
70
79
  return null;
71
80
  };
72
81
  const projectedRecord = (capture, location) => {
@@ -133,7 +142,7 @@ const projectedRecord = (capture, location) => {
133
142
  return {
134
143
  source: projected.source,
135
144
  captured_at_ms: atMilliseconds(payload),
136
- subject_id: subjectFor(projected.source, payload),
145
+ subject_id: processObservationSubject(projected.source, payload),
137
146
  payload,
138
147
  };
139
148
  };
@@ -2,6 +2,22 @@ import { matchProcessReactiveTrigger } from "./processReactiveMatching.js";
2
2
  import { PROCESS_REACTIVE_LIMITS, } from "./processReactiveScenario.js";
3
3
  export { commitProcessReactiveProposal } from "./processReactiveTransition.js";
4
4
  const counterValue = (counters, id) => counters.find((counter) => counter.id === id)?.count ?? 0;
5
+ const observeInput = (snapshot, observation) => {
6
+ const duplicate = snapshot.observations.find(({ event_id }) => event_id === observation.event_id);
7
+ if (duplicate !== undefined)
8
+ return { kind: "invalid" };
9
+ const prior = snapshot.observations.at(-1);
10
+ if ((prior !== undefined && observation.capture_order <= prior.capture_order) ||
11
+ snapshot.observations.length >= PROCESS_REACTIVE_LIMITS.retainedObservations)
12
+ return { kind: "invalid" };
13
+ return {
14
+ kind: "new",
15
+ snapshot: {
16
+ ...snapshot,
17
+ observations: [...snapshot.observations, observation],
18
+ },
19
+ };
20
+ };
5
21
  /** Create the initial immutable reducer state for one validated scenario. */
6
22
  export const createProcessReactiveSnapshot = (scenario) => ({
7
23
  status: "running",
@@ -35,6 +51,8 @@ const reduceControlInput = (snapshot, input) => {
35
51
  : { kind: "waiting", snapshot, evaluations: [] };
36
52
  if (input.kind === "scenario_deadline")
37
53
  return finishedDecision(snapshot, "scenario_deadline");
54
+ if (input.kind === "target_lost")
55
+ return finishedDecision(snapshot, "target_lost");
38
56
  return finishedDecision(snapshot, "cancelled");
39
57
  };
40
58
  /** Advance one validated scenario using only an observation or explicit deadline. */
@@ -44,17 +62,10 @@ export const reduceProcessReactiveScenario = (scenario, snapshot, input) => {
44
62
  return controlDecision;
45
63
  if (input.kind !== "observation")
46
64
  return finishedDecision(snapshot, "capture_incomplete");
47
- const prior = snapshot.observations.at(-1);
48
- if ((prior !== undefined &&
49
- input.observation.capture_order <= prior.capture_order) ||
50
- snapshot.observations.some(({ event_id }) => event_id === input.observation.event_id))
51
- return finishedDecision(snapshot, "capture_incomplete");
52
- if (snapshot.observations.length >= PROCESS_REACTIVE_LIMITS.retainedObservations)
65
+ const admitted = observeInput(snapshot, input.observation);
66
+ if (admitted.kind === "invalid")
53
67
  return finishedDecision(snapshot, "capture_incomplete");
54
- const observed = {
55
- ...snapshot,
56
- observations: [...snapshot.observations, input.observation],
57
- };
68
+ const observed = admitted.snapshot;
58
69
  const state = scenario.states.find(({ id }) => id === observed.active_state);
59
70
  if (state === undefined)
60
71
  return finishedDecision(observed, "target_lost");
@@ -128,6 +128,7 @@ const processReactiveActionSchema = z.discriminatedUnion("type", [
128
128
  columns: z.number().int().min(1).max(1_000),
129
129
  rows: z.number().int().min(1).max(1_000),
130
130
  }),
131
+ z.strictObject({ type: z.literal("close_stdin") }),
131
132
  z.strictObject({
132
133
  type: z.literal("send_signal"),
133
134
  target: signalTargetSchema,