rea-agents 2.2.0 → 2.4.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 (179) hide show
  1. package/README.md +57 -10
  2. package/dist/application/ApplicationWorkflowEvidenceResolver.js +162 -0
  3. package/dist/application/ArtifactExtraction.js +5 -1
  4. package/dist/application/ArtifactGraphConstruction.js +3 -15
  5. package/dist/application/ArtifactInventory/classify.js +5 -1
  6. package/dist/application/ArtifactInventory/reader.js +2 -0
  7. package/dist/application/BinarySession.js +9 -2
  8. package/dist/application/BinarySessionStatus.js +40 -1
  9. package/dist/application/CapabilityInventory.js +26 -3
  10. package/dist/application/CommandShimReplay.js +6 -3
  11. package/dist/application/CompositeProvider.js +5 -2
  12. package/dist/application/DirectAnalysis.js +1 -20
  13. package/dist/application/DirectAnalysisStatus.js +83 -0
  14. package/dist/application/Doctor.js +34 -25
  15. package/dist/application/DoctorDiagnostics.js +1 -4
  16. package/dist/application/DoctorProjection.js +43 -0
  17. package/dist/application/DoctorScope.js +102 -0
  18. package/dist/application/EvidenceReferenceResolver.js +26 -0
  19. package/dist/application/JavaScriptApplicationEvidence.js +1 -1
  20. package/dist/application/JavaScriptApplicationEvidenceGraph.js +10 -1
  21. package/dist/application/JavaScriptApplicationService.js +3 -2
  22. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +26 -0
  23. package/dist/application/JavaScriptApplicationWorkflowService.js +49 -1
  24. package/dist/application/JavaScriptArtifactReconstruction.js +7 -0
  25. package/dist/application/JavaScriptModuleRelationships.js +25 -1
  26. package/dist/application/JavaScriptReturnShapeProjection.js +111 -0
  27. package/dist/application/JavaScriptSemanticGraphBuilder.js +294 -0
  28. package/dist/application/JavaScriptSemanticGraphConstruction.js +144 -0
  29. package/dist/application/JavaScriptSemanticGraphEvidence.js +86 -0
  30. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +72 -0
  31. package/dist/application/JavaScriptSemanticGraphProjection.js +33 -0
  32. package/dist/application/JavaScriptSemanticTraceService.js +45 -0
  33. package/dist/application/LoopbackReplay.js +277 -133
  34. package/dist/application/LoopbackReplayRecorder.js +101 -0
  35. package/dist/application/PermissionAuthority.js +7 -3
  36. package/dist/application/ProcessCaptureEnvironment.js +26 -0
  37. package/dist/application/ProcessCaptureJournal.js +95 -0
  38. package/dist/application/ProcessCaptureLifecycle.js +25 -13
  39. package/dist/application/ProcessCheckpoints.js +35 -4
  40. package/dist/application/ProcessCli.js +26 -4
  41. package/dist/application/ProcessHarness.js +64 -83
  42. package/dist/application/ProcessNormalization.js +9 -2
  43. package/dist/application/ProcessReactiveCoordinator.js +225 -0
  44. package/dist/application/ProcessReactiveEffects.js +141 -0
  45. package/dist/application/ProcessReactiveHarness.js +85 -0
  46. package/dist/application/ProcessReactiveObservations.js +105 -0
  47. package/dist/application/ProcessSampling.js +2 -0
  48. package/dist/application/SessionProviderRouter.js +7 -6
  49. package/dist/application/Setup.js +11 -10
  50. package/dist/application/SetupPlan.js +1 -1
  51. package/dist/application/SetupRegistrationEnvironment.js +12 -0
  52. package/dist/application/TerminalRenderer.js +3 -1
  53. package/dist/browser/CdpEndpoint.js +37 -4
  54. package/dist/catalogIdentity.js +16 -7
  55. package/dist/cli/coreAnalysisCommands.js +34 -2
  56. package/dist/cli/javascriptApplicationAnalysis.js +19 -0
  57. package/dist/cli/setupCommands.js +81 -32
  58. package/dist/cli/utilityCommands.js +10 -2
  59. package/dist/cliApplicationCommands.js +79 -16
  60. package/dist/cliCommandNames.js +3 -0
  61. package/dist/cliElectronCommands.js +23 -33
  62. package/dist/cliObservationOptions.js +1 -1
  63. package/dist/cliOutput.js +56 -0
  64. package/dist/cliProcessCommands.js +32 -1
  65. package/dist/cliSetup.js +30 -21
  66. package/dist/contracts/applicationToolContracts.js +54 -7
  67. package/dist/contracts/applicationWorkflowInputContracts.js +119 -0
  68. package/dist/contracts/electronToolContracts.js +53 -32
  69. package/dist/contracts/javascriptApplicationWorkflowExamples.js +32 -9
  70. package/dist/contracts/processCaptureExample.js +1 -0
  71. package/dist/contracts/replayMachineExample.js +37 -0
  72. package/dist/contracts/toolContractExamples.js +2 -0
  73. package/dist/contracts/toolContracts.js +5 -1
  74. package/dist/contracts/toolEffects.js +3 -0
  75. package/dist/contracts/toolOutputSchemaGroups.js +2 -0
  76. package/dist/contracts/toolOutputSchemaPrimitives.js +43 -0
  77. package/dist/doctorRuntime.js +41 -0
  78. package/dist/domain/analysisErrorPresentation.js +5 -3
  79. package/dist/domain/analysisErrorProjection.js +7 -1
  80. package/dist/domain/analysisSnapshot.js +2 -0
  81. package/dist/domain/artifactGraph.js +2 -0
  82. package/dist/domain/binaryTarget.js +3 -6
  83. package/dist/domain/changedBehavior.js +5 -4
  84. package/dist/domain/completionLedgerGeneration.js +190 -0
  85. package/dist/domain/errors.js +4 -2
  86. package/dist/domain/evidence.js +2 -0
  87. package/dist/domain/evidenceCompletionLedger.js +113 -0
  88. package/dist/domain/hopperStartupFailure.js +9 -0
  89. package/dist/domain/javascriptApplicationAnalysis.js +44 -2
  90. package/dist/domain/javascriptExportShapeComparison.js +145 -0
  91. package/dist/domain/javascriptExportShapeComparisonIdentity.js +11 -0
  92. package/dist/domain/javascriptExportShapeComparisonSchemas.js +277 -0
  93. package/dist/domain/javascriptExportShapeSelection.js +132 -0
  94. package/dist/domain/javascriptExportShapeVariants.js +272 -0
  95. package/dist/domain/javascriptRuntimeReconciliationParsing.js +2 -2
  96. package/dist/domain/javascriptSemanticAnalysis.js +17 -36
  97. package/dist/domain/javascriptSemanticCallResolution.js +129 -0
  98. package/dist/domain/javascriptSemanticCalls.js +269 -0
  99. package/dist/domain/javascriptSemanticGraph.js +356 -0
  100. package/dist/domain/javascriptSemanticGraphSchemas.js +329 -0
  101. package/dist/domain/javascriptSemanticGraphSerialization.js +23 -0
  102. package/dist/domain/javascriptSemanticIr.js +14 -1
  103. package/dist/domain/javascriptSemanticPrimitives.js +36 -0
  104. package/dist/domain/javascriptSemanticProjection.js +36 -4
  105. package/dist/domain/javascriptSemanticProvenance.js +29 -0
  106. package/dist/domain/javascriptSemanticQuery.js +294 -0
  107. package/dist/domain/javascriptSemanticQueryAssessment.js +74 -0
  108. package/dist/domain/javascriptSemanticQueryIdentity.js +86 -0
  109. package/dist/domain/javascriptSemanticQueryRelations.js +15 -0
  110. package/dist/domain/javascriptSemanticQuerySchemas.js +188 -0
  111. package/dist/domain/javascriptSemanticReturns.js +187 -0
  112. package/dist/domain/javascriptSemanticState.js +32 -0
  113. package/dist/domain/javascriptSemanticTraceSchemas.js +14 -0
  114. package/dist/domain/javascriptSemanticValues.js +54 -70
  115. package/dist/domain/processCapture.js +100 -0
  116. package/dist/domain/processCaptureReactiveValidation.js +175 -0
  117. package/dist/domain/processCaptureValidation.js +103 -12
  118. package/dist/domain/processComparison.js +78 -25
  119. package/dist/domain/processObservation.js +162 -0
  120. package/dist/domain/processReactiveCheckpointDataflow.js +32 -0
  121. package/dist/domain/processReactiveMatching.js +210 -0
  122. package/dist/domain/processReactiveRuntime.js +120 -0
  123. package/dist/domain/processReactiveScenario.js +361 -0
  124. package/dist/domain/processReactiveScenarioPreflight.js +120 -0
  125. package/dist/domain/processReactiveTransition.js +161 -0
  126. package/dist/domain/processScenario.js +33 -0
  127. package/dist/domain/processTraceComparison.js +43 -0
  128. package/dist/domain/processTraceDimensionProjection.js +50 -0
  129. package/dist/domain/processTraceEvaluation.js +312 -0
  130. package/dist/domain/processTraceSpecification.js +373 -0
  131. package/dist/domain/reconstructionVerification.js +5 -4
  132. package/dist/domain/replayMachine.js +18 -2
  133. package/dist/domain/replayMachineRun.js +216 -0
  134. package/dist/domain/replayMachineRuntime.js +88 -9
  135. package/dist/domain/staticRuntimeCorrelation.js +4 -2
  136. package/dist/domain/zipPackageFormat.js +20 -0
  137. package/dist/evaluation/CodexAgentEval.js +24 -0
  138. package/dist/generatedPackageMetadata.js +1 -1
  139. package/dist/ghidra/GhidraClient.js +13 -2
  140. package/dist/ghidra/GhidraDiagnostics.js +1 -0
  141. package/dist/ghidra/GhidraLauncher.js +7 -3
  142. package/dist/ghidra/GhidraProvider.js +8 -1
  143. package/dist/hopper/BridgeLauncher.js +56 -14
  144. package/dist/hopper/HopperClient.js +23 -10
  145. package/dist/hopper/HopperDiagnostics.js +14 -0
  146. package/dist/hopper/HopperProcessDiagnostic.js +14 -0
  147. package/dist/hopper/HopperProvider.js +8 -1
  148. package/dist/hopper/LinuxPrivateDisplayDiagnostic.js +72 -0
  149. package/dist/hopper/LinuxPrivateDisplayProbe.js +217 -0
  150. package/dist/main/transport.js +2 -0
  151. package/dist/mcpDoctor.js +310 -0
  152. package/dist/native/NativeMacOSProvider.js +1 -0
  153. package/dist/process/ProcessOwnership.js +200 -53
  154. package/dist/process/ProviderRunLineage.js +21 -0
  155. package/dist/server/createServer.js +4 -1
  156. package/dist/server/javascriptApplicationResult.js +22 -1
  157. package/dist/server/registerApplicationTools/characterization.js +3 -3
  158. package/dist/server/registerApplicationTools/compareExportShapes.js +37 -0
  159. package/dist/server/registerApplicationTools/compareVersions.js +12 -6
  160. package/dist/server/registerApplicationTools/controlledReplay.js +2 -2
  161. package/dist/server/registerApplicationTools/coverage.js +3 -3
  162. package/dist/server/registerApplicationTools/helpers.js +37 -25
  163. package/dist/server/registerApplicationTools/traceFeature.js +9 -5
  164. package/dist/server/registerApplicationTools/traceSemantics.js +30 -0
  165. package/dist/server/registerApplicationTools.js +4 -0
  166. package/dist/server/registerEvidenceResources.js +5 -1
  167. package/dist/server/registerProcessComparisonTool.js +17 -7
  168. package/dist/server/registerReplayMachineTool.js +20 -0
  169. package/dist/server/registerSessionTools.js +12 -7
  170. package/dist/server/sessionAvailabilityPolicy.js +2 -1
  171. package/dist/serverIdentity.js +19 -0
  172. package/package.json +62 -30
  173. package/scripts/hopper-demo-x11.py +459 -61
  174. package/scripts/prepack.mjs +55 -0
  175. package/scripts/prepare.mjs +28 -0
  176. package/scripts/rea.mjs +26 -5
  177. package/skills/reverse-engineer-anything/SKILL.md +4 -3
  178. package/skills/reverse-engineer-anything/references/javascript-applications.md +10 -0
  179. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
@@ -0,0 +1,373 @@
1
+ import canonicalize from "canonicalize";
2
+ import { z } from "zod";
3
+ import { jsonValueSchema } from "./jsonValue.js";
4
+ import { comparableProcessObservationPayload, processObservationSourceSchema, } from "./processObservation.js";
5
+ const identifierSchema = z.string().regex(/^[A-Za-z][A-Za-z0-9._-]{0,63}$/u);
6
+ /** Process observation families admitted by a declared trace specification. */
7
+ export const processTraceSourceSchema = processObservationSourceSchema;
8
+ const cardinalitySchema = z.discriminatedUnion("kind", [
9
+ z.strictObject({ kind: z.literal("required") }),
10
+ z.strictObject({ kind: z.literal("optional") }),
11
+ z.strictObject({
12
+ kind: z.literal("exact"),
13
+ count: z.number().int().min(0).max(10_000),
14
+ }),
15
+ z
16
+ .strictObject({
17
+ kind: z.literal("range"),
18
+ min: z.number().int().min(0).max(10_000),
19
+ max: z.number().int().min(0).max(10_000),
20
+ })
21
+ .refine(({ min, max }) => min <= max, {
22
+ message: "cardinality min must not exceed max",
23
+ path: ["min"],
24
+ }),
25
+ ]);
26
+ const eventDeclarationSchema = z.strictObject({
27
+ id: identifierSchema,
28
+ source: processTraceSourceSchema,
29
+ exact: jsonValueSchema,
30
+ ignore_fields: z
31
+ .array(z.enum([
32
+ "sequence",
33
+ "at_ms",
34
+ "scheduled_at_ms",
35
+ "dispatched_at_ms",
36
+ "elapsed_ms",
37
+ ]))
38
+ .max(5)
39
+ .refine((values) => new Set(values).size === values.length, {
40
+ message: "ignored event fields must be unique",
41
+ })
42
+ .optional(),
43
+ cardinality: cardinalitySchema.default({ kind: "required" }),
44
+ });
45
+ const partialOrderLanguageSchema = z.strictObject({
46
+ kind: z.literal("partial_order"),
47
+ happens_before: z
48
+ .array(z.strictObject({
49
+ before: identifierSchema,
50
+ after: identifierSchema,
51
+ }))
52
+ .max(4_096)
53
+ .default([]),
54
+ not_before: z
55
+ .array(z.strictObject({
56
+ event: identifierSchema,
57
+ anchor: identifierSchema,
58
+ }))
59
+ .max(4_096)
60
+ .default([]),
61
+ unordered_groups: z
62
+ .array(z.strictObject({ events: z.array(identifierSchema).min(2).max(256) }))
63
+ .max(256)
64
+ .default([]),
65
+ prefix: z.array(identifierSchema).max(4_096).default([]),
66
+ suffix: z.array(identifierSchema).max(4_096).default([]),
67
+ });
68
+ const finiteTraceTokenSchema = z.union([
69
+ identifierSchema,
70
+ z.strictObject({ unordered: z.array(identifierSchema).min(2).max(256) }),
71
+ ]);
72
+ const finiteTraceLanguageSchema = z.strictObject({
73
+ kind: z.literal("finite_traces"),
74
+ variants: z
75
+ .array(z.strictObject({
76
+ id: identifierSchema,
77
+ trace: z.array(finiteTraceTokenSchema).max(4_096),
78
+ }))
79
+ .min(1)
80
+ .max(64),
81
+ });
82
+ const specificationShapeSchema = z.strictObject({
83
+ version: z.literal(1),
84
+ events: z.array(eventDeclarationSchema).min(1).max(256),
85
+ language: z.discriminatedUnion("kind", [
86
+ partialOrderLanguageSchema,
87
+ finiteTraceLanguageSchema,
88
+ ]),
89
+ });
90
+ export const canonicalTraceJson = (value) => {
91
+ const serialized = canonicalize(value);
92
+ if (serialized === undefined)
93
+ throw new TypeError("Trace specification contains a non-JSON value");
94
+ return serialized;
95
+ };
96
+ /** Remove only explicitly declared schedule metadata before exact matching. */
97
+ export const comparableTracePayload = (value, ignoredFields = []) => comparableProcessObservationPayload(value, ignoredFields);
98
+ export const processTraceCardinalityBounds = (cardinality) => {
99
+ switch (cardinality.kind) {
100
+ case "required":
101
+ return [1, 1];
102
+ case "optional":
103
+ return [0, 1];
104
+ case "exact":
105
+ return [cardinality.count, cardinality.count];
106
+ case "range":
107
+ return [cardinality.min, cardinality.max];
108
+ }
109
+ };
110
+ const addReferenceIssue = (context, known, id, path) => {
111
+ if (!known.has(id))
112
+ context.addIssue({
113
+ code: "custom",
114
+ message: `unknown event id: ${id}`,
115
+ path,
116
+ });
117
+ };
118
+ const pairKey = (left, right) => [left, right].sort().join("\0");
119
+ const buildAdjacency = (language, context, known) => {
120
+ const adjacency = new Map([...known].map((id) => [id, new Set()]));
121
+ for (const [index, edge] of language.happens_before.entries()) {
122
+ const path = ["language", "happens_before", index];
123
+ addReferenceIssue(context, known, edge.before, [...path, "before"]);
124
+ addReferenceIssue(context, known, edge.after, [...path, "after"]);
125
+ if (edge.before === edge.after)
126
+ context.addIssue({
127
+ code: "custom",
128
+ message: "happens-before edges must reference distinct events",
129
+ path,
130
+ });
131
+ adjacency.get(edge.before)?.add(edge.after);
132
+ }
133
+ return adjacency;
134
+ };
135
+ const validateNotBefore = (language, context, known, reachable) => {
136
+ const pairs = new Set();
137
+ for (const [index, constraint] of language.not_before.entries()) {
138
+ const path = ["language", "not_before", index];
139
+ addReferenceIssue(context, known, constraint.event, [...path, "event"]);
140
+ addReferenceIssue(context, known, constraint.anchor, [...path, "anchor"]);
141
+ if (constraint.event === constraint.anchor)
142
+ context.addIssue({
143
+ code: "custom",
144
+ message: "not-before constraints must reference distinct events",
145
+ path,
146
+ });
147
+ const key = pairKey(constraint.event, constraint.anchor);
148
+ if (pairs.has(key))
149
+ context.addIssue({
150
+ code: "custom",
151
+ message: "not-before event pairs must be declared once",
152
+ path,
153
+ });
154
+ pairs.add(key);
155
+ if (reachable.get(constraint.event)?.has(constraint.anchor) === true)
156
+ context.addIssue({
157
+ code: "custom",
158
+ message: "not-before constraint conflicts with happens-before order",
159
+ path,
160
+ });
161
+ }
162
+ return pairs;
163
+ };
164
+ const graphHasCycle = (ids, adjacency) => {
165
+ const active = new Set();
166
+ const complete = new Set();
167
+ const visit = (current) => {
168
+ if (active.has(current))
169
+ return true;
170
+ if (complete.has(current))
171
+ return false;
172
+ active.add(current);
173
+ const cyclic = [...(adjacency.get(current) ?? [])].some(visit);
174
+ active.delete(current);
175
+ complete.add(current);
176
+ return cyclic;
177
+ };
178
+ return ids.some(visit);
179
+ };
180
+ const computeReachability = (ids, adjacency) => {
181
+ const reachable = new Map();
182
+ for (const origin of ids) {
183
+ const seen = new Set();
184
+ const pending = [...(adjacency.get(origin) ?? [])];
185
+ while (pending.length > 0) {
186
+ const current = pending.pop();
187
+ if (current === undefined || seen.has(current))
188
+ continue;
189
+ seen.add(current);
190
+ pending.push(...(adjacency.get(current) ?? []));
191
+ }
192
+ reachable.set(origin, seen);
193
+ }
194
+ return reachable;
195
+ };
196
+ const isOrdered = (reachable, left, right) => reachable.get(left)?.has(right) === true ||
197
+ reachable.get(right)?.has(left) === true;
198
+ const validateUnorderedGroups = (language, context, known, relations) => {
199
+ const pairs = new Set();
200
+ for (const [groupIndex, group] of language.unordered_groups.entries()) {
201
+ const path = ["language", "unordered_groups", groupIndex];
202
+ if (new Set(group.events).size !== group.events.length)
203
+ context.addIssue({
204
+ code: "custom",
205
+ message: "unordered groups must not repeat event ids",
206
+ path: [...path, "events"],
207
+ });
208
+ for (const [eventIndex, id] of group.events.entries())
209
+ addReferenceIssue(context, known, id, [...path, "events", eventIndex]);
210
+ for (const [leftIndex, leftId] of group.events.entries()) {
211
+ for (const rightId of group.events.slice(leftIndex + 1)) {
212
+ const key = pairKey(leftId, rightId);
213
+ if (pairs.has(key))
214
+ context.addIssue({
215
+ code: "custom",
216
+ message: "unordered event pairs must be declared once",
217
+ path,
218
+ });
219
+ pairs.add(key);
220
+ if (isOrdered(relations.reachable, leftId, rightId) ||
221
+ relations.notBeforePairs.has(key))
222
+ context.addIssue({
223
+ code: "custom",
224
+ message: "ordered events cannot also be declared unordered",
225
+ path,
226
+ });
227
+ }
228
+ }
229
+ }
230
+ return pairs;
231
+ };
232
+ const validateCompletePairDeclarations = (ids, relations, unorderedPairs, context) => {
233
+ for (const [leftIndex, leftId] of ids.entries())
234
+ for (const rightId of ids.slice(leftIndex + 1))
235
+ if (!isOrdered(relations.reachable, leftId, rightId) &&
236
+ !relations.notBeforePairs.has(pairKey(leftId, rightId)) &&
237
+ !unorderedPairs.has(pairKey(leftId, rightId)))
238
+ context.addIssue({
239
+ code: "custom",
240
+ message: `event pair ${leftId}/${rightId} must be explicitly ordered or unordered`,
241
+ path: ["language"],
242
+ });
243
+ };
244
+ const validatePartialOrder = (specification, context) => {
245
+ if (specification.language.kind !== "partial_order")
246
+ return;
247
+ const ids = specification.events.map(({ id }) => id);
248
+ const known = new Set(ids);
249
+ const adjacency = buildAdjacency(specification.language, context, known);
250
+ if (graphHasCycle(ids, adjacency))
251
+ context.addIssue({
252
+ code: "custom",
253
+ message: "happens-before graph must be acyclic",
254
+ path: ["language"],
255
+ });
256
+ const reachable = computeReachability(ids, adjacency);
257
+ const notBeforePairs = validateNotBefore(specification.language, context, known, reachable);
258
+ const relations = { reachable, notBeforePairs };
259
+ const unorderedPairs = validateUnorderedGroups(specification.language, context, known, relations);
260
+ validateCompletePairDeclarations(ids, relations, unorderedPairs, context);
261
+ for (const [kind, values] of [
262
+ ["prefix", specification.language.prefix],
263
+ ["suffix", specification.language.suffix],
264
+ ])
265
+ for (const [index, id] of values.entries())
266
+ addReferenceIssue(context, known, id, ["language", kind, index]);
267
+ };
268
+ const countVariantEvents = (trace) => {
269
+ const counts = new Map();
270
+ for (const token of trace)
271
+ for (const id of typeof token === "string" ? [token] : token.unordered)
272
+ counts.set(id, (counts.get(id) ?? 0) + 1);
273
+ return counts;
274
+ };
275
+ const validateFiniteTraces = (specification, context) => {
276
+ if (specification.language.kind !== "finite_traces")
277
+ return;
278
+ const known = new Set(specification.events.map(({ id }) => id));
279
+ const variantIds = new Set();
280
+ const variantTraces = new Set();
281
+ for (const [variantIndex, variant,] of specification.language.variants.entries()) {
282
+ const variantPath = ["language", "variants", variantIndex];
283
+ if (variantIds.has(variant.id))
284
+ context.addIssue({
285
+ code: "custom",
286
+ message: "finite trace variant ids must be unique",
287
+ path: [...variantPath, "id"],
288
+ });
289
+ variantIds.add(variant.id);
290
+ const canonicalTrace = canonicalTraceJson(variant.trace);
291
+ if (variantTraces.has(canonicalTrace))
292
+ context.addIssue({
293
+ code: "custom",
294
+ message: "finite trace variants must not duplicate a trace",
295
+ path: [...variantPath, "trace"],
296
+ });
297
+ variantTraces.add(canonicalTrace);
298
+ for (const [tokenIndex, token] of variant.trace.entries()) {
299
+ const values = typeof token === "string" ? [token] : token.unordered;
300
+ for (const [eventIndex, id] of values.entries())
301
+ addReferenceIssue(context, known, id, [
302
+ ...variantPath,
303
+ "trace",
304
+ tokenIndex,
305
+ ...(typeof token === "string" ? [] : ["unordered", eventIndex]),
306
+ ]);
307
+ }
308
+ const counts = countVariantEvents(variant.trace);
309
+ for (const event of specification.events) {
310
+ const [minimum, maximum] = processTraceCardinalityBounds(event.cardinality);
311
+ const count = counts.get(event.id) ?? 0;
312
+ if (count < minimum || count > maximum)
313
+ context.addIssue({
314
+ code: "custom",
315
+ message: `variant ${variant.id} violates ${event.id} cardinality`,
316
+ path: [...variantPath, "trace"],
317
+ });
318
+ }
319
+ }
320
+ };
321
+ /** Strict, bounded concurrency model for Process Capture observations. */
322
+ export const processTraceSpecificationSchema = specificationShapeSchema.superRefine((specification, context) => {
323
+ const ids = new Set();
324
+ const predicates = new Set();
325
+ const ignoredFieldsBySource = new Map();
326
+ for (const [index, event] of specification.events.entries()) {
327
+ if (ids.has(event.id))
328
+ context.addIssue({
329
+ code: "custom",
330
+ message: "event ids must be unique",
331
+ path: ["events", index, "id"],
332
+ });
333
+ ids.add(event.id);
334
+ const ignoredFields = [...(event.ignore_fields ?? [])].sort();
335
+ if (ignoredFields.length > 0 &&
336
+ (typeof event.exact !== "object" ||
337
+ event.exact === null ||
338
+ Array.isArray(event.exact)))
339
+ context.addIssue({
340
+ code: "custom",
341
+ message: "ignored schedule fields require an exact object payload",
342
+ path: ["events", index, "ignore_fields"],
343
+ });
344
+ const ignoredKey = canonicalTraceJson(ignoredFields);
345
+ const priorIgnoredKey = ignoredFieldsBySource.get(event.source);
346
+ if (priorIgnoredKey !== undefined && priorIgnoredKey !== ignoredKey)
347
+ context.addIssue({
348
+ code: "custom",
349
+ message: "events from one source must use the same ignored schedule fields",
350
+ path: ["events", index, "ignore_fields"],
351
+ });
352
+ ignoredFieldsBySource.set(event.source, ignoredKey);
353
+ if (ignoredFields.some((field) => typeof event.exact === "object" &&
354
+ event.exact !== null &&
355
+ !Array.isArray(event.exact) &&
356
+ field in event.exact))
357
+ context.addIssue({
358
+ code: "custom",
359
+ message: "exact payload must omit every ignored schedule field",
360
+ path: ["events", index, "exact"],
361
+ });
362
+ const predicate = `${event.source}\0${canonicalTraceJson(comparableTracePayload(event.exact, ignoredFields))}`;
363
+ if (predicates.has(predicate))
364
+ context.addIssue({
365
+ code: "custom",
366
+ message: "exact event predicates must not overlap",
367
+ path: ["events", index],
368
+ });
369
+ predicates.add(predicate);
370
+ }
371
+ validatePartialOrder(specification, context);
372
+ validateFiniteTraces(specification, context);
373
+ });
@@ -18,23 +18,24 @@ const MAX_CANONICAL_BYTES = 32 * 1024 * 1024;
18
18
  const providers = {
19
19
  behavioral: {
20
20
  operation: "compare_process_captures",
21
- predicate: "rea.process-comparison/v3",
21
+ predicates: ["rea.process-comparison/v3", "rea.process-comparison/v4"],
22
22
  id: "rea-process",
23
23
  name: "REA deterministic process harness",
24
24
  },
25
25
  "structural-function": {
26
26
  operation: "compare_functions",
27
- predicate: "rea.function-comparison/v1",
27
+ predicates: ["rea.function-comparison/v1"],
28
28
  id: "rea-function-comparison",
29
29
  name: "REA function comparison",
30
30
  },
31
31
  "structural-artifact": {
32
32
  operation: "compare_artifacts",
33
- predicate: "rea.artifact-comparison/v1",
33
+ predicates: ["rea.artifact-comparison/v1"],
34
34
  id: "rea-artifact-comparison",
35
35
  name: "REA artifact comparison",
36
36
  },
37
37
  };
38
+ const acceptsPredicate = (expected, actual) => expected.some((predicate) => predicate === actual);
38
39
  /** Verify only explicitly declared claims; no global equivalence is inferred. */
39
40
  export const verifyReconstruction = (specificationInput, bundleInput, offset, limit) => {
40
41
  const specification = reconstructionSpecificationSchema.parse(specificationInput);
@@ -157,7 +158,7 @@ const validateComparisonIdentity = (claim, evidence) => {
157
158
  parseEvidence(evidence);
158
159
  const expected = providers[claim.kind];
159
160
  if (evidence.operation !== expected.operation ||
160
- evidence.predicate_type !== expected.predicate ||
161
+ !acceptsPredicate(expected.predicates, evidence.predicate_type) ||
161
162
  evidence.provider.id !== expected.id ||
162
163
  evidence.provider.name !== expected.name ||
163
164
  evidence.provider.version !== (claim.kind === "behavioral" ? "3" : "1") ||
@@ -14,8 +14,17 @@ const sensitiveHeaderNames = new Set([
14
14
  "proxy-authorization",
15
15
  "set-cookie",
16
16
  ]);
17
+ const headerNameSchema = z
18
+ .string()
19
+ .min(1)
20
+ .max(256)
21
+ .regex(/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u);
22
+ const headerValueSchema = z
23
+ .string()
24
+ .max(8_192)
25
+ .regex(/^[\t\x20-\x7e\x80-\xff]*$/u);
17
26
  const replayHeaderMatchersSchema = z
18
- .record(z.string().min(1).max(256), z.string().max(8_192))
27
+ .record(headerNameSchema, headerValueSchema)
19
28
  .superRefine((headers, context) => {
20
29
  const names = Object.keys(headers).map((name) => name.toLowerCase());
21
30
  if (new Set(names).size !== names.length)
@@ -77,7 +86,7 @@ const replayActionSchema = z.discriminatedUnion("type", [
77
86
  z.strictObject({
78
87
  type: z.literal("http_response"),
79
88
  status: z.number().int().min(100).max(599),
80
- headers: z.record(z.string(), z.string()).default({}),
89
+ headers: z.record(headerNameSchema, headerValueSchema).default({}),
81
90
  body: z.string().max(1_000_000),
82
91
  }),
83
92
  z.strictObject({
@@ -189,6 +198,13 @@ const validateReplayVariables = (machine, context) => {
189
198
  const capturedVariables = new Map();
190
199
  for (const [transitionIndex, transition] of machine.transitions.entries()) {
191
200
  for (const capture of transition.captures) {
201
+ if (capture.sensitive && capture.value.source === "action_json")
202
+ context.addIssue({
203
+ code: "custom",
204
+ message: "sensitive replay captures cannot read literal action JSON persisted in the replay plan",
205
+ path: ["transitions", transitionIndex, "captures"],
206
+ input: machine,
207
+ });
192
208
  const sensitivity = capturedVariables.get(capture.variable);
193
209
  if (sensitivity !== undefined && sensitivity !== capture.sensitive)
194
210
  context.addIssue({
@@ -0,0 +1,216 @@
1
+ import { z } from "zod";
2
+ import { replayMachineSchema } from "./replayMachine.js";
3
+ import { replayMachineEventSchema, ReplayMachineRuntime, } from "./replayMachineRuntime.js";
4
+ const refusalOutcomeSchema = z.enum([
5
+ "unmatched",
6
+ "invalid_state",
7
+ "guard_failed",
8
+ "transition_exhausted",
9
+ "invalid_capture",
10
+ "unexpected_reconnect",
11
+ "limit_exhausted",
12
+ ]);
13
+ const MAX_DIRECT_REPLAY_EVENTS = 10_000;
14
+ const MAX_DIRECT_REPLAY_ACTION_BYTES = 4 * 1_024 * 1_024;
15
+ const MAX_DIRECT_REPLAY_ACTION_FIELDS = 1_024;
16
+ const MAX_DIRECT_REPLAY_SENSITIVE_CAPTURES = 1_024;
17
+ const actionFieldCount = (machine) => machine.transitions.reduce((total, transition) => total +
18
+ transition.actions.reduce((fields, action) => fields +
19
+ (action.type === "http_response"
20
+ ? Object.keys(action.headers).length + 1
21
+ : action.type === "websocket_send"
22
+ ? 1
23
+ : 0), 0), 0);
24
+ const maximumSensitiveCapturesPerEvent = (machine) => Math.max(0, ...machine.transitions.map(({ captures }) => captures.filter(({ sensitive }) => sensitive).length));
25
+ /** Complete declarative input for one direct replay-machine run. */
26
+ export const replayMachineRunInputSchema = z
27
+ .strictObject({
28
+ machine: replayMachineSchema,
29
+ events: z.array(replayMachineEventSchema).max(MAX_DIRECT_REPLAY_EVENTS),
30
+ })
31
+ .superRefine(({ machine, events }, context) => {
32
+ const actionBytes = Buffer.byteLength(JSON.stringify(machine.transitions.map(({ id, actions }) => ({ id, actions }))));
33
+ if (actionBytes > MAX_DIRECT_REPLAY_ACTION_BYTES)
34
+ context.addIssue({
35
+ code: "custom",
36
+ path: ["machine", "transitions"],
37
+ message: "direct replay actions exceed the 4 MiB output boundary",
38
+ input: machine.transitions,
39
+ });
40
+ if (actionFieldCount(machine) > MAX_DIRECT_REPLAY_ACTION_FIELDS)
41
+ context.addIssue({
42
+ code: "custom",
43
+ path: ["machine", "transitions"],
44
+ message: "direct replay actions exceed the 1,024-field output boundary",
45
+ input: machine.transitions,
46
+ });
47
+ if (maximumSensitiveCapturesPerEvent(machine) * events.length >
48
+ MAX_DIRECT_REPLAY_SENSITIVE_CAPTURES)
49
+ context.addIssue({
50
+ code: "custom",
51
+ path: ["events"],
52
+ message: "direct replay events exceed the 1,024 sensitive-capture operation boundary",
53
+ input: events,
54
+ });
55
+ });
56
+ const replayMachineRunDecisionSchema = z.strictObject({
57
+ event_sequence: z.number().int().nonnegative(),
58
+ outcome: z.union([z.literal("matched"), refusalOutcomeSchema]),
59
+ transition_sequence: z.number().int().nonnegative().nullable(),
60
+ });
61
+ const replayRunActionSchema = z.discriminatedUnion("type", [
62
+ z.strictObject({
63
+ type: z.literal("http_response"),
64
+ status: z.number().int().min(100).max(599),
65
+ headers: z.record(z
66
+ .string()
67
+ .min(1)
68
+ .max(256)
69
+ .regex(/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u), z.string().max(8_192)),
70
+ body: z.string().max(1_000_000),
71
+ }),
72
+ z.strictObject({
73
+ type: z.literal("websocket_send"),
74
+ data: z.string().max(1_000_000),
75
+ }),
76
+ z.strictObject({ type: z.literal("disconnect") }),
77
+ z.strictObject({
78
+ type: z.literal("delay"),
79
+ duration_ms: z.number().int().nonnegative().max(30_000),
80
+ }),
81
+ ]);
82
+ const replayRunIdentifierSchema = z
83
+ .string()
84
+ .regex(/^[A-Za-z][A-Za-z0-9_-]{0,63}$/u);
85
+ const replayMachineRunJournalEntrySchema = z.strictObject({
86
+ sequence: z.number().int().nonnegative(),
87
+ at_ms: z.number().int().nonnegative(),
88
+ transition_id: replayRunIdentifierSchema,
89
+ state_before: replayRunIdentifierSchema,
90
+ state_after: replayRunIdentifierSchema,
91
+ captured_aliases: z
92
+ .array(z.strictObject({
93
+ name: replayRunIdentifierSchema,
94
+ sensitive: z.boolean(),
95
+ }))
96
+ .max(32),
97
+ });
98
+ /** Capture-value-free result of directly evaluating a finite replay machine. */
99
+ export const replayMachineRunOutputSchema = z.strictObject({
100
+ schema_version: z.literal(1),
101
+ initial_state: replayRunIdentifierSchema,
102
+ final_state: replayRunIdentifierSchema,
103
+ terminal: z.boolean(),
104
+ decisions: z
105
+ .array(replayMachineRunDecisionSchema)
106
+ .max(MAX_DIRECT_REPLAY_EVENTS),
107
+ transition_journal: z
108
+ .array(replayMachineRunJournalEntrySchema)
109
+ .max(MAX_DIRECT_REPLAY_EVENTS),
110
+ transition_actions: z
111
+ .array(z.strictObject({
112
+ transition_id: replayRunIdentifierSchema,
113
+ actions: z.array(replayRunActionSchema).max(32),
114
+ }))
115
+ .max(2_000),
116
+ limits: z.strictObject({
117
+ configured: z.strictObject({
118
+ transitions: z.number().int().positive(),
119
+ connections: z.number().int().positive(),
120
+ messages: z.number().int().positive(),
121
+ bytes: z.number().int().positive(),
122
+ duration_ms: z.number().int().positive(),
123
+ }),
124
+ usage: z.strictObject({
125
+ offered_events: z.number().int().nonnegative(),
126
+ admitted_events: z.number().int().nonnegative(),
127
+ transitions: z.number().int().nonnegative(),
128
+ connections: z.number().int().nonnegative(),
129
+ messages: z.number().int().nonnegative(),
130
+ bytes: z.number().int().nonnegative(),
131
+ last_at_ms: z.number().int().nonnegative(),
132
+ }),
133
+ }),
134
+ });
135
+ const redactAction = (runtime, action) => {
136
+ switch (action.type) {
137
+ case "http_response":
138
+ return {
139
+ ...action,
140
+ headers: Object.fromEntries(Object.entries(action.headers).map(([name, value]) => [
141
+ name,
142
+ runtime.redactField(value),
143
+ ])),
144
+ body: runtime.redactField(action.body),
145
+ };
146
+ case "websocket_send":
147
+ return { ...action, data: runtime.redactField(action.data) };
148
+ case "delay":
149
+ case "disconnect":
150
+ return action;
151
+ }
152
+ };
153
+ /** Evaluate every supplied event in order without opening sockets or processes. */
154
+ export const runReplayMachine = (input) => {
155
+ const runtime = new ReplayMachineRuntime(input.machine);
156
+ const decisions = [];
157
+ const transitionJournal = [];
158
+ const usedTransitionIds = new Set();
159
+ for (const [eventSequence, event] of input.events.entries()) {
160
+ const decision = runtime.dispatch(event);
161
+ decisions.push({
162
+ event_sequence: eventSequence,
163
+ outcome: decision.outcome,
164
+ transition_sequence: decision.transition?.sequence ?? null,
165
+ });
166
+ if (decision.outcome !== "matched")
167
+ continue;
168
+ usedTransitionIds.add(decision.transition.transition_id);
169
+ const definition = input.machine.transitions.find(({ id }) => id === decision.transition.transition_id);
170
+ if (definition === undefined)
171
+ throw new TypeError("Replay runtime selected an unknown transition");
172
+ transitionJournal.push({
173
+ sequence: decision.transition.sequence,
174
+ at_ms: decision.transition.at_ms,
175
+ transition_id: decision.transition.transition_id,
176
+ state_before: decision.transition.state_before,
177
+ state_after: decision.transition.state_after,
178
+ captured_aliases: definition.captures.map(({ variable, sensitive }) => ({
179
+ name: variable,
180
+ sensitive,
181
+ })),
182
+ });
183
+ }
184
+ const usage = runtime.usage;
185
+ const transitionActions = input.machine.transitions
186
+ .filter(({ id }) => usedTransitionIds.has(id))
187
+ .map(({ id, actions }) => ({
188
+ transition_id: id,
189
+ actions: actions.map((action) => redactAction(runtime, action)),
190
+ }));
191
+ return replayMachineRunOutputSchema.parse({
192
+ schema_version: 1,
193
+ initial_state: input.machine.initial_state,
194
+ final_state: runtime.state,
195
+ terminal: input.machine.states.find(({ name }) => name === runtime.state)
196
+ ?.terminal ?? false,
197
+ decisions,
198
+ transition_journal: transitionJournal,
199
+ transition_actions: transitionActions,
200
+ limits: {
201
+ configured: {
202
+ transitions: input.machine.max_transitions,
203
+ ...input.machine.limits,
204
+ },
205
+ usage: {
206
+ offered_events: input.events.length,
207
+ admitted_events: usage.events,
208
+ transitions: usage.transitions,
209
+ connections: usage.connections,
210
+ messages: usage.messages,
211
+ bytes: usage.bytes,
212
+ last_at_ms: usage.last_at_ms,
213
+ },
214
+ },
215
+ });
216
+ };