rea-agents 2.2.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.
Files changed (165) hide show
  1. package/README.md +56 -9
  2. package/dist/application/ApplicationWorkflowEvidenceResolver.js +162 -0
  3. package/dist/application/BinarySession.js +9 -2
  4. package/dist/application/BinarySessionStatus.js +39 -1
  5. package/dist/application/CapabilityInventory.js +15 -0
  6. package/dist/application/CommandShimReplay.js +6 -3
  7. package/dist/application/CompositeProvider.js +5 -2
  8. package/dist/application/DirectAnalysis.js +1 -20
  9. package/dist/application/DirectAnalysisStatus.js +83 -0
  10. package/dist/application/Doctor.js +34 -25
  11. package/dist/application/DoctorDiagnostics.js +1 -4
  12. package/dist/application/DoctorProjection.js +43 -0
  13. package/dist/application/DoctorScope.js +102 -0
  14. package/dist/application/EvidenceReferenceResolver.js +26 -0
  15. package/dist/application/JavaScriptApplicationEvidence.js +1 -1
  16. package/dist/application/JavaScriptApplicationEvidenceGraph.js +10 -1
  17. package/dist/application/JavaScriptApplicationService.js +3 -2
  18. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +26 -0
  19. package/dist/application/JavaScriptApplicationWorkflowService.js +49 -1
  20. package/dist/application/JavaScriptArtifactReconstruction.js +7 -0
  21. package/dist/application/JavaScriptModuleRelationships.js +25 -1
  22. package/dist/application/JavaScriptReturnShapeProjection.js +111 -0
  23. package/dist/application/JavaScriptSemanticGraphBuilder.js +294 -0
  24. package/dist/application/JavaScriptSemanticGraphConstruction.js +144 -0
  25. package/dist/application/JavaScriptSemanticGraphEvidence.js +86 -0
  26. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +72 -0
  27. package/dist/application/JavaScriptSemanticGraphProjection.js +33 -0
  28. package/dist/application/JavaScriptSemanticTraceService.js +45 -0
  29. package/dist/application/LoopbackReplay.js +277 -133
  30. package/dist/application/LoopbackReplayRecorder.js +101 -0
  31. package/dist/application/PermissionAuthority.js +7 -3
  32. package/dist/application/ProcessCaptureJournal.js +95 -0
  33. package/dist/application/ProcessCaptureLifecycle.js +24 -13
  34. package/dist/application/ProcessCheckpoints.js +30 -4
  35. package/dist/application/ProcessCli.js +26 -4
  36. package/dist/application/ProcessHarness.js +20 -50
  37. package/dist/application/ProcessNormalization.js +3 -2
  38. package/dist/application/ProcessReactiveCoordinator.js +218 -0
  39. package/dist/application/ProcessReactiveEffects.js +133 -0
  40. package/dist/application/ProcessReactiveObservations.js +20 -0
  41. package/dist/application/ProcessSampling.js +2 -0
  42. package/dist/application/SessionProviderRouter.js +7 -6
  43. package/dist/application/Setup.js +11 -10
  44. package/dist/application/SetupPlan.js +1 -1
  45. package/dist/application/SetupRegistrationEnvironment.js +12 -0
  46. package/dist/application/TerminalRenderer.js +3 -1
  47. package/dist/browser/CdpEndpoint.js +37 -4
  48. package/dist/catalogIdentity.js +16 -7
  49. package/dist/cli/coreAnalysisCommands.js +34 -2
  50. package/dist/cli/javascriptApplicationAnalysis.js +19 -0
  51. package/dist/cli/setupCommands.js +81 -32
  52. package/dist/cli/utilityCommands.js +10 -2
  53. package/dist/cliApplicationCommands.js +79 -16
  54. package/dist/cliCommandNames.js +3 -0
  55. package/dist/cliElectronCommands.js +23 -33
  56. package/dist/cliObservationOptions.js +1 -1
  57. package/dist/cliOutput.js +56 -0
  58. package/dist/cliProcessCommands.js +32 -1
  59. package/dist/cliSetup.js +30 -21
  60. package/dist/contracts/applicationToolContracts.js +54 -7
  61. package/dist/contracts/applicationWorkflowInputContracts.js +119 -0
  62. package/dist/contracts/electronToolContracts.js +53 -32
  63. package/dist/contracts/javascriptApplicationWorkflowExamples.js +32 -9
  64. package/dist/contracts/processCaptureExample.js +1 -0
  65. package/dist/contracts/replayMachineExample.js +37 -0
  66. package/dist/contracts/toolContractExamples.js +2 -0
  67. package/dist/contracts/toolContracts.js +5 -1
  68. package/dist/contracts/toolEffects.js +3 -0
  69. package/dist/contracts/toolOutputSchemaGroups.js +2 -0
  70. package/dist/contracts/toolOutputSchemaPrimitives.js +40 -0
  71. package/dist/doctorRuntime.js +41 -0
  72. package/dist/domain/analysisErrorPresentation.js +5 -3
  73. package/dist/domain/analysisErrorProjection.js +7 -1
  74. package/dist/domain/changedBehavior.js +5 -4
  75. package/dist/domain/completionLedgerGeneration.js +190 -0
  76. package/dist/domain/errors.js +4 -2
  77. package/dist/domain/evidenceCompletionLedger.js +113 -0
  78. package/dist/domain/hopperStartupFailure.js +9 -0
  79. package/dist/domain/javascriptApplicationAnalysis.js +44 -2
  80. package/dist/domain/javascriptExportShapeComparison.js +145 -0
  81. package/dist/domain/javascriptExportShapeComparisonIdentity.js +11 -0
  82. package/dist/domain/javascriptExportShapeComparisonSchemas.js +277 -0
  83. package/dist/domain/javascriptExportShapeSelection.js +132 -0
  84. package/dist/domain/javascriptExportShapeVariants.js +272 -0
  85. package/dist/domain/javascriptRuntimeReconciliationParsing.js +2 -2
  86. package/dist/domain/javascriptSemanticAnalysis.js +17 -36
  87. package/dist/domain/javascriptSemanticCallResolution.js +129 -0
  88. package/dist/domain/javascriptSemanticCalls.js +269 -0
  89. package/dist/domain/javascriptSemanticGraph.js +356 -0
  90. package/dist/domain/javascriptSemanticGraphSchemas.js +329 -0
  91. package/dist/domain/javascriptSemanticGraphSerialization.js +23 -0
  92. package/dist/domain/javascriptSemanticIr.js +14 -1
  93. package/dist/domain/javascriptSemanticPrimitives.js +36 -0
  94. package/dist/domain/javascriptSemanticProjection.js +36 -4
  95. package/dist/domain/javascriptSemanticProvenance.js +29 -0
  96. package/dist/domain/javascriptSemanticQuery.js +294 -0
  97. package/dist/domain/javascriptSemanticQueryAssessment.js +74 -0
  98. package/dist/domain/javascriptSemanticQueryIdentity.js +86 -0
  99. package/dist/domain/javascriptSemanticQueryRelations.js +15 -0
  100. package/dist/domain/javascriptSemanticQuerySchemas.js +188 -0
  101. package/dist/domain/javascriptSemanticReturns.js +187 -0
  102. package/dist/domain/javascriptSemanticState.js +32 -0
  103. package/dist/domain/javascriptSemanticTraceSchemas.js +14 -0
  104. package/dist/domain/javascriptSemanticValues.js +54 -70
  105. package/dist/domain/processCapture.js +36 -0
  106. package/dist/domain/processCaptureValidation.js +101 -12
  107. package/dist/domain/processComparison.js +78 -25
  108. package/dist/domain/processObservation.js +153 -0
  109. package/dist/domain/processReactiveCheckpointDataflow.js +32 -0
  110. package/dist/domain/processReactiveMatching.js +210 -0
  111. package/dist/domain/processReactiveRuntime.js +109 -0
  112. package/dist/domain/processReactiveScenario.js +361 -0
  113. package/dist/domain/processReactiveScenarioPreflight.js +120 -0
  114. package/dist/domain/processReactiveTransition.js +159 -0
  115. package/dist/domain/processScenario.js +12 -0
  116. package/dist/domain/processTraceComparison.js +43 -0
  117. package/dist/domain/processTraceDimensionProjection.js +50 -0
  118. package/dist/domain/processTraceEvaluation.js +312 -0
  119. package/dist/domain/processTraceSpecification.js +373 -0
  120. package/dist/domain/reconstructionVerification.js +5 -4
  121. package/dist/domain/replayMachine.js +18 -2
  122. package/dist/domain/replayMachineRun.js +216 -0
  123. package/dist/domain/replayMachineRuntime.js +88 -9
  124. package/dist/domain/staticRuntimeCorrelation.js +4 -2
  125. package/dist/evaluation/CodexAgentEval.js +24 -0
  126. package/dist/generatedPackageMetadata.js +1 -1
  127. package/dist/ghidra/GhidraClient.js +13 -2
  128. package/dist/ghidra/GhidraDiagnostics.js +1 -0
  129. package/dist/ghidra/GhidraLauncher.js +7 -3
  130. package/dist/ghidra/GhidraProvider.js +8 -1
  131. package/dist/hopper/BridgeLauncher.js +56 -14
  132. package/dist/hopper/HopperClient.js +23 -10
  133. package/dist/hopper/HopperDiagnostics.js +14 -0
  134. package/dist/hopper/HopperProcessDiagnostic.js +14 -0
  135. package/dist/hopper/HopperProvider.js +8 -1
  136. package/dist/hopper/LinuxPrivateDisplayDiagnostic.js +72 -0
  137. package/dist/hopper/LinuxPrivateDisplayProbe.js +217 -0
  138. package/dist/main/transport.js +2 -0
  139. package/dist/mcpDoctor.js +310 -0
  140. package/dist/process/ProcessOwnership.js +200 -53
  141. package/dist/process/ProviderRunLineage.js +21 -0
  142. package/dist/server/createServer.js +4 -1
  143. package/dist/server/javascriptApplicationResult.js +22 -1
  144. package/dist/server/registerApplicationTools/characterization.js +3 -3
  145. package/dist/server/registerApplicationTools/compareExportShapes.js +37 -0
  146. package/dist/server/registerApplicationTools/compareVersions.js +12 -6
  147. package/dist/server/registerApplicationTools/controlledReplay.js +2 -2
  148. package/dist/server/registerApplicationTools/coverage.js +3 -3
  149. package/dist/server/registerApplicationTools/helpers.js +37 -25
  150. package/dist/server/registerApplicationTools/traceFeature.js +9 -5
  151. package/dist/server/registerApplicationTools/traceSemantics.js +30 -0
  152. package/dist/server/registerApplicationTools.js +4 -0
  153. package/dist/server/registerEvidenceResources.js +5 -1
  154. package/dist/server/registerProcessComparisonTool.js +17 -7
  155. package/dist/server/registerReplayMachineTool.js +20 -0
  156. package/dist/server/registerSessionTools.js +12 -7
  157. package/dist/server/sessionAvailabilityPolicy.js +2 -1
  158. package/dist/serverIdentity.js +19 -0
  159. package/package.json +62 -30
  160. package/scripts/hopper-demo-x11.py +459 -61
  161. package/scripts/prepack.mjs +55 -0
  162. package/scripts/prepare.mjs +28 -0
  163. package/scripts/rea.mjs +26 -5
  164. package/skills/reverse-engineer-anything/SKILL.md +2 -2
  165. package/skills/reverse-engineer-anything/references/javascript-applications.md +10 -0
@@ -0,0 +1,210 @@
1
+ import canonicalize from "canonicalize";
2
+ import { comparableProcessObservationPayload } from "./processObservation.js";
3
+ import { PROCESS_REACTIVE_LIMITS, } from "./processReactiveScenario.js";
4
+ const spendEvaluationWork = (budget, units) => {
5
+ if (units > budget.remaining)
6
+ return false;
7
+ budget.remaining -= units;
8
+ return true;
9
+ };
10
+ const noMatch = (overflow = false) => ({
11
+ matched: false,
12
+ eventIds: [],
13
+ consumeIds: [],
14
+ lastOrder: -1,
15
+ overflow,
16
+ });
17
+ const frontierOrder = (frontier, snapshot) => {
18
+ switch (frontier.kind) {
19
+ case "scenario_start":
20
+ return -1;
21
+ case "state_entry":
22
+ return snapshot.state_entry_capture_order - 1;
23
+ case "checkpoint":
24
+ return (snapshot.checkpoints.findLast(({ name }) => name === frontier.name)
25
+ ?.capture_order ?? null);
26
+ case "event_id":
27
+ return (snapshot.observations.find(({ event_id }) => event_id === frontier.event_id)?.capture_order ?? null);
28
+ }
29
+ };
30
+ const eligibleObservations = (frontier, context) => {
31
+ const frontierValue = frontierOrder(frontier, context.snapshot);
32
+ if (frontierValue === null)
33
+ return [];
34
+ const lowerBound = Math.max(frontierValue, context.afterOrder);
35
+ const consumed = new Set(context.snapshot.consumed_event_ids);
36
+ return context.snapshot.observations.filter(({ capture_order, event_id }) => capture_order > lowerBound &&
37
+ (context.upperOrder === null || capture_order <= context.upperOrder) &&
38
+ !consumed.has(event_id));
39
+ };
40
+ const canonicalJson = (value) => {
41
+ const serialized = canonicalize(value);
42
+ if (serialized === undefined)
43
+ throw new TypeError("Reactive scenario value is not canonical JSON");
44
+ return serialized;
45
+ };
46
+ const matchEvent = (trigger, context) => {
47
+ const expected = canonicalJson(comparableProcessObservationPayload(trigger.exact, trigger.ignore_fields));
48
+ const matches = [];
49
+ for (const observation of eligibleObservations(trigger.since, context)) {
50
+ if (!spendEvaluationWork(context.budget, 1))
51
+ return noMatch(true);
52
+ if (observation.source === trigger.source &&
53
+ canonicalJson(comparableProcessObservationPayload(observation.payload, trigger.ignore_fields)) === expected)
54
+ matches.push(observation);
55
+ }
56
+ if (matches.length < trigger.cardinality.min ||
57
+ matches.length > trigger.cardinality.max)
58
+ return noMatch();
59
+ const eventIds = matches.map(({ event_id }) => event_id);
60
+ return {
61
+ matched: true,
62
+ eventIds,
63
+ consumeIds: trigger.consume ? eventIds : [],
64
+ lastOrder: matches.at(-1)?.capture_order ?? context.afterOrder,
65
+ overflow: false,
66
+ };
67
+ };
68
+ const occurrenceRange = (haystack, needle, occurrence) => {
69
+ let offset = 0;
70
+ let start = -1;
71
+ for (let found = 0; found < occurrence; found += 1) {
72
+ start = haystack.indexOf(needle, offset);
73
+ if (start < 0)
74
+ return null;
75
+ offset = start + needle.length;
76
+ }
77
+ return { start, end: offset };
78
+ };
79
+ const matchTerminalText = (trigger, context) => {
80
+ const chunks = [];
81
+ for (const observation of eligibleObservations(trigger.since, context)) {
82
+ if (!spendEvaluationWork(context.budget, 1))
83
+ return noMatch(true);
84
+ if (observation.source === "terminal_raw" &&
85
+ typeof observation.payload === "object" &&
86
+ observation.payload !== null &&
87
+ !Array.isArray(observation.payload) &&
88
+ typeof observation.payload["data"] === "string")
89
+ chunks.push({ observation, text: observation.payload["data"] });
90
+ }
91
+ let rawBytes = 0;
92
+ for (const { text } of chunks) {
93
+ if (text.length > PROCESS_REACTIVE_LIMITS.terminalMatchBytes)
94
+ return noMatch(true);
95
+ const bytes = Buffer.byteLength(text);
96
+ rawBytes += bytes;
97
+ if (rawBytes > PROCESS_REACTIVE_LIMITS.terminalMatchBytes ||
98
+ !spendEvaluationWork(context.budget, bytes))
99
+ return noMatch(true);
100
+ }
101
+ const range = occurrenceRange(chunks.map(({ text }) => text).join(""), trigger.literal, trigger.occurrence);
102
+ if (range === null)
103
+ return noMatch();
104
+ let firstChunk = 0;
105
+ let lastChunk = 0;
106
+ let characters = 0;
107
+ for (const [index, chunk] of chunks.entries()) {
108
+ characters += chunk.text.length;
109
+ if (characters <= range.start)
110
+ firstChunk = index + 1;
111
+ if (characters >= range.end) {
112
+ lastChunk = index;
113
+ break;
114
+ }
115
+ }
116
+ const matched = chunks
117
+ .slice(firstChunk, lastChunk + 1)
118
+ .map(({ observation }) => observation);
119
+ const eventIds = matched.map(({ event_id }) => event_id);
120
+ return {
121
+ matched: true,
122
+ eventIds,
123
+ consumeIds: trigger.consume ? eventIds : [],
124
+ lastOrder: matched.at(-1)?.capture_order ?? context.afterOrder,
125
+ overflow: false,
126
+ };
127
+ };
128
+ const mergeMatches = (matches) => ({
129
+ matched: matches.every(({ matched }) => matched),
130
+ eventIds: [...new Set(matches.flatMap(({ eventIds }) => eventIds))],
131
+ consumeIds: [...new Set(matches.flatMap(({ consumeIds }) => consumeIds))],
132
+ lastOrder: Math.max(-1, ...matches.map(({ lastOrder }) => lastOrder)),
133
+ overflow: matches.some(({ overflow }) => overflow),
134
+ });
135
+ const matchEarliestPrefix = (trigger, context) => {
136
+ for (const observation of context.snapshot.observations) {
137
+ if (observation.capture_order <= context.afterOrder)
138
+ continue;
139
+ if (context.upperOrder !== null &&
140
+ observation.capture_order > context.upperOrder)
141
+ break;
142
+ const matched = matchTrigger(trigger, {
143
+ ...context,
144
+ upperOrder: observation.capture_order,
145
+ });
146
+ if (matched.matched || matched.overflow)
147
+ return matched;
148
+ }
149
+ return noMatch();
150
+ };
151
+ const matchTrigger = (trigger, context) => {
152
+ if (trigger.kind === "event")
153
+ return matchEvent(trigger, context);
154
+ if (trigger.kind === "terminal_text")
155
+ return matchTerminalText(trigger, context);
156
+ if (trigger.kind === "all")
157
+ return mergeMatches(trigger.triggers.map((child) => matchTrigger(child, context)));
158
+ if (trigger.kind === "any") {
159
+ const evaluated = trigger.triggers.map((child) => matchTrigger(child, context));
160
+ const matches = evaluated.filter(({ matched }) => matched);
161
+ return matches.length === 0
162
+ ? noMatch(evaluated.some(({ overflow }) => overflow))
163
+ : mergeMatches(matches);
164
+ }
165
+ if (trigger.kind === "sequence") {
166
+ const matches = [];
167
+ let cursor = context.afterOrder;
168
+ for (const child of trigger.triggers) {
169
+ const matched = matchEarliestPrefix(child, {
170
+ ...context,
171
+ afterOrder: cursor,
172
+ });
173
+ if (!matched.matched)
174
+ return noMatch(matched.overflow);
175
+ matches.push(matched);
176
+ cursor = matched.lastOrder;
177
+ }
178
+ return mergeMatches(matches);
179
+ }
180
+ const matches = [];
181
+ let cursor = context.afterOrder;
182
+ while (matches.length < trigger.max) {
183
+ const matched = matchEarliestPrefix(trigger.trigger, {
184
+ ...context,
185
+ afterOrder: cursor,
186
+ });
187
+ if (!matched.matched || matched.lastOrder <= cursor)
188
+ break;
189
+ matches.push(matched);
190
+ cursor = matched.lastOrder;
191
+ }
192
+ if (matches.length === trigger.max) {
193
+ const excess = matchEarliestPrefix(trigger.trigger, {
194
+ ...context,
195
+ afterOrder: cursor,
196
+ });
197
+ if (excess.matched || excess.overflow)
198
+ return noMatch(excess.overflow);
199
+ }
200
+ if (matches.length < trigger.min)
201
+ return noMatch(matches.some(({ overflow }) => overflow));
202
+ return mergeMatches(matches);
203
+ };
204
+ /** Match one trigger using a shared deterministic work budget. */
205
+ export const matchProcessReactiveTrigger = (trigger, snapshot, budget) => matchTrigger(trigger, {
206
+ snapshot,
207
+ budget,
208
+ afterOrder: -1,
209
+ upperOrder: null,
210
+ });
@@ -0,0 +1,109 @@
1
+ import { matchProcessReactiveTrigger } from "./processReactiveMatching.js";
2
+ import { PROCESS_REACTIVE_LIMITS, } from "./processReactiveScenario.js";
3
+ export { commitProcessReactiveProposal } from "./processReactiveTransition.js";
4
+ const counterValue = (counters, id) => counters.find((counter) => counter.id === id)?.count ?? 0;
5
+ /** Create the initial immutable reducer state for one validated scenario. */
6
+ export const createProcessReactiveSnapshot = (scenario) => ({
7
+ status: "running",
8
+ outcome: null,
9
+ active_state: scenario.initial_state,
10
+ state_entry_capture_order: 0,
11
+ observations: [],
12
+ consumed_event_ids: [],
13
+ transition_uses: [],
14
+ state_visits: [{ id: scenario.initial_state, count: 1 }],
15
+ checkpoints: [],
16
+ transitions: [],
17
+ });
18
+ const finishedDecision = (snapshot, outcome, evaluations = []) => ({
19
+ kind: "finished",
20
+ outcome,
21
+ evaluations,
22
+ snapshot: { ...snapshot, status: "finished", outcome },
23
+ });
24
+ const reduceControlInput = (snapshot, input) => {
25
+ if (input.kind === "cleanup_failed")
26
+ return finishedDecision(snapshot, "cleanup_failed");
27
+ if (snapshot.status === "finished")
28
+ return { kind: "already_finished", snapshot, evaluations: [] };
29
+ if (input.kind === "observation")
30
+ return null;
31
+ if (input.kind === "state_deadline")
32
+ return input.state_id === snapshot.active_state &&
33
+ input.state_entry_capture_order === snapshot.state_entry_capture_order
34
+ ? finishedDecision(snapshot, "predicate_timeout")
35
+ : { kind: "waiting", snapshot, evaluations: [] };
36
+ if (input.kind === "scenario_deadline")
37
+ return finishedDecision(snapshot, "scenario_deadline");
38
+ return finishedDecision(snapshot, "cancelled");
39
+ };
40
+ /** Advance one validated scenario using only an observation or explicit deadline. */
41
+ export const reduceProcessReactiveScenario = (scenario, snapshot, input) => {
42
+ const controlDecision = reduceControlInput(snapshot, input);
43
+ if (controlDecision !== null)
44
+ return controlDecision;
45
+ if (input.kind !== "observation")
46
+ return finishedDecision(snapshot, "capture_incomplete");
47
+ const prior = snapshot.observations.at(-1);
48
+ if ((prior !== undefined &&
49
+ input.observation.capture_order <= prior.capture_order) ||
50
+ snapshot.observations.some(({ event_id }) => event_id === input.observation.event_id))
51
+ return finishedDecision(snapshot, "capture_incomplete");
52
+ if (snapshot.observations.length >= PROCESS_REACTIVE_LIMITS.retainedObservations)
53
+ return finishedDecision(snapshot, "capture_incomplete");
54
+ const observed = {
55
+ ...snapshot,
56
+ observations: [...snapshot.observations, input.observation],
57
+ };
58
+ const state = scenario.states.find(({ id }) => id === observed.active_state);
59
+ if (state === undefined)
60
+ return finishedDecision(observed, "target_lost");
61
+ const budget = { remaining: PROCESS_REACTIVE_LIMITS.evaluationWork };
62
+ const matches = state.on.map((transition) => ({
63
+ transition,
64
+ match: matchProcessReactiveTrigger(transition.when, observed, budget),
65
+ uses: counterValue(observed.transition_uses, transition.id),
66
+ }));
67
+ if (matches.some(({ match }) => match.overflow))
68
+ return finishedDecision(observed, "capture_incomplete");
69
+ const evaluations = matches.map(({ transition, match, uses }) => ({
70
+ transition_id: transition.id,
71
+ status: uses >= transition.max_uses
72
+ ? "exhausted"
73
+ : match.matched
74
+ ? "matched"
75
+ : "unmatched",
76
+ matched_event_ids: match.eventIds,
77
+ }));
78
+ const eligible = matches.filter(({ transition, match, uses }) => match.matched && uses < transition.max_uses);
79
+ if (eligible.length === 0)
80
+ return { kind: "waiting", snapshot: observed, evaluations };
81
+ const minimumPriority = Math.min(...eligible.map(({ transition }) => transition.priority));
82
+ const winners = eligible.filter(({ transition }) => transition.priority === minimumPriority);
83
+ if (winners.length !== 1)
84
+ return finishedDecision(observed, "ambiguous_match", evaluations);
85
+ const winner = winners[0];
86
+ if (winner === undefined)
87
+ return finishedDecision(observed, "capture_incomplete", evaluations);
88
+ const targetDeclaration = winner.transition.target;
89
+ if (targetDeclaration.kind === "goto") {
90
+ const target = scenario.states.find(({ id }) => id === targetDeclaration.state);
91
+ if (target === undefined ||
92
+ counterValue(observed.state_visits, target.id) >= target.max_visits)
93
+ return finishedDecision(observed, "capture_incomplete", evaluations);
94
+ }
95
+ return {
96
+ kind: "proposal",
97
+ snapshot: observed,
98
+ evaluations,
99
+ effects: winner.transition.actions,
100
+ proposal: {
101
+ transition_id: winner.transition.id,
102
+ transition_identity: canonicalize(winner.transition) ?? "",
103
+ observation_event_id: input.observation.event_id,
104
+ trigger_event_ids: winner.match.eventIds,
105
+ consume_event_ids: winner.match.consumeIds,
106
+ },
107
+ };
108
+ };
109
+ import canonicalize from "canonicalize";
@@ -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)));