rea-agents 1.7.0 → 2.1.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 (333) hide show
  1. package/README.md +112 -59
  2. package/bridge/ghidra/ReaGhidraBridge.java +96 -11
  3. package/dist/application/ArtifactExtraction.js +1 -1
  4. package/dist/application/ArtifactInventory/classify.js +42 -0
  5. package/dist/application/ArtifactInventory/hash.js +43 -0
  6. package/dist/application/ArtifactInventory/reader.js +41 -0
  7. package/dist/application/ArtifactInventory/scanCanonical.js +155 -0
  8. package/dist/application/ArtifactInventory/scanReader.js +124 -0
  9. package/dist/application/ArtifactInventory/types.js +10 -0
  10. package/dist/application/ArtifactInventory.js +9 -351
  11. package/dist/application/AuthorizedArtifactInventory.js +2 -2
  12. package/dist/application/BinarySession.js +47 -270
  13. package/dist/application/BinarySessionExecution.js +28 -0
  14. package/dist/application/BinarySessionOpen.js +39 -0
  15. package/dist/application/BinarySessionRecords.js +149 -0
  16. package/dist/application/BinarySessionStatus.js +75 -0
  17. package/dist/application/CapabilityInventory.js +42 -2
  18. package/dist/application/ClientRegistrationStatus.js +1 -1
  19. package/dist/application/CrossVersionInventory.js +8 -2
  20. package/dist/application/CrossVersionInvestigation.js +11 -29
  21. package/dist/application/CrossVersionInvestigationCollections.js +23 -0
  22. package/dist/application/DirectAnalysis.js +47 -44
  23. package/dist/application/Doctor.js +165 -197
  24. package/dist/application/DoctorDiagnostics.js +180 -0
  25. package/dist/application/ElectronBoundaryAnalysis.js +112 -0
  26. package/dist/application/ElectronBoundaryGraph.js +9 -0
  27. package/dist/application/ElectronBoundaryGraphContext.js +62 -0
  28. package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
  29. package/dist/application/ElectronBoundaryGraphNative.js +86 -0
  30. package/dist/application/ElectronBoundaryGraphWindows.js +223 -0
  31. package/dist/application/EnhancedToolPagination.js +22 -0
  32. package/dist/application/EnhancedToolTypes.js +4 -0
  33. package/dist/application/EnhancedTools.js +58 -50
  34. package/dist/application/InstrumentedJavaScriptReplayHost.js +36 -0
  35. package/dist/application/InvestigationProviders.js +30 -0
  36. package/dist/application/InvestigationWorkspaceStore.js +24 -10
  37. package/dist/application/JavaScriptApplicationEvidence.js +26 -0
  38. package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
  39. package/dist/application/JavaScriptApplicationService.js +74 -0
  40. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
  41. package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
  42. package/dist/application/JavaScriptArtifactAnalysis.js +52 -7
  43. package/dist/application/JavaScriptArtifactFiles.js +6 -2
  44. package/dist/application/JavaScriptArtifactGraphBuilder.js +82 -17
  45. package/dist/application/JavaScriptArtifactGraphBundlers.js +225 -0
  46. package/dist/application/JavaScriptArtifactGraphContext.js +37 -36
  47. package/dist/application/JavaScriptArtifactGraphDocuments.js +27 -4
  48. package/dist/application/JavaScriptArtifactGraphEvidence.js +24 -14
  49. package/dist/application/JavaScriptArtifactGraphFindings.js +54 -8
  50. package/dist/application/JavaScriptArtifactGraphStructure.js +29 -101
  51. package/dist/application/JavaScriptArtifactPathResolution.js +269 -0
  52. package/dist/application/JavaScriptArtifactReconstruction.js +4 -1
  53. package/dist/application/JavaScriptArtifactReconstructionInput.js +2 -83
  54. package/dist/application/JavaScriptJsonModules.js +39 -0
  55. package/dist/application/JavaScriptModuleRelationships.js +262 -0
  56. package/dist/application/JavaScriptReplayPermission.js +16 -0
  57. package/dist/application/JavaScriptReplayPlanning.js +201 -0
  58. package/dist/application/JavaScriptReplayService.js +292 -0
  59. package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
  60. package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
  61. package/dist/application/LinuxHopper.js +10 -0
  62. package/dist/application/MacHopper.js +22 -11
  63. package/dist/application/ManagedApplicationGraphService.js +55 -0
  64. package/dist/application/ManagedMemberComparisonService.js +122 -0
  65. package/dist/application/ManagedNativeVerificationService.js +39 -0
  66. package/dist/application/ManagedReconstructionService.js +58 -0
  67. package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
  68. package/dist/application/NodeRuntimeCharacterizationService.js +196 -0
  69. package/dist/application/PermissionAuthority.js +106 -63
  70. package/dist/application/ProcessCaptureCapability.js +1 -1
  71. package/dist/application/ProcessCapturePermission.js +13 -0
  72. package/dist/application/ProcessCli.js +2 -12
  73. package/dist/application/ProcessHarness.js +1 -1
  74. package/dist/application/ProcessSampling.js +15 -11
  75. package/dist/application/ProjectPermissionStore.js +49 -9
  76. package/dist/application/RealHopperAssertions.js +76 -0
  77. package/dist/application/ReconstructionCoverageService.js +69 -0
  78. package/dist/application/ReconstructionCoverageWorkspaceStore.js +31 -0
  79. package/dist/application/RuntimeExecutableDiagnostics.js +172 -0
  80. package/dist/application/Setup.js +147 -259
  81. package/dist/application/SetupClientConfiguration.js +220 -0
  82. package/dist/application/SetupClients.js +24 -2
  83. package/dist/application/SetupPlan.js +123 -12
  84. package/dist/application/SetupSkill.js +26 -12
  85. package/dist/application/SupportedClients.js +1 -0
  86. package/dist/application/Uninstall.js +6 -3
  87. package/dist/application/WindowsCapabilities.js +86 -0
  88. package/dist/application/runtime.js +2 -0
  89. package/dist/artifacts/AsarArtifactReader.js +11 -0
  90. package/dist/browser/BrowserProcessStartup.js +88 -0
  91. package/dist/browser/CdpCaptureDocuments.js +12 -8
  92. package/dist/browser/CdpCaptureEventBodyShapes.js +75 -0
  93. package/dist/browser/CdpCaptureEventHandlers.js +336 -0
  94. package/dist/browser/CdpCaptureEventHelpers.js +71 -0
  95. package/dist/browser/CdpCaptureEventState.js +1 -0
  96. package/dist/browser/CdpCaptureEventTypes.js +1 -0
  97. package/dist/browser/CdpCaptureEvents.js +46 -464
  98. package/dist/browser/CdpElectronInspection.js +106 -162
  99. package/dist/browser/CdpElectronProvider.js +5 -1
  100. package/dist/browser/CdpElectronScriptEvents.js +69 -0
  101. package/dist/browser/CdpElectronScripts.js +94 -0
  102. package/dist/browser/CdpElectronWorkers.js +56 -0
  103. package/dist/browser/CdpObservationSession.js +51 -8
  104. package/dist/browser/CdpPageCapture.js +12 -129
  105. package/dist/browser/CdpPageCaptureScripts.js +97 -0
  106. package/dist/browser/CdpPageCaptureWorkers.js +46 -0
  107. package/dist/browser/CdpWebMcpDiscovery.js +38 -14
  108. package/dist/browser/PngVisualDiff.js +9 -2
  109. package/dist/catalogIdentity.js +8 -1
  110. package/dist/cli/artifactCommands.js +82 -0
  111. package/dist/cli/coreAnalysisCommands.js +232 -0
  112. package/dist/cli/managedCommands.js +397 -0
  113. package/dist/cli/managedProjectGraphCommand.js +24 -0
  114. package/dist/cli/options.js +9 -0
  115. package/dist/cli/setupCommands.js +108 -0
  116. package/dist/cli/types.js +1 -0
  117. package/dist/cli/utilityCommands.js +122 -0
  118. package/dist/cli.js +13 -383
  119. package/dist/cliApplicationCommands.js +176 -0
  120. package/dist/cliBrowserAdvancedCommands.js +73 -16
  121. package/dist/cliBrowserCommands.js +39 -115
  122. package/dist/cliCommandNames.js +17 -0
  123. package/dist/cliElectronCommands.js +100 -27
  124. package/dist/cliEvidenceCommands.js +13 -2
  125. package/dist/cliInvestigationCommands.js +53 -10
  126. package/dist/cliJsonInput.js +54 -0
  127. package/dist/cliObservationOptions.js +137 -0
  128. package/dist/cliPolicyCommands.js +80 -46
  129. package/dist/cliSetup.js +289 -0
  130. package/dist/config/environment.js +107 -0
  131. package/dist/config/parseConfig.js +228 -0
  132. package/dist/config/parsers.js +68 -0
  133. package/dist/config/permissions.js +30 -0
  134. package/dist/config/types.js +1 -0
  135. package/dist/config.js +1 -292
  136. package/dist/contracts/applicationToolContracts.js +225 -0
  137. package/dist/contracts/artifactToolContracts.js +5 -9
  138. package/dist/contracts/browserToolContracts.js +14 -62
  139. package/dist/contracts/electronToolContracts.js +45 -19
  140. package/dist/contracts/errorSchemas.js +1 -0
  141. package/dist/contracts/investigationExamples.js +0 -6
  142. package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
  143. package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
  144. package/dist/contracts/managedToolContracts.js +153 -0
  145. package/dist/contracts/managedWorkflowExamples.js +346 -0
  146. package/dist/contracts/managedWorkflowToolContracts.js +203 -0
  147. package/dist/contracts/nativeToolContracts.js +2 -6
  148. package/dist/contracts/promptContracts.js +18 -2
  149. package/dist/contracts/providerSelection.js +2 -0
  150. package/dist/contracts/toolContractExamples.js +10 -25
  151. package/dist/contracts/toolContracts.js +49 -47
  152. package/dist/contracts/toolEffects.js +174 -0
  153. package/dist/contracts/toolOutputSchemaGroups.js +232 -0
  154. package/dist/contracts/toolOutputSchemaPrimitives.js +327 -0
  155. package/dist/contracts/toolOutputSchemas.js +2 -535
  156. package/dist/doctorRuntime.js +42 -1
  157. package/dist/domain/analysisErrorPresentation.js +234 -0
  158. package/dist/domain/analysisErrorProjection.js +284 -0
  159. package/dist/domain/artifactComparison.js +37 -19
  160. package/dist/domain/binaryTarget.js +87 -37
  161. package/dist/domain/browserObservation.js +1 -282
  162. package/dist/domain/browserObservationSchemas.js +286 -0
  163. package/dist/domain/bundleComparison.js +4 -4
  164. package/dist/domain/electronObservation.js +13 -0
  165. package/dist/domain/electronStaticAnalysis.js +9 -0
  166. package/dist/domain/electronStaticAnalysisBrowser.js +206 -0
  167. package/dist/domain/electronStaticAnalysisIpc.js +171 -0
  168. package/dist/domain/electronStaticAnalysisNative.js +164 -0
  169. package/dist/domain/electronStaticAnalysisTypes.js +1 -0
  170. package/dist/domain/electronStaticAnalysisValues.js +103 -0
  171. package/dist/domain/errors.js +32 -448
  172. package/dist/domain/functionComparison.js +4 -165
  173. package/dist/domain/functionComparisonDimensions.js +164 -0
  174. package/dist/domain/functionComparisonSchemas.js +3 -8
  175. package/dist/domain/inputIssueProjection.js +52 -0
  176. package/dist/domain/javascriptApplicationAnalysis.js +155 -0
  177. package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
  178. package/dist/domain/javascriptApplicationEvidenceSchemas.js +18 -0
  179. package/dist/domain/javascriptApplicationGraph.js +2 -1
  180. package/dist/domain/javascriptApplicationGraphSchemas.js +7 -0
  181. package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
  182. package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
  183. package/dist/domain/javascriptApplicationVersionItems.js +233 -0
  184. package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
  185. package/dist/domain/javascriptExportInstrumentation.js +97 -0
  186. package/dist/domain/javascriptFeatureSeed.js +140 -0
  187. package/dist/domain/javascriptFeatureTrace.js +232 -0
  188. package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
  189. package/dist/domain/javascriptFeatureTraversal.js +100 -0
  190. package/dist/domain/javascriptNativeHandoff.js +85 -0
  191. package/dist/domain/javascriptReplay.js +361 -0
  192. package/dist/domain/javascriptRuntimeLoadState.js +106 -0
  193. package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
  194. package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
  195. package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
  196. package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
  197. package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
  198. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
  199. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
  200. package/dist/domain/javascriptRuntimeStaticCandidates.js +193 -0
  201. package/dist/domain/javascriptSemanticAnalysis.js +386 -0
  202. package/dist/domain/javascriptSemanticIr.js +30 -0
  203. package/dist/domain/javascriptSemanticProjection.js +366 -0
  204. package/dist/domain/javascriptSemanticState.js +40 -0
  205. package/dist/domain/javascriptSemanticTraversal.js +22 -0
  206. package/dist/domain/javascriptSemanticValues.js +352 -0
  207. package/dist/domain/javascriptStaticAnalysis.js +26 -254
  208. package/dist/domain/javascriptStaticAnalysisBundler.js +209 -0
  209. package/dist/domain/javascriptStaticAnalysisCalls.js +180 -0
  210. package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
  211. package/dist/domain/javascriptStaticAnalysisHelpers.js +81 -2
  212. package/dist/domain/javascriptStaticAnalysisState.js +6 -0
  213. package/dist/domain/knownPageCoverage.js +21 -0
  214. package/dist/domain/managedApplicationGraph.js +206 -0
  215. package/dist/domain/managedApplicationGraphCoverage.js +170 -0
  216. package/dist/domain/managedApplicationGraphNodeCoverage.js +6 -0
  217. package/dist/domain/managedApplicationGraphNodes.js +336 -0
  218. package/dist/domain/managedArtifact.js +452 -0
  219. package/dist/domain/managedMemberComparison.js +211 -0
  220. package/dist/domain/managedMemberComparisonCoverage.js +72 -0
  221. package/dist/domain/managedMemberComparisonMatch.js +356 -0
  222. package/dist/domain/managedNativeVerification.js +46 -0
  223. package/dist/domain/managedNativeVerificationMatch.js +272 -0
  224. package/dist/domain/managedNativeVerificationSchemas.js +141 -0
  225. package/dist/domain/managedReconstruction.js +196 -0
  226. package/dist/domain/managedRuntimeCorrelation.js +184 -0
  227. package/dist/domain/nodeRuntimeCharacterization.js +66 -0
  228. package/dist/domain/permissionPolicy.js +2 -0
  229. package/dist/domain/reconstructionCoverage.js +387 -0
  230. package/dist/domain/reconstructionCoverageEvaluation.js +212 -0
  231. package/dist/domain/reconstructionVerificationSchemas.js +9 -3
  232. package/dist/domain/runtimeCharacterization.js +85 -0
  233. package/dist/domain/webBundleAnalyzer.js +3 -409
  234. package/dist/domain/webBundleAnalyzerAst.js +77 -0
  235. package/dist/domain/webBundleAnalyzerInspection.js +307 -0
  236. package/dist/domain/webBundleAnalyzerResult.js +120 -0
  237. package/dist/dotnet/ManagedArtifactInspector.js +339 -0
  238. package/dist/dotnet/ManagedMemberInspector.js +153 -0
  239. package/dist/dotnet/ManagedMemberInspectorCore.js +297 -0
  240. package/dist/dotnet/ManagedMemberInstructionDecoder.js +427 -0
  241. package/dist/dotnet/ManagedMemberRows.js +120 -0
  242. package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
  243. package/dist/dotnet/ManagedMetadataInventory.js +104 -0
  244. package/dist/dotnet/ManagedMetadataInventoryRows.js +292 -0
  245. package/dist/dotnet/ManagedMetadataLayout.js +259 -0
  246. package/dist/dotnet/ManagedMetadataRowSizes.js +54 -0
  247. package/dist/dotnet/ManagedMethodBodyReader.js +143 -0
  248. package/dist/dotnet/ManagedNativeBoundaryHelpers.js +311 -0
  249. package/dist/dotnet/ManagedNativeBoundaryInspector.js +143 -0
  250. package/dist/dotnet/ManagedPeReader.js +188 -0
  251. package/dist/dotnet/ManagedReaderFailure.js +10 -0
  252. package/dist/dotnet/ManagedStaticProvider.js +203 -0
  253. package/dist/generatedPackageMetadata.js +2 -2
  254. package/dist/ghidra/GhidraClient.js +76 -91
  255. package/dist/ghidra/GhidraClientStartup.js +29 -0
  256. package/dist/ghidra/GhidraClientWire.js +34 -0
  257. package/dist/ghidra/GhidraDefaults.js +1 -1
  258. package/dist/ghidra/GhidraDiagnostics.js +6 -1
  259. package/dist/ghidra/GhidraInstallation.js +20 -15
  260. package/dist/ghidra/GhidraLauncher.js +93 -29
  261. package/dist/ghidra/GhidraProvider.js +67 -161
  262. package/dist/ghidra/GhidraProviderCapabilities.js +173 -0
  263. package/dist/ghidra/GhidraSessionValues.js +2 -0
  264. package/dist/ghidra/GhidraSocketConnection.js +44 -7
  265. package/dist/ghidra/GhidraTargetSnapshot.js +30 -0
  266. package/dist/ghidra/GhidraTransport.js +71 -0
  267. package/dist/identity.js +3 -2
  268. package/dist/main/messages.js +5 -0
  269. package/dist/main/reload.js +49 -0
  270. package/dist/main/shutdown.js +25 -0
  271. package/dist/main/startup.js +14 -0
  272. package/dist/main/state.js +26 -0
  273. package/dist/main/transport.js +61 -0
  274. package/dist/main/types.js +1 -0
  275. package/dist/main.js +35 -132
  276. package/dist/process/PrivateRuntimeRoot.js +3 -2
  277. package/dist/process/ProcessOwnership.js +35 -0
  278. package/dist/process/ProviderProcess.js +2 -0
  279. package/dist/replay/JavaScriptReplayWorker.js +300 -0
  280. package/dist/replay/LinuxJavaScriptReplayRunner.js +217 -0
  281. package/dist/replay/LinuxJavaScriptReplaySandbox.js +140 -0
  282. package/dist/replay/LinuxRuntimeClosure.js +46 -0
  283. package/dist/replay/LinuxSeccompPolicy.js +40 -0
  284. package/dist/replay/ReplayOutcome.js +95 -0
  285. package/dist/replay/ReplayProcessLifecycle.js +71 -0
  286. package/dist/replay/ReplayWorkerProtocol.js +55 -0
  287. package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
  288. package/dist/server/ProcessCaptureElicitation.js +157 -0
  289. package/dist/server/createServer.js +158 -41
  290. package/dist/server/registerApplicationTools/characterization.js +57 -0
  291. package/dist/server/registerApplicationTools/compareVersions.js +34 -0
  292. package/dist/server/registerApplicationTools/controlledReplay.js +38 -0
  293. package/dist/server/registerApplicationTools/coverage.js +84 -0
  294. package/dist/server/registerApplicationTools/helpers.js +42 -0
  295. package/dist/server/registerApplicationTools/traceFeature.js +26 -0
  296. package/dist/server/registerApplicationTools/types.js +1 -0
  297. package/dist/server/registerApplicationTools.js +13 -0
  298. package/dist/server/registerArtifactComparisonTool.js +16 -9
  299. package/dist/server/registerBrowserTools.js +61 -60
  300. package/dist/server/registerBundleComparisonTool.js +17 -20
  301. package/dist/server/registerElectronTools.js +39 -12
  302. package/dist/server/registerEnhancedTools.js +52 -15
  303. package/dist/server/registerEvidenceResources.js +54 -0
  304. package/dist/server/registerEvidenceTools.js +5 -1
  305. package/dist/server/registerFunctionComparisonTool.js +14 -7
  306. package/dist/server/registerInvestigationTools/helpers.js +74 -0
  307. package/dist/server/registerInvestigationTools/types.js +1 -0
  308. package/dist/server/registerInvestigationTools.js +103 -132
  309. package/dist/server/registerManagedTools.js +6 -0
  310. package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +65 -0
  311. package/dist/server/registerManagedWorkflowTools/contract.js +8 -0
  312. package/dist/server/registerManagedWorkflowTools/evidence.js +43 -0
  313. package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +39 -0
  314. package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +39 -0
  315. package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +52 -0
  316. package/dist/server/registerManagedWorkflowTools/types.js +1 -0
  317. package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +75 -0
  318. package/dist/server/registerManagedWorkflowTools.js +13 -0
  319. package/dist/server/registerOfficialTools.js +81 -67
  320. package/dist/server/registerProcessComparisonTool.js +25 -47
  321. package/dist/server/registerSessionRecordTools.js +137 -0
  322. package/dist/server/registerSessionStatusTool.js +9 -8
  323. package/dist/server/registerSessionTools.js +77 -216
  324. package/dist/server/sessionAvailabilityPolicy.js +10 -0
  325. package/dist/server/sessionEvidence.js +14 -19
  326. package/dist/server/toolInputValidation.js +77 -0
  327. package/dist/server/toolRegistrationOptions.js +177 -2
  328. package/dist/server/toolResult.js +30 -17
  329. package/package.json +21 -14
  330. package/scripts/rea.mjs +1 -1
  331. package/skills/{rea-analysis → reverse-engineer-anything}/SKILL.md +75 -6
  332. package/scripts/prepare-node-pty.mjs +0 -35
  333. package/scripts/rebuild-native.mjs +0 -22
@@ -0,0 +1,217 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { readFile } from "node:fs/promises";
4
+ import { digestBytes, } from "../application/JavaScriptReplayPlanning.js";
5
+ import { linuxX64ReplaySeccompDigest } from "./LinuxSeccompPolicy.js";
6
+ import { resolveLinuxRuntimeClosure } from "./LinuxRuntimeClosure.js";
7
+ import { assertRuntimeCommitment, systemdArguments, temporaryFilterHandle, } from "./LinuxJavaScriptReplaySandbox.js";
8
+ import { boundedCollector, killUnit, observeCleanup, observeUnitResult, } from "./ReplayProcessLifecycle.js";
9
+ import { commitOutcomes, compareOutcomes, terminationResult, workerRequest, } from "./ReplayOutcome.js";
10
+ import { parseReplayWorkerResponse, } from "./ReplayWorkerProtocol.js";
11
+ const isAborted = (signal) => signal?.aborted === true;
12
+ /** Run one prepared experiment under the exact Linux ADR-0002 boundary. */
13
+ export class LinuxJavaScriptReplayRunner {
14
+ async execute(prepared, policy, signal) {
15
+ if (isAborted(signal))
16
+ return terminationResult({
17
+ prepared,
18
+ stderr: "",
19
+ termination: "cancelled",
20
+ cleanup: { state: "complete", residual_resources: [] },
21
+ });
22
+ const unit = `rea-replay-${randomBytes(8).toString("hex")}.service`;
23
+ const closure = await validateReplayCommitments(prepared, policy);
24
+ const encoded = buildWorkerPayload(prepared);
25
+ const filter = await temporaryFilterHandle();
26
+ const replayProcess = launchReplayProcess({
27
+ prepared,
28
+ policy,
29
+ encoded,
30
+ closure,
31
+ filterPath: filter.path,
32
+ unit,
33
+ signal,
34
+ });
35
+ try {
36
+ const collected = await replayProcess.completion;
37
+ if (replayProcess.terminationRequest !== undefined)
38
+ await replayProcess.terminationRequest;
39
+ const unitResult = await observeUnitResult(policy.systemctlPath, unit);
40
+ const cleanup = await observeCleanup(policy.systemctlPath, unit);
41
+ return resolveReplayResult({
42
+ prepared,
43
+ collected,
44
+ unitResult,
45
+ cleanup,
46
+ cancelled: replayProcess.cancelled,
47
+ timedOut: replayProcess.timedOut,
48
+ });
49
+ }
50
+ finally {
51
+ signal?.removeEventListener("abort", replayProcess.terminate);
52
+ if (replayProcess.timeout !== undefined)
53
+ clearTimeout(replayProcess.timeout);
54
+ if (replayProcess.child !== undefined &&
55
+ replayProcess.child.exitCode === null) {
56
+ replayProcess.requestTermination();
57
+ await replayProcess.terminationRequest;
58
+ }
59
+ await filter.close();
60
+ }
61
+ }
62
+ }
63
+ const validateReplayCommitments = async (prepared, policy) => {
64
+ const closure = await resolveLinuxRuntimeClosure(policy.nodePath);
65
+ assertRuntimeCommitment(prepared, closure);
66
+ const workerPath = prepared.publicPlan.runtime.worker.path;
67
+ if (digestBytes(await readFile(workerPath)) !==
68
+ prepared.publicPlan.runtime.worker.sha256)
69
+ throw new TypeError("Replay worker changed after approval");
70
+ if (linuxX64ReplaySeccompDigest() !== prepared.publicPlan.sandbox.seccomp_sha256)
71
+ throw new TypeError("Replay seccomp policy changed after approval");
72
+ return closure;
73
+ };
74
+ const buildWorkerPayload = (prepared) => {
75
+ const request = workerRequest(prepared);
76
+ const encoded = Buffer.from(JSON.stringify(request));
77
+ if (encoded.byteLength > prepared.publicPlan.limits.protocol_bytes)
78
+ throw new RangeError("Replay worker protocol input exceeds the committed limit");
79
+ return encoded;
80
+ };
81
+ const launchReplayProcess = (options) => {
82
+ const { prepared, policy, encoded, closure, filterPath, unit, signal } = options;
83
+ const arguments_ = systemdArguments({
84
+ unit,
85
+ policy,
86
+ prepared,
87
+ workerPath: prepared.publicPlan.runtime.worker.path,
88
+ closure,
89
+ filterPath,
90
+ });
91
+ let child;
92
+ let timeout;
93
+ let cancelled = false;
94
+ let timedOut = false;
95
+ let terminationRequest;
96
+ const requestTermination = () => {
97
+ terminationRequest ??= killUnit(policy.systemctlPath, unit);
98
+ child?.kill("SIGKILL");
99
+ };
100
+ const terminate = () => {
101
+ cancelled = true;
102
+ requestTermination();
103
+ };
104
+ const completion = new Promise((resolve, reject) => {
105
+ child = spawn(policy.systemdRunPath, arguments_, {
106
+ stdio: ["pipe", "pipe", "pipe"],
107
+ env: replayProcessEnvironment(),
108
+ });
109
+ const output = boundedCollector(prepared.publicPlan.limits.output_bytes);
110
+ const diagnostics = boundedCollector(prepared.publicPlan.limits.stderr_bytes);
111
+ child.stdout?.on("data", output.append);
112
+ child.stderr?.on("data", diagnostics.append);
113
+ child.once("error", reject);
114
+ child.once("close", (code, signalName) => resolve({
115
+ code,
116
+ signal: signalName,
117
+ stdout: output.value(),
118
+ stderr: diagnostics.value(),
119
+ outputExceeded: output.exceeded(),
120
+ }));
121
+ child.stdin?.end(encoded);
122
+ });
123
+ signal?.addEventListener("abort", terminate, { once: true });
124
+ if (isAborted(signal))
125
+ terminate();
126
+ timeout = setTimeout(() => {
127
+ timedOut = true;
128
+ requestTermination();
129
+ }, prepared.publicPlan.limits.wall_time_ms);
130
+ timeout.unref();
131
+ if (child === undefined || timeout === undefined)
132
+ throw new TypeError("Replay process was not launched");
133
+ return {
134
+ child,
135
+ completion,
136
+ timeout,
137
+ terminate,
138
+ requestTermination,
139
+ get terminationRequest() {
140
+ return terminationRequest;
141
+ },
142
+ get cancelled() {
143
+ return cancelled;
144
+ },
145
+ get timedOut() {
146
+ return timedOut;
147
+ },
148
+ };
149
+ };
150
+ const replayProcessEnvironment = () => ({
151
+ PATH: "/usr/bin:/bin",
152
+ LANG: "C",
153
+ LC_ALL: "C",
154
+ TZ: "UTC",
155
+ ...(process.env.XDG_RUNTIME_DIR === undefined
156
+ ? {}
157
+ : { XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR }),
158
+ ...(process.env.DBUS_SESSION_BUS_ADDRESS === undefined
159
+ ? {}
160
+ : { DBUS_SESSION_BUS_ADDRESS: process.env.DBUS_SESSION_BUS_ADDRESS }),
161
+ });
162
+ const resolveReplayResult = (options) => {
163
+ const { prepared, collected, unitResult, cleanup, cancelled, timedOut } = options;
164
+ if (cancelled || timedOut)
165
+ return terminationResult({
166
+ prepared,
167
+ stderr: collected.stderr,
168
+ termination: cancelled ? "cancelled" : "timeout",
169
+ cleanup,
170
+ });
171
+ if (collected.outputExceeded)
172
+ return terminationResult({
173
+ prepared,
174
+ stderr: collected.stderr,
175
+ termination: "protocol_error",
176
+ cleanup,
177
+ limitation: "Worker stdout exceeded the committed bound",
178
+ });
179
+ if (collected.code !== 0)
180
+ return terminationResult({
181
+ prepared,
182
+ stderr: collected.stderr,
183
+ termination: collected.code === 137 || unitResult === "oom-kill" ? "oom" : "crash",
184
+ cleanup,
185
+ });
186
+ let response;
187
+ try {
188
+ const rawResponse = JSON.parse(collected.stdout);
189
+ response = parseReplayWorkerResponse(rawResponse, prepared.publicPlan.cases, prepared.publicPlan.right !== undefined);
190
+ }
191
+ catch {
192
+ return terminationResult({
193
+ prepared,
194
+ stderr: collected.stderr,
195
+ termination: "protocol_error",
196
+ cleanup,
197
+ });
198
+ }
199
+ const outcomes = commitOutcomes(response.left);
200
+ const right = response.right === undefined ? undefined : commitOutcomes(response.right);
201
+ return {
202
+ schema_version: 1,
203
+ plan_digest: prepared.publicPlan.plan_digest,
204
+ outcomes: [...outcomes, ...(right ?? [])],
205
+ ...(right === undefined
206
+ ? {}
207
+ : { comparison: compareOutcomes(outcomes, right) }),
208
+ stderr: collected.stderr,
209
+ termination: "completed",
210
+ cleanup,
211
+ limitations: [
212
+ "Controlled replay observes the isolated extracted modules, not the original application runtime.",
213
+ "Network and host filesystem access were unavailable; unsupported dependencies may change behavior.",
214
+ ],
215
+ reproducer: null,
216
+ };
217
+ };
@@ -0,0 +1,140 @@
1
+ import { open } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { digestJson, } from "../application/JavaScriptReplayPlanning.js";
4
+ import { buildLinuxX64ReplaySeccomp } from "./LinuxSeccompPolicy.js";
5
+ import { resolveLinuxRuntimeClosure } from "./LinuxRuntimeClosure.js";
6
+ export const assertRuntimeCommitment = (prepared, closure) => {
7
+ const observed = closure.map((item) => ({
8
+ source_path: item.sourcePath,
9
+ destination_path: item.destinationPath,
10
+ sha256: item.sha256,
11
+ }));
12
+ if (digestJson(observed) !==
13
+ digestJson(prepared.publicPlan.runtime.read_only_files))
14
+ throw new TypeError("Replay runtime closure changed after approval");
15
+ };
16
+ export const temporaryFilterHandle = async () => {
17
+ const { mkdtemp, writeFile, rm } = await import("node:fs/promises");
18
+ const { tmpdir } = await import("node:os");
19
+ const { join } = await import("node:path");
20
+ const directory = await mkdtemp(join(tmpdir(), "rea-replay-filter-"));
21
+ const path = join(directory, "seccomp.bpf");
22
+ await writeFile(path, buildLinuxX64ReplaySeccomp(), { mode: 0o600 });
23
+ const handle = await open(path, "r");
24
+ return {
25
+ path,
26
+ handle,
27
+ close: async () => {
28
+ await handle.close();
29
+ await rm(directory, { recursive: true, force: true });
30
+ },
31
+ };
32
+ };
33
+ export const systemdArguments = (options) => {
34
+ const { unit, policy, prepared, workerPath, closure, filterPath } = options;
35
+ const limits = prepared.publicPlan.limits;
36
+ const descriptorPaths = [
37
+ filterPath,
38
+ ...closure.map(({ sourcePath }) => sourcePath),
39
+ workerPath,
40
+ ];
41
+ const workerFd = descriptorPaths.length + 2;
42
+ const wrapper = descriptorPaths
43
+ .map((_, index) => `exec ${String(index + 3)}<"$1"; shift`)
44
+ .concat('exec "$@"')
45
+ .join("\n");
46
+ return [
47
+ "--user",
48
+ "--pipe",
49
+ "--wait",
50
+ "--service-type=exec",
51
+ "--quiet",
52
+ `--unit=${unit}`,
53
+ `--property=MemoryMax=${String(limits.memory_bytes)}`,
54
+ "--property=MemorySwapMax=0",
55
+ `--property=TasksMax=${String(limits.tasks)}`,
56
+ `--property=CPUQuota=${String(limits.cpu_quota_percent)}%`,
57
+ "--property=KillMode=control-group",
58
+ "--property=SendSIGKILL=yes",
59
+ "--",
60
+ policy.shellPath,
61
+ "-c",
62
+ wrapper,
63
+ "rea-replay-wrapper",
64
+ ...descriptorPaths,
65
+ policy.bubblewrapPath,
66
+ "--unshare-all",
67
+ "--unshare-user",
68
+ "--disable-userns",
69
+ "--new-session",
70
+ "--die-with-parent",
71
+ "--cap-drop",
72
+ "ALL",
73
+ "--clearenv",
74
+ "--setenv",
75
+ "HOME",
76
+ "/work",
77
+ "--setenv",
78
+ "TMPDIR",
79
+ "/tmp",
80
+ "--setenv",
81
+ "TZ",
82
+ "UTC",
83
+ "--setenv",
84
+ "LANG",
85
+ "C",
86
+ "--setenv",
87
+ "LC_ALL",
88
+ "C",
89
+ "--tmpfs",
90
+ "/",
91
+ "--dir",
92
+ "/runtime",
93
+ "--dir",
94
+ "/work",
95
+ "--size",
96
+ String(limits.tmpfs_bytes),
97
+ "--tmpfs",
98
+ "/tmp",
99
+ ...bindArguments(closure),
100
+ "--ro-bind-fd",
101
+ String(workerFd),
102
+ "/runtime/worker.js",
103
+ "--proc",
104
+ "/proc",
105
+ "--dev",
106
+ "/dev",
107
+ "--remount-ro",
108
+ "/",
109
+ "--chdir",
110
+ "/work",
111
+ "--seccomp",
112
+ "3",
113
+ "--",
114
+ "/runtime/node",
115
+ `--max-old-space-size=${String(Math.max(8, Math.floor(limits.memory_bytes / 1024 / 1024 / 2)))}`,
116
+ "--experimental-vm-modules",
117
+ "--permission",
118
+ "--allow-fs-read=/runtime/worker.js",
119
+ "/runtime/worker.js",
120
+ ];
121
+ };
122
+ const bindArguments = (closure) => {
123
+ const arguments_ = [];
124
+ const directories = new Set();
125
+ for (const [index, item] of closure.entries()) {
126
+ const destination = item.destinationPath;
127
+ let parent = dirname(destination);
128
+ const chain = [];
129
+ while (parent !== "/" && !directories.has(parent)) {
130
+ chain.push(parent);
131
+ parent = dirname(parent);
132
+ }
133
+ for (const directory of chain.reverse()) {
134
+ directories.add(directory);
135
+ arguments_.push("--dir", directory);
136
+ }
137
+ arguments_.push("--ro-bind-fd", String(index + 4), destination);
138
+ }
139
+ return arguments_;
140
+ };
@@ -0,0 +1,46 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { readFile, realpath } from "node:fs/promises";
4
+ const LDD_OUTPUT_LIMIT = 64 * 1024;
5
+ /** Resolve and content-address the exact ELF closure mounted into a replay. */
6
+ export const resolveLinuxRuntimeClosure = async (nodePath) => {
7
+ const canonicalNode = await realpath(nodePath);
8
+ const collected = await collectLdd(canonicalNode);
9
+ if (collected.code !== 0)
10
+ throw new TypeError("Node runtime dependency closure could not be resolved");
11
+ const paths = [...collected.stdout.matchAll(/(?:=>\s+)?(\/[^\s()]+)/gu)]
12
+ .map((match) => match[1])
13
+ .filter((value) => value !== undefined);
14
+ const libraries = await Promise.all([...new Set(paths)].map(async (path) => {
15
+ const sourcePath = await realpath(path);
16
+ return identity(sourcePath, path);
17
+ }));
18
+ return [await identity(canonicalNode, "/runtime/node"), ...libraries];
19
+ };
20
+ const identity = async (sourcePath, destinationPath) => ({
21
+ sourcePath,
22
+ destinationPath,
23
+ sha256: createHash("sha256")
24
+ .update(await readFile(sourcePath))
25
+ .digest("hex"),
26
+ });
27
+ const collectLdd = async (path) => new Promise((resolve, reject) => {
28
+ const child = spawn("/usr/bin/ldd", [path], {
29
+ stdio: ["ignore", "pipe", "ignore"],
30
+ });
31
+ const timeout = setTimeout(() => child.kill("SIGKILL"), 5_000);
32
+ timeout.unref();
33
+ let stdout = "";
34
+ child.stdout.on("data", (chunk) => {
35
+ const remaining = Math.max(0, LDD_OUTPUT_LIMIT - Buffer.byteLength(stdout));
36
+ stdout += chunk.subarray(0, remaining).toString("utf8");
37
+ });
38
+ child.once("error", (error) => {
39
+ clearTimeout(timeout);
40
+ reject(error);
41
+ });
42
+ child.once("close", (code) => {
43
+ clearTimeout(timeout);
44
+ resolve({ code, stdout });
45
+ });
46
+ });
@@ -0,0 +1,40 @@
1
+ import { createHash } from "node:crypto";
2
+ const BPF_LD_W_ABS = 0x20;
3
+ const BPF_JMP_JEQ_K = 0x15;
4
+ const BPF_RET_K = 0x06;
5
+ const SECCOMP_RET_KILL_PROCESS = 0x8000_0000;
6
+ const SECCOMP_RET_ERRNO = 0x0005_0000 | 1;
7
+ const SECCOMP_RET_ALLOW = 0x7fff_0000;
8
+ const AUDIT_ARCH_X86_64 = 0xc000_003e;
9
+ const IOCTL = 16;
10
+ const TIOCSTI = 0x5412;
11
+ const DENIED_X86_64_SYSCALLS = [
12
+ 101, 103, 155, 161, 163, 165, 166, 167, 168, 169, 172, 173, 175, 176, 179,
13
+ 212, 246, 248, 249, 250, 272, 298, 303, 304, 308, 310, 311, 312, 313, 320,
14
+ 321, 323, 425, 426, 427, 428, 429, 430, 431, 432, 433, 442,
15
+ ];
16
+ /** Build the architecture-checked deny filter committed by ADR-0002. */
17
+ export const buildLinuxX64ReplaySeccomp = () => {
18
+ const instructions = [
19
+ { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 4 },
20
+ { code: BPF_JMP_JEQ_K, jt: 1, jf: 0, k: AUDIT_ARCH_X86_64 },
21
+ { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_KILL_PROCESS },
22
+ { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 0 },
23
+ ];
24
+ for (const syscall of DENIED_X86_64_SYSCALLS) {
25
+ instructions.push({ code: BPF_JMP_JEQ_K, jt: 0, jf: 1, k: syscall });
26
+ instructions.push({ code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_ERRNO });
27
+ }
28
+ instructions.push({ code: BPF_JMP_JEQ_K, jt: 0, jf: 3, k: IOCTL }, { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 24 }, { code: BPF_JMP_JEQ_K, jt: 0, jf: 1, k: TIOCSTI }, { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_ERRNO }, { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_ALLOW });
29
+ const encoded = new Uint8Array(instructions.length * 8);
30
+ const view = new DataView(encoded.buffer);
31
+ instructions.forEach((instruction, index) => {
32
+ const offset = index * 8;
33
+ view.setUint16(offset, instruction.code, true);
34
+ view.setUint8(offset + 2, instruction.jt);
35
+ view.setUint8(offset + 3, instruction.jf);
36
+ view.setUint32(offset + 4, instruction.k, true);
37
+ });
38
+ return encoded;
39
+ };
40
+ export const linuxX64ReplaySeccompDigest = () => createHash("sha256").update(buildLinuxX64ReplaySeccomp()).digest("hex");
@@ -0,0 +1,95 @@
1
+ import { digestJson, } from "../application/JavaScriptReplayPlanning.js";
2
+ import { jsonValueSchema } from "../domain/jsonValue.js";
3
+ export const workerRequest = (prepared) => ({
4
+ schemaVersion: 1,
5
+ left: workerSide(prepared.publicPlan.left, prepared.leftSources),
6
+ ...(prepared.publicPlan.right === undefined ||
7
+ prepared.rightSources === undefined
8
+ ? {}
9
+ : { right: workerSide(prepared.publicPlan.right, prepared.rightSources) }),
10
+ cases: prepared.publicPlan.cases.map((item) => ({
11
+ caseId: item.case_id,
12
+ arguments: item.arguments,
13
+ inputSha256: item.sha256,
14
+ })),
15
+ determinism: {
16
+ clockIso: prepared.publicPlan.determinism.clock_iso,
17
+ randomSeed: prepared.publicPlan.determinism.random_seed,
18
+ },
19
+ limits: {
20
+ resultDepth: prepared.publicPlan.limits.result_depth,
21
+ resultNodes: prepared.publicPlan.limits.result_nodes,
22
+ exceptionBytes: Math.min(64 * 1024, prepared.publicPlan.limits.output_bytes),
23
+ },
24
+ });
25
+ const workerSide = (side, sources) => ({
26
+ modules: side.modules.map((module) => ({
27
+ alias: module.alias,
28
+ format: module.format,
29
+ dependencies: module.dependencies,
30
+ source: sources[module.alias] ?? "",
31
+ })),
32
+ entryAlias: side.entry_alias,
33
+ entryExport: side.entry_export,
34
+ });
35
+ export const commitOutcomes = (outcomes) => outcomes.map((outcome) => {
36
+ const semantic = outcome.value === undefined ? (outcome.exception ?? null) : outcome.value;
37
+ return {
38
+ case_id: outcome.case_id,
39
+ outcome: outcome.outcome,
40
+ ...(outcome.value === undefined
41
+ ? {}
42
+ : { value: jsonValueSchema.parse(outcome.value) }),
43
+ ...(outcome.exception === undefined
44
+ ? {}
45
+ : { exception: outcome.exception }),
46
+ input_sha256: outcome.input_sha256,
47
+ output_sha256: digestJson(semantic),
48
+ truncated: false,
49
+ };
50
+ });
51
+ export const compareOutcomes = (left, right) => left.map((item, index) => ({
52
+ case_id: item.case_id,
53
+ status: right[index] === undefined
54
+ ? "unknown"
55
+ : item.outcome === right[index].outcome &&
56
+ item.output_sha256 === right[index].output_sha256
57
+ ? "equal"
58
+ : "changed",
59
+ left_index: index,
60
+ right_index: index,
61
+ }));
62
+ export const terminationResult = (options) => {
63
+ const { prepared, stderr, termination, cleanup, limitation } = options;
64
+ const outcomes = prepared.publicPlan.cases.map((item) => ({
65
+ case_id: item.case_id,
66
+ outcome: termination,
67
+ input_sha256: item.sha256,
68
+ output_sha256: null,
69
+ truncated: termination === "protocol_error",
70
+ }));
71
+ const differential = prepared.publicPlan.right !== undefined;
72
+ return {
73
+ schema_version: 1,
74
+ plan_digest: prepared.publicPlan.plan_digest,
75
+ outcomes: [...outcomes, ...(differential ? outcomes : [])],
76
+ ...(differential
77
+ ? {
78
+ comparison: outcomes.map((item, index) => ({
79
+ case_id: item.case_id,
80
+ status: "unknown",
81
+ left_index: index,
82
+ right_index: index,
83
+ })),
84
+ }
85
+ : {}),
86
+ stderr,
87
+ termination,
88
+ cleanup,
89
+ limitations: [
90
+ limitation ??
91
+ `Replay terminated with ${termination}; the requested functional result remains unknown.`,
92
+ ],
93
+ reproducer: null,
94
+ };
95
+ };
@@ -0,0 +1,71 @@
1
+ import { spawn } from "node:child_process";
2
+ export const boundedCollector = (maximum) => {
3
+ const chunks = [];
4
+ let bytes = 0;
5
+ let overflow = false;
6
+ return {
7
+ append: (chunk) => {
8
+ const retained = chunk.subarray(0, Math.max(0, maximum - bytes));
9
+ if (retained.byteLength > 0)
10
+ chunks.push(retained);
11
+ bytes += retained.byteLength;
12
+ if (retained.byteLength < chunk.byteLength)
13
+ overflow = true;
14
+ },
15
+ value: () => Buffer.concat(chunks).toString("utf8"),
16
+ exceeded: () => overflow,
17
+ };
18
+ };
19
+ const collect = async (executable, arguments_, maximum) => new Promise((resolve, reject) => {
20
+ const child = spawn(executable, [...arguments_], {
21
+ stdio: ["ignore", "pipe", "pipe"],
22
+ });
23
+ const stdout = boundedCollector(maximum);
24
+ const stderr = boundedCollector(maximum);
25
+ const timeout = setTimeout(() => child.kill("SIGKILL"), 5_000);
26
+ timeout.unref();
27
+ child.stdout?.on("data", stdout.append);
28
+ child.stderr?.on("data", stderr.append);
29
+ child.once("error", (error) => {
30
+ clearTimeout(timeout);
31
+ reject(error);
32
+ });
33
+ child.once("close", (code) => {
34
+ clearTimeout(timeout);
35
+ resolve({ code, stdout: stdout.value(), stderr: stderr.value() });
36
+ });
37
+ });
38
+ export const killUnit = async (systemctl, unit) => {
39
+ try {
40
+ await collect(systemctl, ["--user", "kill", "--kill-whom=all", "--signal=SIGKILL", unit], 4096);
41
+ }
42
+ catch {
43
+ /* cleanup continues */
44
+ }
45
+ };
46
+ export const observeCleanup = async (systemctl, unit) => {
47
+ try {
48
+ const state = await collect(systemctl, ["--user", "show", "--property=ActiveState", "--value", unit], 4096);
49
+ const activeState = state.stdout.trim();
50
+ if (state.code !== 0 ||
51
+ activeState === "inactive" ||
52
+ activeState === "failed" ||
53
+ activeState.length === 0) {
54
+ await collect(systemctl, ["--user", "reset-failed", unit], 4096);
55
+ return { state: "complete", residual_resources: [] };
56
+ }
57
+ return { state: "incomplete", residual_resources: [unit] };
58
+ }
59
+ catch {
60
+ return { state: "incomplete", residual_resources: [unit] };
61
+ }
62
+ };
63
+ export const observeUnitResult = async (systemctl, unit) => {
64
+ try {
65
+ const result = await collect(systemctl, ["--user", "show", "--property=Result", "--value", unit], 4096);
66
+ return result.stdout.trim();
67
+ }
68
+ catch {
69
+ return "unknown";
70
+ }
71
+ };
@@ -0,0 +1,55 @@
1
+ import { z } from "zod";
2
+ const workerOutcomeSchema = z
3
+ .object({
4
+ case_id: z.string(),
5
+ outcome: z.enum(["return", "exception", "serialization_error", "denied"]),
6
+ value: z.json().optional(),
7
+ exception: z
8
+ .object({
9
+ name: z.string(),
10
+ message: z.string(),
11
+ stack: z.string().nullable(),
12
+ })
13
+ .strict()
14
+ .optional(),
15
+ input_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
16
+ output_sha256: z.null(),
17
+ truncated: z.literal(false),
18
+ })
19
+ .strict()
20
+ .superRefine((value, context) => {
21
+ if (value.outcome === "return" && value.value === undefined)
22
+ context.addIssue({ code: "custom", message: "Return value is missing" });
23
+ if (value.outcome !== "return" && value.exception === undefined)
24
+ context.addIssue({ code: "custom", message: "Exception is missing" });
25
+ if (value.outcome === "return" && value.exception !== undefined)
26
+ context.addIssue({ code: "custom", message: "Return has an exception" });
27
+ });
28
+ const workerResponseSchema = z
29
+ .object({
30
+ schema_version: z.literal(1),
31
+ left: z.array(workerOutcomeSchema),
32
+ right: z.array(workerOutcomeSchema).optional(),
33
+ })
34
+ .strict();
35
+ /** Strictly authenticate case ordering and commitments in worker output. */
36
+ export const parseReplayWorkerResponse = (rawResponse, cases, differential) => {
37
+ const response = workerResponseSchema.parse(rawResponse);
38
+ if ((response.right !== undefined) !== differential)
39
+ throw new TypeError("Replay worker differential response is incomplete");
40
+ validateOutcomeSequence(response.left, cases);
41
+ if (response.right !== undefined)
42
+ validateOutcomeSequence(response.right, cases);
43
+ return response;
44
+ };
45
+ const validateOutcomeSequence = (outcomes, cases) => {
46
+ if (outcomes.length !== cases.length)
47
+ throw new TypeError("Replay worker response case count changed");
48
+ outcomes.forEach((outcome, index) => {
49
+ const expected = cases[index];
50
+ if (expected === undefined ||
51
+ outcome.case_id !== expected.case_id ||
52
+ outcome.input_sha256 !== expected.sha256)
53
+ throw new TypeError("Replay worker response case identity changed");
54
+ });
55
+ };