rea-agents 2.1.0 → 2.3.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 +84 -18
- package/dist/application/AndroidApplicationService.js +17 -0
- package/dist/application/AppleApplicationService.js +17 -0
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +162 -0
- package/dist/application/BinarySession.js +9 -2
- package/dist/application/BinarySessionStatus.js +39 -1
- package/dist/application/CapabilityInventory.js +15 -0
- package/dist/application/ClientRegistrationStatus.js +11 -4
- package/dist/application/CommandShimReplay.js +6 -3
- package/dist/application/CompositeProvider.js +5 -2
- package/dist/application/DirectAnalysis.js +1 -20
- package/dist/application/DirectAnalysisStatus.js +83 -0
- package/dist/application/Doctor.js +34 -25
- package/dist/application/DoctorDiagnostics.js +1 -4
- package/dist/application/DoctorProjection.js +43 -0
- package/dist/application/DoctorScope.js +102 -0
- package/dist/application/EvidenceReferenceResolver.js +26 -0
- package/dist/application/InventoryProjectionEvidence.js +41 -0
- package/dist/application/InvestigationProviders.js +12 -0
- package/dist/application/JavaScriptApplicationEvidence.js +1 -1
- package/dist/application/JavaScriptApplicationEvidenceGraph.js +10 -1
- package/dist/application/JavaScriptApplicationService.js +3 -2
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +26 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +49 -1
- package/dist/application/JavaScriptArtifactReconstruction.js +7 -0
- package/dist/application/JavaScriptModuleRelationships.js +25 -1
- package/dist/application/JavaScriptReturnShapeProjection.js +111 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +294 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +144 -0
- package/dist/application/JavaScriptSemanticGraphEvidence.js +86 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +72 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +33 -0
- package/dist/application/JavaScriptSemanticTraceService.js +45 -0
- package/dist/application/LoopbackReplay.js +277 -133
- package/dist/application/LoopbackReplayRecorder.js +101 -0
- package/dist/application/PermissionAuthority.js +7 -3
- package/dist/application/ProcessCaptureJournal.js +95 -0
- package/dist/application/ProcessCaptureLifecycle.js +24 -13
- package/dist/application/ProcessCheckpoints.js +30 -4
- package/dist/application/ProcessCli.js +26 -4
- package/dist/application/ProcessHarness.js +20 -50
- package/dist/application/ProcessNormalization.js +3 -2
- package/dist/application/ProcessPairedExperiment.js +72 -0
- package/dist/application/ProcessReactiveCoordinator.js +218 -0
- package/dist/application/ProcessReactiveEffects.js +133 -0
- package/dist/application/ProcessReactiveObservations.js +20 -0
- package/dist/application/ProcessSampling.js +2 -0
- package/dist/application/SessionProviderRouter.js +7 -6
- package/dist/application/Setup.js +14 -13
- package/dist/application/SetupClientConfiguration.js +10 -6
- package/dist/application/SetupPlan.js +4 -3
- package/dist/application/SetupRegistrationEnvironment.js +12 -0
- package/dist/application/SetupSkill.js +44 -40
- package/dist/application/TerminalRenderer.js +3 -1
- package/dist/application/Uninstall.js +7 -1
- package/dist/application/Upgrade.js +16 -1
- package/dist/browser/CdpEndpoint.js +37 -4
- package/dist/catalogIdentity.js +16 -7
- package/dist/cli/coreAnalysisCommands.js +34 -2
- package/dist/cli/javascriptApplicationAnalysis.js +19 -0
- package/dist/cli/setupCommands.js +84 -30
- package/dist/cli/utilityCommands.js +10 -2
- package/dist/cliApplicationCommands.js +79 -16
- package/dist/cliCommandNames.js +3 -0
- package/dist/cliElectronCommands.js +23 -33
- package/dist/cliObservationOptions.js +1 -1
- package/dist/cliOutput.js +56 -0
- package/dist/cliProcessCommands.js +32 -1
- package/dist/cliSetup.js +55 -29
- package/dist/contracts/applicationToolContracts.js +54 -7
- package/dist/contracts/applicationWorkflowInputContracts.js +119 -0
- package/dist/contracts/electronToolContracts.js +73 -4
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +32 -9
- package/dist/contracts/managedToolContracts.js +7 -2
- package/dist/contracts/processCaptureExample.js +1 -0
- package/dist/contracts/replayMachineExample.js +37 -0
- package/dist/contracts/sessionStatusContract.js +26 -0
- package/dist/contracts/toolContractExamples.js +2 -0
- package/dist/contracts/toolContractTypes.js +12 -1
- package/dist/contracts/toolContracts.js +8 -11
- package/dist/contracts/toolEffects.js +3 -0
- package/dist/contracts/toolOutputSchemaGroups.js +53 -9
- package/dist/contracts/toolOutputSchemaPrimitives.js +73 -32
- package/dist/doctorRuntime.js +41 -0
- package/dist/domain/analysisErrorPresentation.js +5 -3
- package/dist/domain/analysisErrorProjection.js +7 -1
- package/dist/domain/androidApplication.js +206 -0
- package/dist/domain/appleApplication.js +221 -0
- package/dist/domain/boundedCartesianProjection.js +15 -0
- package/dist/domain/changedBehavior.js +5 -4
- package/dist/domain/completionLedgerGeneration.js +190 -0
- package/dist/domain/errors.js +4 -2
- package/dist/domain/evidenceCompletionLedger.js +113 -0
- package/dist/domain/hopperStartupFailure.js +9 -0
- package/dist/domain/javascriptApplicationAnalysis.js +44 -2
- package/dist/domain/javascriptExportShapeComparison.js +145 -0
- package/dist/domain/javascriptExportShapeComparisonIdentity.js +11 -0
- package/dist/domain/javascriptExportShapeComparisonSchemas.js +277 -0
- package/dist/domain/javascriptExportShapeSelection.js +132 -0
- package/dist/domain/javascriptExportShapeVariants.js +272 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +2 -2
- package/dist/domain/javascriptSemanticAnalysis.js +17 -36
- package/dist/domain/javascriptSemanticCallResolution.js +129 -0
- package/dist/domain/javascriptSemanticCalls.js +269 -0
- package/dist/domain/javascriptSemanticGraph.js +356 -0
- package/dist/domain/javascriptSemanticGraphSchemas.js +329 -0
- package/dist/domain/javascriptSemanticGraphSerialization.js +23 -0
- package/dist/domain/javascriptSemanticIr.js +14 -1
- package/dist/domain/javascriptSemanticPrimitives.js +36 -0
- package/dist/domain/javascriptSemanticProjection.js +36 -4
- package/dist/domain/javascriptSemanticProvenance.js +29 -0
- package/dist/domain/javascriptSemanticQuery.js +294 -0
- package/dist/domain/javascriptSemanticQueryAssessment.js +74 -0
- package/dist/domain/javascriptSemanticQueryIdentity.js +86 -0
- package/dist/domain/javascriptSemanticQueryRelations.js +15 -0
- package/dist/domain/javascriptSemanticQuerySchemas.js +188 -0
- package/dist/domain/javascriptSemanticReturns.js +187 -0
- package/dist/domain/javascriptSemanticState.js +32 -0
- package/dist/domain/javascriptSemanticTraceSchemas.js +14 -0
- package/dist/domain/javascriptSemanticValues.js +54 -70
- package/dist/domain/processCapture.js +36 -0
- package/dist/domain/processCaptureValidation.js +101 -12
- package/dist/domain/processComparison.js +96 -24
- package/dist/domain/processObservation.js +153 -0
- package/dist/domain/processPairedExperiment.js +128 -0
- package/dist/domain/processReactiveCheckpointDataflow.js +32 -0
- package/dist/domain/processReactiveMatching.js +210 -0
- package/dist/domain/processReactiveRuntime.js +109 -0
- package/dist/domain/processReactiveScenario.js +361 -0
- package/dist/domain/processReactiveScenarioPreflight.js +120 -0
- package/dist/domain/processReactiveTransition.js +159 -0
- package/dist/domain/processScenario.js +12 -0
- package/dist/domain/processTraceComparison.js +43 -0
- package/dist/domain/processTraceDimensionProjection.js +50 -0
- package/dist/domain/processTraceEvaluation.js +312 -0
- package/dist/domain/processTraceSpecification.js +373 -0
- package/dist/domain/reconstructionVerification.js +5 -4
- package/dist/domain/replayMachine.js +332 -0
- package/dist/domain/replayMachineRun.js +216 -0
- package/dist/domain/replayMachineRuntime.js +294 -0
- package/dist/domain/runtimeIdentification.js +210 -0
- package/dist/domain/staticRuntimeCorrelation.js +4 -2
- package/dist/evaluation/CodexAgentEval.js +156 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraClient.js +13 -2
- package/dist/ghidra/GhidraDiagnostics.js +1 -0
- package/dist/ghidra/GhidraLauncher.js +7 -3
- package/dist/ghidra/GhidraProvider.js +8 -1
- package/dist/hopper/BridgeLauncher.js +56 -14
- package/dist/hopper/HopperClient.js +23 -10
- package/dist/hopper/HopperDiagnostics.js +14 -0
- package/dist/hopper/HopperProcessDiagnostic.js +14 -0
- package/dist/hopper/HopperProvider.js +8 -1
- package/dist/hopper/LinuxPrivateDisplayDiagnostic.js +72 -0
- package/dist/hopper/LinuxPrivateDisplayProbe.js +217 -0
- package/dist/identity.js +2 -1
- package/dist/main/transport.js +2 -0
- package/dist/mcpDoctor.js +310 -0
- package/dist/process/ProcessOwnership.js +200 -53
- package/dist/process/ProviderRunLineage.js +21 -0
- package/dist/server/createServer.js +8 -2
- package/dist/server/javascriptApplicationResult.js +106 -0
- package/dist/server/registerApplicationTools/characterization.js +3 -3
- package/dist/server/registerApplicationTools/compareExportShapes.js +37 -0
- package/dist/server/registerApplicationTools/compareVersions.js +12 -6
- package/dist/server/registerApplicationTools/controlledReplay.js +2 -2
- package/dist/server/registerApplicationTools/coverage.js +3 -3
- package/dist/server/registerApplicationTools/helpers.js +37 -25
- package/dist/server/registerApplicationTools/traceFeature.js +9 -5
- package/dist/server/registerApplicationTools/traceSemantics.js +30 -0
- package/dist/server/registerApplicationTools.js +4 -0
- package/dist/server/registerElectronTools.js +19 -5
- package/dist/server/registerEvidenceResources.js +7 -1
- package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
- package/dist/server/registerManagedTools.js +18 -1
- package/dist/server/registerProcessComparisonTool.js +17 -7
- package/dist/server/registerReplayMachineTool.js +20 -0
- package/dist/server/registerSessionStatusTool.js +95 -25
- package/dist/server/registerSessionTools.js +12 -7
- package/dist/server/sessionAvailabilityPolicy.js +2 -1
- package/dist/server/toolResult.js +10 -4
- package/dist/serverIdentity.js +19 -0
- package/package.json +62 -29
- package/scripts/hopper-demo-x11.py +459 -61
- package/scripts/prepack.mjs +55 -0
- package/scripts/prepare.mjs +28 -0
- package/scripts/rea.mjs +26 -5
- package/skills/reverse-engineer-anything/SKILL.md +69 -228
- package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +35 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
- package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonValueSchema } from "./jsonValue.js";
|
|
3
|
+
import { processObservationSourceSchema } from "./processObservation.js";
|
|
4
|
+
import { definitelyAvailableCheckpoints } from "./processReactiveCheckpointDataflow.js";
|
|
5
|
+
import { preflightProcessReactiveScenario } from "./processReactiveScenarioPreflight.js";
|
|
6
|
+
/** Fixed public bounds for Process Reactive Scenario v1. */
|
|
7
|
+
export const PROCESS_REACTIVE_LIMITS = {
|
|
8
|
+
states: 32,
|
|
9
|
+
transitions: 128,
|
|
10
|
+
triggerDepth: 6,
|
|
11
|
+
predicates: 64,
|
|
12
|
+
actions: 256,
|
|
13
|
+
actionsPerTransition: 16,
|
|
14
|
+
childrenPerComposite: 16,
|
|
15
|
+
stateVisits: 256,
|
|
16
|
+
transitionUses: 256,
|
|
17
|
+
repeat: 64,
|
|
18
|
+
retainedObservations: 256,
|
|
19
|
+
terminalMatchBytes: 65_536,
|
|
20
|
+
evaluationWork: 8_192,
|
|
21
|
+
jsonDepth: 20,
|
|
22
|
+
jsonNodes: 1_024,
|
|
23
|
+
runtimeMs: 300_000,
|
|
24
|
+
};
|
|
25
|
+
const identifierSchema = z.string().regex(/^[A-Za-z][A-Za-z0-9._-]{0,63}$/u);
|
|
26
|
+
const checkpointNameSchema = z
|
|
27
|
+
.string()
|
|
28
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/u);
|
|
29
|
+
const positiveRuntime = z
|
|
30
|
+
.number()
|
|
31
|
+
.int()
|
|
32
|
+
.positive()
|
|
33
|
+
.max(PROCESS_REACTIVE_LIMITS.runtimeMs);
|
|
34
|
+
const frontierSchema = z.discriminatedUnion("kind", [
|
|
35
|
+
z.strictObject({ kind: z.literal("scenario_start") }),
|
|
36
|
+
z.strictObject({ kind: z.literal("state_entry") }),
|
|
37
|
+
z.strictObject({ kind: z.literal("checkpoint"), name: checkpointNameSchema }),
|
|
38
|
+
z.strictObject({ kind: z.literal("event_id"), event_id: identifierSchema }),
|
|
39
|
+
]);
|
|
40
|
+
const ignoredFieldSchema = z.enum([
|
|
41
|
+
"sequence",
|
|
42
|
+
"at_ms",
|
|
43
|
+
"scheduled_at_ms",
|
|
44
|
+
"dispatched_at_ms",
|
|
45
|
+
"elapsed_ms",
|
|
46
|
+
]);
|
|
47
|
+
const processReactiveTriggerSchema = z.lazy(() => z.discriminatedUnion("kind", [
|
|
48
|
+
z.strictObject({
|
|
49
|
+
kind: z.literal("event"),
|
|
50
|
+
source: processObservationSourceSchema,
|
|
51
|
+
exact: jsonValueSchema,
|
|
52
|
+
ignore_fields: z
|
|
53
|
+
.array(ignoredFieldSchema)
|
|
54
|
+
.max(5)
|
|
55
|
+
.refine((values) => new Set(values).size === values.length, {
|
|
56
|
+
message: "ignored event fields must be unique",
|
|
57
|
+
}),
|
|
58
|
+
since: frontierSchema,
|
|
59
|
+
consume: z.boolean(),
|
|
60
|
+
cardinality: z
|
|
61
|
+
.strictObject({
|
|
62
|
+
min: z.number().int().positive().max(256),
|
|
63
|
+
max: z.number().int().positive().max(256),
|
|
64
|
+
})
|
|
65
|
+
.refine(({ min, max }) => min <= max, {
|
|
66
|
+
message: "cardinality min must not exceed max",
|
|
67
|
+
}),
|
|
68
|
+
}),
|
|
69
|
+
z.strictObject({
|
|
70
|
+
kind: z.literal("terminal_text"),
|
|
71
|
+
view: z.literal("decoded"),
|
|
72
|
+
encoding: z.literal("utf8"),
|
|
73
|
+
literal: z.string().min(1).max(10_000),
|
|
74
|
+
case_sensitive: z.literal(true),
|
|
75
|
+
control_sequences: z.literal("include"),
|
|
76
|
+
occurrence: z.number().int().positive().max(1_000),
|
|
77
|
+
since: frontierSchema,
|
|
78
|
+
consume: z.boolean(),
|
|
79
|
+
}),
|
|
80
|
+
z.strictObject({
|
|
81
|
+
kind: z.literal("all"),
|
|
82
|
+
triggers: z
|
|
83
|
+
.array(processReactiveTriggerSchema)
|
|
84
|
+
.min(2)
|
|
85
|
+
.max(PROCESS_REACTIVE_LIMITS.childrenPerComposite),
|
|
86
|
+
}),
|
|
87
|
+
z.strictObject({
|
|
88
|
+
kind: z.literal("any"),
|
|
89
|
+
triggers: z
|
|
90
|
+
.array(processReactiveTriggerSchema)
|
|
91
|
+
.min(2)
|
|
92
|
+
.max(PROCESS_REACTIVE_LIMITS.childrenPerComposite),
|
|
93
|
+
}),
|
|
94
|
+
z.strictObject({
|
|
95
|
+
kind: z.literal("sequence"),
|
|
96
|
+
triggers: z
|
|
97
|
+
.array(processReactiveTriggerSchema)
|
|
98
|
+
.min(1)
|
|
99
|
+
.max(PROCESS_REACTIVE_LIMITS.childrenPerComposite),
|
|
100
|
+
}),
|
|
101
|
+
z
|
|
102
|
+
.strictObject({
|
|
103
|
+
kind: z.literal("repeat"),
|
|
104
|
+
trigger: processReactiveTriggerSchema,
|
|
105
|
+
min: z.number().int().positive().max(PROCESS_REACTIVE_LIMITS.repeat),
|
|
106
|
+
max: z.number().int().positive().max(PROCESS_REACTIVE_LIMITS.repeat),
|
|
107
|
+
})
|
|
108
|
+
.refine(({ min, max }) => min <= max, {
|
|
109
|
+
message: "repeat min must not exceed max",
|
|
110
|
+
}),
|
|
111
|
+
]));
|
|
112
|
+
const signalTargetSchema = z.discriminatedUnion("kind", [
|
|
113
|
+
z.strictObject({ kind: z.literal("root") }),
|
|
114
|
+
z.strictObject({ kind: z.literal("process_group") }),
|
|
115
|
+
z.strictObject({
|
|
116
|
+
kind: z.literal("subject_id"),
|
|
117
|
+
subject_id: identifierSchema,
|
|
118
|
+
}),
|
|
119
|
+
]);
|
|
120
|
+
const processReactiveActionSchema = z.discriminatedUnion("type", [
|
|
121
|
+
z.strictObject({
|
|
122
|
+
type: z.literal("send_input"),
|
|
123
|
+
data: z.string().max(1_000_000),
|
|
124
|
+
sensitive: z.boolean(),
|
|
125
|
+
}),
|
|
126
|
+
z.strictObject({
|
|
127
|
+
type: z.literal("resize"),
|
|
128
|
+
columns: z.number().int().min(1).max(1_000),
|
|
129
|
+
rows: z.number().int().min(1).max(1_000),
|
|
130
|
+
}),
|
|
131
|
+
z.strictObject({
|
|
132
|
+
type: z.literal("send_signal"),
|
|
133
|
+
target: signalTargetSchema,
|
|
134
|
+
signal: z.enum(["SIGINT", "SIGTERM", "SIGKILL"]),
|
|
135
|
+
}),
|
|
136
|
+
z.strictObject({ type: z.literal("checkpoint"), name: checkpointNameSchema }),
|
|
137
|
+
]);
|
|
138
|
+
const transitionTargetSchema = z.discriminatedUnion("kind", [
|
|
139
|
+
z.strictObject({ kind: z.literal("goto"), state: identifierSchema }),
|
|
140
|
+
z.strictObject({ kind: z.literal("finish"), outcome: z.literal("passed") }),
|
|
141
|
+
]);
|
|
142
|
+
const transitionSchema = z.strictObject({
|
|
143
|
+
id: identifierSchema,
|
|
144
|
+
priority: z.number().int().min(0).max(1_000),
|
|
145
|
+
max_uses: z
|
|
146
|
+
.number()
|
|
147
|
+
.int()
|
|
148
|
+
.positive()
|
|
149
|
+
.max(PROCESS_REACTIVE_LIMITS.transitionUses),
|
|
150
|
+
when: processReactiveTriggerSchema,
|
|
151
|
+
actions: z
|
|
152
|
+
.array(processReactiveActionSchema)
|
|
153
|
+
.max(PROCESS_REACTIVE_LIMITS.actionsPerTransition),
|
|
154
|
+
target: transitionTargetSchema,
|
|
155
|
+
});
|
|
156
|
+
const stateSchema = z.strictObject({
|
|
157
|
+
id: identifierSchema,
|
|
158
|
+
max_visits: z
|
|
159
|
+
.number()
|
|
160
|
+
.int()
|
|
161
|
+
.positive()
|
|
162
|
+
.max(PROCESS_REACTIVE_LIMITS.stateVisits),
|
|
163
|
+
deadline_ms: positiveRuntime,
|
|
164
|
+
on: z.array(transitionSchema).min(1).max(128),
|
|
165
|
+
});
|
|
166
|
+
const scenarioShapeSchema = z.strictObject({
|
|
167
|
+
version: z.literal(1),
|
|
168
|
+
initial_state: identifierSchema,
|
|
169
|
+
deadline_ms: positiveRuntime,
|
|
170
|
+
states: z.array(stateSchema).min(1).max(PROCESS_REACTIVE_LIMITS.states),
|
|
171
|
+
});
|
|
172
|
+
const measureTrigger = (trigger) => {
|
|
173
|
+
if (trigger.kind === "event" || trigger.kind === "terminal_text")
|
|
174
|
+
return { depth: 1, predicates: 1 };
|
|
175
|
+
const children = trigger.kind === "repeat" ? [trigger.trigger] : trigger.triggers;
|
|
176
|
+
const measured = children.map(measureTrigger);
|
|
177
|
+
return {
|
|
178
|
+
depth: 1 + Math.max(...measured.map(({ depth }) => depth)),
|
|
179
|
+
predicates: measured.reduce((total, { predicates }) => total + predicates, 0),
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
const collectCheckpointFrontiers = (trigger) => {
|
|
183
|
+
if (trigger.kind === "event" || trigger.kind === "terminal_text")
|
|
184
|
+
return trigger.since.kind === "checkpoint" ? [trigger.since.name] : [];
|
|
185
|
+
const children = trigger.kind === "repeat" ? [trigger.trigger] : trigger.triggers;
|
|
186
|
+
return children.flatMap(collectCheckpointFrontiers);
|
|
187
|
+
};
|
|
188
|
+
const validateTriggerSemantics = (trigger, path, context) => {
|
|
189
|
+
if (trigger.kind === "event") {
|
|
190
|
+
const exact = trigger.exact;
|
|
191
|
+
if (trigger.ignore_fields.length > 0 &&
|
|
192
|
+
(typeof exact !== "object" || exact === null || Array.isArray(exact)))
|
|
193
|
+
context.addIssue({
|
|
194
|
+
code: "custom",
|
|
195
|
+
message: "ignored fields require an exact object payload",
|
|
196
|
+
path: [...path, "ignore_fields"],
|
|
197
|
+
});
|
|
198
|
+
if (typeof exact === "object" &&
|
|
199
|
+
exact !== null &&
|
|
200
|
+
!Array.isArray(exact) &&
|
|
201
|
+
trigger.ignore_fields.some((field) => field in exact))
|
|
202
|
+
context.addIssue({
|
|
203
|
+
code: "custom",
|
|
204
|
+
message: "exact payload must omit every ignored field",
|
|
205
|
+
path: [...path, "exact"],
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (trigger.kind === "terminal_text")
|
|
210
|
+
return;
|
|
211
|
+
if (trigger.kind === "repeat") {
|
|
212
|
+
validateTriggerSemantics(trigger.trigger, [...path, "trigger"], context);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
for (const [index, child] of trigger.triggers.entries())
|
|
216
|
+
validateTriggerSemantics(child, [...path, "triggers", index], context);
|
|
217
|
+
};
|
|
218
|
+
const validateTransition = (input) => {
|
|
219
|
+
const { transition, validation, path, context } = input;
|
|
220
|
+
validation.transitions += 1;
|
|
221
|
+
validation.actions += transition.actions.length;
|
|
222
|
+
const measured = measureTrigger(transition.when);
|
|
223
|
+
validation.predicates += measured.predicates;
|
|
224
|
+
validateTriggerSemantics(transition.when, [...path, "when"], context);
|
|
225
|
+
if (measured.depth > PROCESS_REACTIVE_LIMITS.triggerDepth)
|
|
226
|
+
context.addIssue({
|
|
227
|
+
code: "custom",
|
|
228
|
+
message: "trigger nesting exceeds the v1 limit",
|
|
229
|
+
path: [...path, "when"],
|
|
230
|
+
});
|
|
231
|
+
if (validation.transitionIds.has(transition.id))
|
|
232
|
+
context.addIssue({
|
|
233
|
+
code: "custom",
|
|
234
|
+
message: "transition ids must be unique",
|
|
235
|
+
path: [...path, "id"],
|
|
236
|
+
});
|
|
237
|
+
validation.transitionIds.add(transition.id);
|
|
238
|
+
for (const action of transition.actions) {
|
|
239
|
+
if (action.type !== "checkpoint")
|
|
240
|
+
continue;
|
|
241
|
+
if (validation.checkpoints.has(action.name))
|
|
242
|
+
context.addIssue({
|
|
243
|
+
code: "custom",
|
|
244
|
+
message: "checkpoint action names must be unique",
|
|
245
|
+
path: [...path, "actions"],
|
|
246
|
+
});
|
|
247
|
+
validation.checkpoints.add(action.name);
|
|
248
|
+
}
|
|
249
|
+
for (const name of collectCheckpointFrontiers(transition.when))
|
|
250
|
+
validation.checkpointReferences.push({
|
|
251
|
+
name,
|
|
252
|
+
stateId: input.stateId,
|
|
253
|
+
path: [...path, "when"],
|
|
254
|
+
});
|
|
255
|
+
if (transition.target.kind !== "goto")
|
|
256
|
+
return;
|
|
257
|
+
if (!validation.knownStates.has(transition.target.state)) {
|
|
258
|
+
context.addIssue({
|
|
259
|
+
code: "custom",
|
|
260
|
+
message: "transition target state is not declared",
|
|
261
|
+
path: [...path, "target", "state"],
|
|
262
|
+
});
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
validation.adjacency.get(input.stateId)?.add(transition.target.state);
|
|
266
|
+
};
|
|
267
|
+
const validateTotalsAndReferences = (scenario, validation, context) => {
|
|
268
|
+
for (const [count, limit, message] of [
|
|
269
|
+
[
|
|
270
|
+
validation.transitions,
|
|
271
|
+
PROCESS_REACTIVE_LIMITS.transitions,
|
|
272
|
+
"scenario has too many transitions",
|
|
273
|
+
],
|
|
274
|
+
[
|
|
275
|
+
validation.predicates,
|
|
276
|
+
PROCESS_REACTIVE_LIMITS.predicates,
|
|
277
|
+
"scenario has too many predicates",
|
|
278
|
+
],
|
|
279
|
+
[
|
|
280
|
+
validation.actions,
|
|
281
|
+
PROCESS_REACTIVE_LIMITS.actions,
|
|
282
|
+
"scenario has too many actions",
|
|
283
|
+
],
|
|
284
|
+
])
|
|
285
|
+
if (count > limit)
|
|
286
|
+
context.addIssue({ code: "custom", message, path: ["states"] });
|
|
287
|
+
const available = definitelyAvailableCheckpoints(scenario);
|
|
288
|
+
for (const reference of validation.checkpointReferences)
|
|
289
|
+
if (!available.get(reference.stateId)?.has(reference.name))
|
|
290
|
+
context.addIssue({
|
|
291
|
+
code: "custom",
|
|
292
|
+
message: "checkpoint frontier is not definitely available before this transition",
|
|
293
|
+
path: reference.path,
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
const validateReachability = (scenario, stateIds, validation, context) => {
|
|
297
|
+
const reachable = new Set();
|
|
298
|
+
const pending = validation.knownStates.has(scenario.initial_state)
|
|
299
|
+
? [scenario.initial_state]
|
|
300
|
+
: [];
|
|
301
|
+
while (pending.length > 0) {
|
|
302
|
+
const current = pending.pop();
|
|
303
|
+
if (current === undefined || reachable.has(current))
|
|
304
|
+
continue;
|
|
305
|
+
reachable.add(current);
|
|
306
|
+
pending.push(...(validation.adjacency.get(current) ?? []));
|
|
307
|
+
}
|
|
308
|
+
for (const [index, id] of stateIds.entries())
|
|
309
|
+
if (!reachable.has(id))
|
|
310
|
+
context.addIssue({
|
|
311
|
+
code: "custom",
|
|
312
|
+
message: "state is unreachable from the initial state",
|
|
313
|
+
path: ["states", index],
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
const validateScenarioGraph = (scenario, context) => {
|
|
317
|
+
const stateIds = scenario.states.map(({ id }) => id);
|
|
318
|
+
const knownStates = new Set(stateIds);
|
|
319
|
+
if (knownStates.size !== stateIds.length)
|
|
320
|
+
context.addIssue({
|
|
321
|
+
code: "custom",
|
|
322
|
+
message: "state ids must be unique",
|
|
323
|
+
path: ["states"],
|
|
324
|
+
});
|
|
325
|
+
if (!knownStates.has(scenario.initial_state))
|
|
326
|
+
context.addIssue({
|
|
327
|
+
code: "custom",
|
|
328
|
+
message: "initial state is not declared",
|
|
329
|
+
path: ["initial_state"],
|
|
330
|
+
});
|
|
331
|
+
const validation = {
|
|
332
|
+
knownStates,
|
|
333
|
+
transitionIds: new Set(),
|
|
334
|
+
checkpoints: new Set(),
|
|
335
|
+
checkpointReferences: [],
|
|
336
|
+
adjacency: new Map(stateIds.map((id) => [id, new Set()])),
|
|
337
|
+
transitions: 0,
|
|
338
|
+
predicates: 0,
|
|
339
|
+
actions: 0,
|
|
340
|
+
};
|
|
341
|
+
for (const [stateIndex, state] of scenario.states.entries()) {
|
|
342
|
+
if (state.deadline_ms > scenario.deadline_ms)
|
|
343
|
+
context.addIssue({
|
|
344
|
+
code: "custom",
|
|
345
|
+
message: "state deadline must not exceed scenario deadline",
|
|
346
|
+
path: ["states", stateIndex, "deadline_ms"],
|
|
347
|
+
});
|
|
348
|
+
for (const [transitionIndex, transition] of state.on.entries())
|
|
349
|
+
validateTransition({
|
|
350
|
+
stateId: state.id,
|
|
351
|
+
transition,
|
|
352
|
+
path: ["states", stateIndex, "on", transitionIndex],
|
|
353
|
+
validation,
|
|
354
|
+
context,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
validateTotalsAndReferences(scenario, validation, context);
|
|
358
|
+
validateReachability(scenario, stateIds, validation, context);
|
|
359
|
+
};
|
|
360
|
+
/** Strict boundary schema for a bounded declarative reactive scenario. */
|
|
361
|
+
export const processReactiveScenarioSchema = z.preprocess((input, context) => preflightProcessReactiveScenario(input, context, PROCESS_REACTIVE_LIMITS), scenarioShapeSchema.superRefine((scenario, context) => validateScenarioGraph(scenario, context)));
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const isUnknownRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
const reject = (context, message) => {
|
|
4
|
+
context.addIssue({ code: "custom", message });
|
|
5
|
+
return false;
|
|
6
|
+
};
|
|
7
|
+
const inspectJsonValue = (value, context, limits) => {
|
|
8
|
+
const pending = [{ value, depth: 1 }];
|
|
9
|
+
const seen = new WeakSet();
|
|
10
|
+
let nodes = 0;
|
|
11
|
+
while (pending.length > 0) {
|
|
12
|
+
const current = pending.pop();
|
|
13
|
+
if (current === undefined)
|
|
14
|
+
break;
|
|
15
|
+
nodes += 1;
|
|
16
|
+
if (nodes > limits.jsonNodes)
|
|
17
|
+
return reject(context, "event exact JSON exceeds the v1 node limit");
|
|
18
|
+
if (current.depth > limits.jsonDepth)
|
|
19
|
+
return reject(context, "event exact JSON exceeds the v1 depth limit");
|
|
20
|
+
if (typeof current.value !== "object" || current.value === null)
|
|
21
|
+
continue;
|
|
22
|
+
if (seen.has(current.value))
|
|
23
|
+
return reject(context, "event exact JSON must not contain cycles");
|
|
24
|
+
seen.add(current.value);
|
|
25
|
+
if (Array.isArray(current.value)) {
|
|
26
|
+
if (current.value.length + nodes > limits.jsonNodes)
|
|
27
|
+
return reject(context, "event exact JSON exceeds the v1 node limit");
|
|
28
|
+
for (const child of current.value)
|
|
29
|
+
pending.push({ value: child, depth: current.depth + 1 });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (!isUnknownRecord(current.value))
|
|
33
|
+
continue;
|
|
34
|
+
for (const key in current.value) {
|
|
35
|
+
if (!Object.hasOwn(current.value, key))
|
|
36
|
+
continue;
|
|
37
|
+
if (nodes + pending.length >= limits.jsonNodes)
|
|
38
|
+
return reject(context, "event exact JSON exceeds the v1 node limit");
|
|
39
|
+
pending.push({
|
|
40
|
+
value: current.value[key],
|
|
41
|
+
depth: current.depth + 1,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
};
|
|
47
|
+
const collectTriggerRoots = (states, context, limits) => {
|
|
48
|
+
const pending = [];
|
|
49
|
+
let transitions = 0;
|
|
50
|
+
for (const stateValue of states) {
|
|
51
|
+
if (!isUnknownRecord(stateValue) || !Array.isArray(stateValue["on"]))
|
|
52
|
+
continue;
|
|
53
|
+
transitions += stateValue["on"].length;
|
|
54
|
+
if (transitions > limits.transitions) {
|
|
55
|
+
reject(context, "scenario has too many transitions");
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
for (const transitionValue of stateValue["on"])
|
|
59
|
+
if (isUnknownRecord(transitionValue))
|
|
60
|
+
pending.push({ value: transitionValue["when"], depth: 1 });
|
|
61
|
+
}
|
|
62
|
+
return pending;
|
|
63
|
+
};
|
|
64
|
+
const enqueueTriggerChildren = (trigger, depth, traversal) => {
|
|
65
|
+
if (trigger["kind"] === "repeat") {
|
|
66
|
+
traversal.pending.push({ value: trigger["trigger"], depth: depth + 1 });
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (trigger["kind"] !== "all" &&
|
|
70
|
+
trigger["kind"] !== "any" &&
|
|
71
|
+
trigger["kind"] !== "sequence")
|
|
72
|
+
return true;
|
|
73
|
+
const children = trigger["triggers"];
|
|
74
|
+
if (!Array.isArray(children))
|
|
75
|
+
return true;
|
|
76
|
+
if (children.length > traversal.limits.childrenPerComposite)
|
|
77
|
+
return reject(traversal.context, "trigger group has too many children");
|
|
78
|
+
for (const child of children)
|
|
79
|
+
traversal.pending.push({ value: child, depth: depth + 1 });
|
|
80
|
+
return true;
|
|
81
|
+
};
|
|
82
|
+
const inspectTriggerTree = (pending, context, limits) => {
|
|
83
|
+
let nodes = 0;
|
|
84
|
+
const maximumNodes = limits.predicates * limits.triggerDepth;
|
|
85
|
+
while (pending.length > 0) {
|
|
86
|
+
const current = pending.pop();
|
|
87
|
+
if (current === undefined)
|
|
88
|
+
break;
|
|
89
|
+
nodes += 1;
|
|
90
|
+
if (nodes > maximumNodes)
|
|
91
|
+
return reject(context, "trigger tree exceeds the v1 limit");
|
|
92
|
+
if (current.depth > limits.triggerDepth)
|
|
93
|
+
return reject(context, "trigger nesting exceeds the v1 limit");
|
|
94
|
+
if (isUnknownRecord(current.value) &&
|
|
95
|
+
current.value["kind"] === "event" &&
|
|
96
|
+
!inspectJsonValue(current.value["exact"], context, limits))
|
|
97
|
+
return false;
|
|
98
|
+
if (isUnknownRecord(current.value) &&
|
|
99
|
+
!enqueueTriggerChildren(current.value, current.depth, {
|
|
100
|
+
pending,
|
|
101
|
+
context,
|
|
102
|
+
limits,
|
|
103
|
+
}))
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
};
|
|
108
|
+
/** Reject oversized unknown trees before the recursive Zod schema visits them. */
|
|
109
|
+
export const preflightProcessReactiveScenario = (input, context, limits) => {
|
|
110
|
+
if (!isUnknownRecord(input) || !Array.isArray(input["states"]))
|
|
111
|
+
return input;
|
|
112
|
+
if (input["states"].length > limits.states) {
|
|
113
|
+
reject(context, "scenario has too many states");
|
|
114
|
+
return z.NEVER;
|
|
115
|
+
}
|
|
116
|
+
const pending = collectTriggerRoots(input["states"], context, limits);
|
|
117
|
+
if (pending === null || !inspectTriggerTree(pending, context, limits))
|
|
118
|
+
return z.NEVER;
|
|
119
|
+
return input;
|
|
120
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const counterValue = (counters, id) => counters.find((counter) => counter.id === id)?.count ?? 0;
|
|
2
|
+
const incrementCounter = (counters, id) => {
|
|
3
|
+
let found = false;
|
|
4
|
+
const updated = counters.map((counter) => {
|
|
5
|
+
if (counter.id !== id)
|
|
6
|
+
return counter;
|
|
7
|
+
found = true;
|
|
8
|
+
return { id, count: counter.count + 1 };
|
|
9
|
+
});
|
|
10
|
+
return found ? updated : [...updated, { id, count: 1 }];
|
|
11
|
+
};
|
|
12
|
+
/** Commit one uniquely selected transition after its declarative effects succeed. */
|
|
13
|
+
const applyProcessReactiveTransition = (input) => {
|
|
14
|
+
const outcome = input.transition.target.kind === "finish"
|
|
15
|
+
? input.transition.target.outcome
|
|
16
|
+
: null;
|
|
17
|
+
const stateAfter = input.transition.target.kind === "goto"
|
|
18
|
+
? input.transition.target.state
|
|
19
|
+
: null;
|
|
20
|
+
const record = {
|
|
21
|
+
sequence: input.snapshot.transitions.length,
|
|
22
|
+
transition_id: input.transition.id,
|
|
23
|
+
state_before: input.snapshot.active_state,
|
|
24
|
+
state_after: stateAfter,
|
|
25
|
+
outcome,
|
|
26
|
+
trigger_event_ids: input.triggerEventIds,
|
|
27
|
+
action_event_ids: input.effectResults.map(({ observation }) => observation.event_id),
|
|
28
|
+
action_types: input.transition.actions.map(({ type }) => type),
|
|
29
|
+
};
|
|
30
|
+
const consumed = new Set(input.snapshot.consumed_event_ids);
|
|
31
|
+
for (const eventId of input.consumeEventIds)
|
|
32
|
+
consumed.add(eventId);
|
|
33
|
+
const checkpoints = [...input.snapshot.checkpoints];
|
|
34
|
+
for (const [index, action] of input.transition.actions.entries())
|
|
35
|
+
if (action.type === "checkpoint")
|
|
36
|
+
checkpoints.push({
|
|
37
|
+
name: action.name,
|
|
38
|
+
event_id: input.effectResults[index].observation.event_id,
|
|
39
|
+
capture_order: input.effectResults[index].observation.capture_order,
|
|
40
|
+
});
|
|
41
|
+
const completedCaptureOrder = Math.max(input.observation.capture_order, ...input.effectResults.map(({ observation }) => observation.capture_order));
|
|
42
|
+
let next = {
|
|
43
|
+
...input.snapshot,
|
|
44
|
+
status: outcome === null ? "running" : "finished",
|
|
45
|
+
outcome,
|
|
46
|
+
active_state: stateAfter ?? input.snapshot.active_state,
|
|
47
|
+
state_entry_capture_order: stateAfter === null
|
|
48
|
+
? input.snapshot.state_entry_capture_order
|
|
49
|
+
: completedCaptureOrder + 1,
|
|
50
|
+
consumed_event_ids: [...consumed],
|
|
51
|
+
transition_uses: incrementCounter(input.snapshot.transition_uses, input.transition.id),
|
|
52
|
+
state_visits: stateAfter === null
|
|
53
|
+
? input.snapshot.state_visits
|
|
54
|
+
: incrementCounter(input.snapshot.state_visits, stateAfter),
|
|
55
|
+
checkpoints,
|
|
56
|
+
transitions: [...input.snapshot.transitions, record],
|
|
57
|
+
};
|
|
58
|
+
if (stateAfter !== null) {
|
|
59
|
+
const nextState = input.scenario.states.find(({ id }) => id === stateAfter);
|
|
60
|
+
if (nextState === undefined ||
|
|
61
|
+
counterValue(next.state_visits, stateAfter) > nextState.max_visits)
|
|
62
|
+
next = { ...next, status: "finished", outcome: "capture_incomplete" };
|
|
63
|
+
}
|
|
64
|
+
return next.outcome === "capture_incomplete"
|
|
65
|
+
? {
|
|
66
|
+
kind: "finished",
|
|
67
|
+
snapshot: next,
|
|
68
|
+
evaluations: input.evaluations,
|
|
69
|
+
outcome: next.outcome,
|
|
70
|
+
}
|
|
71
|
+
: {
|
|
72
|
+
kind: "transition",
|
|
73
|
+
snapshot: next,
|
|
74
|
+
evaluations: input.evaluations,
|
|
75
|
+
record,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const effectObservationMatchesAction = (action, observation) => {
|
|
79
|
+
const payload = typeof observation.payload === "object" &&
|
|
80
|
+
observation.payload !== null &&
|
|
81
|
+
!Array.isArray(observation.payload)
|
|
82
|
+
? observation.payload
|
|
83
|
+
: null;
|
|
84
|
+
const journalIdentityMatches = observation.source_sequence === observation.location.index &&
|
|
85
|
+
observation.capture_order === observation.location.capture_order &&
|
|
86
|
+
observation.event_id ===
|
|
87
|
+
`obs.${observation.location.collection}.${String(observation.location.index)}`;
|
|
88
|
+
if (!journalIdentityMatches || payload === null)
|
|
89
|
+
return false;
|
|
90
|
+
if (action.type === "checkpoint")
|
|
91
|
+
return (observation.source === "filesystem" &&
|
|
92
|
+
observation.location.collection === "filesystem_checkpoints" &&
|
|
93
|
+
payload["name"] === action.name);
|
|
94
|
+
if (observation.source !== "interaction" ||
|
|
95
|
+
observation.location.collection !== "interaction_events" ||
|
|
96
|
+
payload["outcome"] !== "dispatched")
|
|
97
|
+
return false;
|
|
98
|
+
if (action.type === "send_input")
|
|
99
|
+
return (payload["type"] === "input" &&
|
|
100
|
+
payload["data"] ===
|
|
101
|
+
(action.sensitive
|
|
102
|
+
? `<redacted-input:${String(Buffer.byteLength(action.data))}-bytes>`
|
|
103
|
+
: action.data));
|
|
104
|
+
if (action.type === "resize")
|
|
105
|
+
return (payload["type"] === "resize" &&
|
|
106
|
+
payload["data"] === `${String(action.columns)}x${String(action.rows)}`);
|
|
107
|
+
return payload["type"] === "signal" && payload["data"] === action.signal;
|
|
108
|
+
};
|
|
109
|
+
const failedProposal = (proposed, outcome) => ({
|
|
110
|
+
kind: "finished",
|
|
111
|
+
outcome,
|
|
112
|
+
evaluations: proposed.evaluations,
|
|
113
|
+
snapshot: { ...proposed.snapshot, status: "finished", outcome },
|
|
114
|
+
});
|
|
115
|
+
/** Commit a proposal only after the host reports every effect as successful. */
|
|
116
|
+
export const commitProcessReactiveProposal = (scenario, proposed, results) => {
|
|
117
|
+
if (results.some(({ status }) => status === "target_lost"))
|
|
118
|
+
return failedProposal(proposed, "target_lost");
|
|
119
|
+
const state = scenario.states.find(({ id }) => id === proposed.snapshot.active_state);
|
|
120
|
+
const transition = state?.on.find(({ id }) => id === proposed.proposal.transition_id);
|
|
121
|
+
const observation = proposed.snapshot.observations.find(({ event_id }) => event_id === proposed.proposal.observation_event_id);
|
|
122
|
+
if (transition === undefined ||
|
|
123
|
+
observation === undefined ||
|
|
124
|
+
canonicalize(transition) !== proposed.proposal.transition_identity ||
|
|
125
|
+
canonicalize(proposed.effects) !== canonicalize(transition.actions))
|
|
126
|
+
return failedProposal(proposed, "target_lost");
|
|
127
|
+
if (results.length !== transition.actions.length ||
|
|
128
|
+
results.some(({ status }) => status === "rejected"))
|
|
129
|
+
return failedProposal(proposed, "action_rejected");
|
|
130
|
+
const succeeded = results.filter((result) => result.status === "succeeded");
|
|
131
|
+
let previousOrder = proposed.snapshot.observations.find(({ event_id }) => event_id === proposed.proposal.observation_event_id)?.capture_order ?? -1;
|
|
132
|
+
const knownEventIds = new Set([
|
|
133
|
+
...proposed.snapshot.observations.map(({ event_id }) => event_id),
|
|
134
|
+
...proposed.snapshot.transitions.flatMap(({ action_event_ids }) => action_event_ids),
|
|
135
|
+
]);
|
|
136
|
+
for (const [index, result] of succeeded.entries()) {
|
|
137
|
+
const action = transition.actions[index];
|
|
138
|
+
const effectObservation = result.observation;
|
|
139
|
+
if (action === undefined ||
|
|
140
|
+
!effectObservationMatchesAction(action, effectObservation) ||
|
|
141
|
+
!Number.isSafeInteger(effectObservation.capture_order) ||
|
|
142
|
+
effectObservation.capture_order <= previousOrder ||
|
|
143
|
+
knownEventIds.has(effectObservation.event_id))
|
|
144
|
+
return failedProposal(proposed, "action_rejected");
|
|
145
|
+
knownEventIds.add(effectObservation.event_id);
|
|
146
|
+
previousOrder = effectObservation.capture_order;
|
|
147
|
+
}
|
|
148
|
+
return applyProcessReactiveTransition({
|
|
149
|
+
scenario,
|
|
150
|
+
snapshot: proposed.snapshot,
|
|
151
|
+
observation,
|
|
152
|
+
transition,
|
|
153
|
+
triggerEventIds: proposed.proposal.trigger_event_ids,
|
|
154
|
+
consumeEventIds: proposed.proposal.consume_event_ids,
|
|
155
|
+
effectResults: succeeded,
|
|
156
|
+
evaluations: proposed.evaluations,
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
import canonicalize from "canonicalize";
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import canonicalize from "canonicalize";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { replayMachineSchema } from "./replayMachine.js";
|
|
5
6
|
const positiveBudget = z.number().int().positive();
|
|
6
7
|
const timedEventBase = { at_ms: z.number().int().nonnegative() };
|
|
7
8
|
const environmentName = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/u);
|
|
@@ -213,6 +214,7 @@ export const processScenarioSchema = z
|
|
|
213
214
|
}),
|
|
214
215
|
replay: z
|
|
215
216
|
.object({
|
|
217
|
+
machine: replayMachineSchema.nullable().default(null),
|
|
216
218
|
http: z
|
|
217
219
|
.array(z.object({
|
|
218
220
|
method: z.string().max(16),
|
|
@@ -251,6 +253,7 @@ export const processScenarioSchema = z
|
|
|
251
253
|
.default([]),
|
|
252
254
|
})
|
|
253
255
|
.default({
|
|
256
|
+
machine: null,
|
|
254
257
|
http: [],
|
|
255
258
|
websocket_messages: [],
|
|
256
259
|
websocket_connections: [],
|
|
@@ -327,6 +330,15 @@ export const processScenarioSchema = z
|
|
|
327
330
|
message: "command shim names must be unique",
|
|
328
331
|
path: ["command_shims"],
|
|
329
332
|
});
|
|
333
|
+
if (scenario.replay.machine !== null &&
|
|
334
|
+
(scenario.replay.http.length > 0 ||
|
|
335
|
+
scenario.replay.websocket_messages.length > 0 ||
|
|
336
|
+
scenario.replay.websocket_connections.length > 0))
|
|
337
|
+
context.addIssue({
|
|
338
|
+
code: "custom",
|
|
339
|
+
message: "a replay machine cannot be combined with static HTTP or WebSocket scripts",
|
|
340
|
+
path: ["replay"],
|
|
341
|
+
});
|
|
330
342
|
});
|
|
331
343
|
/** Parse untrusted process scenario input and apply safe default budgets. */
|
|
332
344
|
export const parseProcessScenario = (input) => processScenarioSchema.parse(input);
|