rea-agents 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/README.md +80 -18
  2. package/bridge/ghidra/ReaGhidraBridge.java +48 -1
  3. package/bridge/hopper_bridge.py +259 -24
  4. package/dist/application/AnalysisClientCleanup.js +14 -0
  5. package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
  6. package/dist/application/ArtifactExtraction.js +5 -1
  7. package/dist/application/ArtifactGraphConstruction.js +3 -15
  8. package/dist/application/ArtifactInventory/classify.js +5 -1
  9. package/dist/application/ArtifactInventory/reader.js +2 -0
  10. package/dist/application/BinarySession.js +15 -4
  11. package/dist/application/BinarySessionStatus.js +32 -1
  12. package/dist/application/BrowserEvidence.js +14 -0
  13. package/dist/application/BrowserScenarioCapturePort.js +1 -0
  14. package/dist/application/BrowserScenarioCaptureService.js +53 -0
  15. package/dist/application/BrowserScenarioEvidence.js +40 -0
  16. package/dist/application/CallPathTracing.js +160 -0
  17. package/dist/application/CapabilityInventory.js +79 -15
  18. package/dist/application/CompositeProvider.js +10 -1
  19. package/dist/application/DirectAnalysis.js +7 -2
  20. package/dist/application/EnhancedLiteralTracing.js +118 -0
  21. package/dist/application/EnhancedTools.js +35 -121
  22. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
  23. package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
  24. package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
  25. package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
  26. package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
  27. package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
  28. package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
  29. package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
  30. package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
  31. package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
  32. package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
  33. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
  34. package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
  35. package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
  36. package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
  37. package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
  38. package/dist/application/ProcessCaptureEnvironment.js +26 -0
  39. package/dist/application/ProcessCaptureLifecycle.js +1 -0
  40. package/dist/application/ProcessCheckpoints.js +5 -0
  41. package/dist/application/ProcessHarness.js +45 -34
  42. package/dist/application/ProcessNormalization.js +6 -0
  43. package/dist/application/ProcessReactiveCoordinator.js +10 -3
  44. package/dist/application/ProcessReactiveEffects.js +15 -1
  45. package/dist/application/ProcessReactiveHarness.js +100 -0
  46. package/dist/application/ProcessReactiveObservations.js +101 -16
  47. package/dist/application/ProjectPermissionStore.js +1 -0
  48. package/dist/application/ReconstructionObligationAuthority.js +72 -0
  49. package/dist/application/ReconstructionObligationCandidates.js +265 -0
  50. package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
  51. package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
  52. package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
  53. package/dist/application/ReconstructionObligationLedgerService.js +72 -0
  54. package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
  55. package/dist/application/ReconstructionObligationPolicies.js +132 -0
  56. package/dist/application/ReconstructionReadinessService.js +48 -0
  57. package/dist/artifacts/ArtifactProvider.js +87 -22
  58. package/dist/browser/BrowserScenarioSecrets.js +53 -0
  59. package/dist/browser/BrowserScenarioSessionPort.js +1 -0
  60. package/dist/browser/CdpBrowserProvider.js +2 -2
  61. package/dist/browser/CdpCaptureDocuments.js +57 -0
  62. package/dist/browser/CdpCaptureStorage.js +71 -29
  63. package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
  64. package/dist/browser/CdpEndpoint.js +8 -6
  65. package/dist/browser/CdpPageCapture.js +2 -0
  66. package/dist/browser/JavaScriptRuntimeScope.js +38 -0
  67. package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
  68. package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
  69. package/dist/browser/PlaywrightScenarioActions.js +62 -0
  70. package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
  71. package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
  72. package/dist/browser/PlaywrightScenarioEvents.js +301 -0
  73. package/dist/browser/PlaywrightScenarioSession.js +300 -0
  74. package/dist/browser/V8InspectorCaptureProjection.js +110 -0
  75. package/dist/browser/V8InspectorEndpoint.js +72 -0
  76. package/dist/browser/V8InspectorProvider.js +267 -0
  77. package/dist/catalogIdentity.js +8 -0
  78. package/dist/cli/artifactCommands.js +39 -0
  79. package/dist/cli/coreAnalysisCommands.js +37 -0
  80. package/dist/cli.js +4 -0
  81. package/dist/cliApplicationCommands.js +30 -2
  82. package/dist/cliBrowserAdvancedCommands.js +17 -5
  83. package/dist/cliBrowserCommands.js +1 -0
  84. package/dist/cliBrowserScenarioCommands.js +43 -0
  85. package/dist/cliCommandNames.js +8 -0
  86. package/dist/cliJavaScriptRuntimeCommands.js +164 -0
  87. package/dist/cliObservationOptions.js +4 -0
  88. package/dist/cliPolicyCommands.js +1 -0
  89. package/dist/config/environment.js +12 -0
  90. package/dist/config/parseConfig.js +88 -17
  91. package/dist/contracts/analysisSearchContract.js +14 -0
  92. package/dist/contracts/applicationToolContracts.js +86 -2
  93. package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
  94. package/dist/contracts/artifactToolContracts.js +22 -0
  95. package/dist/contracts/browserProviderToolContracts.js +7 -0
  96. package/dist/contracts/browserScenarioToolContracts.js +81 -0
  97. package/dist/contracts/browserToolContracts.js +88 -98
  98. package/dist/contracts/enhancedInputs.js +25 -6
  99. package/dist/contracts/functionInstructionContract.js +8 -0
  100. package/dist/contracts/hopperMemoryContracts.js +22 -0
  101. package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
  102. package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
  103. package/dist/contracts/reconstructionReadinessExample.js +436 -0
  104. package/dist/contracts/toolContractExamples.js +5 -0
  105. package/dist/contracts/toolContractTypes.js +1 -0
  106. package/dist/contracts/toolContracts.js +13 -21
  107. package/dist/contracts/toolEffects.js +21 -0
  108. package/dist/contracts/toolOutputSchemaGroups.js +70 -20
  109. package/dist/contracts/toolOutputSchemaPrimitives.js +40 -0
  110. package/dist/domain/analysisErrorProjection.js +8 -0
  111. package/dist/domain/analysisSnapshot.js +2 -0
  112. package/dist/domain/artifactGraph.js +2 -0
  113. package/dist/domain/artifactInspection.js +347 -0
  114. package/dist/domain/binaryTarget.js +3 -6
  115. package/dist/domain/browserCaptureComparison.js +82 -0
  116. package/dist/domain/browserObservation.js +8 -1
  117. package/dist/domain/browserObservationErrors.js +1 -0
  118. package/dist/domain/browserObservationSchemas.js +26 -0
  119. package/dist/domain/browserScenario.js +165 -0
  120. package/dist/domain/browserScenarioCapture.js +57 -0
  121. package/dist/domain/browserScenarioCaptureValues.js +204 -0
  122. package/dist/domain/browserScenarioDiff.js +324 -0
  123. package/dist/domain/browserScenarioDiffValues.js +157 -0
  124. package/dist/domain/browserScenarioNormalization.js +45 -0
  125. package/dist/domain/browserScenarioValues.js +372 -0
  126. package/dist/domain/evidence.js +2 -0
  127. package/dist/domain/hopperValues.js +10 -0
  128. package/dist/domain/javascriptRuntimeObservation.js +170 -0
  129. package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
  130. package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
  131. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
  132. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  133. package/dist/domain/javascriptSemanticAnalysis.js +5 -4
  134. package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
  135. package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
  136. package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
  137. package/dist/domain/javascriptSemanticDataEffects.js +284 -0
  138. package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
  139. package/dist/domain/javascriptSemanticFingerprints.js +174 -0
  140. package/dist/domain/javascriptSemanticIr.js +21 -1
  141. package/dist/domain/javascriptSemanticObjects.js +115 -0
  142. package/dist/domain/javascriptSemanticPromises.js +290 -0
  143. package/dist/domain/javascriptSemanticResources.js +136 -0
  144. package/dist/domain/javascriptStaticAnalysis.js +4 -3
  145. package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
  146. package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
  147. package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
  148. package/dist/domain/processCapture.js +71 -1
  149. package/dist/domain/processCaptureReactiveValidation.js +175 -0
  150. package/dist/domain/processCaptureValidation.js +2 -0
  151. package/dist/domain/processObservation.js +15 -6
  152. package/dist/domain/processReactiveRuntime.js +21 -10
  153. package/dist/domain/processReactiveScenario.js +1 -0
  154. package/dist/domain/processReactiveTransition.js +17 -10
  155. package/dist/domain/processScenario.js +21 -0
  156. package/dist/domain/providerCleanupError.js +11 -0
  157. package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
  158. package/dist/domain/reconstructionReadiness.js +121 -0
  159. package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
  160. package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
  161. package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
  162. package/dist/domain/reconstructionReadinessSchemas.js +240 -0
  163. package/dist/domain/sourceToBundleComparison.js +335 -0
  164. package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
  165. package/dist/domain/sourceToBundleSignals.js +202 -0
  166. package/dist/domain/webCaptureDiff.js +141 -27
  167. package/dist/domain/webScreenshot.js +1 -1
  168. package/dist/domain/zipPackageFormat.js +20 -0
  169. package/dist/generatedPackageMetadata.js +1 -1
  170. package/dist/ghidra/GhidraDefaults.js +1 -1
  171. package/dist/ghidra/GhidraFunctionValues.js +13 -1
  172. package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
  173. package/dist/hopper/HopperCleanup.js +158 -0
  174. package/dist/hopper/HopperClient.js +69 -99
  175. package/dist/hopper/HopperProvider.js +20 -1
  176. package/dist/hopper/HopperRequestQueue.js +187 -0
  177. package/dist/hopper/HopperResponseStream.js +46 -0
  178. package/dist/hopper/protocol.js +1 -0
  179. package/dist/main/transport.js +24 -24
  180. package/dist/main.js +6 -0
  181. package/dist/mcpDoctor.js +25 -2
  182. package/dist/native/NativeMacOSProvider.js +1 -0
  183. package/dist/process/ProcessCommandIdentity.js +19 -0
  184. package/dist/process/ProcessOwnership.js +11 -8
  185. package/dist/server/DynamicToolAvailability.js +70 -0
  186. package/dist/server/createServer.js +46 -1
  187. package/dist/server/lifecycleProgress.js +10 -0
  188. package/dist/server/permissionFailure.js +12 -0
  189. package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
  190. package/dist/server/registerApplicationTools/helpers.js +11 -7
  191. package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
  192. package/dist/server/registerApplicationTools/readiness.js +55 -0
  193. package/dist/server/registerApplicationTools.js +6 -0
  194. package/dist/server/registerArtifactTools.js +9 -1
  195. package/dist/server/registerBrowserScenarioTool.js +25 -0
  196. package/dist/server/registerBrowserTools.js +2 -2
  197. package/dist/server/registerCloseLifecycleTool.js +57 -0
  198. package/dist/server/registerEnhancedTools.js +16 -5
  199. package/dist/server/registerEvidenceResources.js +4 -0
  200. package/dist/server/registerEvidenceTools.js +9 -2
  201. package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
  202. package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
  203. package/dist/server/registerReconstructionReadinessResource.js +39 -0
  204. package/dist/server/registerSessionStatusTool.js +11 -8
  205. package/dist/server/registerSessionTools.js +11 -57
  206. package/dist/server/sessionAvailabilityPolicy.js +10 -8
  207. package/package.json +12 -6
  208. package/skills/reverse-engineer-anything/SKILL.md +4 -3
  209. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
@@ -0,0 +1,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
+ });
@@ -0,0 +1,157 @@
1
+ import { z } from "zod";
2
+ import { browserScenarioCaptureSchema } from "./browserScenarioCapture.js";
3
+ /** Scenario artifact families compared after step alignment. */
4
+ export const browserScenarioArtifactKindSchema = z.enum([
5
+ "action_state",
6
+ "screenshot",
7
+ "dom",
8
+ "accessibility",
9
+ "url",
10
+ "history",
11
+ "storage",
12
+ "events",
13
+ ]);
14
+ const normalizableArtifactKindSchema = z.enum([
15
+ "action_state",
16
+ "dom",
17
+ "accessibility",
18
+ "url",
19
+ "history",
20
+ "storage",
21
+ "events",
22
+ ]);
23
+ const normalizationRuleSchema = z.strictObject({
24
+ rule_id: z
25
+ .string()
26
+ .trim()
27
+ .min(1)
28
+ .max(64)
29
+ .describe("Stable rule identity; rules execute in rule_id order."),
30
+ artifacts: z
31
+ .array(normalizableArtifactKindSchema)
32
+ .min(1)
33
+ .max(7)
34
+ .describe("Artifact families whose durable string fields use this rule."),
35
+ match: z
36
+ .string()
37
+ .min(1)
38
+ .max(4_096)
39
+ .describe("Exact public literal to replace; regular expressions are not used."),
40
+ replacement: z
41
+ .string()
42
+ .max(4_096)
43
+ .describe("Recorded replacement text used in normalized comparison."),
44
+ });
45
+ const normalizationInputSchema = z
46
+ .strictObject({
47
+ rules: z.array(normalizationRuleSchema).max(128).default([]),
48
+ })
49
+ .superRefine((normalization, context) => {
50
+ const ruleIds = new Set();
51
+ for (const [index, rule] of normalization.rules.entries()) {
52
+ if (ruleIds.has(rule.rule_id))
53
+ context.addIssue({
54
+ code: "custom",
55
+ path: ["rules", index, "rule_id"],
56
+ message: "Normalization rule IDs must be unique",
57
+ });
58
+ ruleIds.add(rule.rule_id);
59
+ if (new Set(rule.artifacts).size !== rule.artifacts.length)
60
+ context.addIssue({
61
+ code: "custom",
62
+ path: ["rules", index, "artifacts"],
63
+ message: "Normalization rule artifact kinds must be unique",
64
+ });
65
+ }
66
+ });
67
+ /** Input for step-aligned comparison of two browser scenario captures. */
68
+ export const compareBrowserScenariosInputSchema = z.strictObject({
69
+ before_scenario: browserScenarioCaptureSchema.describe("Earlier complete capture_browser_scenario result."),
70
+ after_scenario: browserScenarioCaptureSchema.describe("Later complete capture_browser_scenario result."),
71
+ normalization: normalizationInputSchema
72
+ .default({ rules: [] })
73
+ .describe("Bounded exact-literal rules committed in the comparison result."),
74
+ max_changes: z
75
+ .number()
76
+ .int()
77
+ .min(1)
78
+ .max(20_000)
79
+ .default(2_000)
80
+ .describe("Maximum changed or unknown artifact records to retain."),
81
+ });
82
+ const evidenceStateSchema = z.enum([
83
+ "captured",
84
+ "not_requested",
85
+ "missing",
86
+ "truncated",
87
+ ]);
88
+ const artifactDiffSchema = z.strictObject({
89
+ artifact: browserScenarioArtifactKindSchema,
90
+ status: z.enum(["changed", "unknown"]),
91
+ before_state: evidenceStateSchema,
92
+ after_state: evidenceStateSchema,
93
+ before_sha256: z
94
+ .string()
95
+ .regex(/^[a-f0-9]{64}$/u)
96
+ .nullable(),
97
+ after_sha256: z
98
+ .string()
99
+ .regex(/^[a-f0-9]{64}$/u)
100
+ .nullable(),
101
+ reason: z.string().min(1).max(2_048).nullable(),
102
+ });
103
+ const alignmentFailureSchema = z.strictObject({
104
+ code: z.enum([
105
+ "duplicate_before_step_id",
106
+ "duplicate_after_step_id",
107
+ "missing_before_step",
108
+ "missing_after_step",
109
+ "action_mismatch",
110
+ "capture_context_mismatch",
111
+ ]),
112
+ step_id: z.string().min(1).max(64).nullable(),
113
+ reason: z.string().min(1).max(2_048),
114
+ });
115
+ const builtInNormalizationSchema = z.enum([
116
+ "ignore_step_elapsed_ms",
117
+ "ignore_event_sequence",
118
+ "ignore_event_step_index",
119
+ "compare_screenshot_content_digest",
120
+ "compare_normalized_text_content",
121
+ ]);
122
+ /** Step alignment, recorded normalization, and bounded artifact differences. */
123
+ export const browserScenarioDiffSchema = z.strictObject({
124
+ schema_version: z.literal(1),
125
+ comparison_kind: z.literal("browser_scenario"),
126
+ overall_status: z.enum(["changed", "unchanged", "unknown"]),
127
+ normalization: z.strictObject({
128
+ built_in_rules: z.array(builtInNormalizationSchema).length(5),
129
+ rules: z.array(normalizationRuleSchema).max(128),
130
+ sha256: z.string().regex(/^[a-f0-9]{64}$/u),
131
+ }),
132
+ alignment: z.strictObject({
133
+ status: z.enum(["aligned", "partial", "failed"]),
134
+ aligned_steps: z.number().int().min(0).max(129),
135
+ before_only: z.array(z.string().min(1).max(64)).max(129),
136
+ after_only: z.array(z.string().min(1).max(64)).max(129),
137
+ failures: z.array(alignmentFailureSchema).max(520),
138
+ }),
139
+ steps: z
140
+ .array(z.strictObject({
141
+ step_id: z.string().min(1).max(64),
142
+ before_step_index: z.number().int().min(0).max(128),
143
+ after_step_index: z.number().int().min(0).max(128),
144
+ before_action: z.string().min(1).max(64),
145
+ after_action: z.string().min(1).max(64),
146
+ status: z.enum(["changed", "unchanged", "unknown"]),
147
+ artifact_diffs: z.array(artifactDiffSchema).max(8),
148
+ omitted_artifact_diffs: z.number().int().min(0).max(8),
149
+ }))
150
+ .max(129),
151
+ artifact_diffs: z.strictObject({
152
+ total: z.number().int().min(0).max(1_032),
153
+ retained: z.number().int().min(0).max(1_032),
154
+ omitted: z.number().int().min(0).max(1_032),
155
+ }),
156
+ limitations: z.array(z.string().min(1).max(2_048)).max(64),
157
+ });
@@ -0,0 +1,45 @@
1
+ import { createHash } from "node:crypto";
2
+ import canonicalize from "canonicalize";
3
+ const BUILT_IN_RULES = [
4
+ "ignore_step_elapsed_ms",
5
+ "ignore_event_sequence",
6
+ "ignore_event_step_index",
7
+ "compare_screenshot_content_digest",
8
+ "compare_normalized_text_content",
9
+ ];
10
+ /** Canonically order and commit a parsed browser scenario normalization policy. */
11
+ export const commitBrowserScenarioNormalization = (input) => {
12
+ const rules = [...input.rules]
13
+ .map((rule) => ({
14
+ ...rule,
15
+ artifacts: [...rule.artifacts].sort(),
16
+ }))
17
+ .sort((left, right) => left.rule_id < right.rule_id ? -1 : left.rule_id > right.rule_id ? 1 : 0);
18
+ const commitment = { built_in_rules: [...BUILT_IN_RULES], rules };
19
+ return { ...commitment, sha256: digestCanonicalJson(commitment) };
20
+ };
21
+ /** Digest one artifact after applying committed exact-literal rules. */
22
+ export const digestNormalizedScenarioValue = (value, artifact, rules) => digestCanonicalJson(normalize(value, artifact, rules));
23
+ /** Compute a canonical SHA-256 commitment for JSON-compatible domain data. */
24
+ export const digestCanonicalJson = (value) => {
25
+ const encoded = canonicalize(value);
26
+ if (encoded === undefined)
27
+ throw new TypeError("Expected canonical JSON");
28
+ return createHash("sha256").update(encoded).digest("hex");
29
+ };
30
+ const normalize = (value, artifact, rules) => {
31
+ if (artifact === "screenshot")
32
+ return value;
33
+ if (typeof value === "string")
34
+ return rules
35
+ .filter(({ artifacts }) => artifacts.includes(artifact))
36
+ .reduce((normalized, rule) => normalized.split(rule.match).join(rule.replacement), value);
37
+ if (Array.isArray(value))
38
+ return value.map((item) => normalize(item, artifact, rules));
39
+ if (typeof value !== "object" || value === null)
40
+ return value;
41
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
42
+ key,
43
+ normalize(item, artifact, rules),
44
+ ]));
45
+ };