rea-agents 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/README.md +91 -19
  2. package/bridge/ghidra/ReaGhidraBridge.java +524 -4
  3. package/bridge/hopper_bridge.py +313 -28
  4. package/dist/application/AnalysisClientCleanup.js +14 -0
  5. package/dist/application/AnalysisContextQueries.js +130 -0
  6. package/dist/application/AnalysisSnapshotCache.js +4 -2
  7. package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
  8. package/dist/application/ArtifactInventory/scanCanonical.js +1 -1
  9. package/dist/application/BinarySession.js +20 -8
  10. package/dist/application/BinarySessionComposition.js +21 -0
  11. package/dist/application/BinarySessionRecords.js +79 -9
  12. package/dist/application/BinarySessionStatus.js +31 -1
  13. package/dist/application/BrowserEvidence.js +14 -0
  14. package/dist/application/BrowserScenarioCapturePort.js +1 -0
  15. package/dist/application/BrowserScenarioCaptureService.js +53 -0
  16. package/dist/application/BrowserScenarioEvidence.js +40 -0
  17. package/dist/application/CallPathTracing.js +160 -0
  18. package/dist/application/CapabilityInventory.js +84 -14
  19. package/dist/application/ClientRegistrationStatus.js +4 -1
  20. package/dist/application/CompositeProvider.js +10 -1
  21. package/dist/application/DirectAnalysis.js +13 -4
  22. package/dist/application/EnhancedLiteralTracing.js +118 -0
  23. package/dist/application/EnhancedTools.js +71 -127
  24. package/dist/application/EvidenceLedger.js +45 -16
  25. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
  26. package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
  27. package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
  28. package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
  29. package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
  30. package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
  31. package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
  32. package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
  33. package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
  34. package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
  35. package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
  36. package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
  37. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
  38. package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
  39. package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
  40. package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
  41. package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
  42. package/dist/application/LazyAnalysisProvider.js +77 -0
  43. package/dist/application/LoopbackReplay.js +1 -1
  44. package/dist/application/NativeApiInspection.js +80 -0
  45. package/dist/application/ProcessReactiveEffects.js +6 -0
  46. package/dist/application/ProcessReactiveHarness.js +16 -1
  47. package/dist/application/ProjectPermissionStore.js +1 -0
  48. package/dist/application/ReconstructionObligationAuthority.js +72 -0
  49. package/dist/application/ReconstructionObligationCandidates.js +265 -0
  50. package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
  51. package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
  52. package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
  53. package/dist/application/ReconstructionObligationLedgerService.js +72 -0
  54. package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
  55. package/dist/application/ReconstructionObligationPolicies.js +132 -0
  56. package/dist/application/ReconstructionReadinessService.js +48 -0
  57. package/dist/application/SessionProviderRouter.js +6 -6
  58. package/dist/application/SetupClientConfiguration.js +4 -2
  59. package/dist/application/runtime.js +26 -9
  60. package/dist/artifacts/ArtifactProvider.js +87 -22
  61. package/dist/browser/BrowserScenarioSecrets.js +53 -0
  62. package/dist/browser/BrowserScenarioSessionPort.js +1 -0
  63. package/dist/browser/CdpBrowserProvider.js +2 -2
  64. package/dist/browser/CdpCaptureDocuments.js +57 -0
  65. package/dist/browser/CdpCaptureStorage.js +71 -29
  66. package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
  67. package/dist/browser/CdpEndpoint.js +8 -6
  68. package/dist/browser/CdpPageCapture.js +2 -0
  69. package/dist/browser/JavaScriptRuntimeScope.js +38 -0
  70. package/dist/browser/PlaywrightBrowserScenarioProvider.js +259 -0
  71. package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
  72. package/dist/browser/PlaywrightScenarioActions.js +62 -0
  73. package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
  74. package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
  75. package/dist/browser/PlaywrightScenarioEvents.js +301 -0
  76. package/dist/browser/PlaywrightScenarioSession.js +300 -0
  77. package/dist/browser/V8InspectorCaptureProjection.js +110 -0
  78. package/dist/browser/V8InspectorEndpoint.js +72 -0
  79. package/dist/browser/V8InspectorProvider.js +267 -0
  80. package/dist/catalogIdentity.js +11 -3
  81. package/dist/cli/artifactCommands.js +44 -3
  82. package/dist/cli/coreAnalysisCommands.js +78 -0
  83. package/dist/cli.js +4 -0
  84. package/dist/cliApplicationCommands.js +30 -2
  85. package/dist/cliBrowserAdvancedCommands.js +17 -5
  86. package/dist/cliBrowserCommands.js +1 -0
  87. package/dist/cliBrowserScenarioCommands.js +43 -0
  88. package/dist/cliCommandNames.js +9 -0
  89. package/dist/cliJavaScriptRuntimeCommands.js +164 -0
  90. package/dist/cliObservationOptions.js +4 -0
  91. package/dist/cliPolicyCommands.js +1 -0
  92. package/dist/config/environment.js +12 -0
  93. package/dist/config/parseConfig.js +88 -17
  94. package/dist/contracts/analysisSearchContract.js +14 -0
  95. package/dist/contracts/applicationToolContracts.js +86 -2
  96. package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
  97. package/dist/contracts/artifactToolContracts.js +29 -2
  98. package/dist/contracts/browserProviderToolContracts.js +7 -0
  99. package/dist/contracts/browserScenarioToolContracts.js +81 -0
  100. package/dist/contracts/browserToolContracts.js +88 -98
  101. package/dist/contracts/enhancedInputs.js +35 -6
  102. package/dist/contracts/functionInstructionContract.js +8 -0
  103. package/dist/contracts/functionWorkflowToolContracts.js +30 -0
  104. package/dist/contracts/hopperMemoryContracts.js +22 -0
  105. package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
  106. package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
  107. package/dist/contracts/promptContracts.js +14 -15
  108. package/dist/contracts/reconstructionReadinessExample.js +436 -0
  109. package/dist/contracts/toolContractExamples.js +8 -0
  110. package/dist/contracts/toolContractTypes.js +1 -0
  111. package/dist/contracts/toolContracts.js +34 -25
  112. package/dist/contracts/toolEffects.js +25 -0
  113. package/dist/contracts/toolOutputSchemaGroups.js +120 -30
  114. package/dist/contracts/toolOutputSchemaPrimitives.js +37 -0
  115. package/dist/domain/analysisErrorPresentation.js +8 -2
  116. package/dist/domain/analysisErrorProjection.js +9 -0
  117. package/dist/domain/artifactInspection.js +347 -0
  118. package/dist/domain/browserCaptureComparison.js +82 -0
  119. package/dist/domain/browserObservation.js +8 -1
  120. package/dist/domain/browserObservationErrors.js +1 -0
  121. package/dist/domain/browserObservationSchemas.js +26 -0
  122. package/dist/domain/browserScenario.js +165 -0
  123. package/dist/domain/browserScenarioCapture.js +57 -0
  124. package/dist/domain/browserScenarioCaptureValues.js +204 -0
  125. package/dist/domain/browserScenarioDiff.js +324 -0
  126. package/dist/domain/browserScenarioDiffValues.js +157 -0
  127. package/dist/domain/browserScenarioNormalization.js +45 -0
  128. package/dist/domain/browserScenarioValues.js +372 -0
  129. package/dist/domain/bytecodeProvider.js +179 -0
  130. package/dist/domain/conformancePackage.js +169 -0
  131. package/dist/domain/conformanceReplay.js +104 -0
  132. package/dist/domain/conformanceTrustGate.js +193 -0
  133. package/dist/domain/customProtocolCapture.js +137 -0
  134. package/dist/domain/eventProcessTree.js +214 -0
  135. package/dist/domain/evidenceBundle.js +47 -0
  136. package/dist/domain/hopperStartupFailure.js +1 -1
  137. package/dist/domain/hopperValues.js +14 -1
  138. package/dist/domain/inputIssueProjection.js +7 -1
  139. package/dist/domain/javascriptRuntimeObservation.js +170 -0
  140. package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
  141. package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
  142. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
  143. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  144. package/dist/domain/javascriptSemanticAnalysis.js +13 -16
  145. package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
  146. package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
  147. package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
  148. package/dist/domain/javascriptSemanticDataEffects.js +284 -0
  149. package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
  150. package/dist/domain/javascriptSemanticFingerprints.js +174 -0
  151. package/dist/domain/javascriptSemanticIr.js +21 -1
  152. package/dist/domain/javascriptSemanticObjects.js +115 -0
  153. package/dist/domain/javascriptSemanticPromises.js +290 -0
  154. package/dist/domain/javascriptSemanticResources.js +136 -0
  155. package/dist/domain/javascriptSourceParser.js +15 -0
  156. package/dist/domain/javascriptStaticAnalysis.js +12 -15
  157. package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
  158. package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
  159. package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
  160. package/dist/domain/mobileApplicationInvestigation.js +159 -0
  161. package/dist/domain/nativeApiBoundary.js +130 -0
  162. package/dist/domain/objcSwiftMetadata.js +150 -0
  163. package/dist/domain/packageAnalysis.js +123 -0
  164. package/dist/domain/peInspection.js +196 -0
  165. package/dist/domain/processCapture.js +8 -2
  166. package/dist/domain/processReactiveScenario.js +1 -0
  167. package/dist/domain/processReactiveTransition.js +9 -4
  168. package/dist/domain/protocolCapture.js +243 -0
  169. package/dist/domain/providerCleanupError.js +11 -0
  170. package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
  171. package/dist/domain/reconstructionReadiness.js +121 -0
  172. package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
  173. package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
  174. package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
  175. package/dist/domain/reconstructionReadinessSchemas.js +240 -0
  176. package/dist/domain/sourceToBundleComparison.js +335 -0
  177. package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
  178. package/dist/domain/sourceToBundleSignals.js +202 -0
  179. package/dist/domain/webCaptureDiff.js +141 -27
  180. package/dist/domain/webScreenshot.js +1 -1
  181. package/dist/dotnet/ManagedStaticProvider.js +34 -5
  182. package/dist/generatedMcpToolCatalog.js +37 -0
  183. package/dist/generatedPackageMetadata.js +4 -4
  184. package/dist/ghidra/GhidraDefaults.js +1 -1
  185. package/dist/ghidra/GhidraFunctionValues.js +40 -2
  186. package/dist/ghidra/GhidraProviderCapabilities.js +47 -38
  187. package/dist/hopper/HopperCleanup.js +158 -0
  188. package/dist/hopper/HopperClient.js +93 -99
  189. package/dist/hopper/HopperProvider.js +61 -30
  190. package/dist/hopper/HopperRequestQueue.js +215 -0
  191. package/dist/hopper/HopperResponseStream.js +46 -0
  192. package/dist/hopper/protocol.js +63 -20
  193. package/dist/main/transport.js +24 -27
  194. package/dist/main.js +14 -6
  195. package/dist/mcpDoctor.js +27 -4
  196. package/dist/mcpStartupPolicy.js +14 -0
  197. package/dist/process/ProcessCommandIdentity.js +19 -0
  198. package/dist/process/ProcessOwnership.js +11 -8
  199. package/dist/server/DynamicToolAvailability.js +70 -0
  200. package/dist/server/LazyToolCatalog.js +134 -0
  201. package/dist/server/createServer.js +92 -154
  202. package/dist/server/hydrateServerTools.js +191 -0
  203. package/dist/server/lifecycleProgress.js +10 -0
  204. package/dist/server/permissionFailure.js +12 -0
  205. package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
  206. package/dist/server/registerApplicationTools/helpers.js +11 -7
  207. package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
  208. package/dist/server/registerApplicationTools/readiness.js +55 -0
  209. package/dist/server/registerApplicationTools.js +6 -0
  210. package/dist/server/registerArtifactTools.js +9 -1
  211. package/dist/server/registerBrowserScenarioTool.js +25 -0
  212. package/dist/server/registerBrowserTools.js +2 -2
  213. package/dist/server/registerCloseLifecycleTool.js +57 -0
  214. package/dist/server/registerEnhancedTools.js +28 -7
  215. package/dist/server/registerEvidenceResources.js +54 -101
  216. package/dist/server/registerEvidenceTools.js +9 -2
  217. package/dist/server/registerInvestigationTools.js +1 -2
  218. package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
  219. package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
  220. package/dist/server/registerReconstructionObligationLedgerResource.js +32 -0
  221. package/dist/server/registerReconstructionReadinessResource.js +32 -0
  222. package/dist/server/registerSessionRecordTools.js +82 -12
  223. package/dist/server/registerSessionStatusTool.js +11 -8
  224. package/dist/server/registerSessionTools.js +32 -58
  225. package/dist/server/sessionAvailabilityPolicy.js +10 -8
  226. package/dist/server/toolRegistrationOptions.js +21 -6
  227. package/dist/server/toolResult.js +12 -0
  228. package/package.json +28 -11
  229. package/scripts/package-runner-bootstrap.mjs +72 -0
  230. package/scripts/rea.mjs +11 -1
  231. package/skills/reverse-engineer-anything/SKILL.md +10 -3
@@ -0,0 +1,57 @@
1
+ import { z } from "zod";
2
+ import { browserScenarioCompletenessSchema, browserScenarioEventSchema, browserScenarioStepSchema, } from "./browserScenarioCaptureValues.js";
3
+ export { browserScenarioCompletenessSchema, browserScenarioEventSchema, browserScenarioStepSchema, browserStepArtifactsSchema, captureStateSchema, } from "./browserScenarioCaptureValues.js";
4
+ /** Step-indexed, explicitly bounded browser scenario observation. */
5
+ export const browserScenarioCaptureSchema = z
6
+ .strictObject({
7
+ schema_version: z.literal(1),
8
+ browser: z.strictObject({
9
+ mode: z.enum(["launch", "connect"]),
10
+ process_ownership: z.enum(["provider-owned", "external"]),
11
+ cleanup: z.enum(["terminated-owned-process", "disconnected-external"]),
12
+ product: z.string().min(1).max(1_024),
13
+ version: z.string().min(1).max(256),
14
+ }),
15
+ scenario: z.strictObject({
16
+ start_origin: z.string().min(1).max(2_048),
17
+ allowed_origins: z.array(z.string().min(1).max(2_048)).min(1).max(32),
18
+ action_count: z.number().int().min(1).max(128),
19
+ secret_references: z.array(z.string().min(1).max(64)).max(64),
20
+ }),
21
+ duration_ms: z.number().int().min(0),
22
+ steps: z.array(browserScenarioStepSchema).min(1).max(129),
23
+ events: z.strictObject({
24
+ retained: z.number().int().min(0),
25
+ dropped: z.number().int().min(0),
26
+ items: z.array(browserScenarioEventSchema).max(100_000),
27
+ }),
28
+ completeness: browserScenarioCompletenessSchema,
29
+ limitations: z.array(z.string().min(1).max(2_048)).max(64),
30
+ })
31
+ .superRefine((capture, context) => {
32
+ if (capture.events.retained !== capture.events.items.length)
33
+ context.addIssue({
34
+ code: "custom",
35
+ path: ["events", "retained"],
36
+ message: "Retained event count must equal items length",
37
+ });
38
+ if (capture.steps.length !== capture.scenario.action_count + 1)
39
+ context.addIssue({
40
+ code: "custom",
41
+ path: ["steps"],
42
+ message: "Capture must include initial state and one entry per action",
43
+ });
44
+ const expectedIndices = capture.steps.map((_, index) => index);
45
+ if (capture.steps.some(({ step_index: index }, offset) => index !== expectedIndices[offset]))
46
+ context.addIssue({
47
+ code: "custom",
48
+ path: ["steps"],
49
+ message: "Step indices must be contiguous from zero",
50
+ });
51
+ if (capture.events.items.some(({ step_index: index }) => index >= capture.steps.length))
52
+ context.addIssue({
53
+ code: "custom",
54
+ path: ["events", "items"],
55
+ message: "Event references an unknown step index",
56
+ });
57
+ });
@@ -0,0 +1,204 @@
1
+ import { createHash } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { sanitizedBrowserUrlSchema } from "./browserObservation.js";
4
+ import { webScreenshotArtifactSchema } from "./webScreenshot.js";
5
+ const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
6
+ const boundedTextSchema = z.string().max(1_048_576);
7
+ const textArtifactSchema = z
8
+ .strictObject({
9
+ sha256: digestSchema,
10
+ bytes: z
11
+ .number()
12
+ .int()
13
+ .min(0)
14
+ .max(16 * 1_024 * 1_024),
15
+ text: boundedTextSchema,
16
+ })
17
+ .superRefine((artifact, context) => {
18
+ const bytes = Buffer.from(artifact.text);
19
+ const sha256 = createHash("sha256").update(bytes).digest("hex");
20
+ if (bytes.byteLength !== artifact.bytes || sha256 !== artifact.sha256)
21
+ context.addIssue({
22
+ code: "custom",
23
+ message: "Text artifact digest or size mismatch",
24
+ });
25
+ });
26
+ export const captureStateSchema = (value) => z.discriminatedUnion("state", [
27
+ z.strictObject({ state: z.literal("captured"), value }),
28
+ z.strictObject({
29
+ state: z.literal("not_requested"),
30
+ }),
31
+ z.strictObject({
32
+ state: z.literal("missing"),
33
+ reason: z.string().min(1).max(1_024),
34
+ }),
35
+ z.strictObject({
36
+ state: z.literal("truncated"),
37
+ observed: z.number().int().min(0),
38
+ retained: z.number().int().min(0),
39
+ reason: z.string().min(1).max(1_024),
40
+ }),
41
+ ]);
42
+ const historySchema = z.strictObject({
43
+ length: z.number().int().min(0),
44
+ current_url: sanitizedBrowserUrlSchema,
45
+ navigation_entries: z
46
+ .array(z.strictObject({
47
+ type: z.enum([
48
+ "navigate",
49
+ "reload",
50
+ "back_forward",
51
+ "prerender",
52
+ "unknown",
53
+ ]),
54
+ name: sanitizedBrowserUrlSchema,
55
+ }))
56
+ .max(256),
57
+ });
58
+ const storageValueSchema = z.strictObject({
59
+ name: z.string().max(1_024),
60
+ value_state: z.enum(["hashed", "redacted-secret"]),
61
+ value_sha256: digestSchema.nullable(),
62
+ });
63
+ const storageSnapshotSchema = z.strictObject({
64
+ cookies: z
65
+ .array(z.strictObject({
66
+ name: z.string().max(1_024),
67
+ domain: z.string().max(2_048),
68
+ path: z.string().max(4_096),
69
+ secure: z.boolean(),
70
+ http_only: z.boolean(),
71
+ same_site: z.enum(["Strict", "Lax", "None"]),
72
+ value_state: z.enum(["hashed", "redacted-secret"]),
73
+ value_sha256: digestSchema.nullable(),
74
+ }))
75
+ .max(512),
76
+ local_storage: z.array(storageValueSchema).max(512),
77
+ session_storage: z.array(storageValueSchema).max(512),
78
+ });
79
+ export const browserStepArtifactsSchema = z.strictObject({
80
+ screenshot: captureStateSchema(webScreenshotArtifactSchema),
81
+ dom: captureStateSchema(textArtifactSchema),
82
+ accessibility: captureStateSchema(textArtifactSchema),
83
+ url: captureStateSchema(sanitizedBrowserUrlSchema),
84
+ history: captureStateSchema(historySchema),
85
+ storage: captureStateSchema(storageSnapshotSchema),
86
+ });
87
+ const eventBase = {
88
+ sequence: z.number().int().min(1),
89
+ step_index: z.number().int().min(0),
90
+ };
91
+ const eventUrl = sanitizedBrowserUrlSchema.nullable();
92
+ export const browserScenarioEventSchema = z.discriminatedUnion("kind", [
93
+ z.strictObject({
94
+ ...eventBase,
95
+ kind: z.literal("console"),
96
+ level: z.string().min(1).max(64),
97
+ text: z.string().max(65_536),
98
+ url: eventUrl,
99
+ }),
100
+ z.strictObject({
101
+ ...eventBase,
102
+ kind: z.literal("page-error"),
103
+ message: z.string().max(65_536),
104
+ stack: z.string().max(262_144).nullable(),
105
+ }),
106
+ z.strictObject({
107
+ ...eventBase,
108
+ kind: z.enum(["request", "response", "request-failed"]),
109
+ method: z.string().min(1).max(32),
110
+ url: sanitizedBrowserUrlSchema,
111
+ resource_type: z.string().min(1).max(64),
112
+ status: z.number().int().min(100).max(599).nullable(),
113
+ header_names: z.array(z.string().max(256)).max(256),
114
+ failure: z.string().max(1_024).nullable(),
115
+ }),
116
+ z.strictObject({
117
+ ...eventBase,
118
+ kind: z.enum(["websocket-opened", "websocket-closed"]),
119
+ url: sanitizedBrowserUrlSchema,
120
+ }),
121
+ z.strictObject({
122
+ ...eventBase,
123
+ kind: z.enum(["websocket-frame-sent", "websocket-frame-received"]),
124
+ url: sanitizedBrowserUrlSchema,
125
+ payload_type: z.enum(["text", "binary"]),
126
+ payload_bytes: z.number().int().min(0),
127
+ payload_text: z.string().max(65_536).nullable(),
128
+ truncated: z.boolean(),
129
+ }),
130
+ z.strictObject({
131
+ ...eventBase,
132
+ kind: z.enum([
133
+ "frame-attached",
134
+ "frame-detached",
135
+ "frame-navigated",
136
+ "worker-created",
137
+ "worker-closed",
138
+ "popup-opened",
139
+ "popup-closed",
140
+ ]),
141
+ url: eventUrl,
142
+ name: z.string().max(1_024).nullable(),
143
+ }),
144
+ z.strictObject({
145
+ ...eventBase,
146
+ kind: z.literal("download-cancelled"),
147
+ suggested_filename: z.string().max(1_024),
148
+ url: sanitizedBrowserUrlSchema,
149
+ }),
150
+ ]);
151
+ const completenessSectionSchema = z.enum([
152
+ "action",
153
+ "screenshot",
154
+ "dom",
155
+ "accessibility",
156
+ "url",
157
+ "history",
158
+ "storage",
159
+ "events",
160
+ "frames",
161
+ "workers",
162
+ "popups",
163
+ "websockets",
164
+ ]);
165
+ export const browserScenarioCompletenessSchema = z
166
+ .strictObject({
167
+ status: z.enum(["complete", "incomplete", "truncated"]),
168
+ equality_eligible: z.boolean(),
169
+ missing_sections: z.array(completenessSectionSchema),
170
+ truncated_sections: z.array(completenessSectionSchema),
171
+ })
172
+ .superRefine((value, context) => {
173
+ const expectedStatus = value.truncated_sections.length > 0
174
+ ? "truncated"
175
+ : value.missing_sections.length > 0
176
+ ? "incomplete"
177
+ : "complete";
178
+ if (value.status !== expectedStatus)
179
+ context.addIssue({
180
+ code: "custom",
181
+ path: ["status"],
182
+ message: "Completeness status does not match section accounting",
183
+ });
184
+ if (value.equality_eligible !== (expectedStatus === "complete"))
185
+ context.addIssue({
186
+ code: "custom",
187
+ path: ["equality_eligible"],
188
+ message: "Missing or truncated evidence is never equality-eligible",
189
+ });
190
+ });
191
+ export const browserScenarioStepSchema = z.strictObject({
192
+ step_index: z.number().int().min(0),
193
+ step_id: z.string().min(1).max(64),
194
+ action: z.string().min(1).max(64),
195
+ status: z.enum(["completed", "failed", "cancelled"]),
196
+ elapsed_ms: z.number().int().min(0),
197
+ before_url: sanitizedBrowserUrlSchema,
198
+ after_url: sanitizedBrowserUrlSchema,
199
+ error: z.string().max(4_096).nullable(),
200
+ event_sequence_start: z.number().int().min(1),
201
+ event_sequence_end: z.number().int().min(0),
202
+ artifacts: browserStepArtifactsSchema,
203
+ completeness: browserScenarioCompletenessSchema,
204
+ });
@@ -0,0 +1,324 @@
1
+ import {} from "./browserScenarioCapture.js";
2
+ import { browserScenarioDiffSchema, } from "./browserScenarioDiffValues.js";
3
+ import { commitBrowserScenarioNormalization, digestCanonicalJson, digestNormalizedScenarioValue, } from "./browserScenarioNormalization.js";
4
+ export { browserScenarioDiffSchema, compareBrowserScenariosInputSchema, } from "./browserScenarioDiffValues.js";
5
+ /** Compare scenario steps by stable step ID and apply only committed literal rules. */
6
+ export const compareBrowserScenarios = (input) => {
7
+ const normalization = commitBrowserScenarioNormalization(input.normalization);
8
+ const alignment = alignScenarioSteps(input.before_scenario, input.after_scenario);
9
+ const compared = compareAlignedSteps({
10
+ beforeCapture: input.before_scenario,
11
+ afterCapture: input.after_scenario,
12
+ alignment,
13
+ rules: normalization.rules,
14
+ maxChanges: input.max_changes,
15
+ });
16
+ const failures = [...alignment.failures, ...compared.failures];
17
+ const alignmentStatus = failures.length === 0
18
+ ? "aligned"
19
+ : compared.steps.length === 0
20
+ ? "failed"
21
+ : "partial";
22
+ const stepStatuses = compared.steps.map(({ status }) => status);
23
+ const equalityEligible = input.before_scenario.completeness.equality_eligible &&
24
+ input.after_scenario.completeness.equality_eligible &&
25
+ alignmentStatus === "aligned";
26
+ const overallStatus = stepStatuses.includes("changed")
27
+ ? "changed"
28
+ : stepStatuses.includes("unknown") || !equalityEligible
29
+ ? "unknown"
30
+ : "unchanged";
31
+ return browserScenarioDiffSchema.parse({
32
+ schema_version: 1,
33
+ comparison_kind: "browser_scenario",
34
+ overall_status: overallStatus,
35
+ normalization,
36
+ alignment: {
37
+ status: alignmentStatus,
38
+ aligned_steps: compared.steps.length,
39
+ before_only: alignment.beforeOnly,
40
+ after_only: alignment.afterOnly,
41
+ failures,
42
+ },
43
+ steps: compared.steps,
44
+ artifact_diffs: {
45
+ total: compared.totalArtifactDiffs,
46
+ retained: compared.totalArtifactDiffs -
47
+ Math.max(0, compared.totalArtifactDiffs - input.max_changes),
48
+ omitted: Math.max(0, compared.totalArtifactDiffs - input.max_changes),
49
+ },
50
+ limitations: [
51
+ "Steps align only by exact, unique step_id; missing, duplicate, or action-mismatched steps are reported and prevent an unchanged claim.",
52
+ "Only declared literal normalization rules are applied, in canonical rule_id order, to already-redacted durable capture fields.",
53
+ "Elapsed time and event sequence/index values are excluded as volatile; screenshot bytes are compared by content digest.",
54
+ "A changed status proves an observed normalized difference. Missing or truncated evidence, incompatible capture context, or alignment failure prevents an unchanged claim.",
55
+ ...(compared.totalArtifactDiffs > input.max_changes
56
+ ? [
57
+ `${String(compared.totalArtifactDiffs - input.max_changes)} artifact difference record(s) were omitted by max_changes.`,
58
+ ]
59
+ : []),
60
+ ],
61
+ });
62
+ };
63
+ const alignScenarioSteps = (beforeCapture, afterCapture) => {
64
+ const beforeDuplicates = duplicateStepIds(beforeCapture.steps);
65
+ const afterDuplicates = duplicateStepIds(afterCapture.steps);
66
+ const before = uniqueSteps(beforeCapture.steps, beforeDuplicates);
67
+ const after = uniqueSteps(afterCapture.steps, afterDuplicates);
68
+ const beforeOnly = [...before.keys()]
69
+ .filter((stepId) => !after.has(stepId))
70
+ .sort();
71
+ const afterOnly = [...after.keys()]
72
+ .filter((stepId) => !before.has(stepId))
73
+ .sort();
74
+ const failures = [
75
+ ...duplicateFailures("before", beforeDuplicates),
76
+ ...duplicateFailures("after", afterDuplicates),
77
+ ...missingFailures("before", afterOnly),
78
+ ...missingFailures("after", beforeOnly),
79
+ ];
80
+ if (!captureContextsMatch(beforeCapture, afterCapture))
81
+ failures.push({
82
+ code: "capture_context_mismatch",
83
+ step_id: null,
84
+ reason: "Browser product/version or scenario origin scope differs between captures.",
85
+ });
86
+ return {
87
+ before,
88
+ after,
89
+ beforeOnly,
90
+ afterOnly,
91
+ failures,
92
+ orderedStepIds: beforeCapture.steps
93
+ .map(({ step_id }) => step_id)
94
+ .filter((stepId, index, values) => values.indexOf(stepId) === index &&
95
+ before.has(stepId) &&
96
+ after.has(stepId)),
97
+ };
98
+ };
99
+ const compareAlignedSteps = (input) => {
100
+ const remaining = { value: input.maxChanges };
101
+ let totalArtifactDiffs = 0;
102
+ const steps = [];
103
+ const failures = [];
104
+ for (const stepId of input.alignment.orderedStepIds) {
105
+ const beforeStep = input.alignment.before.get(stepId);
106
+ const afterStep = input.alignment.after.get(stepId);
107
+ if (beforeStep === undefined || afterStep === undefined)
108
+ continue;
109
+ if (beforeStep.action !== afterStep.action)
110
+ failures.push({
111
+ code: "action_mismatch",
112
+ step_id: stepId,
113
+ reason: `Aligned step action changed from ${beforeStep.action} to ${afterStep.action}.`,
114
+ });
115
+ const artifacts = compareStepArtifacts({
116
+ beforeCapture: input.beforeCapture,
117
+ beforeStep,
118
+ afterCapture: input.afterCapture,
119
+ afterStep,
120
+ rules: input.rules,
121
+ });
122
+ const reportable = artifacts.flatMap(({ diff }) => diff === null ? [] : [diff]);
123
+ totalArtifactDiffs += reportable.length;
124
+ const retained = reportable.slice(0, remaining.value);
125
+ remaining.value -= retained.length;
126
+ const statuses = artifacts.map(({ status }) => status);
127
+ steps.push({
128
+ step_id: stepId,
129
+ before_step_index: beforeStep.step_index,
130
+ after_step_index: afterStep.step_index,
131
+ before_action: beforeStep.action,
132
+ after_action: afterStep.action,
133
+ status: statuses.includes("changed")
134
+ ? "changed"
135
+ : statuses.includes("unknown")
136
+ ? "unknown"
137
+ : "unchanged",
138
+ artifact_diffs: retained,
139
+ omitted_artifact_diffs: reportable.length - retained.length,
140
+ });
141
+ }
142
+ return { steps, failures, totalArtifactDiffs };
143
+ };
144
+ const duplicateStepIds = (steps) => {
145
+ const seen = new Set();
146
+ const duplicates = new Set();
147
+ for (const { step_id: stepId } of steps) {
148
+ if (seen.has(stepId))
149
+ duplicates.add(stepId);
150
+ seen.add(stepId);
151
+ }
152
+ return duplicates;
153
+ };
154
+ const uniqueSteps = (steps, duplicates) => new Map(steps.flatMap((step) => duplicates.has(step.step_id) ? [] : [[step.step_id, step]]));
155
+ const duplicateFailures = (side, duplicates) => [...duplicates].sort().map((stepId) => ({
156
+ code: side === "before"
157
+ ? "duplicate_before_step_id"
158
+ : "duplicate_after_step_id",
159
+ step_id: stepId,
160
+ reason: `${side} capture contains duplicate step_id ${stepId}.`,
161
+ }));
162
+ const missingFailures = (side, stepIds) => stepIds.map((stepId) => ({
163
+ code: side === "before" ? "missing_before_step" : "missing_after_step",
164
+ step_id: stepId,
165
+ reason: `Step ${stepId} is missing from the ${side} capture.`,
166
+ }));
167
+ const captureContextsMatch = (before, after) => before.browser.product === after.browser.product &&
168
+ before.browser.version === after.browser.version &&
169
+ before.scenario.start_origin === after.scenario.start_origin &&
170
+ digestCanonicalJson([...before.scenario.allowed_origins].sort()) ===
171
+ digestCanonicalJson([...after.scenario.allowed_origins].sort());
172
+ const compareStepArtifacts = (input) => {
173
+ const common = {
174
+ beforeStep: input.beforeStep,
175
+ afterStep: input.afterStep,
176
+ rules: input.rules,
177
+ };
178
+ return [
179
+ compareCapturedValues({
180
+ ...common,
181
+ artifact: "action_state",
182
+ before: captured(actionState(input.beforeStep)),
183
+ after: captured(actionState(input.afterStep)),
184
+ }),
185
+ compareCapturedValues({
186
+ ...common,
187
+ artifact: "screenshot",
188
+ before: input.beforeStep.artifacts.screenshot,
189
+ after: input.afterStep.artifacts.screenshot,
190
+ project: screenshotProjection,
191
+ }),
192
+ compareCapturedValues({
193
+ ...common,
194
+ artifact: "dom",
195
+ before: input.beforeStep.artifacts.dom,
196
+ after: input.afterStep.artifacts.dom,
197
+ project: ({ text }) => text,
198
+ }),
199
+ compareCapturedValues({
200
+ ...common,
201
+ artifact: "accessibility",
202
+ before: input.beforeStep.artifacts.accessibility,
203
+ after: input.afterStep.artifacts.accessibility,
204
+ project: ({ text }) => text,
205
+ }),
206
+ compareCapturedValues({
207
+ ...common,
208
+ artifact: "url",
209
+ before: input.beforeStep.artifacts.url,
210
+ after: input.afterStep.artifacts.url,
211
+ }),
212
+ compareCapturedValues({
213
+ ...common,
214
+ artifact: "history",
215
+ before: input.beforeStep.artifacts.history,
216
+ after: input.afterStep.artifacts.history,
217
+ }),
218
+ compareCapturedValues({
219
+ ...common,
220
+ artifact: "storage",
221
+ before: input.beforeStep.artifacts.storage,
222
+ after: input.afterStep.artifacts.storage,
223
+ }),
224
+ compareCapturedValues({
225
+ ...common,
226
+ artifact: "events",
227
+ before: captured(eventsFor(input.beforeCapture, input.beforeStep.step_index)),
228
+ after: captured(eventsFor(input.afterCapture, input.afterStep.step_index)),
229
+ beforeState: eventEvidenceState(input.beforeCapture, input.beforeStep),
230
+ afterState: eventEvidenceState(input.afterCapture, input.afterStep),
231
+ }),
232
+ ];
233
+ };
234
+ const compareCapturedValues = (input) => {
235
+ const { artifact, before, after, beforeStep, afterStep, rules, project = (value) => value, } = input;
236
+ const beforeState = input.beforeState ?? artifactEvidenceState(artifact, before, beforeStep);
237
+ const afterState = input.afterState ?? artifactEvidenceState(artifact, after, afterStep);
238
+ if (beforeState === "not_requested" && afterState === "not_requested")
239
+ return { artifact, status: "not_compared", diff: null };
240
+ const beforeSha256 = beforeState === "captured" && before.state === "captured"
241
+ ? digestNormalizedScenarioValue(project(before.value), artifact, rules)
242
+ : null;
243
+ const afterSha256 = afterState === "captured" && after.state === "captured"
244
+ ? digestNormalizedScenarioValue(project(after.value), artifact, rules)
245
+ : null;
246
+ if (beforeState !== "captured" ||
247
+ afterState !== "captured" ||
248
+ before.state !== "captured" ||
249
+ after.state !== "captured")
250
+ return {
251
+ artifact,
252
+ status: "unknown",
253
+ diff: {
254
+ artifact,
255
+ status: "unknown",
256
+ before_state: beforeState,
257
+ after_state: afterState,
258
+ before_sha256: beforeSha256,
259
+ after_sha256: afterSha256,
260
+ reason: `Artifact coverage is not comparable (${beforeState} before, ${afterState} after).`,
261
+ },
262
+ };
263
+ if (beforeSha256 === null || afterSha256 === null)
264
+ throw new TypeError("Captured artifact digest refinement failed");
265
+ if (beforeSha256 === afterSha256)
266
+ return { artifact, status: "unchanged", diff: null };
267
+ return {
268
+ artifact,
269
+ status: "changed",
270
+ diff: {
271
+ artifact,
272
+ status: "changed",
273
+ before_state: beforeState,
274
+ after_state: afterState,
275
+ before_sha256: beforeSha256,
276
+ after_sha256: afterSha256,
277
+ reason: null,
278
+ },
279
+ };
280
+ };
281
+ const artifactEvidenceState = (artifact, state, step) => {
282
+ if (state.state !== "captured")
283
+ return state.state;
284
+ const section = artifact === "action_state" ? "action" : artifact;
285
+ if (step.completeness.truncated_sections.includes(section))
286
+ return "truncated";
287
+ if (step.completeness.missing_sections.includes(section))
288
+ return "missing";
289
+ return "captured";
290
+ };
291
+ const eventEvidenceState = (capture, step) => {
292
+ const sections = [
293
+ "events",
294
+ "frames",
295
+ "workers",
296
+ "popups",
297
+ "websockets",
298
+ ];
299
+ if (capture.events.dropped > 0 ||
300
+ sections.some((section) => capture.completeness.truncated_sections.includes(section) ||
301
+ step.completeness.truncated_sections.includes(section)))
302
+ return "truncated";
303
+ if (sections.some((section) => capture.completeness.missing_sections.includes(section) ||
304
+ step.completeness.missing_sections.includes(section)))
305
+ return "missing";
306
+ return "captured";
307
+ };
308
+ const actionState = (step) => ({
309
+ action: step.action,
310
+ status: step.status,
311
+ before_url: step.before_url,
312
+ after_url: step.after_url,
313
+ error: step.error,
314
+ });
315
+ const eventsFor = (capture, stepIndex) => capture.events.items.flatMap(({ sequence: _sequence, step_index: eventStepIndex, ...event }) => eventStepIndex === stepIndex ? [event] : []);
316
+ const captured = (value) => ({
317
+ state: "captured",
318
+ value,
319
+ });
320
+ const screenshotProjection = (screenshot) => ({
321
+ sha256: screenshot.sha256,
322
+ bytes: screenshot.bytes,
323
+ media_type: screenshot.media_type,
324
+ });