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.
- package/README.md +80 -18
- package/bridge/ghidra/ReaGhidraBridge.java +48 -1
- package/bridge/hopper_bridge.py +259 -24
- package/dist/application/AnalysisClientCleanup.js +14 -0
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
- package/dist/application/ArtifactExtraction.js +5 -1
- package/dist/application/ArtifactGraphConstruction.js +3 -15
- package/dist/application/ArtifactInventory/classify.js +5 -1
- package/dist/application/ArtifactInventory/reader.js +2 -0
- package/dist/application/BinarySession.js +15 -4
- package/dist/application/BinarySessionStatus.js +32 -1
- package/dist/application/BrowserEvidence.js +14 -0
- package/dist/application/BrowserScenarioCapturePort.js +1 -0
- package/dist/application/BrowserScenarioCaptureService.js +53 -0
- package/dist/application/BrowserScenarioEvidence.js +40 -0
- package/dist/application/CallPathTracing.js +160 -0
- package/dist/application/CapabilityInventory.js +79 -15
- package/dist/application/CompositeProvider.js +10 -1
- package/dist/application/DirectAnalysis.js +7 -2
- package/dist/application/EnhancedLiteralTracing.js +118 -0
- package/dist/application/EnhancedTools.js +35 -121
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
- package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
- package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
- package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
- package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
- package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
- package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
- package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
- package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
- package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
- package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
- package/dist/application/ProcessCaptureEnvironment.js +26 -0
- package/dist/application/ProcessCaptureLifecycle.js +1 -0
- package/dist/application/ProcessCheckpoints.js +5 -0
- package/dist/application/ProcessHarness.js +45 -34
- package/dist/application/ProcessNormalization.js +6 -0
- package/dist/application/ProcessReactiveCoordinator.js +10 -3
- package/dist/application/ProcessReactiveEffects.js +15 -1
- package/dist/application/ProcessReactiveHarness.js +100 -0
- package/dist/application/ProcessReactiveObservations.js +101 -16
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReconstructionObligationAuthority.js +72 -0
- package/dist/application/ReconstructionObligationCandidates.js +265 -0
- package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
- package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
- package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
- package/dist/application/ReconstructionObligationLedgerService.js +72 -0
- package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
- package/dist/application/ReconstructionObligationPolicies.js +132 -0
- package/dist/application/ReconstructionReadinessService.js +48 -0
- package/dist/artifacts/ArtifactProvider.js +87 -22
- package/dist/browser/BrowserScenarioSecrets.js +53 -0
- package/dist/browser/BrowserScenarioSessionPort.js +1 -0
- package/dist/browser/CdpBrowserProvider.js +2 -2
- package/dist/browser/CdpCaptureDocuments.js +57 -0
- package/dist/browser/CdpCaptureStorage.js +71 -29
- package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
- package/dist/browser/CdpEndpoint.js +8 -6
- package/dist/browser/CdpPageCapture.js +2 -0
- package/dist/browser/JavaScriptRuntimeScope.js +38 -0
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
- package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
- package/dist/browser/PlaywrightScenarioActions.js +62 -0
- package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
- package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
- package/dist/browser/PlaywrightScenarioEvents.js +301 -0
- package/dist/browser/PlaywrightScenarioSession.js +300 -0
- package/dist/browser/V8InspectorCaptureProjection.js +110 -0
- package/dist/browser/V8InspectorEndpoint.js +72 -0
- package/dist/browser/V8InspectorProvider.js +267 -0
- package/dist/catalogIdentity.js +8 -0
- package/dist/cli/artifactCommands.js +39 -0
- package/dist/cli/coreAnalysisCommands.js +37 -0
- package/dist/cli.js +4 -0
- package/dist/cliApplicationCommands.js +30 -2
- package/dist/cliBrowserAdvancedCommands.js +17 -5
- package/dist/cliBrowserCommands.js +1 -0
- package/dist/cliBrowserScenarioCommands.js +43 -0
- package/dist/cliCommandNames.js +8 -0
- package/dist/cliJavaScriptRuntimeCommands.js +164 -0
- package/dist/cliObservationOptions.js +4 -0
- package/dist/cliPolicyCommands.js +1 -0
- package/dist/config/environment.js +12 -0
- package/dist/config/parseConfig.js +88 -17
- package/dist/contracts/analysisSearchContract.js +14 -0
- package/dist/contracts/applicationToolContracts.js +86 -2
- package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
- package/dist/contracts/artifactToolContracts.js +22 -0
- package/dist/contracts/browserProviderToolContracts.js +7 -0
- package/dist/contracts/browserScenarioToolContracts.js +81 -0
- package/dist/contracts/browserToolContracts.js +88 -98
- package/dist/contracts/enhancedInputs.js +25 -6
- package/dist/contracts/functionInstructionContract.js +8 -0
- package/dist/contracts/hopperMemoryContracts.js +22 -0
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
- package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
- package/dist/contracts/reconstructionReadinessExample.js +436 -0
- package/dist/contracts/toolContractExamples.js +5 -0
- package/dist/contracts/toolContractTypes.js +1 -0
- package/dist/contracts/toolContracts.js +13 -21
- package/dist/contracts/toolEffects.js +21 -0
- package/dist/contracts/toolOutputSchemaGroups.js +70 -20
- package/dist/contracts/toolOutputSchemaPrimitives.js +40 -0
- package/dist/domain/analysisErrorProjection.js +8 -0
- package/dist/domain/analysisSnapshot.js +2 -0
- package/dist/domain/artifactGraph.js +2 -0
- package/dist/domain/artifactInspection.js +347 -0
- package/dist/domain/binaryTarget.js +3 -6
- package/dist/domain/browserCaptureComparison.js +82 -0
- package/dist/domain/browserObservation.js +8 -1
- package/dist/domain/browserObservationErrors.js +1 -0
- package/dist/domain/browserObservationSchemas.js +26 -0
- package/dist/domain/browserScenario.js +165 -0
- package/dist/domain/browserScenarioCapture.js +57 -0
- package/dist/domain/browserScenarioCaptureValues.js +204 -0
- package/dist/domain/browserScenarioDiff.js +324 -0
- package/dist/domain/browserScenarioDiffValues.js +157 -0
- package/dist/domain/browserScenarioNormalization.js +45 -0
- package/dist/domain/browserScenarioValues.js +372 -0
- package/dist/domain/evidence.js +2 -0
- package/dist/domain/hopperValues.js +10 -0
- package/dist/domain/javascriptRuntimeObservation.js +170 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
- package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
- package/dist/domain/javascriptSemanticAnalysis.js +5 -4
- package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
- package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
- package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
- package/dist/domain/javascriptSemanticDataEffects.js +284 -0
- package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
- package/dist/domain/javascriptSemanticFingerprints.js +174 -0
- package/dist/domain/javascriptSemanticIr.js +21 -1
- package/dist/domain/javascriptSemanticObjects.js +115 -0
- package/dist/domain/javascriptSemanticPromises.js +290 -0
- package/dist/domain/javascriptSemanticResources.js +136 -0
- package/dist/domain/javascriptStaticAnalysis.js +4 -3
- package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
- package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
- package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
- package/dist/domain/processCapture.js +71 -1
- package/dist/domain/processCaptureReactiveValidation.js +175 -0
- package/dist/domain/processCaptureValidation.js +2 -0
- package/dist/domain/processObservation.js +15 -6
- package/dist/domain/processReactiveRuntime.js +21 -10
- package/dist/domain/processReactiveScenario.js +1 -0
- package/dist/domain/processReactiveTransition.js +17 -10
- package/dist/domain/processScenario.js +21 -0
- package/dist/domain/providerCleanupError.js +11 -0
- package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
- package/dist/domain/reconstructionReadiness.js +121 -0
- package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
- package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
- package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
- package/dist/domain/reconstructionReadinessSchemas.js +240 -0
- package/dist/domain/sourceToBundleComparison.js +335 -0
- package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
- package/dist/domain/sourceToBundleSignals.js +202 -0
- package/dist/domain/webCaptureDiff.js +141 -27
- package/dist/domain/webScreenshot.js +1 -1
- package/dist/domain/zipPackageFormat.js +20 -0
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +13 -1
- package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
- package/dist/hopper/HopperCleanup.js +158 -0
- package/dist/hopper/HopperClient.js +69 -99
- package/dist/hopper/HopperProvider.js +20 -1
- package/dist/hopper/HopperRequestQueue.js +187 -0
- package/dist/hopper/HopperResponseStream.js +46 -0
- package/dist/hopper/protocol.js +1 -0
- package/dist/main/transport.js +24 -24
- package/dist/main.js +6 -0
- package/dist/mcpDoctor.js +25 -2
- package/dist/native/NativeMacOSProvider.js +1 -0
- package/dist/process/ProcessCommandIdentity.js +19 -0
- package/dist/process/ProcessOwnership.js +11 -8
- package/dist/server/DynamicToolAvailability.js +70 -0
- package/dist/server/createServer.js +46 -1
- package/dist/server/lifecycleProgress.js +10 -0
- package/dist/server/permissionFailure.js +12 -0
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
- package/dist/server/registerApplicationTools/helpers.js +11 -7
- package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
- package/dist/server/registerApplicationTools/readiness.js +55 -0
- package/dist/server/registerApplicationTools.js +6 -0
- package/dist/server/registerArtifactTools.js +9 -1
- package/dist/server/registerBrowserScenarioTool.js +25 -0
- package/dist/server/registerBrowserTools.js +2 -2
- package/dist/server/registerCloseLifecycleTool.js +57 -0
- package/dist/server/registerEnhancedTools.js +16 -5
- package/dist/server/registerEvidenceResources.js +4 -0
- package/dist/server/registerEvidenceTools.js +9 -2
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
- package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
- package/dist/server/registerReconstructionReadinessResource.js +39 -0
- package/dist/server/registerSessionStatusTool.js +11 -8
- package/dist/server/registerSessionTools.js +11 -57
- package/dist/server/sessionAvailabilityPolicy.js +10 -8
- package/package.json +12 -6
- package/skills/reverse-engineer-anything/SKILL.md +4 -3
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
|
@@ -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);
|
|
@@ -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
|
+
});
|