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,347 @@
1
+ import { createHash } from "node:crypto";
2
+ import canonicalize from "canonicalize";
3
+ import { z } from "zod";
4
+ import { artifactInventoryResultSchema, } from "./artifactGraph.js";
5
+ import { evidenceSchema, parseEvidence } from "./evidence.js";
6
+ import { jsonValueSchema } from "./jsonValue.js";
7
+ const boundedTextSchema = z.string().min(1).max(4_096);
8
+ const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
9
+ const artifactIdSchema = z.string().regex(/^art_[a-f0-9]{64}$/u);
10
+ const inspectionLimitsSchema = z.strictObject({
11
+ max_observations: z.number().int().min(1).max(500).default(100),
12
+ max_relationships: z.number().int().min(1).max(500).default(100),
13
+ max_hypotheses: z.number().int().min(1).max(100).default(20),
14
+ max_unexplored_branches: z.number().int().min(1).max(100).default(20),
15
+ max_next_probes: z.number().int().min(1).max(50).default(20),
16
+ max_substeps: z.literal(1).default(1),
17
+ });
18
+ export const artifactInspectionLimitsSchema = inspectionLimitsSchema;
19
+ const inspectionObservationSchema = z.strictObject({
20
+ observation_id: z.string().regex(/^aio_[a-f0-9]{64}$/u),
21
+ kind: z.enum(["root-manifest", "artifact", "occurrence", "integrity"]),
22
+ subject: boundedTextSchema,
23
+ value: jsonValueSchema,
24
+ evidence_id: evidenceIdSchema,
25
+ });
26
+ const inspectionRelationshipSchema = z.strictObject({
27
+ relationship_id: z.string().regex(/^air_[a-f0-9]{64}$/u),
28
+ relation: z.enum([
29
+ "contains",
30
+ "extracts",
31
+ "slice-of",
32
+ "embeds",
33
+ "loads",
34
+ "maps-source",
35
+ "derived-from",
36
+ ]),
37
+ source_artifact_id: artifactIdSchema,
38
+ target_artifact_id: artifactIdSchema,
39
+ occurrence_id: z.string().regex(/^occ_[a-f0-9]{64}$/u),
40
+ logical_path: boundedTextSchema.nullable(),
41
+ evidence_id: evidenceIdSchema,
42
+ });
43
+ const inspectionHypothesisSchema = z.strictObject({
44
+ hypothesis_id: z.string().regex(/^aih_[a-f0-9]{64}$/u),
45
+ statement: boundedTextSchema,
46
+ confidence: z.enum(["medium", "low"]),
47
+ basis_evidence_ids: z.array(evidenceIdSchema).min(1).max(8),
48
+ limitation: boundedTextSchema,
49
+ });
50
+ const inspectionContradictionSchema = z.strictObject({
51
+ contradiction_id: z.string().regex(/^aic_[a-f0-9]{64}$/u),
52
+ statement: boundedTextSchema,
53
+ occurrence_id: z.string().regex(/^occ_[a-f0-9]{64}$/u),
54
+ declared_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
55
+ observed_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
56
+ evidence_id: evidenceIdSchema,
57
+ });
58
+ const nextProbeSchema = z.strictObject({
59
+ operation: z.string().min(1).max(128),
60
+ rationale: boundedTextSchema,
61
+ authority: z.enum([
62
+ "artifact-bytes",
63
+ "managed-static-analysis",
64
+ "native-analysis-provider",
65
+ "ast-static-analysis",
66
+ ]),
67
+ });
68
+ const unexploredBranchSchema = z.strictObject({
69
+ branch_id: z.string().regex(/^aib_[a-f0-9]{64}$/u),
70
+ reason: z.enum([
71
+ "page-limit",
72
+ "format-specific-analysis-required",
73
+ "unknown-format",
74
+ ]),
75
+ detail: boundedTextSchema,
76
+ next_probe: nextProbeSchema.nullable(),
77
+ evidence_id: evidenceIdSchema,
78
+ });
79
+ /** Provider-neutral inspection result with its atomic source Evidence. */
80
+ export const artifactInspectionResultSchema = z.strictObject({
81
+ schema_version: z.literal(1),
82
+ inspection_id: z.string().regex(/^ai_[a-f0-9]{64}$/u),
83
+ subject: z.strictObject({
84
+ manifest_id: z.string().regex(/^agm_[a-f0-9]{64}$/u),
85
+ root_artifact_id: artifactIdSchema,
86
+ root_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
87
+ root_format: z.string().min(1).max(128),
88
+ }),
89
+ substeps: z
90
+ .array(z.strictObject({
91
+ substep_id: z.string().regex(/^ais_[a-f0-9]{64}$/u),
92
+ operation: z.literal("inventory_artifact"),
93
+ status: z.literal("completed"),
94
+ evidence_id: evidenceIdSchema,
95
+ evidence: evidenceSchema,
96
+ }))
97
+ .min(1)
98
+ .max(1),
99
+ observations: z.array(inspectionObservationSchema).max(500),
100
+ derived_relationships: z.array(inspectionRelationshipSchema).max(500),
101
+ hypotheses: z.array(inspectionHypothesisSchema).max(100),
102
+ contradictions: z.array(inspectionContradictionSchema).max(100),
103
+ unexplored_branches: z.array(unexploredBranchSchema).max(100),
104
+ next_probes: z.array(nextProbeSchema).max(50),
105
+ coverage: z.strictObject({
106
+ status: z.enum(["complete-within-substeps", "partial", "truncated"]),
107
+ substeps_completed: z.literal(1),
108
+ observations_retained: z.number().int().min(0),
109
+ observations_omitted: z.number().int().min(0),
110
+ relationships_retained: z.number().int().min(0),
111
+ relationships_omitted: z.number().int().min(0),
112
+ hypotheses_retained: z.number().int().min(0),
113
+ hypotheses_omitted: z.number().int().min(0),
114
+ unexplored_branches_retained: z.number().int().min(0),
115
+ unexplored_branches_omitted: z.number().int().min(0),
116
+ next_probes_retained: z.number().int().min(0),
117
+ next_probes_omitted: z.number().int().min(0),
118
+ }),
119
+ evidence_links: z.array(evidenceIdSchema).min(1).max(1),
120
+ limitations: z.array(boundedTextSchema).max(1_000),
121
+ });
122
+ /** Project one successful inventory Evidence record into an inspection report. */
123
+ export const createArtifactInspection = (inventoryInput, limits) => {
124
+ const inventoryEvidence = parseInventoryEvidence(inventoryInput);
125
+ const inventory = artifactInventoryResultSchema.parse(inventoryEvidence.normalized_result);
126
+ const observations = allObservations(inventory, inventoryEvidence.evidence_id);
127
+ const relationships = allRelationships(inventory, inventoryEvidence.evidence_id);
128
+ const hypotheses = allHypotheses(inventory, inventoryEvidence.evidence_id);
129
+ const nextProbes = probesFor(inventory);
130
+ const branches = allUnexploredBranches(inventory, inventoryEvidence.evidence_id, nextProbes);
131
+ const retained = {
132
+ observations: observations.slice(0, limits.max_observations),
133
+ relationships: relationships.slice(0, limits.max_relationships),
134
+ hypotheses: hypotheses.slice(0, limits.max_hypotheses),
135
+ branches: branches.slice(0, limits.max_unexplored_branches),
136
+ probes: nextProbes.slice(0, limits.max_next_probes),
137
+ };
138
+ const omissions = {
139
+ observations: observations.length - retained.observations.length,
140
+ relationships: relationships.length - retained.relationships.length,
141
+ hypotheses: hypotheses.length - retained.hypotheses.length,
142
+ branches: branches.length - retained.branches.length,
143
+ probes: nextProbes.length - retained.probes.length,
144
+ };
145
+ const semantic = {
146
+ schema_version: 1,
147
+ subject: {
148
+ manifest_id: inventory.manifest.manifest_id,
149
+ root_artifact_id: inventory.manifest.root_artifact_id,
150
+ root_sha256: inventory.manifest.root_sha256,
151
+ root_format: inventory.manifest.root_format,
152
+ },
153
+ substeps: [
154
+ {
155
+ substep_id: `ais_${digestCanonical({
156
+ operation: "inventory_artifact",
157
+ evidence_id: inventoryEvidence.evidence_id,
158
+ })}`,
159
+ operation: "inventory_artifact",
160
+ status: "completed",
161
+ evidence_id: inventoryEvidence.evidence_id,
162
+ evidence: inventoryEvidence,
163
+ },
164
+ ],
165
+ observations: retained.observations,
166
+ derived_relationships: retained.relationships,
167
+ hypotheses: retained.hypotheses,
168
+ contradictions: contradictions(inventory, inventoryEvidence.evidence_id),
169
+ unexplored_branches: retained.branches,
170
+ next_probes: retained.probes,
171
+ coverage: {
172
+ status: inspectionCoverage(inventory, omissions),
173
+ substeps_completed: 1,
174
+ observations_retained: retained.observations.length,
175
+ observations_omitted: omissions.observations,
176
+ relationships_retained: retained.relationships.length,
177
+ relationships_omitted: omissions.relationships,
178
+ hypotheses_retained: retained.hypotheses.length,
179
+ hypotheses_omitted: omissions.hypotheses,
180
+ unexplored_branches_retained: retained.branches.length,
181
+ unexplored_branches_omitted: omissions.branches,
182
+ next_probes_retained: retained.probes.length,
183
+ next_probes_omitted: omissions.probes,
184
+ },
185
+ evidence_links: [inventoryEvidence.evidence_id],
186
+ limitations: uniqueSorted([
187
+ ...inventory.limitations,
188
+ "Inspection derives bounded static hypotheses and next probes; it does not execute the artifact or claim format-specific semantics.",
189
+ ]),
190
+ };
191
+ return artifactInspectionResultSchema.parse({
192
+ ...semantic,
193
+ inspection_id: `ai_${digestCanonical(semantic)}`,
194
+ });
195
+ };
196
+ const parseInventoryEvidence = (input) => {
197
+ const evidence = parseEvidence(input);
198
+ if (evidence.operation !== "inventory_artifact")
199
+ throw new TypeError("Artifact inspection substep requires inventory_artifact Evidence");
200
+ return evidence;
201
+ };
202
+ const allObservations = (inventory, evidenceId) => [
203
+ observation("root-manifest", inventory.manifest.root_artifact_id, inventory.manifest, evidenceId),
204
+ ...inventory.nodes.items.map((node) => observation("artifact", node.artifact_id, node, evidenceId)),
205
+ ...inventory.occurrences.items.map((occurrence) => observation("occurrence", occurrence.occurrence_id, occurrence, evidenceId)),
206
+ ...inventory.integrity_contradictions.map((value) => observation("integrity", value.contradiction_id, value, evidenceId)),
207
+ ];
208
+ const observation = (kind, subject, value, evidenceId) => {
209
+ const semantic = {
210
+ kind,
211
+ subject,
212
+ value: jsonValueSchema.parse(value),
213
+ evidence_id: evidenceId,
214
+ };
215
+ return { observation_id: `aio_${digestCanonical(semantic)}`, ...semantic };
216
+ };
217
+ const allRelationships = (inventory, evidenceId) => inventory.edges.items.map((edge) => {
218
+ const semantic = {
219
+ relation: edge.relation,
220
+ source_artifact_id: edge.parent_artifact_id,
221
+ target_artifact_id: edge.child_artifact_id,
222
+ occurrence_id: edge.occurrence_id,
223
+ logical_path: edge.logical_path,
224
+ evidence_id: evidenceId,
225
+ };
226
+ return {
227
+ relationship_id: `air_${digestCanonical(semantic)}`,
228
+ ...semantic,
229
+ };
230
+ });
231
+ const allHypotheses = (inventory, evidenceId) => {
232
+ const statements = inventory.manifest.root_format === "pe"
233
+ ? [
234
+ "The PE artifact may contain managed CLI metadata or native code; inventory bytes alone do not distinguish them.",
235
+ ]
236
+ : ["asar", "javascript-bundle"].includes(inventory.manifest.root_format)
237
+ ? [
238
+ "The artifact may contain a recoverable JavaScript application graph.",
239
+ ]
240
+ : [];
241
+ return statements.map((statement) => {
242
+ const semantic = {
243
+ statement,
244
+ confidence: "low",
245
+ basis_evidence_ids: [evidenceId],
246
+ limitation: "Format classification proposes a bounded follow-up; it is not semantic proof.",
247
+ };
248
+ return { hypothesis_id: `aih_${digestCanonical(semantic)}`, ...semantic };
249
+ });
250
+ };
251
+ const contradictions = (inventory, evidenceId) => inventory.integrity_contradictions.map((value) => {
252
+ const semantic = {
253
+ statement: `Declared integrity for ${value.logical_path} contradicts observed bytes.`,
254
+ occurrence_id: value.occurrence_id,
255
+ declared_sha256: value.declared_sha256,
256
+ observed_sha256: value.observed_sha256,
257
+ evidence_id: evidenceId,
258
+ };
259
+ return {
260
+ contradiction_id: `aic_${digestCanonical(semantic)}`,
261
+ ...semantic,
262
+ };
263
+ });
264
+ const probesFor = (inventory) => {
265
+ const format = inventory.manifest.root_format;
266
+ if (["asar", "javascript-bundle", "directory"].includes(format))
267
+ return [
268
+ {
269
+ operation: "analyze_javascript_application",
270
+ rationale: "Recover bounded JavaScript modules, source maps, Electron boundaries, and application relationships.",
271
+ authority: "ast-static-analysis",
272
+ },
273
+ ];
274
+ if (format === "pe")
275
+ return [
276
+ {
277
+ operation: "inspect_managed_artifact",
278
+ rationale: "Check execution-free CLI metadata before choosing a deep native provider.",
279
+ authority: "managed-static-analysis",
280
+ },
281
+ {
282
+ operation: "open_binary",
283
+ rationale: "Open as native code only when managed inspection reports a native or mixed boundary.",
284
+ authority: "native-analysis-provider",
285
+ },
286
+ ];
287
+ if (["mach-o", "mach-o-universal", "elf"].includes(format))
288
+ return [
289
+ {
290
+ operation: "open_binary",
291
+ rationale: "Bind one deep native provider before function-level analysis.",
292
+ authority: "native-analysis-provider",
293
+ },
294
+ ];
295
+ if (format === "plist")
296
+ return [
297
+ {
298
+ operation: "inspect_plist",
299
+ rationale: "Parse property-list values without executing the artifact.",
300
+ authority: "artifact-bytes",
301
+ },
302
+ ];
303
+ return [];
304
+ };
305
+ const allUnexploredBranches = (inventory, evidenceId, nextProbes) => {
306
+ const pages = [
307
+ ["nodes", inventory.nodes.next_offset],
308
+ ["occurrences", inventory.occurrences.next_offset],
309
+ ["relationships", inventory.edges.next_offset],
310
+ ];
311
+ const branches = pages.flatMap(([collection, nextOffset]) => nextOffset === null
312
+ ? []
313
+ : [
314
+ branch("page-limit", `${collection} continue at offset ${String(nextOffset)}.`, null, evidenceId),
315
+ ]);
316
+ branches.push(...nextProbes.map((probe) => branch("format-specific-analysis-required", `${probe.operation} was not run by provider-neutral artifact inspection.`, probe, evidenceId)));
317
+ if (inventory.manifest.root_format === "unknown")
318
+ branches.push(branch("unknown-format", "Artifact bytes did not match an admitted format classifier.", null, evidenceId));
319
+ return branches;
320
+ };
321
+ const branch = (reason, detail, nextProbe, evidenceId) => {
322
+ const semantic = {
323
+ reason,
324
+ detail,
325
+ next_probe: nextProbe,
326
+ evidence_id: evidenceId,
327
+ };
328
+ return { branch_id: `aib_${digestCanonical(semantic)}`, ...semantic };
329
+ };
330
+ const inspectionCoverage = (inventory, omissions) => {
331
+ if (Object.values(omissions).some((count) => count > 0))
332
+ return "truncated";
333
+ return [
334
+ inventory.nodes.next_offset,
335
+ inventory.occurrences.next_offset,
336
+ inventory.edges.next_offset,
337
+ ].some((offset) => offset !== null)
338
+ ? "partial"
339
+ : "complete-within-substeps";
340
+ };
341
+ const digestCanonical = (value) => {
342
+ const encoded = canonicalize(value);
343
+ if (encoded === undefined)
344
+ throw new TypeError("Artifact inspection could not canonicalize");
345
+ return createHash("sha256").update(encoded).digest("hex");
346
+ };
347
+ const uniqueSorted = (values) => [...new Set(values)].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
@@ -0,0 +1,82 @@
1
+ import { z } from "zod";
2
+ import { browserScenarioDiffSchema, compareBrowserScenarios, compareBrowserScenariosInputSchema, } from "./browserScenarioDiff.js";
3
+ import { compareWebCaptures, captureSnapshotSchema, webCaptureDiffSchema, } from "./webCaptureDiff.js";
4
+ /** Top-level MCP shape for legacy page or step-indexed scenario comparison. */
5
+ export const browserCaptureComparisonWireSchema = z
6
+ .strictObject({
7
+ before: captureSnapshotSchema
8
+ .optional()
9
+ .describe("Earlier passive web-page capture; pair with after."),
10
+ after: captureSnapshotSchema
11
+ .optional()
12
+ .describe("Later passive web-page capture; pair with before."),
13
+ before_scenario: compareBrowserScenariosInputSchema.shape.before_scenario
14
+ .optional()
15
+ .describe("Earlier browser scenario capture; pair with after_scenario."),
16
+ after_scenario: compareBrowserScenariosInputSchema.shape.after_scenario
17
+ .optional()
18
+ .describe("Later browser scenario capture; pair with before_scenario."),
19
+ normalization: compareBrowserScenariosInputSchema.shape.normalization.describe("Scenario-only exact-literal normalization policy."),
20
+ max_changes: z
21
+ .number()
22
+ .int()
23
+ .min(1)
24
+ .max(20_000)
25
+ .default(2_000)
26
+ .describe("Maximum normalized change records to retain."),
27
+ })
28
+ .superRefine((input, context) => {
29
+ const hasPassive = input.before !== undefined || input.after !== undefined;
30
+ const hasScenario = input.before_scenario !== undefined || input.after_scenario !== undefined;
31
+ if (hasPassive === hasScenario)
32
+ context.addIssue({
33
+ code: "custom",
34
+ message: "Provide exactly one complete pair: before/after or before_scenario/after_scenario",
35
+ });
36
+ if (hasPassive && (input.before === undefined || input.after === undefined))
37
+ context.addIssue({
38
+ code: "custom",
39
+ message: "Passive comparison requires both before and after",
40
+ });
41
+ if (hasScenario &&
42
+ (input.before_scenario === undefined ||
43
+ input.after_scenario === undefined))
44
+ context.addIssue({
45
+ code: "custom",
46
+ message: "Browser scenario comparison requires both before_scenario and after_scenario",
47
+ });
48
+ if (hasPassive && input.normalization.rules.length > 0)
49
+ context.addIssue({
50
+ code: "custom",
51
+ path: ["normalization"],
52
+ message: "Literal normalization rules apply only to browser scenario captures",
53
+ });
54
+ });
55
+ /** Parsed legacy page snapshots or step-indexed browser scenarios. */
56
+ export const browserCaptureComparisonInputSchema = browserCaptureComparisonWireSchema.transform((input) => {
57
+ if (input.before_scenario !== undefined &&
58
+ input.after_scenario !== undefined)
59
+ return {
60
+ before_scenario: input.before_scenario,
61
+ after_scenario: input.after_scenario,
62
+ normalization: input.normalization,
63
+ max_changes: input.max_changes,
64
+ };
65
+ if (input.before !== undefined && input.after !== undefined)
66
+ return {
67
+ before: input.before,
68
+ after: input.after,
69
+ max_changes: input.max_changes,
70
+ };
71
+ throw new TypeError("Capture comparison pair refinement failed");
72
+ });
73
+ /** Result from passive page or browser scenario comparison. */
74
+ export const browserCaptureComparisonSchema = z.union([
75
+ browserScenarioDiffSchema,
76
+ webCaptureDiffSchema,
77
+ ]);
78
+ /** Dispatch a parsed capture comparison to its pure domain comparator. */
79
+ export const compareBrowserCaptures = (input) => isScenarioComparison(input)
80
+ ? compareBrowserScenarios(input)
81
+ : compareWebCaptures(input);
82
+ const isScenarioComparison = (input) => "before_scenario" in input;
@@ -201,9 +201,16 @@ export const inspectWebPageInputSchema = z
201
201
  websocket_shape_approved: z.boolean().default(false),
202
202
  include_script_sources: z.boolean().default(false),
203
203
  include_storage_keys: z.boolean().default(false),
204
+ include_storage_fingerprints: z.boolean().default(false),
204
205
  limits: browserInspectionLimitsSchema.default(DEFAULT_BROWSER_INSPECTION_LIMITS),
205
206
  })
206
207
  .superRefine((input, context) => {
208
+ if (input.include_storage_fingerprints && !input.include_storage_keys)
209
+ context.addIssue({
210
+ code: "custom",
211
+ path: ["include_storage_fingerprints"],
212
+ message: "Storage fingerprints require storage key capture",
213
+ });
207
214
  if (input.limits.max_script_source_bytes >
208
215
  input.limits.max_total_script_source_bytes)
209
216
  context.addIssue({
@@ -261,7 +268,7 @@ export const inspectWebPageInputSchema = z
261
268
  message: "Per-field accessibility text limit cannot exceed the total text limit",
262
269
  });
263
270
  });
264
- const sanitizedBrowserUrlSchema = z.object({
271
+ export const sanitizedBrowserUrlSchema = z.object({
265
272
  url: z.string().max(MAX_SANITIZED_BROWSER_URL_CHARS),
266
273
  origin: z.string().max(2_048).nullable(),
267
274
  query_parameter_names: z
@@ -0,0 +1 @@
1
+ export {};
@@ -57,6 +57,12 @@ const browserAxNodeSchema = z.object({
57
57
  name: z.string().nullable(),
58
58
  description: z.string().nullable(),
59
59
  ignored: z.boolean(),
60
+ states: z
61
+ .array(z.object({
62
+ name: z.string(),
63
+ value: z.union([z.boolean(), z.number(), z.string()]),
64
+ }))
65
+ .default([]),
60
66
  });
61
67
  const browserScriptSourceSchema = z.discriminatedUnion("included", [
62
68
  z.object({ included: z.literal(false), reason: z.string() }),
@@ -280,6 +286,26 @@ export const webPageInspectionSchema = z.object({
280
286
  session_storage_keys: z.array(z.string()),
281
287
  indexed_db_names: z.array(z.string()),
282
288
  cache_names: z.array(z.string()),
289
+ content_fingerprints: z
290
+ .array(z.object({
291
+ scope: z.enum([
292
+ "cookie",
293
+ "local_storage",
294
+ "session_storage",
295
+ "indexed_db_schema",
296
+ "indexed_db_record",
297
+ "cache_entry",
298
+ ]),
299
+ identity_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
300
+ value_sha256: z
301
+ .string()
302
+ .regex(/^[a-f0-9]{64}$/u)
303
+ .nullable(),
304
+ complete: z.boolean(),
305
+ }))
306
+ .default([]),
307
+ fingerprint_algorithm: z.literal("sha256").default("sha256"),
308
+ fingerprints_complete: z.boolean().default(false),
283
309
  values_redacted: z.literal(true),
284
310
  }),
285
311
  limitations: z.array(z.string()),
@@ -0,0 +1,165 @@
1
+ import { z } from "zod";
2
+ import { BROWSER_SCENARIO_LIMITS, browserScenarioActionSchema, browserScenarioAllowedOriginsSchema, browserScenarioBrowserSchema, browserScenarioCaptureLimitsSchema, browserScenarioCaptureSchema, browserScenarioEnvironmentSchema, browserScenarioRedactionSchema, browserScenarioRequestReplaySchema, browserScenarioSecretSchema, browserScenarioStorageSchema, browserScenarioUrlSchema, } from "./browserScenarioValues.js";
3
+ export { BROWSER_SCENARIO_LIMITS, browserScenarioActionSchema, browserScenarioUrlSchema, browserScenarioValueSchema, } from "./browserScenarioValues.js";
4
+ const scenarioShapeSchema = z.strictObject({
5
+ schema_version: z.literal(1),
6
+ browser: browserScenarioBrowserSchema,
7
+ start_url: browserScenarioUrlSchema,
8
+ allowed_origins: browserScenarioAllowedOriginsSchema,
9
+ environment: browserScenarioEnvironmentSchema,
10
+ actions: z
11
+ .array(browserScenarioActionSchema)
12
+ .min(1)
13
+ .max(BROWSER_SCENARIO_LIMITS.actions),
14
+ storage: browserScenarioStorageSchema,
15
+ request_replay: browserScenarioRequestReplaySchema,
16
+ secrets: z
17
+ .array(browserScenarioSecretSchema)
18
+ .max(BROWSER_SCENARIO_LIMITS.secrets)
19
+ .default([]),
20
+ redaction: browserScenarioRedactionSchema,
21
+ capture: browserScenarioCaptureSchema,
22
+ limits: browserScenarioCaptureLimitsSchema,
23
+ approved: z.literal(true),
24
+ });
25
+ const addIssue = (context, path, message) => context.addIssue({ code: "custom", path, message });
26
+ const assertUnique = (values, path, label, context) => {
27
+ if (new Set(values).size !== values.length)
28
+ addIssue(context, path, `${label} must be unique`);
29
+ };
30
+ const originFor = (destination) => new URL(destination.url).origin;
31
+ const assertAllowedDestination = (destination, allowedOrigins, path, context) => {
32
+ if (!allowedOrigins.has(originFor(destination)))
33
+ addIssue(context, path, `URL origin ${originFor(destination)} is not declared in allowed_origins`);
34
+ };
35
+ const secretReferencesInValue = (value) => (value?.source === "secret" ? [value.secret_id] : []);
36
+ const destinationSecretReferences = (destination) => destination.query.flatMap(({ value }) => secretReferencesInValue(value));
37
+ const actionSecretReferences = (action) => {
38
+ if (action.action === "fill" || action.action === "select_option")
39
+ return secretReferencesInValue(action.value);
40
+ if (action.action === "goto")
41
+ return destinationSecretReferences(action.destination);
42
+ return [];
43
+ };
44
+ const validateStorage = (scenario, allowedOrigins, context) => {
45
+ const references = [];
46
+ scenario.storage.cookies.forEach((cookie, index) => {
47
+ assertAllowedDestination(cookie.destination, allowedOrigins, ["storage", "cookies", index, "destination"], context);
48
+ references.push(...secretReferencesInValue(cookie.value));
49
+ references.push(...destinationSecretReferences(cookie.destination));
50
+ });
51
+ for (const collection of ["local_storage", "session_storage"])
52
+ scenario.storage[collection].forEach((storage, index) => {
53
+ if (!allowedOrigins.has(storage.origin))
54
+ addIssue(context, ["storage", collection, index, "origin"], `Storage origin ${storage.origin} is not declared in allowed_origins`);
55
+ for (const entry of storage.entries)
56
+ references.push(...secretReferencesInValue(entry.value));
57
+ });
58
+ return references;
59
+ };
60
+ const SENSITIVE_HEADERS = new Set([
61
+ "authorization",
62
+ "cookie",
63
+ "proxy-authorization",
64
+ "set-cookie",
65
+ ]);
66
+ const FORBIDDEN_REPLAY_HEADERS = new Set([
67
+ "content-length",
68
+ "location",
69
+ "transfer-encoding",
70
+ ]);
71
+ const validateRequestReplay = (scenario, allowedOrigins, context) => {
72
+ if (scenario.request_replay.mode === "disabled")
73
+ return [];
74
+ assertUnique(scenario.request_replay.routes.map(({ route_id: id }) => id), ["request_replay", "routes"], "Replay route IDs", context);
75
+ assertUnique(scenario.request_replay.routes.map(({ method, request }) => `${method} ${request.url}?${request.query
76
+ .map(({ name, value }) => value.source === "secret"
77
+ ? `${name}=\${secret:${value.secret_id}}`
78
+ : `${name}=${value.value}`)
79
+ .join("&")}`), ["request_replay", "routes"], "Replay request matchers", context);
80
+ const references = [];
81
+ scenario.request_replay.routes.forEach((route, routeIndex) => {
82
+ assertAllowedDestination(route.request, allowedOrigins, ["request_replay", "routes", routeIndex, "request"], context);
83
+ references.push(...destinationSecretReferences(route.request));
84
+ if (route.response.kind === "redirect") {
85
+ assertAllowedDestination(route.response.destination, allowedOrigins, ["request_replay", "routes", routeIndex, "response", "destination"], context);
86
+ references.push(...destinationSecretReferences(route.response.destination));
87
+ return;
88
+ }
89
+ assertUnique(route.response.headers.map(({ name }) => name.toLowerCase()), ["request_replay", "routes", routeIndex, "response", "headers"], "Replay response header names", context);
90
+ route.response.headers.forEach((header, headerIndex) => {
91
+ if (FORBIDDEN_REPLAY_HEADERS.has(header.name))
92
+ addIssue(context, [
93
+ "request_replay",
94
+ "routes",
95
+ routeIndex,
96
+ "response",
97
+ "headers",
98
+ headerIndex,
99
+ ], `Replay header ${header.name} must be modeled by the provider`);
100
+ if (SENSITIVE_HEADERS.has(header.name) &&
101
+ header.value.source === "literal")
102
+ addIssue(context, [
103
+ "request_replay",
104
+ "routes",
105
+ routeIndex,
106
+ "response",
107
+ "headers",
108
+ headerIndex,
109
+ "value",
110
+ ], `Credential header ${header.name} requires a declared secret`);
111
+ references.push(...secretReferencesInValue(header.value));
112
+ });
113
+ references.push(...secretReferencesInValue(route.response.body));
114
+ });
115
+ return references;
116
+ };
117
+ const validateSecretReferences = (scenario, references, context) => {
118
+ const declared = new Set(scenario.secrets.map(({ secret_id: id }) => id));
119
+ for (const reference of references)
120
+ if (!declared.has(reference))
121
+ addIssue(context, ["secrets"], `Secret reference ${reference} is not declared`);
122
+ const used = new Set(references);
123
+ scenario.secrets.forEach(({ secret_id: id }, index) => {
124
+ if (!used.has(id))
125
+ addIssue(context, ["secrets", index, "secret_id"], `Secret declaration ${id} is unused`);
126
+ });
127
+ };
128
+ const validateRedactionCoverage = (scenario, context) => {
129
+ const redactedQueries = new Set(scenario.redaction.query_parameter_names);
130
+ const destinations = [scenario.start_url];
131
+ for (const action of scenario.actions)
132
+ if (action.action === "goto")
133
+ destinations.push(action.destination);
134
+ for (const cookie of scenario.storage.cookies)
135
+ destinations.push(cookie.destination);
136
+ if (scenario.request_replay.mode === "exact")
137
+ for (const route of scenario.request_replay.routes) {
138
+ destinations.push(route.request);
139
+ if (route.response.kind === "redirect")
140
+ destinations.push(route.response.destination);
141
+ }
142
+ for (const destination of destinations)
143
+ for (const query of destination.query)
144
+ if (query.value.source === "secret" &&
145
+ !redactedQueries.has(query.name.toLowerCase()))
146
+ addIssue(context, ["redaction", "query_parameter_names"], `Secret query parameter ${query.name} must be declared for redaction`);
147
+ };
148
+ const validateBrowserScenario = (scenario, context) => {
149
+ const allowedOrigins = new Set(scenario.allowed_origins);
150
+ assertAllowedDestination(scenario.start_url, allowedOrigins, ["start_url"], context);
151
+ assertUnique(scenario.actions.map(({ step_id: id }) => id), ["actions"], "Action step IDs", context);
152
+ assertUnique(scenario.secrets.map(({ secret_id: id }) => id), ["secrets"], "Secret IDs", context);
153
+ assertUnique(scenario.secrets.map(({ environment_variable: name }) => name), ["secrets"], "Secret environment variables", context);
154
+ const references = scenario.actions.flatMap(actionSecretReferences);
155
+ for (const [index, action] of scenario.actions.entries())
156
+ if (action.action === "goto")
157
+ assertAllowedDestination(action.destination, allowedOrigins, ["actions", index, "destination"], context);
158
+ references.push(...destinationSecretReferences(scenario.start_url));
159
+ references.push(...validateStorage(scenario, allowedOrigins, context));
160
+ references.push(...validateRequestReplay(scenario, allowedOrigins, context));
161
+ validateSecretReferences(scenario, references, context);
162
+ validateRedactionCoverage(scenario, context);
163
+ };
164
+ /** Strict provider-neutral contract for one bounded browser capture scenario. */
165
+ export const browserScenarioSchema = scenarioShapeSchema.superRefine(validateBrowserScenario);