rea-agents 2.0.0 → 2.2.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 (273) hide show
  1. package/README.md +103 -38
  2. package/bridge/ghidra/ReaGhidraBridge.java +96 -11
  3. package/dist/application/AndroidApplicationService.js +17 -0
  4. package/dist/application/AppleApplicationService.js +17 -0
  5. package/dist/application/ArtifactExtraction.js +1 -1
  6. package/dist/application/ArtifactInventory/classify.js +42 -0
  7. package/dist/application/ArtifactInventory/hash.js +43 -0
  8. package/dist/application/ArtifactInventory/reader.js +41 -0
  9. package/dist/application/ArtifactInventory/scanCanonical.js +155 -0
  10. package/dist/application/ArtifactInventory/scanReader.js +124 -0
  11. package/dist/application/ArtifactInventory/types.js +10 -0
  12. package/dist/application/ArtifactInventory.js +9 -351
  13. package/dist/application/AuthorizedArtifactInventory.js +2 -2
  14. package/dist/application/BinarySession.js +47 -270
  15. package/dist/application/BinarySessionExecution.js +28 -0
  16. package/dist/application/BinarySessionOpen.js +39 -0
  17. package/dist/application/BinarySessionRecords.js +149 -0
  18. package/dist/application/BinarySessionStatus.js +75 -0
  19. package/dist/application/CapabilityInventory.js +24 -2
  20. package/dist/application/ClientRegistrationStatus.js +11 -4
  21. package/dist/application/CrossVersionInventory.js +8 -2
  22. package/dist/application/CrossVersionInvestigation.js +1 -23
  23. package/dist/application/CrossVersionInvestigationCollections.js +23 -0
  24. package/dist/application/DirectAnalysis.js +47 -44
  25. package/dist/application/Doctor.js +156 -238
  26. package/dist/application/DoctorDiagnostics.js +180 -0
  27. package/dist/application/ElectronBoundaryAnalysis.js +23 -7
  28. package/dist/application/ElectronBoundaryGraphWindows.js +32 -8
  29. package/dist/application/EnhancedToolPagination.js +22 -0
  30. package/dist/application/EnhancedToolTypes.js +4 -0
  31. package/dist/application/EnhancedTools.js +20 -37
  32. package/dist/application/InstrumentedJavaScriptReplayHost.js +36 -0
  33. package/dist/application/InventoryProjectionEvidence.js +41 -0
  34. package/dist/application/InvestigationProviders.js +12 -0
  35. package/dist/application/InvestigationWorkspaceStore.js +24 -10
  36. package/dist/application/JavaScriptArtifactAnalysis.js +45 -6
  37. package/dist/application/JavaScriptArtifactFiles.js +6 -2
  38. package/dist/application/JavaScriptArtifactGraphBuilder.js +23 -1
  39. package/dist/application/JavaScriptArtifactGraphBundlers.js +225 -0
  40. package/dist/application/JavaScriptArtifactGraphContext.js +37 -36
  41. package/dist/application/JavaScriptArtifactGraphDocuments.js +27 -4
  42. package/dist/application/JavaScriptArtifactGraphFindings.js +54 -8
  43. package/dist/application/JavaScriptArtifactGraphStructure.js +29 -101
  44. package/dist/application/JavaScriptArtifactPathResolution.js +269 -0
  45. package/dist/application/JavaScriptArtifactReconstruction.js +1 -1
  46. package/dist/application/JavaScriptJsonModules.js +39 -0
  47. package/dist/application/JavaScriptModuleRelationships.js +262 -0
  48. package/dist/application/JavaScriptReplayPermission.js +16 -0
  49. package/dist/application/JavaScriptReplayService.js +182 -156
  50. package/dist/application/LinuxHopper.js +10 -0
  51. package/dist/application/MacHopper.js +22 -11
  52. package/dist/application/NodeRuntimeCharacterizationService.js +196 -0
  53. package/dist/application/PermissionAuthority.js +106 -63
  54. package/dist/application/ProcessCaptureCapability.js +1 -1
  55. package/dist/application/ProcessCapturePermission.js +13 -0
  56. package/dist/application/ProcessCli.js +2 -12
  57. package/dist/application/ProcessHarness.js +1 -1
  58. package/dist/application/ProcessPairedExperiment.js +72 -0
  59. package/dist/application/ProjectPermissionStore.js +48 -9
  60. package/dist/application/RealHopperAssertions.js +76 -0
  61. package/dist/application/ReconstructionCoverageService.js +69 -0
  62. package/dist/application/ReconstructionCoverageWorkspaceStore.js +31 -0
  63. package/dist/application/RuntimeExecutableDiagnostics.js +172 -0
  64. package/dist/application/Setup.js +148 -260
  65. package/dist/application/SetupClientConfiguration.js +224 -0
  66. package/dist/application/SetupClients.js +24 -2
  67. package/dist/application/SetupPlan.js +124 -12
  68. package/dist/application/SetupSkill.js +54 -36
  69. package/dist/application/SupportedClients.js +1 -0
  70. package/dist/application/Uninstall.js +12 -3
  71. package/dist/application/Upgrade.js +16 -1
  72. package/dist/application/WindowsCapabilities.js +86 -0
  73. package/dist/artifacts/AsarArtifactReader.js +11 -0
  74. package/dist/browser/BrowserProcessStartup.js +88 -0
  75. package/dist/browser/CdpCaptureDocuments.js +12 -8
  76. package/dist/browser/CdpCaptureEventBodyShapes.js +75 -0
  77. package/dist/browser/CdpCaptureEventHandlers.js +336 -0
  78. package/dist/browser/CdpCaptureEventHelpers.js +71 -0
  79. package/dist/browser/CdpCaptureEventState.js +1 -0
  80. package/dist/browser/CdpCaptureEventTypes.js +1 -0
  81. package/dist/browser/CdpCaptureEvents.js +29 -496
  82. package/dist/browser/CdpObservationSession.js +51 -8
  83. package/dist/browser/CdpPageCapture.js +12 -133
  84. package/dist/browser/CdpPageCaptureScripts.js +97 -0
  85. package/dist/browser/CdpPageCaptureWorkers.js +46 -0
  86. package/dist/browser/CdpWebMcpDiscovery.js +38 -14
  87. package/dist/browser/PngVisualDiff.js +9 -2
  88. package/dist/catalogIdentity.js +4 -0
  89. package/dist/cli/artifactCommands.js +82 -0
  90. package/dist/cli/coreAnalysisCommands.js +232 -0
  91. package/dist/cli/managedCommands.js +397 -0
  92. package/dist/cli/managedProjectGraphCommand.js +24 -0
  93. package/dist/cli/options.js +9 -0
  94. package/dist/cli/setupCommands.js +113 -0
  95. package/dist/cli/types.js +1 -0
  96. package/dist/cli/utilityCommands.js +122 -0
  97. package/dist/cli.js +10 -768
  98. package/dist/cliApplicationCommands.js +118 -4
  99. package/dist/cliBrowserAdvancedCommands.js +73 -16
  100. package/dist/cliBrowserCommands.js +39 -115
  101. package/dist/cliCommandNames.js +4 -0
  102. package/dist/cliElectronCommands.js +33 -62
  103. package/dist/cliEvidenceCommands.js +13 -2
  104. package/dist/cliInvestigationCommands.js +53 -10
  105. package/dist/cliObservationOptions.js +137 -0
  106. package/dist/cliPolicyCommands.js +80 -46
  107. package/dist/cliSetup.js +306 -0
  108. package/dist/config/environment.js +107 -0
  109. package/dist/config/parseConfig.js +228 -0
  110. package/dist/config/parsers.js +68 -0
  111. package/dist/config/permissions.js +30 -0
  112. package/dist/config/types.js +1 -0
  113. package/dist/config.js +1 -375
  114. package/dist/contracts/applicationToolContracts.js +134 -0
  115. package/dist/contracts/electronToolContracts.js +51 -3
  116. package/dist/contracts/managedToolContracts.js +8 -3
  117. package/dist/contracts/managedWorkflowToolContracts.js +3 -3
  118. package/dist/contracts/providerSelection.js +2 -0
  119. package/dist/contracts/sessionStatusContract.js +26 -0
  120. package/dist/contracts/toolContractTypes.js +12 -1
  121. package/dist/contracts/toolContracts.js +5 -12
  122. package/dist/contracts/toolEffects.js +14 -0
  123. package/dist/contracts/toolOutputSchemaGroups.js +274 -0
  124. package/dist/contracts/toolOutputSchemaPrimitives.js +328 -0
  125. package/dist/contracts/toolOutputSchemas.js +2 -550
  126. package/dist/domain/analysisErrorPresentation.js +234 -0
  127. package/dist/domain/analysisErrorProjection.js +284 -0
  128. package/dist/domain/androidApplication.js +206 -0
  129. package/dist/domain/appleApplication.js +221 -0
  130. package/dist/domain/artifactComparison.js +34 -15
  131. package/dist/domain/binaryTarget.js +87 -37
  132. package/dist/domain/boundedCartesianProjection.js +15 -0
  133. package/dist/domain/browserObservation.js +1 -285
  134. package/dist/domain/browserObservationSchemas.js +286 -0
  135. package/dist/domain/electronStaticAnalysisBrowser.js +14 -2
  136. package/dist/domain/errors.js +4 -486
  137. package/dist/domain/functionComparison.js +4 -165
  138. package/dist/domain/functionComparisonDimensions.js +164 -0
  139. package/dist/domain/javascriptApplicationVersionItems.js +18 -6
  140. package/dist/domain/javascriptApplicationVersionKeys.js +8 -8
  141. package/dist/domain/javascriptExportInstrumentation.js +97 -0
  142. package/dist/domain/javascriptFeatureTraversal.js +19 -7
  143. package/dist/domain/javascriptRuntimeStaticCandidates.js +3 -1
  144. package/dist/domain/javascriptSemanticAnalysis.js +386 -0
  145. package/dist/domain/javascriptSemanticIr.js +30 -0
  146. package/dist/domain/javascriptSemanticProjection.js +366 -0
  147. package/dist/domain/javascriptSemanticState.js +40 -0
  148. package/dist/domain/javascriptSemanticTraversal.js +22 -0
  149. package/dist/domain/javascriptSemanticValues.js +352 -0
  150. package/dist/domain/javascriptStaticAnalysis.js +5 -221
  151. package/dist/domain/javascriptStaticAnalysisBundler.js +209 -0
  152. package/dist/domain/javascriptStaticAnalysisCalls.js +180 -0
  153. package/dist/domain/javascriptStaticAnalysisHelpers.js +71 -2
  154. package/dist/domain/knownPageCoverage.js +21 -0
  155. package/dist/domain/managedApplicationGraph.js +14 -364
  156. package/dist/domain/managedApplicationGraphCoverage.js +170 -0
  157. package/dist/domain/managedApplicationGraphNodeCoverage.js +6 -0
  158. package/dist/domain/managedApplicationGraphNodes.js +336 -0
  159. package/dist/domain/managedArtifact.js +16 -8
  160. package/dist/domain/managedMemberComparison.js +37 -391
  161. package/dist/domain/managedMemberComparisonCoverage.js +72 -0
  162. package/dist/domain/managedMemberComparisonMatch.js +356 -0
  163. package/dist/domain/managedNativeVerification.js +27 -389
  164. package/dist/domain/managedNativeVerificationMatch.js +272 -0
  165. package/dist/domain/managedNativeVerificationSchemas.js +141 -0
  166. package/dist/domain/managedReconstruction.js +82 -69
  167. package/dist/domain/managedRuntimeCorrelation.js +12 -3
  168. package/dist/domain/nodeRuntimeCharacterization.js +66 -0
  169. package/dist/domain/permissionPolicy.js +2 -0
  170. package/dist/domain/processComparison.js +19 -0
  171. package/dist/domain/processPairedExperiment.js +128 -0
  172. package/dist/domain/reconstructionCoverage.js +387 -0
  173. package/dist/domain/reconstructionCoverageEvaluation.js +212 -0
  174. package/dist/domain/reconstructionVerificationSchemas.js +8 -0
  175. package/dist/domain/replayMachine.js +316 -0
  176. package/dist/domain/replayMachineRuntime.js +215 -0
  177. package/dist/domain/runtimeCharacterization.js +85 -0
  178. package/dist/domain/runtimeIdentification.js +210 -0
  179. package/dist/domain/webBundleAnalyzer.js +3 -409
  180. package/dist/domain/webBundleAnalyzerAst.js +77 -0
  181. package/dist/domain/webBundleAnalyzerInspection.js +307 -0
  182. package/dist/domain/webBundleAnalyzerResult.js +120 -0
  183. package/dist/dotnet/ManagedArtifactInspector.js +50 -29
  184. package/dist/dotnet/ManagedMemberInspector.js +51 -785
  185. package/dist/dotnet/ManagedMemberInspectorCore.js +297 -0
  186. package/dist/dotnet/ManagedMemberInstructionDecoder.js +427 -0
  187. package/dist/dotnet/ManagedMemberRows.js +120 -0
  188. package/dist/dotnet/ManagedMetadataInventory.js +42 -306
  189. package/dist/dotnet/ManagedMetadataInventoryRows.js +292 -0
  190. package/dist/dotnet/ManagedMetadataLayout.js +48 -111
  191. package/dist/dotnet/ManagedMetadataRowSizes.js +54 -0
  192. package/dist/dotnet/ManagedMethodBodyReader.js +143 -0
  193. package/dist/dotnet/ManagedNativeBoundaryHelpers.js +311 -0
  194. package/dist/dotnet/ManagedNativeBoundaryInspector.js +102 -350
  195. package/dist/dotnet/ManagedPeReader.js +59 -28
  196. package/dist/evaluation/CodexAgentEval.js +132 -0
  197. package/dist/generatedPackageMetadata.js +2 -2
  198. package/dist/ghidra/GhidraClient.js +76 -91
  199. package/dist/ghidra/GhidraClientStartup.js +29 -0
  200. package/dist/ghidra/GhidraClientWire.js +34 -0
  201. package/dist/ghidra/GhidraDefaults.js +1 -1
  202. package/dist/ghidra/GhidraDiagnostics.js +6 -1
  203. package/dist/ghidra/GhidraInstallation.js +20 -15
  204. package/dist/ghidra/GhidraLauncher.js +93 -29
  205. package/dist/ghidra/GhidraProvider.js +61 -161
  206. package/dist/ghidra/GhidraProviderCapabilities.js +173 -0
  207. package/dist/ghidra/GhidraSessionValues.js +2 -0
  208. package/dist/ghidra/GhidraSocketConnection.js +44 -7
  209. package/dist/ghidra/GhidraTargetSnapshot.js +30 -0
  210. package/dist/ghidra/GhidraTransport.js +71 -0
  211. package/dist/identity.js +4 -2
  212. package/dist/main/messages.js +5 -0
  213. package/dist/main/reload.js +49 -0
  214. package/dist/main/shutdown.js +25 -0
  215. package/dist/main/startup.js +14 -0
  216. package/dist/main/state.js +26 -0
  217. package/dist/main/transport.js +61 -0
  218. package/dist/main/types.js +1 -0
  219. package/dist/main.js +35 -146
  220. package/dist/process/PrivateRuntimeRoot.js +3 -2
  221. package/dist/process/ProcessOwnership.js +35 -0
  222. package/dist/process/ProviderProcess.js +2 -0
  223. package/dist/replay/JavaScriptReplayWorker.js +64 -50
  224. package/dist/replay/LinuxJavaScriptReplayRunner.js +173 -390
  225. package/dist/replay/LinuxJavaScriptReplaySandbox.js +140 -0
  226. package/dist/replay/ReplayOutcome.js +95 -0
  227. package/dist/replay/ReplayProcessLifecycle.js +71 -0
  228. package/dist/server/ProcessCaptureElicitation.js +157 -0
  229. package/dist/server/createServer.js +124 -56
  230. package/dist/server/javascriptApplicationResult.js +85 -0
  231. package/dist/server/registerApplicationTools/characterization.js +57 -0
  232. package/dist/server/registerApplicationTools/compareVersions.js +34 -0
  233. package/dist/server/registerApplicationTools/controlledReplay.js +38 -0
  234. package/dist/server/registerApplicationTools/coverage.js +84 -0
  235. package/dist/server/registerApplicationTools/helpers.js +42 -0
  236. package/dist/server/registerApplicationTools/traceFeature.js +26 -0
  237. package/dist/server/registerApplicationTools/types.js +1 -0
  238. package/dist/server/registerApplicationTools.js +10 -112
  239. package/dist/server/registerBrowserTools.js +59 -83
  240. package/dist/server/registerElectronTools.js +48 -49
  241. package/dist/server/registerEnhancedTools.js +34 -42
  242. package/dist/server/registerEvidenceResources.js +56 -0
  243. package/dist/server/registerInvestigationTools/helpers.js +74 -0
  244. package/dist/server/registerInvestigationTools/types.js +1 -0
  245. package/dist/server/registerInvestigationTools.js +83 -116
  246. package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
  247. package/dist/server/registerManagedTools.js +18 -1
  248. package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +65 -0
  249. package/dist/server/registerManagedWorkflowTools/contract.js +8 -0
  250. package/dist/server/registerManagedWorkflowTools/evidence.js +43 -0
  251. package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +39 -0
  252. package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +39 -0
  253. package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +52 -0
  254. package/dist/server/registerManagedWorkflowTools/types.js +1 -0
  255. package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +75 -0
  256. package/dist/server/registerManagedWorkflowTools.js +10 -274
  257. package/dist/server/registerOfficialTools.js +73 -59
  258. package/dist/server/registerSessionRecordTools.js +137 -0
  259. package/dist/server/registerSessionStatusTool.js +97 -26
  260. package/dist/server/registerSessionTools.js +60 -164
  261. package/dist/server/sessionAvailabilityPolicy.js +10 -0
  262. package/dist/server/toolRegistrationOptions.js +101 -3
  263. package/dist/server/toolResult.js +15 -4
  264. package/package.json +11 -11
  265. package/skills/reverse-engineer-anything/SKILL.md +76 -0
  266. package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
  267. package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
  268. package/skills/reverse-engineer-anything/references/javascript-applications.md +25 -0
  269. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
  270. package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
  271. package/scripts/prepare-node-pty.mjs +0 -35
  272. package/scripts/rebuild-native.mjs +0 -22
  273. package/skills/rea-analysis/SKILL.md +0 -222
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
- import { lstat, mkdir, open, readFile, realpath, stat, unlink, } from "node:fs/promises";
2
+ import { constants } from "node:fs";
3
+ import { lstat, mkdir, open, readFile, realpath, unlink, } from "node:fs/promises";
3
4
  import { dirname } from "node:path";
4
5
  import { setTimeout as delay } from "node:timers/promises";
5
6
  import writeFileAtomic from "write-file-atomic";
@@ -22,6 +23,7 @@ const grantSchema = z.object({
22
23
  "native_mount",
23
24
  "reference_read",
24
25
  "javascript_replay",
26
+ "managed_runtime",
25
27
  ]),
26
28
  roots: z.array(z.string()),
27
29
  executables: z.array(z.string()),
@@ -66,11 +68,17 @@ export const readProjectPermissionStore = async (path, projectRoot) => {
66
68
  const project = await identifyPermissionProject(projectRoot);
67
69
  if (!project.ok)
68
70
  return project;
71
+ if (process.getuid === undefined)
72
+ return err(new ProjectPermissionStoreError("not_owner_only"));
73
+ let handle;
69
74
  try {
70
- const metadata = await stat(path);
71
- if ((metadata.mode & 0o077) !== 0)
72
- return err(new ProjectPermissionStoreError("not_owner_only"));
73
- const encoded = await readFile(path, "utf8");
75
+ const opened = await openPrivateStore(path);
76
+ if (!opened.ok)
77
+ return opened;
78
+ if (opened.value === null)
79
+ return ok(null);
80
+ handle = opened.value;
81
+ const encoded = await handle.readFile("utf8");
74
82
  let decoded;
75
83
  try {
76
84
  decoded = JSON.parse(encoded);
@@ -90,6 +98,9 @@ export const readProjectPermissionStore = async (path, projectRoot) => {
90
98
  return ok(null);
91
99
  return err(new ProjectPermissionStoreError("io", { cause }));
92
100
  }
101
+ finally {
102
+ await handle?.close().catch(() => undefined);
103
+ }
93
104
  };
94
105
  /** Atomically replace explicit project grants with owner-only permissions. */
95
106
  export const writeProjectPermissionStore = async (path, projectRoot, grants) => {
@@ -104,16 +115,20 @@ export const writeProjectPermissionStore = async (path, projectRoot, grants) =>
104
115
  });
105
116
  if (!candidate.success)
106
117
  return err(new ProjectPermissionStoreError("invalid", { cause: candidate.error }));
118
+ if (process.getuid === undefined)
119
+ return err(new ProjectPermissionStoreError("not_owner_only"));
107
120
  try {
108
121
  await mkdir(dirname(path), { recursive: true, mode: 0o700 });
109
122
  await writeFileAtomic(path, `${JSON.stringify(candidate.data, null, 2)}\n`, {
110
123
  encoding: "utf8",
111
124
  mode: 0o600,
112
125
  });
113
- const metadata = await stat(path);
114
- if ((metadata.mode & 0o077) !== 0) {
115
- return err(new ProjectPermissionStoreError("not_owner_only"));
116
- }
126
+ const verified = await readProjectPermissionStore(path, projectRoot);
127
+ if (!verified.ok)
128
+ return verified;
129
+ if (verified.value === null ||
130
+ JSON.stringify(verified.value) !== JSON.stringify(candidate.data))
131
+ return err(new ProjectPermissionStoreError("invalid"));
117
132
  return ok(candidate.data);
118
133
  }
119
134
  catch (cause) {
@@ -209,7 +224,31 @@ const releasePermissionStoreLock = async (lock) => {
209
224
  await unlink(lock.path).catch(() => undefined);
210
225
  };
211
226
  const isNotFound = (cause) => errorCode(cause) === "ENOENT";
227
+ const openPrivateStore = async (path) => {
228
+ let handle;
229
+ try {
230
+ handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
231
+ const metadata = await handle.stat();
232
+ if (!privateRegularFile(metadata)) {
233
+ await handle.close();
234
+ return err(new ProjectPermissionStoreError("not_owner_only"));
235
+ }
236
+ return ok(handle);
237
+ }
238
+ catch (cause) {
239
+ await handle?.close().catch(() => undefined);
240
+ if (isNotFound(cause))
241
+ return ok(null);
242
+ return err(new ProjectPermissionStoreError(isSymlinkRefusal(cause) ? "not_owner_only" : "io", { cause }));
243
+ }
244
+ };
245
+ const privateRegularFile = (metadata) => metadata.isFile() &&
246
+ !metadata.isSymbolicLink() &&
247
+ (Number(metadata.mode) & 0o077) === 0 &&
248
+ process.getuid !== undefined &&
249
+ metadata.uid === process.getuid();
212
250
  const isAlreadyExists = (cause) => errorCode(cause) === "EEXIST";
213
251
  const errorCode = (cause) => typeof cause === "object" && cause !== null && "code" in cause
214
252
  ? cause.code
215
253
  : undefined;
254
+ const isSymlinkRefusal = (cause) => errorCode(cause) === "ELOOP" || errorCode(cause) === "EMLINK";
@@ -61,6 +61,81 @@ export const requireFunctionDossier = (input, expectedAddress) => {
61
61
  validateInstructionScan(value.instruction_scan);
62
62
  return value;
63
63
  };
64
+ /** Reject a structurally valid dossier that omits fixture-proven semantics. */
65
+ export const requireFunctionDossierOracle = (input, oracle) => {
66
+ const value = requireFunctionDossier(input, oracle.procedure_address);
67
+ if (oracle.callee_address !== undefined) {
68
+ const callees = z
69
+ .array(identitySchema)
70
+ .parse(collectionSchema.parse(value.callees).items);
71
+ if (!callees.some(({ address }) => address === oracle.callee_address))
72
+ throw new TypeError("analyze_function omitted the expected fixture callee");
73
+ const references = z
74
+ .array(z.object({
75
+ source_address: addressSchema,
76
+ target_address: addressSchema,
77
+ }))
78
+ .parse(collectionSchema.parse(value.outgoing_references).items);
79
+ if (!references.some(({ target_address }) => target_address === oracle.callee_address))
80
+ throw new TypeError("analyze_function omitted the expected fixture reference");
81
+ }
82
+ if (oracle.caller_address !== undefined) {
83
+ const callers = z
84
+ .array(identitySchema)
85
+ .parse(collectionSchema.parse(value.callers).items);
86
+ if (!callers.some(({ address }) => address === oracle.caller_address))
87
+ throw new TypeError("analyze_function omitted the expected fixture caller");
88
+ }
89
+ if (oracle.referenced_string !== undefined)
90
+ requireReferencedValue(value, "referenced_strings", oracle.referenced_string, (candidate) => candidate);
91
+ if (oracle.referenced_name !== undefined)
92
+ requireReferencedValue(value, "referenced_names", oracle.referenced_name, (candidate) => candidate.replace(/^_+/u, ""));
93
+ if (oracle.comment !== undefined) {
94
+ const comments = z
95
+ .array(z.object({
96
+ address: addressSchema,
97
+ kind: z.enum(["comment", "inline"]),
98
+ text: z.string(),
99
+ }))
100
+ .parse(collectionSchema.parse(value.comments).items);
101
+ if (!comments.some(({ address, kind, text }) => address === oracle.procedure_address &&
102
+ kind === "comment" &&
103
+ text === oracle.comment))
104
+ throw new TypeError("analyze_function omitted the verifier comment");
105
+ }
106
+ if (oracle.require_assembly === true &&
107
+ collectionSchema.parse(value.assembly).items.length === 0)
108
+ throw new TypeError("analyze_function omitted fixture assembly");
109
+ if (oracle.require_cfg_successor) {
110
+ const blocks = z
111
+ .array(z.object({ successors: z.array(addressSchema) }))
112
+ .parse(collectionSchema.parse(value.basic_blocks).items);
113
+ if (!blocks.some(({ successors }) => successors.length > 0))
114
+ throw new TypeError("analyze_function omitted a real CFG successor");
115
+ }
116
+ return value;
117
+ };
118
+ const referencedValueSchema = z.object({
119
+ address: addressSchema,
120
+ value: z.string(),
121
+ source_address: addressSchema,
122
+ });
123
+ const referenceEdgeSchema = z.object({
124
+ source_address: addressSchema,
125
+ target_address: addressSchema,
126
+ });
127
+ const requireReferencedValue = (dossier, field, expected, normalize) => {
128
+ const values = z
129
+ .array(referencedValueSchema)
130
+ .parse(collectionSchema.parse(dossier[field]).items);
131
+ const references = z
132
+ .array(referenceEdgeSchema)
133
+ .parse(collectionSchema.parse(dossier.outgoing_references).items);
134
+ const found = values.some(({ address, value, source_address: sourceAddress }) => normalize(value) === expected &&
135
+ references.some(({ source_address, target_address }) => source_address === sourceAddress && target_address === address));
136
+ if (!found)
137
+ throw new TypeError(`analyze_function omitted the expected fixture ${field === "referenced_strings" ? "string" : "name"}`);
138
+ };
64
139
  /** Require every relationship to be an actual hexadecimal address. */
65
140
  export const requireAddressArray = (input, operation) => {
66
141
  const parsed = z.array(addressSchema).parse(input);
@@ -69,6 +144,7 @@ export const requireAddressArray = (input, operation) => {
69
144
  return parsed;
70
145
  };
71
146
  const COLLECTION_FIELDS = [
147
+ "assembly",
72
148
  "comments",
73
149
  "callers",
74
150
  "callees",
@@ -0,0 +1,69 @@
1
+ import { z } from "zod";
2
+ import { AnalysisCancelledError, AnalysisInputError, } from "../domain/errors.js";
3
+ import { jsonValueSchema } from "../domain/jsonValue.js";
4
+ import { evaluateReconstructionClosure, reconstructionCoverageWorkspaceSchema, } from "../domain/reconstructionCoverage.js";
5
+ import { err, ok } from "../domain/result.js";
6
+ import { readReconstructionCoverageWorkspace, writeReconstructionCoverageWorkspace, } from "./ReconstructionCoverageWorkspaceStore.js";
7
+ const COMMIT_OPERATION = "commit_reconstruction_coverage";
8
+ const QUERY_OPERATION = "query_reconstruction_coverage";
9
+ const isAborted = (signal) => signal?.aborted === true;
10
+ export const reconstructionCoverageCommitInputSchema = z.strictObject({
11
+ approved: z.literal(true),
12
+ workspace_path: z.string().min(1).max(4_096),
13
+ expected_revision: z.number().int().min(1).nullable(),
14
+ workspace: reconstructionCoverageWorkspaceSchema,
15
+ });
16
+ export const reconstructionCoverageQueryInputSchema = z.strictObject({
17
+ workspace_path: z.string().min(1).max(4_096),
18
+ boundary_id: z.string().min(1).max(200),
19
+ });
20
+ export const reconstructionCoverageCommitOutputSchema = z.strictObject({
21
+ workspace_path: z.string(),
22
+ bytes: z.number().int().min(0),
23
+ workspace_id: z.string().regex(/^rcw_[a-f0-9]{64}$/u),
24
+ revision: z.number().int().min(1),
25
+ revision_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
26
+ evidence_records: z.number().int().min(0),
27
+ });
28
+ /** Commit one caller-approved canonical reconstruction coverage CAS revision. */
29
+ export const commitReconstructionCoverage = async (rawInput, policy, options = {}) => {
30
+ const parsed = reconstructionCoverageCommitInputSchema.safeParse(rawInput);
31
+ if (!parsed.success)
32
+ return err(new AnalysisInputError(COMMIT_OPERATION, { cause: parsed.error }));
33
+ if (isAborted(options.signal))
34
+ return err(new AnalysisCancelledError(COMMIT_OPERATION));
35
+ const written = await writeReconstructionCoverageWorkspace(parsed.data.workspace, parsed.data.workspace_path, parsed.data.expected_revision, policy);
36
+ if (!written.ok)
37
+ return written;
38
+ return ok(jsonValueSchema.parse({
39
+ workspace_path: written.value.path,
40
+ bytes: written.value.bytes,
41
+ workspace_id: parsed.data.workspace.workspace_id,
42
+ revision: parsed.data.workspace.revision,
43
+ revision_sha256: parsed.data.workspace.revision_sha256,
44
+ evidence_records: parsed.data.workspace.evidence_bundle.records.length,
45
+ }));
46
+ };
47
+ /** Read one coverage revision and evaluate a named fail-closed boundary. */
48
+ export const queryReconstructionCoverage = async (rawInput, policy, nowEpochMs, options = {}) => {
49
+ const parsed = reconstructionCoverageQueryInputSchema.safeParse(rawInput);
50
+ if (!parsed.success)
51
+ return err(new AnalysisInputError(QUERY_OPERATION, { cause: parsed.error }));
52
+ if (isAborted(options.signal))
53
+ return err(new AnalysisCancelledError(QUERY_OPERATION));
54
+ const loaded = await readReconstructionCoverageWorkspace(parsed.data.workspace_path, policy);
55
+ if (!loaded.ok)
56
+ return loaded;
57
+ if (loaded.value === null)
58
+ return err(new AnalysisInputError(QUERY_OPERATION, {
59
+ cause: new TypeError("Reconstruction coverage workspace does not exist"),
60
+ }));
61
+ if (isAborted(options.signal))
62
+ return err(new AnalysisCancelledError(QUERY_OPERATION));
63
+ try {
64
+ return ok(jsonValueSchema.parse(evaluateReconstructionClosure(loaded.value, parsed.data.boundary_id, nowEpochMs)));
65
+ }
66
+ catch (cause) {
67
+ return err(new AnalysisInputError(QUERY_OPERATION, { cause }));
68
+ }
69
+ };
@@ -0,0 +1,31 @@
1
+ import { InvestigationWorkspaceError } from "../domain/errors.js";
2
+ import { parseReconstructionCoverageWorkspace, serializeReconstructionCoverageWorkspace, } from "../domain/reconstructionCoverage.js";
3
+ import { err, ok } from "../domain/result.js";
4
+ import { readRevisionedWorkspace, writeRevisionedWorkspace, } from "./InvestigationWorkspaceStore.js";
5
+ const codec = {
6
+ parse: parseReconstructionCoverageWorkspace,
7
+ serialize: serializeReconstructionCoverageWorkspace,
8
+ validateNext: (current, next, expectedRevision) => {
9
+ if ((current?.revision ?? null) !== expectedRevision)
10
+ return err(new InvestigationWorkspaceError("update", "revision-conflict"));
11
+ if (current === null)
12
+ return next.revision === 1 && next.previous_revision_sha256 === null
13
+ ? ok(null)
14
+ : err(new InvestigationWorkspaceError("update", "revision-conflict"));
15
+ if (current.name !== next.name ||
16
+ current.workspace_id !== next.workspace_id)
17
+ return err(new InvestigationWorkspaceError("update", "name-conflict"));
18
+ return next.revision === current.revision + 1 &&
19
+ next.previous_revision_sha256 === current.revision_sha256
20
+ ? ok(null)
21
+ : err(new InvestigationWorkspaceError("update", "revision-conflict"));
22
+ },
23
+ };
24
+ /** Read a validated reconstruction coverage workspace revision. */
25
+ export const readReconstructionCoverageWorkspace = (path, policy) => readRevisionedWorkspace(path, policy, codec);
26
+ /** Atomically append one reconstruction coverage workspace CAS revision. */
27
+ export const writeReconstructionCoverageWorkspace = (workspace, path, expectedRevision, policy) => writeRevisionedWorkspace(path, policy, {
28
+ document: workspace,
29
+ expectedRevision,
30
+ codec,
31
+ });
@@ -0,0 +1,172 @@
1
+ import { execFile } from "node:child_process";
2
+ import { constants } from "node:fs";
3
+ import { access, realpath } from "node:fs/promises";
4
+ import { delimiter, join } from "node:path";
5
+ const TOOL_NAMES = ["node", "npm", "npx"];
6
+ const MAX_CANDIDATES_PER_TOOL = 16;
7
+ const MAX_OUTPUT_BYTES = 65_536;
8
+ const MAX_DIAGNOSTIC_BYTES = 4_096;
9
+ const PROBE_CONCURRENCY = 4;
10
+ /** Inventory and bounded-probe Node toolchain candidates under one exact PATH. */
11
+ export const inspectRuntimeExecutables = async (options) => {
12
+ const pathEntries = unique(options.path.split(delimiter).filter(Boolean));
13
+ const candidates = [
14
+ {
15
+ tool: "node",
16
+ path: options.launcherNode,
17
+ pathIndex: null,
18
+ selection: "rea-launcher",
19
+ },
20
+ ];
21
+ for (const tool of TOOL_NAMES) {
22
+ const discovered = await discoverToolCandidates(tool, pathEntries, options.platform, options.pathExtensions);
23
+ for (const [index, candidate] of discovered.entries())
24
+ candidates.push({
25
+ tool,
26
+ path: candidate.path,
27
+ pathIndex: candidate.pathIndex,
28
+ selection: index === 0 ? "path-primary" : "path-shadowed",
29
+ });
30
+ }
31
+ const seen = new Set();
32
+ const uniqueCandidates = [];
33
+ for (const candidate of candidates) {
34
+ const key = `${candidate.tool}\0${candidate.path}`;
35
+ if (seen.has(key))
36
+ continue;
37
+ seen.add(key);
38
+ uniqueCandidates.push(candidate);
39
+ }
40
+ const diagnostics = await mapConcurrentBounded(uniqueCandidates, PROBE_CONCURRENCY, (candidate) => probeCandidate(candidate, options.timeoutMs ?? 5_000, options.path));
41
+ return {
42
+ launcher_node: await canonicalPath(options.launcherNode),
43
+ candidates: diagnostics,
44
+ };
45
+ };
46
+ const discoverToolCandidates = async (tool, pathEntries, platform, configuredExtensions) => {
47
+ const extensions = platform === "win32"
48
+ ? (configuredExtensions ?? [".COM", ".EXE", ".BAT", ".CMD"])
49
+ : [""];
50
+ const candidates = [];
51
+ for (const [pathIndex, directory] of pathEntries.entries()) {
52
+ for (const extension of extensions) {
53
+ const path = join(directory, `${tool}${extension}`);
54
+ if (await isExecutable(path, platform)) {
55
+ candidates.push({ path, pathIndex });
56
+ break;
57
+ }
58
+ }
59
+ if (candidates.length >= MAX_CANDIDATES_PER_TOOL)
60
+ break;
61
+ }
62
+ return candidates;
63
+ };
64
+ const probeCandidate = async (candidate, timeoutMs, effectivePath) => {
65
+ const canonical = await canonicalPath(candidate.path);
66
+ const result = await executeVersion(candidate.path, timeoutMs, effectivePath);
67
+ return {
68
+ tool: candidate.tool,
69
+ lexical_path: candidate.path,
70
+ canonical_path: canonical,
71
+ path_index: candidate.pathIndex,
72
+ selection: candidate.selection,
73
+ version: result.ok ? result.version : null,
74
+ healthy: result.ok,
75
+ failure: result.ok ? null : result.failure,
76
+ };
77
+ };
78
+ const executeVersion = (path, timeoutMs, effectivePath) => new Promise((resolve) => {
79
+ const child = execFile(path, ["--version"], {
80
+ encoding: "utf8",
81
+ timeout: timeoutMs,
82
+ maxBuffer: MAX_OUTPUT_BYTES,
83
+ windowsHide: true,
84
+ env: environmentWithPath(effectivePath),
85
+ }, (cause, stdout, stderr) => {
86
+ if (cause === null) {
87
+ const version = firstNonemptyLine(stdout) ?? firstNonemptyLine(stderr);
88
+ if (version !== undefined)
89
+ resolve({ ok: true, version });
90
+ else
91
+ resolve({
92
+ ok: false,
93
+ failure: failure("runtime_invalid_version_output", child, stderr),
94
+ });
95
+ return;
96
+ }
97
+ resolve({
98
+ ok: false,
99
+ failure: classifyProbeFailure(cause, child, stderr),
100
+ });
101
+ });
102
+ });
103
+ const classifyProbeFailure = (cause, child, stderr) => {
104
+ const dependency = missingDynamicLibrary(stderr);
105
+ if (dependency !== null)
106
+ return failure("runtime_dynamic_library_missing", child, stderr, dependency);
107
+ if ("killed" in cause && cause.killed === true)
108
+ return failure("runtime_timeout", child, stderr);
109
+ if (child.signalCode !== null)
110
+ return failure("runtime_signal", child, stderr);
111
+ if ("code" in cause && typeof cause.code === "string")
112
+ return failure("runtime_spawn_failed", child, stderr);
113
+ return failure("runtime_nonzero_exit", child, stderr);
114
+ };
115
+ const failure = (code, child, stderr, dependency = null) => ({
116
+ code,
117
+ exit_code: child.exitCode,
118
+ signal: child.signalCode,
119
+ dependency,
120
+ stderr: bounded(stderr.trim()),
121
+ });
122
+ const missingDynamicLibrary = (stderr) => /(?:dyld(?:\[[^\]]+\])?: )?Library not loaded:\s*([^\r\n]+)/u
123
+ .exec(stderr)?.[1]
124
+ ?.trim() ?? null;
125
+ const isExecutable = async (path, platform) => {
126
+ try {
127
+ await access(path, platform === "win32" ? constants.F_OK : constants.X_OK);
128
+ return true;
129
+ }
130
+ catch {
131
+ return false;
132
+ }
133
+ };
134
+ const canonicalPath = async (path) => {
135
+ try {
136
+ return await realpath(path);
137
+ }
138
+ catch {
139
+ return path;
140
+ }
141
+ };
142
+ const firstNonemptyLine = (value) => value
143
+ .split(/\r?\n/u)
144
+ .find((line) => line.trim().length > 0)
145
+ ?.trim();
146
+ const bounded = (value) => value.length <= MAX_DIAGNOSTIC_BYTES
147
+ ? value
148
+ : value.slice(-MAX_DIAGNOSTIC_BYTES);
149
+ const unique = (values) => [...new Set(values)];
150
+ const environmentWithPath = (path) => {
151
+ const environment = {};
152
+ for (const [name, value] of Object.entries(process.env))
153
+ if (name.toUpperCase() !== "PATH")
154
+ environment[name] = value;
155
+ environment.PATH = path;
156
+ return environment;
157
+ };
158
+ const mapConcurrentBounded = async (inputs, concurrency, operation) => {
159
+ const outputs = [];
160
+ let nextIndex = 0;
161
+ const workers = Array.from({ length: Math.min(concurrency, inputs.length) }, async () => {
162
+ while (nextIndex < inputs.length) {
163
+ const index = nextIndex;
164
+ nextIndex += 1;
165
+ const input = inputs[index];
166
+ if (input !== undefined)
167
+ outputs[index] = await operation(input);
168
+ }
169
+ });
170
+ await Promise.all(workers);
171
+ return outputs;
172
+ };