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
@@ -0,0 +1,66 @@
1
+ import { z } from "zod";
2
+ import { controlledReplayInputSchema, controlledReplayOutputSchema, } from "./javascriptReplay.js";
3
+ import { javascriptExportInstrumentationInputSchema, javascriptExportTransformationManifestSchema, } from "./javascriptExportInstrumentation.js";
4
+ import { runtimeCharacterizationPlanSchema } from "./runtimeCharacterization.js";
5
+ import { evidenceRecordSchema } from "./evidence.js";
6
+ const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
7
+ export const nodeCharacterizationPreparationInputSchema = z
8
+ .strictObject({
9
+ preparation_approved: z.literal(true),
10
+ selected_alias: z.string().min(1).max(200),
11
+ expected_effect: z.enum(["pure", "observation-only"]),
12
+ instrumentation: javascriptExportInstrumentationInputSchema,
13
+ replay: controlledReplayInputSchema,
14
+ })
15
+ .superRefine((input, context) => {
16
+ if (input.replay.mode !== "plan")
17
+ context.addIssue({
18
+ code: "custom",
19
+ path: ["replay", "mode"],
20
+ message: "Characterization preparation requires replay plan mode",
21
+ });
22
+ const selected = input.replay.left.modules.filter(({ alias }) => alias === input.selected_alias);
23
+ if (selected.length !== 1 ||
24
+ selected[0]?.path !== input.instrumentation.artifact_path ||
25
+ selected[0]?.format !== "commonjs-factory")
26
+ context.addIssue({
27
+ code: "custom",
28
+ path: ["selected_alias"],
29
+ message: "Selected alias must identify exactly one commonjs-factory instrumented artifact",
30
+ });
31
+ if (input.replay.left.entry_alias !== input.selected_alias)
32
+ context.addIssue({
33
+ code: "custom",
34
+ path: ["replay", "left", "entry_alias"],
35
+ message: "Instrumented alias must be the characterization entry",
36
+ });
37
+ if (input.replay.left.entry_export !==
38
+ input.instrumentation.selection.export_name)
39
+ context.addIssue({
40
+ code: "custom",
41
+ path: ["replay", "left", "entry_export"],
42
+ message: "Replay entry export must match the instrumented export",
43
+ });
44
+ });
45
+ export const nodeCharacterizationExecutionInputSchema = z.strictObject({
46
+ execution_approved: z.literal(true),
47
+ approved_plan_sha256: digestSchema,
48
+ preparation: nodeCharacterizationPreparationInputSchema,
49
+ });
50
+ export const nodeCharacterizationPreparationOutputSchema = z.strictObject({
51
+ schema_version: z.literal(1),
52
+ phase: z.literal("preparation"),
53
+ plan: runtimeCharacterizationPlanSchema,
54
+ transformation: javascriptExportTransformationManifestSchema,
55
+ transformation_evidence: evidenceRecordSchema,
56
+ replay: controlledReplayOutputSchema,
57
+ });
58
+ export const nodeCharacterizationExecutionOutputSchema = z.strictObject({
59
+ schema_version: z.literal(1),
60
+ phase: z.literal("execution"),
61
+ plan: runtimeCharacterizationPlanSchema,
62
+ transformation: javascriptExportTransformationManifestSchema,
63
+ transformation_evidence: evidenceRecordSchema,
64
+ evidence: evidenceRecordSchema,
65
+ replay: controlledReplayOutputSchema,
66
+ });
@@ -65,6 +65,8 @@ export const evaluatePermission = (policy, request, now = new Date()) => {
65
65
  missing: missingFromScopes(normalized, candidates),
66
66
  };
67
67
  };
68
+ /** Test whether one exact request is wholly contained by one ceiling. */
69
+ export const isPermissionRequestWithinCeiling = (request, ceiling) => !hasMissing(missingFromScopes(normalizeRequest(request), [ceiling]));
68
70
  /** Consume an allowed one-shot decision; longer-lived grants are unchanged. */
69
71
  export const consumePermission = (policy, decision) => decision.lifetime === "once"
70
72
  ? {
@@ -94,6 +94,25 @@ const filesystemObservations = (capture) => [
94
94
  effects: capture.filesystem_effects,
95
95
  },
96
96
  ];
97
+ /** Canonical observations for one process-comparison dimension. */
98
+ export const processDimensionObservations = (capture, dimension) => {
99
+ switch (dimension) {
100
+ case "terminal":
101
+ return terminalObservations(capture);
102
+ case "interaction":
103
+ return capture.interaction_events;
104
+ case "exit":
105
+ return [{ ...capture.exit, settlement: capture.settlement }];
106
+ case "filesystem":
107
+ return filesystemObservations(capture);
108
+ case "protocol":
109
+ return capture.protocol_events;
110
+ case "process":
111
+ return capture.process_samples;
112
+ case "shim":
113
+ return capture.shim_events;
114
+ }
115
+ };
97
116
  const classifyCollection = (left, right) => {
98
117
  if (JSON.stringify(left) === JSON.stringify(right))
99
118
  return "unchanged";
@@ -0,0 +1,128 @@
1
+ import { z } from "zod";
2
+ import { PROCESS_COMPARISON_DIMENSIONS, compareProcessCaptures, processDimensionObservations, } from "./processComparison.js";
3
+ import { digestProcessCommitment, processScenarioSchema, } from "./processScenario.js";
4
+ const sideIdentifierSchema = z
5
+ .string()
6
+ .regex(/^[A-Za-z][A-Za-z0-9._-]{0,127}$/u);
7
+ const environmentNameSchema = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/u);
8
+ const processDimensionSchema = z.enum(PROCESS_COMPARISON_DIMENSIONS);
9
+ const sideBindingSchema = z
10
+ .object({
11
+ id: sideIdentifierSchema,
12
+ executable: z.string().startsWith("/"),
13
+ arguments: z.array(z.string()).max(256).optional(),
14
+ environment: z.record(environmentNameSchema, z.string()).default({}),
15
+ })
16
+ .strict();
17
+ /** Boundary contract for one bounded authority/candidate process experiment. */
18
+ export const processPairedExperimentSchema = z
19
+ .object({
20
+ approved: z.literal(true),
21
+ shared_scenario: processScenarioSchema,
22
+ authority: sideBindingSchema,
23
+ candidate: sideBindingSchema,
24
+ repeat_count: z.number().int().min(2).max(10).default(3),
25
+ freshness_policy: z
26
+ .object({
27
+ max_capture_age_ms: z.number().int().positive().max(86_400_000),
28
+ })
29
+ .strict(),
30
+ required_dimensions: z
31
+ .array(processDimensionSchema)
32
+ .min(1)
33
+ .max(PROCESS_COMPARISON_DIMENSIONS.length)
34
+ .default([...PROCESS_COMPARISON_DIMENSIONS]),
35
+ })
36
+ .strict()
37
+ .superRefine((experiment, context) => {
38
+ if (experiment.authority.id === experiment.candidate.id)
39
+ context.addIssue({
40
+ code: "custom",
41
+ message: "authority and candidate identities must differ",
42
+ path: ["candidate", "id"],
43
+ });
44
+ if (new Set(experiment.required_dimensions).size !==
45
+ experiment.required_dimensions.length)
46
+ context.addIssue({
47
+ code: "custom",
48
+ message: "required process dimensions must be unique",
49
+ path: ["required_dimensions"],
50
+ });
51
+ });
52
+ /** Apply a side binding without changing the shared observation contract. */
53
+ export const bindProcessScenario = (shared, side) => processScenarioSchema.parse({
54
+ ...shared,
55
+ executable: side.executable,
56
+ arguments: side.arguments ?? shared.arguments,
57
+ environment: { ...shared.environment, ...side.environment },
58
+ });
59
+ const classifyInstability = (dimensions) => {
60
+ if (dimensions.has("exit"))
61
+ return "flaky_exit";
62
+ if (dimensions.has("filesystem") || dimensions.has("process"))
63
+ return "environmental_drift";
64
+ return "nondeterministic_output";
65
+ };
66
+ const timingKeys = new Set([
67
+ "at_ms",
68
+ "scheduled_at_ms",
69
+ "dispatched_at_ms",
70
+ "elapsed_ms",
71
+ ]);
72
+ const withoutTiming = (value) => typeof value !== "object" || value === null || Array.isArray(value)
73
+ ? value
74
+ : Object.fromEntries(Object.entries(value).filter(([key]) => !timingKeys.has(key)));
75
+ const observationsWithoutTiming = (capture, dimension) => dimension === "exit"
76
+ ? [
77
+ {
78
+ ...capture.exit,
79
+ settlement: {
80
+ state: capture.settlement.state,
81
+ cleanup_outcome: capture.settlement.cleanup_outcome,
82
+ },
83
+ },
84
+ ]
85
+ : processDimensionObservations(capture, dimension).map(withoutTiming);
86
+ const hasOnlyTimingVariance = (captures, dimensions) => dimensions.every((dimension) => {
87
+ const first = captures[0];
88
+ if (first === undefined)
89
+ return false;
90
+ const baseline = JSON.stringify(observationsWithoutTiming(first, dimension));
91
+ return captures
92
+ .slice(1)
93
+ .every((capture) => JSON.stringify(observationsWithoutTiming(capture, dimension)) ===
94
+ baseline);
95
+ });
96
+ /** Compare same-side repeats before permitting a cross-side claim. */
97
+ export const analyzeProcessRepeatability = (captures, requiredDimensions) => {
98
+ if (captures.length < 2)
99
+ return {
100
+ classification: "insufficient_samples",
101
+ stable: false,
102
+ digest: digestProcessCommitment({ captures: [], requiredDimensions }),
103
+ comparisons: [],
104
+ unstable_dimensions: [],
105
+ };
106
+ const baseline = captures[0];
107
+ if (baseline === undefined)
108
+ throw new TypeError("Repeatability analysis lost its baseline capture");
109
+ const comparisons = captures
110
+ .slice(1)
111
+ .map((capture) => compareProcessCaptures(baseline, capture));
112
+ const unstable = new Set(requiredDimensions.filter((dimension) => comparisons.some((comparison) => comparison[dimension] !== "unchanged")));
113
+ const unstableDimensions = PROCESS_COMPARISON_DIMENSIONS.filter((dimension) => unstable.has(dimension));
114
+ return {
115
+ classification: unstable.size === 0
116
+ ? "stable"
117
+ : hasOnlyTimingVariance(captures, unstableDimensions)
118
+ ? "timing_variance"
119
+ : classifyInstability(unstable),
120
+ stable: unstable.size === 0,
121
+ digest: digestProcessCommitment({
122
+ requiredDimensions,
123
+ comparisons,
124
+ }),
125
+ comparisons,
126
+ unstable_dimensions: unstableDimensions,
127
+ };
128
+ };
@@ -0,0 +1,387 @@
1
+ import { createHash } from "node:crypto";
2
+ import canonicalize from "canonicalize";
3
+ import { z } from "zod";
4
+ import { evidenceBundleSchema, parseEvidenceBundle } from "./evidenceBundle.js";
5
+ import { evaluateReconstructionClaims, evaluateReconstructionPackageProofs, evaluateReconstructionSurfaces, evaluateReconstructionWorkspaceRisks, recommendedReconstructionProbes, } from "./reconstructionCoverageEvaluation.js";
6
+ const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
7
+ const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
8
+ const stableIdSchema = z.string().regex(/^[A-Za-z][A-Za-z0-9._:/-]{0,199}$/u);
9
+ const boundedTextSchema = z.string().trim().min(1).max(4_096);
10
+ const authoritySchema = z.enum([
11
+ "observed",
12
+ "derived",
13
+ "inferred",
14
+ "historical",
15
+ "external",
16
+ ]);
17
+ const artifactSchema = z.strictObject({
18
+ artifact_id: stableIdSchema,
19
+ artifact_sha256: digestSchema,
20
+ version: z.string().trim().min(1).max(500),
21
+ environment_sha256: digestSchema,
22
+ evidence_ids: z.array(evidenceIdSchema).min(1).max(1_000),
23
+ });
24
+ const surfaceSchema = z.strictObject({
25
+ surface_id: stableIdSchema,
26
+ family: z.string().trim().min(1).max(100),
27
+ artifact_id: stableIdSchema,
28
+ occurrence_id: z.string().trim().min(1).max(500).nullable(),
29
+ location: boundedTextSchema,
30
+ authority: authoritySchema,
31
+ dependency_surface_ids: z.array(stableIdSchema).max(1_000),
32
+ evidence_ids: z.array(evidenceIdSchema).min(1).max(1_000),
33
+ });
34
+ const implementationOwnerSchema = z.strictObject({
35
+ disposition: z.literal("implemented"),
36
+ owner_path: boundedTextSchema,
37
+ owner_export: z.string().trim().min(1).max(500).nullable(),
38
+ owner_sha256: digestSchema,
39
+ path_state: z.enum(["present", "missing", "unknown"]),
40
+ package_state: z.enum(["distributed", "missing", "unknown"]),
41
+ authority_route: z.enum(["none", "detected", "unknown"]),
42
+ });
43
+ const externalOwnerSchema = z.strictObject({
44
+ disposition: z.enum(["external", "non-goal"]),
45
+ rationale: boundedTextSchema,
46
+ });
47
+ const ownerSchema = z.strictObject({
48
+ surface_id: stableIdSchema,
49
+ ownership: z.discriminatedUnion("disposition", [
50
+ implementationOwnerSchema,
51
+ externalOwnerSchema,
52
+ ]),
53
+ });
54
+ const claimSchema = z.strictObject({
55
+ claim_id: stableIdSchema,
56
+ title: z.string().trim().min(1).max(500),
57
+ kind: z.string().trim().min(1).max(100),
58
+ surface_ids: z.array(stableIdSchema).min(1).max(1_000),
59
+ required_dimensions: z.array(stableIdSchema).min(1).max(100),
60
+ required_authority: z.enum([
61
+ "shipped-artifact",
62
+ "controlled-replay",
63
+ "live-observation",
64
+ "external",
65
+ ]),
66
+ });
67
+ const verifierContractBaseSchema = z.strictObject({
68
+ verifier_id: stableIdSchema,
69
+ claim_ids: z.array(stableIdSchema).min(1).max(1_000),
70
+ dimensions: z.array(stableIdSchema).min(1).max(100),
71
+ authority: z.enum([
72
+ "shipped-artifact",
73
+ "controlled-replay",
74
+ "live-observation",
75
+ "external",
76
+ ]),
77
+ max_age_ms: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
78
+ minimum_repeats: z.number().int().min(1).max(1_000),
79
+ normalization_sha256: digestSchema,
80
+ normalization_removes_dimensions: z.literal(false),
81
+ });
82
+ const verifierContractSchema = verifierContractBaseSchema.extend({
83
+ contract_sha256: digestSchema,
84
+ });
85
+ const verifierResultSchema = z.strictObject({
86
+ verifier_id: stableIdSchema,
87
+ contract_sha256: digestSchema,
88
+ observed_at: z.string().datetime({ offset: true }),
89
+ status: z.enum([
90
+ "pass",
91
+ "fail",
92
+ "unknown",
93
+ "unsupported",
94
+ "truncated",
95
+ "skipped",
96
+ ]),
97
+ covered_claim_ids: z.array(stableIdSchema).min(1).max(1_000),
98
+ covered_dimensions: z.array(stableIdSchema).min(1).max(100),
99
+ artifact_sha256s: z.array(digestSchema).min(1).max(1_000),
100
+ owner_sha256s: z.array(digestSchema).max(1_000),
101
+ normalization_sha256: digestSchema,
102
+ repeats: z.number().int().min(1).max(1_000),
103
+ evidence_ids: z.array(evidenceIdSchema).min(1).max(1_000),
104
+ });
105
+ const contradictionSchema = z.strictObject({
106
+ contradiction_id: stableIdSchema,
107
+ status: z.enum(["active", "resolved"]),
108
+ surface_ids: z.array(stableIdSchema).max(1_000),
109
+ claim_ids: z.array(stableIdSchema).max(1_000),
110
+ evidence_ids: z.array(evidenceIdSchema).min(2).max(1_000),
111
+ });
112
+ const packageProofSchema = z.strictObject({
113
+ proof_id: stableIdSchema,
114
+ kind: z.enum([
115
+ "build",
116
+ "clean-install",
117
+ "package-contents",
118
+ "runtime-routing",
119
+ "authority-independence",
120
+ ]),
121
+ status: z.enum(["pass", "fail", "unknown", "unsupported", "skipped"]),
122
+ artifact_sha256s: z.array(digestSchema).min(1).max(1_000),
123
+ evidence_ids: z.array(evidenceIdSchema).min(1).max(1_000),
124
+ });
125
+ const boundarySchema = z.strictObject({
126
+ boundary_id: stableIdSchema,
127
+ title: z.string().trim().min(1).max(500),
128
+ required_surface_ids: z.array(stableIdSchema).min(1).max(10_000),
129
+ required_claim_ids: z.array(stableIdSchema).min(1).max(10_000),
130
+ required_package_proof_kinds: z
131
+ .array(packageProofSchema.shape.kind)
132
+ .min(1)
133
+ .max(5),
134
+ allowed_dispositions: z.array(z.enum(["external", "non-goal"])).max(2),
135
+ allowed_unknown_ids: z.array(stableIdSchema).max(1_000),
136
+ });
137
+ const workspaceSemanticSchema = z.strictObject({
138
+ schema_version: z.literal(1),
139
+ workspace_id: z.string().regex(/^rcw_[a-f0-9]{64}$/u),
140
+ name: z.string().trim().min(1).max(200),
141
+ revision: z.number().int().min(1),
142
+ previous_revision_sha256: digestSchema.nullable(),
143
+ evidence_bundle: evidenceBundleSchema,
144
+ artifacts: z.array(artifactSchema).max(1_000),
145
+ surfaces: z.array(surfaceSchema).max(10_000),
146
+ owners: z.array(ownerSchema).max(10_000),
147
+ claims: z.array(claimSchema).max(10_000),
148
+ verifier_contracts: z.array(verifierContractSchema).max(10_000),
149
+ verifier_results: z.array(verifierResultSchema).max(100_000),
150
+ residual_unknown_ids: z.array(stableIdSchema).max(10_000),
151
+ contradictions: z.array(contradictionSchema).max(10_000),
152
+ package_proofs: z.array(packageProofSchema).max(10_000),
153
+ boundaries: z.array(boundarySchema).max(1_000),
154
+ });
155
+ export const reconstructionCoverageWorkspaceSchema = workspaceSemanticSchema.extend({ revision_sha256: digestSchema });
156
+ const closureReasonSchema = z.strictObject({
157
+ code: z.enum([
158
+ "surface-missing",
159
+ "owner-missing",
160
+ "owner-duplicate",
161
+ "owner-path-missing",
162
+ "owner-path-unknown",
163
+ "owner-undistributed",
164
+ "owner-distribution-unknown",
165
+ "authority-routing-detected",
166
+ "authority-routing-unknown",
167
+ "disposition-not-allowed",
168
+ "dependency-surface-missing",
169
+ "claim-missing",
170
+ "verifier-missing",
171
+ "verifier-duplicate",
172
+ "verifier-result-missing",
173
+ "verifier-result-stale",
174
+ "verifier-result-incompatible",
175
+ "verifier-failed",
176
+ "verifier-unknown",
177
+ "active-unknown",
178
+ "active-contradiction",
179
+ "package-proof-missing",
180
+ "package-proof-failed",
181
+ "package-proof-unknown",
182
+ ]),
183
+ subject_id: stableIdSchema,
184
+ detail: boundedTextSchema,
185
+ });
186
+ export const reconstructionClosureResultSchema = z.strictObject({
187
+ boundary_id: stableIdSchema,
188
+ status: z.enum(["partial", "failed", "unknown", "ready"]),
189
+ workspace_revision_sha256: digestSchema,
190
+ summary: z.strictObject({
191
+ required_surfaces: z.number().int().min(0),
192
+ required_claims: z.number().int().min(0),
193
+ reasons: z.number().int().min(0),
194
+ }),
195
+ reasons: z.array(closureReasonSchema).max(100_000),
196
+ recommended_probes: z
197
+ .array(z.strictObject({
198
+ operation: stableIdSchema,
199
+ subject_id: stableIdSchema,
200
+ rationale: boundedTextSchema,
201
+ }))
202
+ .max(100_000),
203
+ evidence_ids: z.array(evidenceIdSchema).max(100_000),
204
+ });
205
+ /** Build a verifier contract whose digest prevents retrospective coverage edits. */
206
+ export const createReconstructionVerifierContract = (input) => {
207
+ const parsed = verifierContractBaseSchema.parse(input);
208
+ return verifierContractSchema.parse({
209
+ ...parsed,
210
+ contract_sha256: digest(parsed),
211
+ });
212
+ };
213
+ /** Build one canonical immutable coverage-workspace revision. */
214
+ export const createReconstructionCoverageWorkspace = (input) => {
215
+ const semanticInput = {
216
+ schema_version: 1,
217
+ workspace_id: `rcw_${digest({ schema: "rea.reconstruction-coverage-workspace/v1", name: input.name })}`,
218
+ ...input,
219
+ };
220
+ const semantic = canonicalWorkspace(workspaceSemanticSchema.parse(semanticInput));
221
+ validateWorkspaceReferences(semantic);
222
+ return reconstructionCoverageWorkspaceSchema.parse({
223
+ ...semantic,
224
+ revision_sha256: digest(semantic),
225
+ });
226
+ };
227
+ /** Parse persisted coverage state and verify canonical order, references, and digests. */
228
+ export const parseReconstructionCoverageWorkspace = (input) => {
229
+ const parsed = reconstructionCoverageWorkspaceSchema.parse(input);
230
+ const rebuilt = createReconstructionCoverageWorkspace({
231
+ name: parsed.name,
232
+ revision: parsed.revision,
233
+ previous_revision_sha256: parsed.previous_revision_sha256,
234
+ evidence_bundle: parsed.evidence_bundle,
235
+ artifacts: parsed.artifacts,
236
+ surfaces: parsed.surfaces,
237
+ owners: parsed.owners,
238
+ claims: parsed.claims,
239
+ verifier_contracts: parsed.verifier_contracts,
240
+ verifier_results: parsed.verifier_results,
241
+ residual_unknown_ids: parsed.residual_unknown_ids,
242
+ contradictions: parsed.contradictions,
243
+ package_proofs: parsed.package_proofs,
244
+ boundaries: parsed.boundaries,
245
+ });
246
+ if (parsed.workspace_id !== rebuilt.workspace_id ||
247
+ parsed.revision_sha256 !== rebuilt.revision_sha256 ||
248
+ JSON.stringify(parsed) !== JSON.stringify(rebuilt))
249
+ throw new TypeError("Reconstruction coverage workspace is not canonical");
250
+ return rebuilt;
251
+ };
252
+ /** Serialize one validated coverage revision as byte-stable canonical JSON. */
253
+ export const serializeReconstructionCoverageWorkspace = (workspace) => {
254
+ const encoded = canonicalize(parseReconstructionCoverageWorkspace(workspace));
255
+ if (encoded === undefined)
256
+ throw new TypeError("Reconstruction coverage workspace is not canonical JSON");
257
+ return encoded;
258
+ };
259
+ /** Evaluate one named completion boundary without inferring undiscovered coverage. */
260
+ export const evaluateReconstructionClosure = (workspaceInput, boundaryId, nowEpochMs) => {
261
+ const workspace = parseReconstructionCoverageWorkspace(workspaceInput);
262
+ const boundary = workspace.boundaries.find(({ boundary_id: id }) => id === boundaryId);
263
+ if (boundary === undefined)
264
+ throw new TypeError(`Unknown reconstruction boundary: ${boundaryId}`);
265
+ const context = {
266
+ workspace,
267
+ boundary,
268
+ nowEpochMs,
269
+ reasons: [],
270
+ evidenceIds: new Set(),
271
+ };
272
+ evaluateReconstructionSurfaces(context);
273
+ evaluateReconstructionClaims(context);
274
+ evaluateReconstructionWorkspaceRisks(context);
275
+ evaluateReconstructionPackageProofs(context);
276
+ const reasons = [...context.reasons].sort(reasonOrder);
277
+ return reconstructionClosureResultSchema.parse({
278
+ boundary_id: boundary.boundary_id,
279
+ status: closureStatus(reasons),
280
+ workspace_revision_sha256: workspace.revision_sha256,
281
+ summary: {
282
+ required_surfaces: boundary.required_surface_ids.length,
283
+ required_claims: boundary.required_claim_ids.length,
284
+ reasons: reasons.length,
285
+ },
286
+ reasons,
287
+ recommended_probes: recommendedReconstructionProbes(reasons),
288
+ evidence_ids: [...context.evidenceIds].sort(),
289
+ });
290
+ };
291
+ const validateWorkspaceReferences = (workspace) => {
292
+ const evidenceBundle = parseEvidenceBundle(workspace.evidence_bundle);
293
+ const evidenceIds = new Set(evidenceBundle.records.map(({ evidence_id: id }) => id));
294
+ const unknownIds = new Set(evidenceBundle.unknowns.map(({ unknown_id: id }) => id));
295
+ const artifacts = assertUnique(workspace.artifacts, "artifact_id");
296
+ const surfaces = assertUnique(workspace.surfaces, "surface_id");
297
+ const claims = assertUnique(workspace.claims, "claim_id");
298
+ const contracts = assertUnique(workspace.verifier_contracts, "verifier_id");
299
+ assertUnique(workspace.contradictions, "contradiction_id");
300
+ assertUnique(workspace.package_proofs, "proof_id");
301
+ assertUnique(workspace.boundaries, "boundary_id");
302
+ for (const contract of workspace.verifier_contracts) {
303
+ const { contract_sha256: contractSha256, ...base } = contract;
304
+ if (contractSha256 !== digest(verifierContractBaseSchema.parse(base)))
305
+ throw new TypeError(`Verifier contract digest is invalid: ${contract.verifier_id}`);
306
+ }
307
+ for (const surface of workspace.surfaces)
308
+ if (!artifacts.has(surface.artifact_id))
309
+ throw new TypeError(`Surface artifact is missing: ${surface.surface_id}`);
310
+ for (const owner of workspace.owners)
311
+ if (!surfaces.has(owner.surface_id))
312
+ throw new TypeError(`Owner surface is missing: ${owner.surface_id}`);
313
+ for (const claim of workspace.claims)
314
+ if (claim.surface_ids.some((id) => !surfaces.has(id)))
315
+ throw new TypeError(`Claim surface is missing: ${claim.claim_id}`);
316
+ for (const contract of workspace.verifier_contracts)
317
+ if (contract.claim_ids.some((id) => !claims.has(id)))
318
+ throw new TypeError(`Verifier claim is missing: ${contract.verifier_id}`);
319
+ for (const result of workspace.verifier_results)
320
+ if (!contracts.has(result.verifier_id))
321
+ throw new TypeError(`Verifier result contract is missing: ${result.verifier_id}`);
322
+ const referencedEvidenceIds = [
323
+ ...workspace.artifacts.flatMap(({ evidence_ids: ids }) => ids),
324
+ ...workspace.surfaces.flatMap(({ evidence_ids: ids }) => ids),
325
+ ...workspace.verifier_results.flatMap(({ evidence_ids: ids }) => ids),
326
+ ...workspace.contradictions.flatMap(({ evidence_ids: ids }) => ids),
327
+ ...workspace.package_proofs.flatMap(({ evidence_ids: ids }) => ids),
328
+ ];
329
+ const danglingEvidence = referencedEvidenceIds.find((id) => !evidenceIds.has(id));
330
+ if (danglingEvidence !== undefined)
331
+ throw new TypeError(`Coverage workspace Evidence is missing: ${danglingEvidence}`);
332
+ const danglingUnknown = workspace.residual_unknown_ids.find((id) => !unknownIds.has(id));
333
+ if (danglingUnknown !== undefined)
334
+ throw new TypeError(`Coverage workspace residual unknown is missing: ${danglingUnknown}`);
335
+ };
336
+ const canonicalWorkspace = (workspace) => ({
337
+ ...workspace,
338
+ artifacts: sorted(workspace.artifacts, "artifact_id"),
339
+ surfaces: sorted(workspace.surfaces, "surface_id"),
340
+ owners: [...workspace.owners].sort((left, right) => left.surface_id.localeCompare(right.surface_id)),
341
+ claims: sorted(workspace.claims, "claim_id"),
342
+ verifier_contracts: sorted(workspace.verifier_contracts, "verifier_id"),
343
+ verifier_results: [...workspace.verifier_results].sort((left, right) => `${left.verifier_id}:${left.observed_at}`.localeCompare(`${right.verifier_id}:${right.observed_at}`)),
344
+ residual_unknown_ids: [...new Set(workspace.residual_unknown_ids)].sort(),
345
+ contradictions: sorted(workspace.contradictions, "contradiction_id"),
346
+ package_proofs: sorted(workspace.package_proofs, "proof_id"),
347
+ boundaries: sorted(workspace.boundaries, "boundary_id"),
348
+ });
349
+ const sorted = (items, key) => [...items].sort((left, right) => left[key].localeCompare(right[key]));
350
+ const assertUnique = (items, key) => {
351
+ const values = items.map((item) => item[key]);
352
+ if (new Set(values).size !== values.length)
353
+ throw new TypeError(`Duplicate reconstruction coverage ${String(key)}`);
354
+ return new Set(values);
355
+ };
356
+ const closureStatus = (reasons) => {
357
+ if (reasons.length === 0)
358
+ return "ready";
359
+ if (reasons.some(({ code }) => [
360
+ "verifier-failed",
361
+ "active-contradiction",
362
+ "authority-routing-detected",
363
+ "owner-path-missing",
364
+ "owner-undistributed",
365
+ "package-proof-failed",
366
+ ].includes(code)))
367
+ return "failed";
368
+ if (reasons.some(({ code }) => [
369
+ "verifier-result-stale",
370
+ "verifier-result-incompatible",
371
+ "verifier-unknown",
372
+ "active-unknown",
373
+ "owner-path-unknown",
374
+ "owner-distribution-unknown",
375
+ "authority-routing-unknown",
376
+ "package-proof-unknown",
377
+ ].includes(code)))
378
+ return "unknown";
379
+ return "partial";
380
+ };
381
+ const reasonOrder = (left, right) => `${left.code}:${left.subject_id}`.localeCompare(`${right.code}:${right.subject_id}`);
382
+ const digest = (value) => {
383
+ const encoded = canonicalize(value);
384
+ if (encoded === undefined)
385
+ throw new TypeError("Reconstruction coverage value is not canonical JSON");
386
+ return createHash("sha256").update(encoded).digest("hex");
387
+ };