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.
- package/README.md +57 -10
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +162 -0
- package/dist/application/ArtifactExtraction.js +5 -1
- package/dist/application/ArtifactGraphConstruction.js +3 -15
- package/dist/application/ArtifactInventory/classify.js +5 -1
- package/dist/application/ArtifactInventory/reader.js +2 -0
- package/dist/application/BinarySession.js +9 -2
- package/dist/application/BinarySessionStatus.js +40 -1
- package/dist/application/CapabilityInventory.js +26 -3
- package/dist/application/CommandShimReplay.js +6 -3
- package/dist/application/CompositeProvider.js +5 -2
- package/dist/application/DirectAnalysis.js +1 -20
- package/dist/application/DirectAnalysisStatus.js +83 -0
- package/dist/application/Doctor.js +34 -25
- package/dist/application/DoctorDiagnostics.js +1 -4
- package/dist/application/DoctorProjection.js +43 -0
- package/dist/application/DoctorScope.js +102 -0
- package/dist/application/EvidenceReferenceResolver.js +26 -0
- package/dist/application/JavaScriptApplicationEvidence.js +1 -1
- package/dist/application/JavaScriptApplicationEvidenceGraph.js +10 -1
- package/dist/application/JavaScriptApplicationService.js +3 -2
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +26 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +49 -1
- package/dist/application/JavaScriptArtifactReconstruction.js +7 -0
- package/dist/application/JavaScriptModuleRelationships.js +25 -1
- package/dist/application/JavaScriptReturnShapeProjection.js +111 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +294 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +144 -0
- package/dist/application/JavaScriptSemanticGraphEvidence.js +86 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +72 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +33 -0
- package/dist/application/JavaScriptSemanticTraceService.js +45 -0
- package/dist/application/LoopbackReplay.js +277 -133
- package/dist/application/LoopbackReplayRecorder.js +101 -0
- package/dist/application/PermissionAuthority.js +7 -3
- package/dist/application/ProcessCaptureEnvironment.js +26 -0
- package/dist/application/ProcessCaptureJournal.js +95 -0
- package/dist/application/ProcessCaptureLifecycle.js +25 -13
- package/dist/application/ProcessCheckpoints.js +35 -4
- package/dist/application/ProcessCli.js +26 -4
- package/dist/application/ProcessHarness.js +64 -83
- package/dist/application/ProcessNormalization.js +9 -2
- package/dist/application/ProcessReactiveCoordinator.js +225 -0
- package/dist/application/ProcessReactiveEffects.js +141 -0
- package/dist/application/ProcessReactiveHarness.js +85 -0
- package/dist/application/ProcessReactiveObservations.js +105 -0
- package/dist/application/ProcessSampling.js +2 -0
- package/dist/application/SessionProviderRouter.js +7 -6
- package/dist/application/Setup.js +11 -10
- package/dist/application/SetupPlan.js +1 -1
- package/dist/application/SetupRegistrationEnvironment.js +12 -0
- package/dist/application/TerminalRenderer.js +3 -1
- package/dist/browser/CdpEndpoint.js +37 -4
- package/dist/catalogIdentity.js +16 -7
- package/dist/cli/coreAnalysisCommands.js +34 -2
- package/dist/cli/javascriptApplicationAnalysis.js +19 -0
- package/dist/cli/setupCommands.js +81 -32
- package/dist/cli/utilityCommands.js +10 -2
- package/dist/cliApplicationCommands.js +79 -16
- package/dist/cliCommandNames.js +3 -0
- package/dist/cliElectronCommands.js +23 -33
- package/dist/cliObservationOptions.js +1 -1
- package/dist/cliOutput.js +56 -0
- package/dist/cliProcessCommands.js +32 -1
- package/dist/cliSetup.js +30 -21
- package/dist/contracts/applicationToolContracts.js +54 -7
- package/dist/contracts/applicationWorkflowInputContracts.js +119 -0
- package/dist/contracts/electronToolContracts.js +53 -32
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +32 -9
- package/dist/contracts/processCaptureExample.js +1 -0
- package/dist/contracts/replayMachineExample.js +37 -0
- package/dist/contracts/toolContractExamples.js +2 -0
- package/dist/contracts/toolContracts.js +5 -1
- package/dist/contracts/toolEffects.js +3 -0
- package/dist/contracts/toolOutputSchemaGroups.js +2 -0
- package/dist/contracts/toolOutputSchemaPrimitives.js +43 -0
- package/dist/doctorRuntime.js +41 -0
- package/dist/domain/analysisErrorPresentation.js +5 -3
- package/dist/domain/analysisErrorProjection.js +7 -1
- package/dist/domain/analysisSnapshot.js +2 -0
- package/dist/domain/artifactGraph.js +2 -0
- package/dist/domain/binaryTarget.js +3 -6
- package/dist/domain/changedBehavior.js +5 -4
- package/dist/domain/completionLedgerGeneration.js +190 -0
- package/dist/domain/errors.js +4 -2
- package/dist/domain/evidence.js +2 -0
- package/dist/domain/evidenceCompletionLedger.js +113 -0
- package/dist/domain/hopperStartupFailure.js +9 -0
- package/dist/domain/javascriptApplicationAnalysis.js +44 -2
- package/dist/domain/javascriptExportShapeComparison.js +145 -0
- package/dist/domain/javascriptExportShapeComparisonIdentity.js +11 -0
- package/dist/domain/javascriptExportShapeComparisonSchemas.js +277 -0
- package/dist/domain/javascriptExportShapeSelection.js +132 -0
- package/dist/domain/javascriptExportShapeVariants.js +272 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +2 -2
- package/dist/domain/javascriptSemanticAnalysis.js +17 -36
- package/dist/domain/javascriptSemanticCallResolution.js +129 -0
- package/dist/domain/javascriptSemanticCalls.js +269 -0
- package/dist/domain/javascriptSemanticGraph.js +356 -0
- package/dist/domain/javascriptSemanticGraphSchemas.js +329 -0
- package/dist/domain/javascriptSemanticGraphSerialization.js +23 -0
- package/dist/domain/javascriptSemanticIr.js +14 -1
- package/dist/domain/javascriptSemanticPrimitives.js +36 -0
- package/dist/domain/javascriptSemanticProjection.js +36 -4
- package/dist/domain/javascriptSemanticProvenance.js +29 -0
- package/dist/domain/javascriptSemanticQuery.js +294 -0
- package/dist/domain/javascriptSemanticQueryAssessment.js +74 -0
- package/dist/domain/javascriptSemanticQueryIdentity.js +86 -0
- package/dist/domain/javascriptSemanticQueryRelations.js +15 -0
- package/dist/domain/javascriptSemanticQuerySchemas.js +188 -0
- package/dist/domain/javascriptSemanticReturns.js +187 -0
- package/dist/domain/javascriptSemanticState.js +32 -0
- package/dist/domain/javascriptSemanticTraceSchemas.js +14 -0
- package/dist/domain/javascriptSemanticValues.js +54 -70
- package/dist/domain/processCapture.js +100 -0
- package/dist/domain/processCaptureReactiveValidation.js +175 -0
- package/dist/domain/processCaptureValidation.js +103 -12
- package/dist/domain/processComparison.js +78 -25
- package/dist/domain/processObservation.js +162 -0
- package/dist/domain/processReactiveCheckpointDataflow.js +32 -0
- package/dist/domain/processReactiveMatching.js +210 -0
- package/dist/domain/processReactiveRuntime.js +120 -0
- package/dist/domain/processReactiveScenario.js +361 -0
- package/dist/domain/processReactiveScenarioPreflight.js +120 -0
- package/dist/domain/processReactiveTransition.js +161 -0
- package/dist/domain/processScenario.js +33 -0
- package/dist/domain/processTraceComparison.js +43 -0
- package/dist/domain/processTraceDimensionProjection.js +50 -0
- package/dist/domain/processTraceEvaluation.js +312 -0
- package/dist/domain/processTraceSpecification.js +373 -0
- package/dist/domain/reconstructionVerification.js +5 -4
- package/dist/domain/replayMachine.js +18 -2
- package/dist/domain/replayMachineRun.js +216 -0
- package/dist/domain/replayMachineRuntime.js +88 -9
- package/dist/domain/staticRuntimeCorrelation.js +4 -2
- package/dist/domain/zipPackageFormat.js +20 -0
- package/dist/evaluation/CodexAgentEval.js +24 -0
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraClient.js +13 -2
- package/dist/ghidra/GhidraDiagnostics.js +1 -0
- package/dist/ghidra/GhidraLauncher.js +7 -3
- package/dist/ghidra/GhidraProvider.js +8 -1
- package/dist/hopper/BridgeLauncher.js +56 -14
- package/dist/hopper/HopperClient.js +23 -10
- package/dist/hopper/HopperDiagnostics.js +14 -0
- package/dist/hopper/HopperProcessDiagnostic.js +14 -0
- package/dist/hopper/HopperProvider.js +8 -1
- package/dist/hopper/LinuxPrivateDisplayDiagnostic.js +72 -0
- package/dist/hopper/LinuxPrivateDisplayProbe.js +217 -0
- package/dist/main/transport.js +2 -0
- package/dist/mcpDoctor.js +310 -0
- package/dist/native/NativeMacOSProvider.js +1 -0
- package/dist/process/ProcessOwnership.js +200 -53
- package/dist/process/ProviderRunLineage.js +21 -0
- package/dist/server/createServer.js +4 -1
- package/dist/server/javascriptApplicationResult.js +22 -1
- package/dist/server/registerApplicationTools/characterization.js +3 -3
- package/dist/server/registerApplicationTools/compareExportShapes.js +37 -0
- package/dist/server/registerApplicationTools/compareVersions.js +12 -6
- package/dist/server/registerApplicationTools/controlledReplay.js +2 -2
- package/dist/server/registerApplicationTools/coverage.js +3 -3
- package/dist/server/registerApplicationTools/helpers.js +37 -25
- package/dist/server/registerApplicationTools/traceFeature.js +9 -5
- package/dist/server/registerApplicationTools/traceSemantics.js +30 -0
- package/dist/server/registerApplicationTools.js +4 -0
- package/dist/server/registerEvidenceResources.js +5 -1
- package/dist/server/registerProcessComparisonTool.js +17 -7
- package/dist/server/registerReplayMachineTool.js +20 -0
- package/dist/server/registerSessionTools.js +12 -7
- package/dist/server/sessionAvailabilityPolicy.js +2 -1
- package/dist/serverIdentity.js +19 -0
- package/package.json +62 -30
- package/scripts/hopper-demo-x11.py +459 -61
- package/scripts/prepack.mjs +55 -0
- package/scripts/prepare.mjs +28 -0
- package/scripts/rea.mjs +26 -5
- package/skills/reverse-engineer-anything/SKILL.md +4 -3
- package/skills/reverse-engineer-anything/references/javascript-applications.md +10 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
|
@@ -2,6 +2,8 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import canonicalize from "canonicalize";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { processReactiveScenarioSchema, } from "./processReactiveScenario.js";
|
|
6
|
+
import { replayMachineSchema } from "./replayMachine.js";
|
|
5
7
|
const positiveBudget = z.number().int().positive();
|
|
6
8
|
const timedEventBase = { at_ms: z.number().int().nonnegative() };
|
|
7
9
|
const environmentName = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/u);
|
|
@@ -43,6 +45,23 @@ export const digestProcessCommitment = (value) => {
|
|
|
43
45
|
throw new TypeError("Process capture commitment is not canonical JSON");
|
|
44
46
|
return createHash("sha256").update(serialized).digest("hex");
|
|
45
47
|
};
|
|
48
|
+
const reactiveScenarioCommitment = (scenario) => scenario === null
|
|
49
|
+
? null
|
|
50
|
+
: {
|
|
51
|
+
...scenario,
|
|
52
|
+
states: scenario.states.map((state) => ({
|
|
53
|
+
...state,
|
|
54
|
+
on: state.on.map((transition) => ({
|
|
55
|
+
...transition,
|
|
56
|
+
actions: transition.actions.map((action) => action.type === "send_input" && action.sensitive
|
|
57
|
+
? {
|
|
58
|
+
...action,
|
|
59
|
+
data: `<redacted-input:${String(Buffer.byteLength(action.data))}-bytes>`,
|
|
60
|
+
}
|
|
61
|
+
: action),
|
|
62
|
+
})),
|
|
63
|
+
})),
|
|
64
|
+
};
|
|
46
65
|
/** Build the secret-safe scenario projection committed by Process Capture v4. */
|
|
47
66
|
export const processScenarioCommitment = (scenario, executableSha256) => ({
|
|
48
67
|
...scenario,
|
|
@@ -50,6 +69,7 @@ export const processScenarioCommitment = (scenario, executableSha256) => ({
|
|
|
50
69
|
name,
|
|
51
70
|
scenario.secret_aliases.includes(name) ? "<redacted-secret>" : value,
|
|
52
71
|
])),
|
|
72
|
+
reactive: reactiveScenarioCommitment(scenario.reactive),
|
|
53
73
|
unknown_registry_approved: scenario.unknown_registry_approved === true,
|
|
54
74
|
executable_sha256: executableSha256 ?? null,
|
|
55
75
|
});
|
|
@@ -70,6 +90,7 @@ export const processComparisonContract = (scenario) => ({
|
|
|
70
90
|
normalization: scenario.normalization,
|
|
71
91
|
command_shims: scenario.command_shims,
|
|
72
92
|
replay: scenario.replay,
|
|
93
|
+
reactive: reactiveScenarioCommitment(scenario.reactive),
|
|
73
94
|
});
|
|
74
95
|
/**
|
|
75
96
|
* Boundary schema for one explicitly approved, bounded process experiment.
|
|
@@ -213,6 +234,7 @@ export const processScenarioSchema = z
|
|
|
213
234
|
}),
|
|
214
235
|
replay: z
|
|
215
236
|
.object({
|
|
237
|
+
machine: replayMachineSchema.nullable().default(null),
|
|
216
238
|
http: z
|
|
217
239
|
.array(z.object({
|
|
218
240
|
method: z.string().max(16),
|
|
@@ -251,10 +273,12 @@ export const processScenarioSchema = z
|
|
|
251
273
|
.default([]),
|
|
252
274
|
})
|
|
253
275
|
.default({
|
|
276
|
+
machine: null,
|
|
254
277
|
http: [],
|
|
255
278
|
websocket_messages: [],
|
|
256
279
|
websocket_connections: [],
|
|
257
280
|
}),
|
|
281
|
+
reactive: processReactiveScenarioSchema.nullable().default(null),
|
|
258
282
|
})
|
|
259
283
|
.strict()
|
|
260
284
|
.superRefine((scenario, context) => {
|
|
@@ -327,6 +351,15 @@ export const processScenarioSchema = z
|
|
|
327
351
|
message: "command shim names must be unique",
|
|
328
352
|
path: ["command_shims"],
|
|
329
353
|
});
|
|
354
|
+
if (scenario.replay.machine !== null &&
|
|
355
|
+
(scenario.replay.http.length > 0 ||
|
|
356
|
+
scenario.replay.websocket_messages.length > 0 ||
|
|
357
|
+
scenario.replay.websocket_connections.length > 0))
|
|
358
|
+
context.addIssue({
|
|
359
|
+
code: "custom",
|
|
360
|
+
message: "a replay machine cannot be combined with static HTTP or WebSocket scripts",
|
|
361
|
+
path: ["replay"],
|
|
362
|
+
});
|
|
330
363
|
});
|
|
331
364
|
/** Parse untrusted process scenario input and apply safe default budgets. */
|
|
332
365
|
export const parseProcessScenario = (input) => processScenarioSchema.parse(input);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { validateProcessCapture } from "./processCaptureValidation.js";
|
|
2
|
+
import { evaluateProcessTraceSide, processTraceComparisonResultSchema, } from "./processTraceEvaluation.js";
|
|
3
|
+
import { processTraceSpecificationSchema, } from "./processTraceSpecification.js";
|
|
4
|
+
export { processTraceComparisonResultSchema, } from "./processTraceEvaluation.js";
|
|
5
|
+
export { processTraceSourceSchema, processTraceSpecificationSchema, } from "./processTraceSpecification.js";
|
|
6
|
+
/** Compare two captures against an explicit finite trace language. */
|
|
7
|
+
export const compareProcessTraces = (left, right, specificationInput) => {
|
|
8
|
+
const captureIssues = [
|
|
9
|
+
...validateProcessCapture(left),
|
|
10
|
+
...validateProcessCapture(right),
|
|
11
|
+
];
|
|
12
|
+
if (captureIssues.length > 0)
|
|
13
|
+
throw new TypeError(`Invalid Process Capture v4: ${captureIssues[0].path}`);
|
|
14
|
+
const specification = processTraceSpecificationSchema.parse(specificationInput);
|
|
15
|
+
const leftEvaluation = evaluateProcessTraceSide(left, specification);
|
|
16
|
+
const rightEvaluation = evaluateProcessTraceSide(right, specification);
|
|
17
|
+
const diagnostic = leftEvaluation.diagnostic === null
|
|
18
|
+
? rightEvaluation.diagnostic === null
|
|
19
|
+
? null
|
|
20
|
+
: { side: "right", ...rightEvaluation.diagnostic }
|
|
21
|
+
: { side: "left", ...leftEvaluation.diagnostic };
|
|
22
|
+
const outcomesDiffer = leftEvaluation.result.status !== rightEvaluation.result.status ||
|
|
23
|
+
JSON.stringify(leftEvaluation.result.raw_trace.map(({ event_id }) => event_id)) !==
|
|
24
|
+
JSON.stringify(rightEvaluation.result.raw_trace.map(({ event_id }) => event_id));
|
|
25
|
+
return processTraceComparisonResultSchema.parse({
|
|
26
|
+
verdict: leftEvaluation.result.status === "unknown" ||
|
|
27
|
+
rightEvaluation.result.status === "unknown"
|
|
28
|
+
? "unknown"
|
|
29
|
+
: leftEvaluation.result.status === "pass" &&
|
|
30
|
+
rightEvaluation.result.status === "pass"
|
|
31
|
+
? "equivalent"
|
|
32
|
+
: outcomesDiffer
|
|
33
|
+
? "different"
|
|
34
|
+
: "nonconforming",
|
|
35
|
+
left: leftEvaluation.result,
|
|
36
|
+
right: rightEvaluation.result,
|
|
37
|
+
diagnostic,
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
/** Whether trace conformance or raw declared order differs across captures. */
|
|
41
|
+
export const processTraceOutcomesDiffer = (comparison) => comparison.left.status !== comparison.right.status ||
|
|
42
|
+
JSON.stringify(comparison.left.raw_trace.map(({ event_id }) => event_id)) !==
|
|
43
|
+
JSON.stringify(comparison.right.raw_trace.map(({ event_id }) => event_id));
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Map declared trace sources to legacy process-comparison dimensions. */
|
|
2
|
+
export const dimensionsForTraceSources = (sources) => {
|
|
3
|
+
const dimensions = new Set();
|
|
4
|
+
for (const source of sources) {
|
|
5
|
+
if (source.startsWith("terminal_"))
|
|
6
|
+
dimensions.add("terminal");
|
|
7
|
+
else if (source === "interaction")
|
|
8
|
+
dimensions.add("interaction");
|
|
9
|
+
else if (source === "lifecycle")
|
|
10
|
+
dimensions.add("exit");
|
|
11
|
+
else if (source === "filesystem")
|
|
12
|
+
dimensions.add("filesystem");
|
|
13
|
+
else if (source === "http" ||
|
|
14
|
+
source === "websocket" ||
|
|
15
|
+
source === "replay_transition")
|
|
16
|
+
dimensions.add("protocol");
|
|
17
|
+
else if (source === "process")
|
|
18
|
+
dimensions.add("process");
|
|
19
|
+
else if (source === "shim")
|
|
20
|
+
dimensions.add("shim");
|
|
21
|
+
}
|
|
22
|
+
return dimensions;
|
|
23
|
+
};
|
|
24
|
+
/** Whether a trace specification covers every observed source in a dimension. */
|
|
25
|
+
export const traceCoversObservedDimension = (dimension, sources, left, right) => {
|
|
26
|
+
const any = (leftValues, rightValues) => leftValues.length > 0 || rightValues.length > 0;
|
|
27
|
+
switch (dimension) {
|
|
28
|
+
case "terminal":
|
|
29
|
+
return ((!any(left.frames, right.frames) || sources.has("terminal_raw")) &&
|
|
30
|
+
(!any(left.rendered_frames, right.rendered_frames) ||
|
|
31
|
+
sources.has("terminal_rendered")));
|
|
32
|
+
case "interaction":
|
|
33
|
+
return sources.has("interaction");
|
|
34
|
+
case "exit":
|
|
35
|
+
return sources.has("lifecycle");
|
|
36
|
+
case "filesystem":
|
|
37
|
+
return false;
|
|
38
|
+
case "protocol":
|
|
39
|
+
return ((![...left.protocol_events, ...right.protocol_events].some(({ protocol }) => protocol === "http") ||
|
|
40
|
+
sources.has("http")) &&
|
|
41
|
+
(![...left.protocol_events, ...right.protocol_events].some(({ protocol }) => protocol === "websocket") ||
|
|
42
|
+
sources.has("websocket")) &&
|
|
43
|
+
(!any(left.replay_transitions, right.replay_transitions) ||
|
|
44
|
+
sources.has("replay_transition")));
|
|
45
|
+
case "process":
|
|
46
|
+
return sources.has("process");
|
|
47
|
+
case "shim":
|
|
48
|
+
return sources.has("shim");
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { processObservationLocationSchema, projectProcessObservation, } from "./processObservation.js";
|
|
3
|
+
import { canonicalTraceJson, comparableTracePayload, processTraceCardinalityBounds, } from "./processTraceSpecification.js";
|
|
4
|
+
const identifierSchema = z.string().regex(/^[A-Za-z][A-Za-z0-9._-]{0,63}$/u);
|
|
5
|
+
const locationSchema = processObservationLocationSchema;
|
|
6
|
+
const matchedEventSchema = z.strictObject({
|
|
7
|
+
event_id: identifierSchema,
|
|
8
|
+
location: locationSchema,
|
|
9
|
+
});
|
|
10
|
+
const sideResultSchema = z.strictObject({
|
|
11
|
+
status: z.enum(["pass", "fail", "unknown"]),
|
|
12
|
+
matched_variant: identifierSchema.nullable(),
|
|
13
|
+
satisfied_constraints: z.array(z.string()),
|
|
14
|
+
raw_trace: z.array(matchedEventSchema),
|
|
15
|
+
});
|
|
16
|
+
const diagnosticSchema = z.strictObject({
|
|
17
|
+
kind: z.enum([
|
|
18
|
+
"journal",
|
|
19
|
+
"predicate",
|
|
20
|
+
"cardinality",
|
|
21
|
+
"edge",
|
|
22
|
+
"prefix",
|
|
23
|
+
"suffix",
|
|
24
|
+
"trace",
|
|
25
|
+
]),
|
|
26
|
+
side: z.enum(["left", "right"]),
|
|
27
|
+
message: z.string(),
|
|
28
|
+
event_ids: z.array(identifierSchema),
|
|
29
|
+
locations: z.array(locationSchema),
|
|
30
|
+
});
|
|
31
|
+
/** Structured verdict for one declared process trace language. */
|
|
32
|
+
export const processTraceComparisonResultSchema = z.strictObject({
|
|
33
|
+
verdict: z.enum(["equivalent", "different", "nonconforming", "unknown"]),
|
|
34
|
+
left: sideResultSchema,
|
|
35
|
+
right: sideResultSchema,
|
|
36
|
+
diagnostic: diagnosticSchema.nullable(),
|
|
37
|
+
});
|
|
38
|
+
const scopesFor = (sources) => new Set([...sources].map((source) => {
|
|
39
|
+
if (source.startsWith("terminal_"))
|
|
40
|
+
return "terminal";
|
|
41
|
+
if (source === "lifecycle")
|
|
42
|
+
return "exit";
|
|
43
|
+
if (source === "http" ||
|
|
44
|
+
source === "websocket" ||
|
|
45
|
+
source === "replay_transition")
|
|
46
|
+
return "protocol";
|
|
47
|
+
if (source === "filesystem")
|
|
48
|
+
return "filesystem";
|
|
49
|
+
if (source === "process")
|
|
50
|
+
return "process";
|
|
51
|
+
if (source === "shim")
|
|
52
|
+
return "shim";
|
|
53
|
+
return "interaction";
|
|
54
|
+
}));
|
|
55
|
+
const unknownSide = () => ({
|
|
56
|
+
status: "unknown",
|
|
57
|
+
matched_variant: null,
|
|
58
|
+
satisfied_constraints: [],
|
|
59
|
+
raw_trace: [],
|
|
60
|
+
});
|
|
61
|
+
const failure = (input) => ({
|
|
62
|
+
result: {
|
|
63
|
+
status: "fail",
|
|
64
|
+
matched_variant: null,
|
|
65
|
+
satisfied_constraints: [],
|
|
66
|
+
raw_trace: input.rawTrace,
|
|
67
|
+
},
|
|
68
|
+
diagnostic: {
|
|
69
|
+
kind: input.kind,
|
|
70
|
+
message: input.message,
|
|
71
|
+
event_ids: [...input.eventIds],
|
|
72
|
+
locations: [...input.locations],
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
const evaluateFiniteTrace = (ids, specification) => {
|
|
76
|
+
if (specification.language.kind !== "finite_traces")
|
|
77
|
+
return null;
|
|
78
|
+
for (const variant of specification.language.variants) {
|
|
79
|
+
let offset = 0;
|
|
80
|
+
let accepted = true;
|
|
81
|
+
for (const token of variant.trace) {
|
|
82
|
+
if (typeof token === "string") {
|
|
83
|
+
if (ids[offset] !== token)
|
|
84
|
+
accepted = false;
|
|
85
|
+
offset += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const actual = ids.slice(offset, offset + token.unordered.length).sort();
|
|
89
|
+
const expected = [...token.unordered].sort();
|
|
90
|
+
if (canonicalTraceJson(actual) !== canonicalTraceJson(expected))
|
|
91
|
+
accepted = false;
|
|
92
|
+
offset += token.unordered.length;
|
|
93
|
+
}
|
|
94
|
+
if (accepted && offset === ids.length)
|
|
95
|
+
return variant.id;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
};
|
|
99
|
+
const collectRecords = (capture, sources) => {
|
|
100
|
+
const records = [];
|
|
101
|
+
for (const location of capture.event_journal ?? []) {
|
|
102
|
+
const record = projectProcessObservation(capture, location);
|
|
103
|
+
if (record !== null && sources.has(record.source))
|
|
104
|
+
records.push({
|
|
105
|
+
source: record.source,
|
|
106
|
+
payload: record.payload,
|
|
107
|
+
location,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return records;
|
|
111
|
+
};
|
|
112
|
+
const matchRecords = (records, specification) => {
|
|
113
|
+
const rawTrace = [];
|
|
114
|
+
const locationsById = new Map();
|
|
115
|
+
const ignoredFieldsBySource = new Map();
|
|
116
|
+
const eventsByKey = new Map();
|
|
117
|
+
for (const event of specification.events) {
|
|
118
|
+
const ignoredFields = event.ignore_fields ?? [];
|
|
119
|
+
ignoredFieldsBySource.set(event.source, ignoredFields);
|
|
120
|
+
eventsByKey.set(`${event.source}\0${canonicalTraceJson(comparableTracePayload(event.exact, ignoredFields))}`, event);
|
|
121
|
+
}
|
|
122
|
+
for (const record of records) {
|
|
123
|
+
const match = eventsByKey.get(`${record.source}\0${canonicalTraceJson(comparableTracePayload(record.payload, ignoredFieldsBySource.get(record.source)))}`);
|
|
124
|
+
if (match === undefined)
|
|
125
|
+
return failure({
|
|
126
|
+
kind: "predicate",
|
|
127
|
+
message: "Observed event does not match any declared exact predicate.",
|
|
128
|
+
eventIds: [],
|
|
129
|
+
locations: [record.location],
|
|
130
|
+
rawTrace,
|
|
131
|
+
});
|
|
132
|
+
rawTrace.push({ event_id: match.id, location: record.location });
|
|
133
|
+
const locations = locationsById.get(match.id) ?? [];
|
|
134
|
+
locations.push(record.location);
|
|
135
|
+
locationsById.set(match.id, locations);
|
|
136
|
+
}
|
|
137
|
+
return { rawTrace, locationsById };
|
|
138
|
+
};
|
|
139
|
+
const cardinalityFailure = (trace, specification) => {
|
|
140
|
+
for (const event of specification.events) {
|
|
141
|
+
const [minimum, maximum] = processTraceCardinalityBounds(event.cardinality);
|
|
142
|
+
const locations = trace.locationsById.get(event.id) ?? [];
|
|
143
|
+
if (locations.length < minimum || locations.length > maximum)
|
|
144
|
+
return failure({
|
|
145
|
+
kind: "cardinality",
|
|
146
|
+
message: `${event.id} observed ${String(locations.length)} times; expected ${String(minimum)}..${String(maximum)}.`,
|
|
147
|
+
eventIds: [event.id],
|
|
148
|
+
locations,
|
|
149
|
+
rawTrace: trace.rawTrace,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
};
|
|
154
|
+
const evaluateOrderingConstraints = (trace, language) => {
|
|
155
|
+
const satisfied = [];
|
|
156
|
+
for (const edge of language.happens_before) {
|
|
157
|
+
const before = trace.locationsById.get(edge.before) ?? [];
|
|
158
|
+
const after = trace.locationsById.get(edge.after) ?? [];
|
|
159
|
+
const lastBefore = before.at(-1);
|
|
160
|
+
const firstAfter = after[0];
|
|
161
|
+
if (lastBefore === undefined && firstAfter !== undefined)
|
|
162
|
+
return {
|
|
163
|
+
failure: failure({
|
|
164
|
+
kind: "edge",
|
|
165
|
+
message: `${edge.before} is required when ${edge.after} occurs.`,
|
|
166
|
+
eventIds: [edge.before, edge.after],
|
|
167
|
+
locations: [firstAfter],
|
|
168
|
+
rawTrace: trace.rawTrace,
|
|
169
|
+
}),
|
|
170
|
+
satisfied,
|
|
171
|
+
};
|
|
172
|
+
if (lastBefore !== undefined &&
|
|
173
|
+
firstAfter !== undefined &&
|
|
174
|
+
lastBefore.capture_order >= firstAfter.capture_order)
|
|
175
|
+
return {
|
|
176
|
+
failure: failure({
|
|
177
|
+
kind: "edge",
|
|
178
|
+
message: `${edge.before} must happen before ${edge.after}.`,
|
|
179
|
+
eventIds: [edge.before, edge.after],
|
|
180
|
+
locations: [lastBefore, firstAfter],
|
|
181
|
+
rawTrace: trace.rawTrace,
|
|
182
|
+
}),
|
|
183
|
+
satisfied,
|
|
184
|
+
};
|
|
185
|
+
satisfied.push(`happens_before:${edge.before}:${edge.after}`);
|
|
186
|
+
}
|
|
187
|
+
for (const constraint of language.not_before) {
|
|
188
|
+
const events = trace.locationsById.get(constraint.event) ?? [];
|
|
189
|
+
const anchors = trace.locationsById.get(constraint.anchor) ?? [];
|
|
190
|
+
const firstEvent = events[0];
|
|
191
|
+
const firstAnchor = anchors[0];
|
|
192
|
+
if (firstEvent !== undefined && firstAnchor === undefined)
|
|
193
|
+
return {
|
|
194
|
+
failure: failure({
|
|
195
|
+
kind: "edge",
|
|
196
|
+
message: `${constraint.anchor} is required when ${constraint.event} occurs.`,
|
|
197
|
+
eventIds: [constraint.event, constraint.anchor],
|
|
198
|
+
locations: [firstEvent],
|
|
199
|
+
rawTrace: trace.rawTrace,
|
|
200
|
+
}),
|
|
201
|
+
satisfied,
|
|
202
|
+
};
|
|
203
|
+
if (firstEvent !== undefined &&
|
|
204
|
+
firstAnchor !== undefined &&
|
|
205
|
+
firstEvent.capture_order < firstAnchor.capture_order)
|
|
206
|
+
return {
|
|
207
|
+
failure: failure({
|
|
208
|
+
kind: "edge",
|
|
209
|
+
message: `${constraint.event} must not occur before ${constraint.anchor}.`,
|
|
210
|
+
eventIds: [constraint.event, constraint.anchor],
|
|
211
|
+
locations: [firstEvent, firstAnchor],
|
|
212
|
+
rawTrace: trace.rawTrace,
|
|
213
|
+
}),
|
|
214
|
+
satisfied,
|
|
215
|
+
};
|
|
216
|
+
satisfied.push(`not_before:${constraint.event}:${constraint.anchor}`);
|
|
217
|
+
}
|
|
218
|
+
return { failure: null, satisfied };
|
|
219
|
+
};
|
|
220
|
+
const boundaryFailure = (trace, kind, expected) => {
|
|
221
|
+
const ids = trace.rawTrace.map(({ event_id }) => event_id);
|
|
222
|
+
const actual = kind === "prefix"
|
|
223
|
+
? ids.slice(0, expected.length)
|
|
224
|
+
: expected.length === 0
|
|
225
|
+
? []
|
|
226
|
+
: ids.slice(-expected.length);
|
|
227
|
+
if (canonicalTraceJson(actual) === canonicalTraceJson(expected))
|
|
228
|
+
return null;
|
|
229
|
+
const locations = kind === "prefix"
|
|
230
|
+
? trace.rawTrace.slice(0, expected.length)
|
|
231
|
+
: trace.rawTrace.slice(-expected.length);
|
|
232
|
+
return failure({
|
|
233
|
+
kind,
|
|
234
|
+
message: `Observed trace does not satisfy the declared ${kind}.`,
|
|
235
|
+
eventIds: expected,
|
|
236
|
+
locations: locations.map(({ location }) => location),
|
|
237
|
+
rawTrace: trace.rawTrace,
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
const evaluatePartialOrder = (trace, language) => {
|
|
241
|
+
const ordering = evaluateOrderingConstraints(trace, language);
|
|
242
|
+
if (ordering.failure !== null)
|
|
243
|
+
return ordering.failure;
|
|
244
|
+
const prefixFailure = boundaryFailure(trace, "prefix", language.prefix);
|
|
245
|
+
if (prefixFailure !== null)
|
|
246
|
+
return prefixFailure;
|
|
247
|
+
const suffixFailure = boundaryFailure(trace, "suffix", language.suffix);
|
|
248
|
+
if (suffixFailure !== null)
|
|
249
|
+
return suffixFailure;
|
|
250
|
+
const satisfied = ordering.satisfied;
|
|
251
|
+
satisfied.push(...language.unordered_groups.map(({ events }) => `unordered:${events.join(":")}`));
|
|
252
|
+
if (language.prefix.length > 0)
|
|
253
|
+
satisfied.push(`prefix:${language.prefix.join(":")}`);
|
|
254
|
+
if (language.suffix.length > 0)
|
|
255
|
+
satisfied.push(`suffix:${language.suffix.join(":")}`);
|
|
256
|
+
return {
|
|
257
|
+
result: {
|
|
258
|
+
status: "pass",
|
|
259
|
+
matched_variant: null,
|
|
260
|
+
satisfied_constraints: satisfied,
|
|
261
|
+
raw_trace: trace.rawTrace,
|
|
262
|
+
},
|
|
263
|
+
diagnostic: null,
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
const evaluateMatchedTrace = (trace, specification) => {
|
|
267
|
+
const invalidCardinality = cardinalityFailure(trace, specification);
|
|
268
|
+
if (invalidCardinality !== null)
|
|
269
|
+
return invalidCardinality;
|
|
270
|
+
if (specification.language.kind === "partial_order")
|
|
271
|
+
return evaluatePartialOrder(trace, specification.language);
|
|
272
|
+
const ids = trace.rawTrace.map(({ event_id }) => event_id);
|
|
273
|
+
const variant = evaluateFiniteTrace(ids, specification);
|
|
274
|
+
return variant === null
|
|
275
|
+
? failure({
|
|
276
|
+
kind: "trace",
|
|
277
|
+
message: "Observed events do not match any finite trace variant.",
|
|
278
|
+
eventIds: ids,
|
|
279
|
+
locations: trace.rawTrace.map(({ location }) => location),
|
|
280
|
+
rawTrace: trace.rawTrace,
|
|
281
|
+
})
|
|
282
|
+
: {
|
|
283
|
+
result: {
|
|
284
|
+
status: "pass",
|
|
285
|
+
matched_variant: variant,
|
|
286
|
+
satisfied_constraints: [`variant:${variant}`],
|
|
287
|
+
raw_trace: trace.rawTrace,
|
|
288
|
+
},
|
|
289
|
+
diagnostic: null,
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
export const evaluateProcessTraceSide = (capture, specification) => {
|
|
293
|
+
const sources = new Set(specification.events.map(({ source }) => source));
|
|
294
|
+
const relevantScopes = scopesFor(sources);
|
|
295
|
+
if (capture.truncated ||
|
|
296
|
+
capture.residual_unknowns.some(({ scope }) => relevantScopes.has(scope)))
|
|
297
|
+
return { result: unknownSide(), diagnostic: null };
|
|
298
|
+
if ((capture.event_journal ?? []).length === 0)
|
|
299
|
+
return {
|
|
300
|
+
result: unknownSide(),
|
|
301
|
+
diagnostic: {
|
|
302
|
+
kind: "journal",
|
|
303
|
+
message: "Capture has no cross-source event journal.",
|
|
304
|
+
event_ids: [],
|
|
305
|
+
locations: [],
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
const matched = matchRecords(collectRecords(capture, sources), specification);
|
|
309
|
+
return "rawTrace" in matched
|
|
310
|
+
? evaluateMatchedTrace(matched, specification)
|
|
311
|
+
: matched;
|
|
312
|
+
};
|