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
@@ -1,3 +1,4 @@
1
+ import { replayMachineSchema } from "./replayMachine.js";
1
2
  import { digestProcessCommitment } from "./processScenario.js";
2
3
  const orderedTimestamps = (values) => values.every((value, index) => index === 0 || value.at_ms >= values[index - 1].at_ms);
3
4
  const validateCommitments = (capture, require) => {
@@ -23,6 +24,7 @@ const validateOrdering = (capture, require) => {
23
24
  ["interaction_events", capture.interaction_events],
24
25
  ["shim_events", capture.shim_events],
25
26
  ["protocol_events", capture.protocol_events],
27
+ ["replay_transitions", capture.replay_transitions],
26
28
  ])
27
29
  require(values.every(({ sequence }, index) => sequence === index), name, "sequence values must be contiguous from zero");
28
30
  for (const [name, values] of [
@@ -32,9 +34,40 @@ const validateOrdering = (capture, require) => {
32
34
  ["filesystem_checkpoints", capture.filesystem_checkpoints],
33
35
  ["shim_events", capture.shim_events],
34
36
  ["protocol_events", capture.protocol_events],
37
+ ["replay_transitions", capture.replay_transitions],
35
38
  ])
36
39
  require(orderedTimestamps(values), name, "timestamps must be ordered");
37
40
  };
41
+ const journalCollectionSizes = (capture) => ({
42
+ frames: capture.frames.length,
43
+ rendered_frames: capture.rendered_frames.length,
44
+ interaction_events: capture.interaction_events.length,
45
+ lifecycle: 2,
46
+ process_samples: capture.process_samples.length,
47
+ filesystem_checkpoints: capture.filesystem_checkpoints.length,
48
+ shim_events: capture.shim_events.length,
49
+ protocol_events: capture.protocol_events.length,
50
+ replay_transitions: capture.replay_transitions.length,
51
+ });
52
+ const validateEventJournal = (capture, require) => {
53
+ const journal = capture.event_journal ?? [];
54
+ if (journal.length === 0)
55
+ return;
56
+ const sizes = journalCollectionSizes(capture);
57
+ const references = new Set();
58
+ for (const [position, entry] of journal.entries()) {
59
+ require(entry.capture_order ===
60
+ position, `event_journal.${String(position)}.capture_order`, "capture order must be contiguous from zero");
61
+ require(entry.index <
62
+ sizes[entry.collection], `event_journal.${String(position)}.index`, "journal reference is outside its capture collection");
63
+ const reference = `${entry.collection}:${String(entry.index)}`;
64
+ require(!references.has(reference), `event_journal.${String(position)}`, "journal references must be unique");
65
+ references.add(reference);
66
+ }
67
+ const expectedSize = Object.values(sizes).reduce((total, size) => total + size, 0);
68
+ require(journal.length ===
69
+ expectedSize, "event_journal", "nonempty journal must reference every captured observation");
70
+ };
38
71
  const validateLifecycle = (capture, require) => {
39
72
  const checkpointNames = capture.filesystem_checkpoints.map(({ name }) => name);
40
73
  require(new Set(checkpointNames).size ===
@@ -89,23 +122,78 @@ const validateShimEvents = (capture, require) => {
89
122
  maximum, "shim_events", "matched shim events exceed the declared route max_calls");
90
123
  }
91
124
  };
92
- const validateReplayEvents = (capture, require) => {
93
- const { replay_plan: replayPlan } = capture.manifest;
94
- const routes = "http" in replayPlan && Array.isArray(replayPlan.http)
95
- ? replayPlan.http
96
- : [];
125
+ const isRecord = (value) => typeof value === "object" && value !== null;
126
+ const machinePlan = (replayPlan) => {
127
+ const parsed = replayMachineSchema.safeParse(replayPlan["machine"]);
128
+ return parsed.success ? parsed.data : undefined;
129
+ };
130
+ const matchesHttpPlan = (value, event) => {
131
+ if (!isRecord(value))
132
+ return false;
133
+ const route = isRecord(value["trigger"]) ? value["trigger"] : value;
134
+ return ((route["protocol"] === undefined || route["protocol"] === "http") &&
135
+ route["method"] === event.method &&
136
+ route["path"] === event.path);
137
+ };
138
+ const validateMatchedHttpEvents = (capture, plans, require) => {
97
139
  for (const event of capture.protocol_events) {
98
140
  if (event.protocol !== "http" ||
99
141
  event.direction !== "request" ||
100
142
  event.outcome !== "matched")
101
143
  continue;
102
- const declared = routes.some((route) => typeof route === "object" &&
103
- route !== null &&
104
- "method" in route &&
105
- route.method === event.method &&
106
- "path" in route &&
107
- route.path === event.path);
108
- require(declared, "protocol_events", "matched HTTP event has no declared replay route");
144
+ require(plans.some((plan) => matchesHttpPlan(plan, event)), "protocol_events", "matched HTTP event has no declared replay route");
145
+ }
146
+ };
147
+ const validateReplayEvents = (capture, require) => {
148
+ const replayPlan = capture.manifest.replay_plan;
149
+ const routes = Array.isArray(replayPlan["http"]) ? replayPlan["http"] : [];
150
+ const machine = machinePlan(replayPlan);
151
+ require(replayPlan["machine"] === undefined ||
152
+ replayPlan["machine"] === null ||
153
+ machine !==
154
+ undefined, "manifest.replay_plan.machine", "replay machine does not satisfy its declared schema");
155
+ validateMatchedHttpEvents(capture, [...routes, ...(machine?.transitions ?? [])], require);
156
+ const plans = new Map((machine?.transitions ?? []).map((transition) => [
157
+ transition.id,
158
+ transition,
159
+ ]));
160
+ const transitionUses = new Map();
161
+ const stateVisits = new Map();
162
+ if (machine !== undefined)
163
+ stateVisits.set(machine.initial_state, 1);
164
+ require(machine === undefined ||
165
+ capture.replay_transitions.length <=
166
+ machine.max_transitions, "replay_transitions", "replay transition journal exceeds the declared machine limit");
167
+ for (const [index, transition] of capture.replay_transitions.entries()) {
168
+ const plan = plans.get(transition.transition_id);
169
+ require(plan !==
170
+ undefined, "replay_transitions", "replay transition has no declared machine transition");
171
+ if (plan === undefined)
172
+ continue;
173
+ require(transition.state_before === plan.from &&
174
+ transition.state_after ===
175
+ plan.to, "replay_transitions", "replay transition states do not match the declared transition");
176
+ require(JSON.stringify(transition.sensitive_aliases) ===
177
+ JSON.stringify(plan.captures
178
+ .filter(({ sensitive }) => sensitive)
179
+ .map(({ variable }) => variable)
180
+ .sort()), "replay_transitions", "replay transition sensitive aliases do not match the declared captures");
181
+ const uses = (transitionUses.get(plan.id) ?? 0) + 1;
182
+ transitionUses.set(plan.id, uses);
183
+ require(uses <=
184
+ plan.max_uses, "replay_transitions", "replay transition journal exceeds the transition use limit");
185
+ const visits = (stateVisits.get(transition.state_after) ?? 0) + 1;
186
+ stateVisits.set(transition.state_after, visits);
187
+ const state = machine?.states.find(({ name }) => name === transition.state_after);
188
+ require(state === undefined ||
189
+ visits <=
190
+ state.max_visits, "replay_transitions", "replay transition journal exceeds the state visit limit");
191
+ if (index === 0)
192
+ require(transition.state_before ===
193
+ machine?.initial_state, "replay_transitions", "first replay transition does not start at the declared initial state");
194
+ if (index > 0)
195
+ require(capture.replay_transitions[index - 1].state_after ===
196
+ transition.state_before, "replay_transitions", "replay transition states are not contiguous");
109
197
  }
110
198
  };
111
199
  /** Recompute v4 commitments and cross-field invariants without side effects. */
@@ -117,6 +205,7 @@ export const validateProcessCapture = (capture) => {
117
205
  };
118
206
  validateCommitments(capture, require);
119
207
  validateOrdering(capture, require);
208
+ validateEventJournal(capture, require);
120
209
  validateLifecycle(capture, require);
121
210
  validateShimEvents(capture, require);
122
211
  validateReplayEvents(capture, require);
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { validateProcessCapture } from "./processCapture.js";
3
3
  import { jsonValueSchema } from "./jsonValue.js";
4
+ import { compareProcessTraces, processTraceOutcomesDiffer, processTraceComparisonResultSchema, } from "./processTraceComparison.js";
5
+ import { dimensionsForTraceSources, traceCoversObservedDimension, } from "./processTraceDimensionProjection.js";
4
6
  /** Comparison classification that never equates incomplete evidence. */
5
7
  export const comparisonStatusSchema = z.enum([
6
8
  "unchanged",
@@ -61,6 +63,7 @@ export const processCaptureComparisonSchema = z
61
63
  right: jsonValueSchema.nullable(),
62
64
  }),
63
65
  ]),
66
+ trace: processTraceComparisonResultSchema.optional(),
64
67
  limitations: z.array(z.string()),
65
68
  })
66
69
  .superRefine((comparison, context) => {
@@ -106,7 +109,7 @@ export const processDimensionObservations = (capture, dimension) => {
106
109
  case "filesystem":
107
110
  return filesystemObservations(capture);
108
111
  case "protocol":
109
- return capture.protocol_events;
112
+ return [...capture.protocol_events, ...capture.replay_transitions];
110
113
  case "process":
111
114
  return capture.process_samples;
112
115
  case "shim":
@@ -193,6 +196,40 @@ const truncatedComparison = () => ({
193
196
  },
194
197
  limitations: ["At least one capture is truncated."],
195
198
  });
199
+ const applyTraceVerdict = (dimensions, specification, trace, captures) => {
200
+ const traceShowsDifference = processTraceOutcomesDiffer(trace);
201
+ const traceStatus = trace.verdict === "unknown"
202
+ ? "unknown"
203
+ : trace.verdict === "equivalent" || !traceShowsDifference
204
+ ? "unchanged"
205
+ : "changed";
206
+ const sources = new Set(specification.events.map(({ source }) => source));
207
+ const covered = dimensionsForTraceSources(sources);
208
+ const statusFor = (dimension) => {
209
+ const exact = dimensions[dimension];
210
+ if (!covered.has(dimension))
211
+ return exact;
212
+ if (trace.verdict === "nonconforming")
213
+ return exact;
214
+ if (trace.verdict === "different")
215
+ return traceShowsDifference ? "changed" : exact;
216
+ if (trace.verdict === "unknown" &&
217
+ ["changed", "added", "removed"].includes(exact))
218
+ return exact;
219
+ return traceCoversObservedDimension(dimension, sources, captures[0], captures[1])
220
+ ? traceStatus
221
+ : exact;
222
+ };
223
+ return {
224
+ terminal: statusFor("terminal"),
225
+ interaction: statusFor("interaction"),
226
+ exit: statusFor("exit"),
227
+ filesystem: statusFor("filesystem"),
228
+ protocol: statusFor("protocol"),
229
+ process: statusFor("process"),
230
+ shim: statusFor("shim"),
231
+ };
232
+ };
196
233
  const compareDimensions = (left, right, sameNormalization) => {
197
234
  const classify = (scope, leftValues, rightValues) => !sameNormalization || hasUnknown(left, scope) || hasUnknown(right, scope)
198
235
  ? "unknown"
@@ -207,12 +244,11 @@ const compareDimensions = (left, right, sameNormalization) => {
207
244
  ? "unchanged"
208
245
  : "changed",
209
246
  filesystem: classify("filesystem", filesystemObservations(left), filesystemObservations(right)),
210
- protocol: classify("protocol", left.protocol_events, right.protocol_events),
247
+ protocol: classify("protocol", [...left.protocol_events, ...left.replay_transitions], [...right.protocol_events, ...right.replay_transitions]),
211
248
  process: classify("process", left.process_samples, right.process_samples),
212
249
  shim: classify("shim", left.shim_events, right.shim_events),
213
250
  };
214
251
  };
215
- /** Compare bounded captures without equating missing or incompatible evidence. */
216
252
  /**
217
253
  * Compare two Process Capture v4 observations without treating absence as proof.
218
254
  *
@@ -221,33 +257,50 @@ const compareDimensions = (left, right, sameNormalization) => {
221
257
  */
222
258
  export const compareProcessCaptures = (left, right, options = {}) => {
223
259
  assertComparable(left, right, options);
224
- if (left.truncated || right.truncated)
225
- return truncatedComparison();
260
+ if (left.truncated || right.truncated) {
261
+ const truncated = truncatedComparison();
262
+ return options.traceSpecification === undefined
263
+ ? truncated
264
+ : {
265
+ ...truncated,
266
+ trace: compareProcessTraces(left, right, options.traceSpecification),
267
+ };
268
+ }
226
269
  const sameNormalization = JSON.stringify(left.normalization) === JSON.stringify(right.normalization);
227
- const dimensions = compareDimensions(left, right, sameNormalization);
228
- const observedFirstDivergence = chooseFirstDivergence([
229
- firstCollectionDivergence("terminal", terminalObservations(left), terminalObservations(right)),
230
- firstCollectionDivergence("interaction", left.interaction_events, right.interaction_events),
231
- firstCollectionDivergence("exit", [{ ...left.exit, settlement: left.settlement }], [{ ...right.exit, settlement: right.settlement }]),
232
- firstCollectionDivergence("filesystem", filesystemObservations(left), filesystemObservations(right)),
233
- firstCollectionDivergence("protocol", left.protocol_events, right.protocol_events),
234
- firstCollectionDivergence("process", left.process_samples, right.process_samples),
235
- firstCollectionDivergence("shim", left.shim_events, right.shim_events),
236
- ]);
237
- const firstDivergence = observedFirstDivergence.status === "found"
238
- ? observedFirstDivergence
239
- : !sameNormalization ||
240
- left.residual_unknowns.length > 0 ||
241
- right.residual_unknowns.length > 0
242
- ? {
243
- status: "unknown",
244
- reason: "Residual unknowns prevent proving that no divergence occurred.",
245
- }
246
- : observedFirstDivergence;
270
+ const exactDimensions = compareDimensions(left, right, sameNormalization);
271
+ const trace = options.traceSpecification === undefined
272
+ ? undefined
273
+ : compareProcessTraces(left, right, options.traceSpecification);
274
+ const dimensions = trace === undefined || options.traceSpecification === undefined
275
+ ? exactDimensions
276
+ : applyTraceVerdict(exactDimensions, options.traceSpecification, trace, [
277
+ left,
278
+ right,
279
+ ]);
280
+ const divergenceCandidates = PROCESS_COMPARISON_DIMENSIONS.map((dimension) => firstCollectionDivergence(dimension, processDimensionObservations(left, dimension), processDimensionObservations(right, dimension)));
281
+ const observedFirstDivergence = chooseFirstDivergence(divergenceCandidates.map((candidate) => candidate !== null &&
282
+ trace?.verdict === "equivalent" &&
283
+ dimensions[candidate.dimension] === "unchanged"
284
+ ? null
285
+ : candidate));
286
+ const firstDivergence = trace?.verdict === "equivalent" &&
287
+ deriveProcessComparisonStatus(PROCESS_COMPARISON_DIMENSIONS.map((dimension) => dimensions[dimension])) === "unchanged"
288
+ ? { status: "none" }
289
+ : observedFirstDivergence.status === "found"
290
+ ? observedFirstDivergence
291
+ : !sameNormalization ||
292
+ left.residual_unknowns.length > 0 ||
293
+ right.residual_unknowns.length > 0
294
+ ? {
295
+ status: "unknown",
296
+ reason: "Residual unknowns prevent proving that no divergence occurred.",
297
+ }
298
+ : observedFirstDivergence;
247
299
  return {
248
300
  status: deriveProcessComparisonStatus(PROCESS_COMPARISON_DIMENSIONS.map((dimension) => dimensions[dimension])),
249
301
  ...dimensions,
250
302
  first_divergence: firstDivergence,
303
+ ...(trace === undefined ? {} : { trace }),
251
304
  limitations: [
252
305
  ...left.limitations,
253
306
  ...right.limitations,
@@ -0,0 +1,153 @@
1
+ import { z } from "zod";
2
+ import { jsonValueSchema } from "./jsonValue.js";
3
+ /** Process-capture observation families shared by live drivers and trace assertions. */
4
+ export const processObservationSourceSchema = z.enum([
5
+ "terminal_raw",
6
+ "terminal_rendered",
7
+ "interaction",
8
+ "lifecycle",
9
+ "process",
10
+ "filesystem",
11
+ "http",
12
+ "websocket",
13
+ "shim",
14
+ "replay_transition",
15
+ ]);
16
+ /** Stable raw-record location within one Process Capture observation journal. */
17
+ export const processObservationLocationSchema = z.strictObject({
18
+ collection: z.enum([
19
+ "frames",
20
+ "rendered_frames",
21
+ "interaction_events",
22
+ "lifecycle",
23
+ "process_samples",
24
+ "filesystem_checkpoints",
25
+ "shim_events",
26
+ "protocol_events",
27
+ "replay_transitions",
28
+ ]),
29
+ index: z.number().int().nonnegative(),
30
+ capture_order: z.number().int().nonnegative(),
31
+ });
32
+ /** Remove only explicitly declared top-level volatile fields before matching. */
33
+ export const comparableProcessObservationPayload = (value, ignoredFields = []) => {
34
+ if (ignoredFields.length === 0 ||
35
+ typeof value !== "object" ||
36
+ value === null ||
37
+ Array.isArray(value))
38
+ return value;
39
+ return Object.fromEntries(Object.entries(value).filter(([name]) => !ignoredFields.includes(name)));
40
+ };
41
+ /** Construct a validated observation from one live producer record. */
42
+ export const createProcessObservation = (input) => ({
43
+ event_id: `obs.${input.location.collection}.${String(input.location.index)}`,
44
+ source: input.source,
45
+ source_sequence: input.source_sequence,
46
+ capture_order: input.location.capture_order,
47
+ captured_at_ms: input.captured_at_ms,
48
+ subject_id: input.subject_id,
49
+ location: input.location,
50
+ payload: jsonValueSchema.parse(input.payload),
51
+ });
52
+ const atMilliseconds = (value) => {
53
+ if (typeof value !== "object" || value === null)
54
+ return null;
55
+ for (const name of ["at_ms", "dispatched_at_ms"])
56
+ if (name in value) {
57
+ const candidate = value[name];
58
+ if (typeof candidate === "number" && Number.isSafeInteger(candidate))
59
+ return candidate;
60
+ }
61
+ return null;
62
+ };
63
+ const subjectFor = (source, value) => {
64
+ if (typeof value !== "object" || value === null || Array.isArray(value))
65
+ return null;
66
+ if (source === "process" && typeof value["pid"] === "number")
67
+ return `process:${String(value["pid"])}`;
68
+ if (source === "filesystem" && typeof value["name"] === "string")
69
+ return `checkpoint:${value["name"]}`;
70
+ return null;
71
+ };
72
+ const projectedRecord = (capture, location) => {
73
+ const projected = (() => {
74
+ switch (location.collection) {
75
+ case "frames":
76
+ return {
77
+ source: "terminal_raw",
78
+ value: capture.frames[location.index],
79
+ };
80
+ case "rendered_frames":
81
+ return {
82
+ source: "terminal_rendered",
83
+ value: capture.rendered_frames[location.index],
84
+ };
85
+ case "interaction_events":
86
+ return {
87
+ source: "interaction",
88
+ value: capture.interaction_events[location.index],
89
+ };
90
+ case "lifecycle":
91
+ return location.index === 0
92
+ ? {
93
+ source: "lifecycle",
94
+ value: { event: "exit", ...capture.exit },
95
+ }
96
+ : location.index === 1
97
+ ? {
98
+ source: "lifecycle",
99
+ value: { event: "settlement", ...capture.settlement },
100
+ }
101
+ : null;
102
+ case "process_samples":
103
+ return {
104
+ source: "process",
105
+ value: capture.process_samples[location.index],
106
+ };
107
+ case "filesystem_checkpoints":
108
+ return {
109
+ source: "filesystem",
110
+ value: capture.filesystem_checkpoints[location.index],
111
+ };
112
+ case "shim_events":
113
+ return {
114
+ source: "shim",
115
+ value: capture.shim_events[location.index],
116
+ };
117
+ case "protocol_events": {
118
+ const event = capture.protocol_events[location.index];
119
+ return event === undefined
120
+ ? null
121
+ : { source: event.protocol, value: event };
122
+ }
123
+ case "replay_transitions":
124
+ return {
125
+ source: "replay_transition",
126
+ value: capture.replay_transitions[location.index],
127
+ };
128
+ }
129
+ })();
130
+ if (projected === null || projected.value === undefined)
131
+ return null;
132
+ const payload = jsonValueSchema.parse(projected.value);
133
+ return {
134
+ source: projected.source,
135
+ captured_at_ms: atMilliseconds(payload),
136
+ subject_id: subjectFor(projected.source, payload),
137
+ payload,
138
+ };
139
+ };
140
+ /** Project one validated capture-journal reference into the shared observation shape. */
141
+ export const projectProcessObservation = (capture, location) => {
142
+ const projected = projectedRecord(capture, location);
143
+ if (projected === null)
144
+ return null;
145
+ return createProcessObservation({
146
+ source: projected.source,
147
+ source_sequence: location.index,
148
+ captured_at_ms: projected.captured_at_ms,
149
+ subject_id: projected.subject_id,
150
+ location,
151
+ payload: projected.payload,
152
+ });
153
+ };
@@ -0,0 +1,32 @@
1
+ /** Checkpoints guaranteed on every known path entering each reactive state. */
2
+ export const definitelyAvailableCheckpoints = (scenario) => {
3
+ const available = new Map([
4
+ [scenario.initial_state, new Set()],
5
+ ]);
6
+ let changed = true;
7
+ while (changed) {
8
+ changed = false;
9
+ for (const state of scenario.states) {
10
+ const before = available.get(state.id);
11
+ if (before === undefined)
12
+ continue;
13
+ for (const transition of state.on) {
14
+ if (transition.target.kind !== "goto")
15
+ continue;
16
+ const after = new Set(before);
17
+ for (const name of transition.actions.flatMap((action) => action.type === "checkpoint" ? [action.name] : []))
18
+ after.add(name);
19
+ const prior = available.get(transition.target.state);
20
+ const next = prior === undefined
21
+ ? after
22
+ : new Set([...prior].filter((name) => after.has(name)));
23
+ if (prior !== undefined && setsEqual(prior, next))
24
+ continue;
25
+ available.set(transition.target.state, next);
26
+ changed = true;
27
+ }
28
+ }
29
+ }
30
+ return available;
31
+ };
32
+ const setsEqual = (left, right) => left.size === right.size && [...left].every((value) => right.has(value));