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
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import canonicalize from "canonicalize";
|
|
2
|
+
import { projectProcessObservation, } from "./processObservation.js";
|
|
3
|
+
import { commitProcessReactiveProposal, createProcessReactiveSnapshot, reduceProcessReactiveScenario, } from "./processReactiveRuntime.js";
|
|
4
|
+
import { processReactiveScenarioSchema, } from "./processReactiveScenario.js";
|
|
5
|
+
const admittedCollection = (collection) => [
|
|
6
|
+
"frames",
|
|
7
|
+
"interaction_events",
|
|
8
|
+
"process_samples",
|
|
9
|
+
"filesystem_checkpoints",
|
|
10
|
+
"shim_events",
|
|
11
|
+
"protocol_events",
|
|
12
|
+
].includes(collection);
|
|
13
|
+
const reactiveObservations = (capture) => (capture.event_journal ?? []).flatMap((location) => {
|
|
14
|
+
if (!admittedCollection(location.collection))
|
|
15
|
+
return [];
|
|
16
|
+
const observation = projectProcessObservation(capture, location);
|
|
17
|
+
return observation === null ? [] : [observation];
|
|
18
|
+
});
|
|
19
|
+
const validateTransitionDeclaration = (transition, next, context) => {
|
|
20
|
+
const { index, scenario, require } = context;
|
|
21
|
+
const path = `reactive_run.transitions[${String(index)}]`;
|
|
22
|
+
const declared = scenario.states
|
|
23
|
+
.find(({ id }) => id === transition.state_before)
|
|
24
|
+
?.on.find(({ id }) => id === transition.transition_id);
|
|
25
|
+
require(declared !==
|
|
26
|
+
undefined, path, "reactive transition is not declared by its recorded source state");
|
|
27
|
+
if (declared !== undefined) {
|
|
28
|
+
const stateAfter = declared.target.kind === "goto" ? declared.target.state : null;
|
|
29
|
+
const outcome = declared.target.kind === "finish" ? declared.target.outcome : null;
|
|
30
|
+
require(transition.state_after === stateAfter &&
|
|
31
|
+
transition.outcome ===
|
|
32
|
+
outcome, path, "reactive transition target or outcome differs from the committed graph");
|
|
33
|
+
require(canonicalize(transition.action_types) ===
|
|
34
|
+
canonicalize(declared.actions.map(({ type }) => type)), `${path}.action_types`, "reactive transition actions differ from the committed graph");
|
|
35
|
+
}
|
|
36
|
+
require(transition.sequence ===
|
|
37
|
+
index, `${path}.sequence`, "reactive transition sequence must be contiguous from zero");
|
|
38
|
+
if (next !== undefined)
|
|
39
|
+
require(transition.state_after ===
|
|
40
|
+
next.state_before, `reactive_run.transitions[${String(index + 1)}].state_before`, "reactive transition states must be contiguous");
|
|
41
|
+
};
|
|
42
|
+
const validateTransitionDeclarations = (run, scenario, require) => {
|
|
43
|
+
require(run.transitions[0]?.state_before === scenario.initial_state ||
|
|
44
|
+
run.transitions.length ===
|
|
45
|
+
0, "reactive_run.transitions[0].state_before", "reactive transition journal must start at the declared initial state");
|
|
46
|
+
for (const [index, transition] of run.transitions.entries())
|
|
47
|
+
validateTransitionDeclaration(transition, run.transitions[index + 1], {
|
|
48
|
+
index,
|
|
49
|
+
scenario,
|
|
50
|
+
require,
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const commitRecordedEffects = (options) => commitProcessReactiveProposal(options.scenario, options.proposal, options.recordedFailure === undefined
|
|
54
|
+
? options.proposal.effects.map((_, index) => {
|
|
55
|
+
const eventId = options.expected?.action_event_ids[index];
|
|
56
|
+
const observation = eventId === undefined
|
|
57
|
+
? undefined
|
|
58
|
+
: options.observations.get(eventId);
|
|
59
|
+
return observation === undefined
|
|
60
|
+
? { status: "rejected" }
|
|
61
|
+
: { status: "succeeded", observation };
|
|
62
|
+
})
|
|
63
|
+
: [
|
|
64
|
+
options.recordedFailure === "target_lost"
|
|
65
|
+
? { status: "target_lost" }
|
|
66
|
+
: { status: "rejected" },
|
|
67
|
+
], { committedSensitiveInputs: true });
|
|
68
|
+
const replayTransitions = (scenario, run, observations, require) => {
|
|
69
|
+
const observationsById = new Map(observations.map((observation) => [observation.event_id, observation]));
|
|
70
|
+
let snapshot = createProcessReactiveSnapshot(scenario);
|
|
71
|
+
let transitionIndex = 0;
|
|
72
|
+
const advance = (input) => {
|
|
73
|
+
let decision = reduceProcessReactiveScenario(scenario, snapshot, input);
|
|
74
|
+
if (decision.kind === "proposal") {
|
|
75
|
+
const proposal = decision;
|
|
76
|
+
decision = commitRecordedEffects({
|
|
77
|
+
scenario,
|
|
78
|
+
proposal: decision,
|
|
79
|
+
expected: run.transitions[transitionIndex],
|
|
80
|
+
observations: observationsById,
|
|
81
|
+
recordedFailure: run.transitions[transitionIndex] === undefined &&
|
|
82
|
+
decision.effects.length > 0 &&
|
|
83
|
+
(run.outcome === "action_rejected" || run.outcome === "target_lost")
|
|
84
|
+
? run.outcome
|
|
85
|
+
: undefined,
|
|
86
|
+
});
|
|
87
|
+
require(decision.kind !== "finished" ||
|
|
88
|
+
decision.outcome !== "action_rejected" ||
|
|
89
|
+
run.outcome ===
|
|
90
|
+
"action_rejected", `reactive_run.transitions[${String(transitionIndex)}].action_event_ids`, `recorded action observations do not match proposed effects: ${canonicalize({
|
|
91
|
+
effects: proposal.effects,
|
|
92
|
+
observations: run.transitions[transitionIndex]?.action_event_ids.map((eventId) => observationsById.get(eventId)),
|
|
93
|
+
})}`);
|
|
94
|
+
}
|
|
95
|
+
if (decision.kind === "transition") {
|
|
96
|
+
const expected = run.transitions[transitionIndex];
|
|
97
|
+
require(expected !== undefined &&
|
|
98
|
+
canonicalize(decision.record) ===
|
|
99
|
+
canonicalize(expected), `reactive_run.transitions[${String(transitionIndex)}]`, "reactive transition differs from deterministic journal replay");
|
|
100
|
+
transitionIndex += 1;
|
|
101
|
+
}
|
|
102
|
+
snapshot = decision.snapshot;
|
|
103
|
+
};
|
|
104
|
+
const advanceControl = (control, index) => {
|
|
105
|
+
require(snapshot.status !== "finished" ||
|
|
106
|
+
control.kind ===
|
|
107
|
+
"cleanup_failed", `reactive_run.controls[${String(index)}]`, "reactive control cannot follow a finished run");
|
|
108
|
+
advance(control.kind === "state_deadline"
|
|
109
|
+
? {
|
|
110
|
+
kind: control.kind,
|
|
111
|
+
state_id: snapshot.active_state,
|
|
112
|
+
state_entry_capture_order: snapshot.state_entry_capture_order,
|
|
113
|
+
}
|
|
114
|
+
: { kind: control.kind });
|
|
115
|
+
};
|
|
116
|
+
let controlIndex = 0;
|
|
117
|
+
for (const observation of observations) {
|
|
118
|
+
while ((run.controls[controlIndex]?.after_capture_order ??
|
|
119
|
+
Number.MAX_SAFE_INTEGER) < observation.capture_order) {
|
|
120
|
+
const control = run.controls[controlIndex];
|
|
121
|
+
advanceControl(control, controlIndex);
|
|
122
|
+
controlIndex += 1;
|
|
123
|
+
}
|
|
124
|
+
advance({ kind: "observation", observation });
|
|
125
|
+
}
|
|
126
|
+
for (; controlIndex < run.controls.length; controlIndex += 1) {
|
|
127
|
+
const control = run.controls[controlIndex];
|
|
128
|
+
advanceControl(control, controlIndex);
|
|
129
|
+
}
|
|
130
|
+
require(transitionIndex ===
|
|
131
|
+
run.transitions
|
|
132
|
+
.length, "reactive_run.transitions", `reactive transition journal is not reproduced by deterministic replay (replayed ${String(transitionIndex)} of ${String(run.transitions.length)}; outcome ${snapshot.outcome})`);
|
|
133
|
+
return snapshot;
|
|
134
|
+
};
|
|
135
|
+
const validateControls = (run, observations, require) => {
|
|
136
|
+
const admittedOrders = new Set([
|
|
137
|
+
-1,
|
|
138
|
+
...observations.map(({ capture_order }) => capture_order),
|
|
139
|
+
]);
|
|
140
|
+
for (const [index, control] of run.controls.entries()) {
|
|
141
|
+
require(control.sequence ===
|
|
142
|
+
index, `reactive_run.controls[${String(index)}].sequence`, "reactive control sequence must be contiguous");
|
|
143
|
+
require(admittedOrders.has(control.after_capture_order), `reactive_run.controls[${String(index)}].after_capture_order`, "reactive control cutoff must identify the last admitted observation");
|
|
144
|
+
require(index === 0 ||
|
|
145
|
+
control.after_capture_order >=
|
|
146
|
+
run.controls[index - 1]
|
|
147
|
+
.after_capture_order, `reactive_run.controls[${String(index)}].after_capture_order`, "reactive control cutoffs must be ordered");
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
/** Validate one capture's reactive journal by replaying the production reducer. */
|
|
151
|
+
export const validateProcessCaptureReactiveRun = (capture, require) => {
|
|
152
|
+
const declared = capture.manifest.scenario["reactive"];
|
|
153
|
+
const run = capture.reactive_run;
|
|
154
|
+
const declaredReactive = declared !== undefined && declared !== null;
|
|
155
|
+
require(declaredReactive ===
|
|
156
|
+
(run !==
|
|
157
|
+
null), "reactive_run", "reactive result presence must match the committed scenario");
|
|
158
|
+
if (run === null)
|
|
159
|
+
return;
|
|
160
|
+
const parsed = processReactiveScenarioSchema.safeParse(declared);
|
|
161
|
+
require(parsed.success, "manifest.scenario.reactive", "committed reactive scenario does not satisfy its declared schema");
|
|
162
|
+
if (!parsed.success)
|
|
163
|
+
return;
|
|
164
|
+
require(run.status === "finished" &&
|
|
165
|
+
run.outcome !==
|
|
166
|
+
null, "reactive_run.outcome", "completed capture requires a finished reactive outcome");
|
|
167
|
+
validateTransitionDeclarations(run, parsed.data, require);
|
|
168
|
+
const observations = reactiveObservations(capture);
|
|
169
|
+
validateControls(run, observations, require);
|
|
170
|
+
const snapshot = replayTransitions(parsed.data, run, observations, require);
|
|
171
|
+
require(snapshot.outcome ===
|
|
172
|
+
run.outcome, "reactive_run.outcome", "reactive outcome differs from deterministic journal replay");
|
|
173
|
+
require(snapshot.active_state ===
|
|
174
|
+
run.active_state, "reactive_run.active_state", "reactive active state differs from deterministic journal replay");
|
|
175
|
+
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { validateProcessCaptureReactiveRun } from "./processCaptureReactiveValidation.js";
|
|
2
|
+
import { replayMachineSchema } from "./replayMachine.js";
|
|
1
3
|
import { digestProcessCommitment } from "./processScenario.js";
|
|
2
4
|
const orderedTimestamps = (values) => values.every((value, index) => index === 0 || value.at_ms >= values[index - 1].at_ms);
|
|
3
5
|
const validateCommitments = (capture, require) => {
|
|
@@ -23,6 +25,7 @@ const validateOrdering = (capture, require) => {
|
|
|
23
25
|
["interaction_events", capture.interaction_events],
|
|
24
26
|
["shim_events", capture.shim_events],
|
|
25
27
|
["protocol_events", capture.protocol_events],
|
|
28
|
+
["replay_transitions", capture.replay_transitions],
|
|
26
29
|
])
|
|
27
30
|
require(values.every(({ sequence }, index) => sequence === index), name, "sequence values must be contiguous from zero");
|
|
28
31
|
for (const [name, values] of [
|
|
@@ -32,9 +35,40 @@ const validateOrdering = (capture, require) => {
|
|
|
32
35
|
["filesystem_checkpoints", capture.filesystem_checkpoints],
|
|
33
36
|
["shim_events", capture.shim_events],
|
|
34
37
|
["protocol_events", capture.protocol_events],
|
|
38
|
+
["replay_transitions", capture.replay_transitions],
|
|
35
39
|
])
|
|
36
40
|
require(orderedTimestamps(values), name, "timestamps must be ordered");
|
|
37
41
|
};
|
|
42
|
+
const journalCollectionSizes = (capture) => ({
|
|
43
|
+
frames: capture.frames.length,
|
|
44
|
+
rendered_frames: capture.rendered_frames.length,
|
|
45
|
+
interaction_events: capture.interaction_events.length,
|
|
46
|
+
lifecycle: 2,
|
|
47
|
+
process_samples: capture.process_samples.length,
|
|
48
|
+
filesystem_checkpoints: capture.filesystem_checkpoints.length,
|
|
49
|
+
shim_events: capture.shim_events.length,
|
|
50
|
+
protocol_events: capture.protocol_events.length,
|
|
51
|
+
replay_transitions: capture.replay_transitions.length,
|
|
52
|
+
});
|
|
53
|
+
const validateEventJournal = (capture, require) => {
|
|
54
|
+
const journal = capture.event_journal ?? [];
|
|
55
|
+
if (journal.length === 0)
|
|
56
|
+
return;
|
|
57
|
+
const sizes = journalCollectionSizes(capture);
|
|
58
|
+
const references = new Set();
|
|
59
|
+
for (const [position, entry] of journal.entries()) {
|
|
60
|
+
require(entry.capture_order ===
|
|
61
|
+
position, `event_journal.${String(position)}.capture_order`, "capture order must be contiguous from zero");
|
|
62
|
+
require(entry.index <
|
|
63
|
+
sizes[entry.collection], `event_journal.${String(position)}.index`, "journal reference is outside its capture collection");
|
|
64
|
+
const reference = `${entry.collection}:${String(entry.index)}`;
|
|
65
|
+
require(!references.has(reference), `event_journal.${String(position)}`, "journal references must be unique");
|
|
66
|
+
references.add(reference);
|
|
67
|
+
}
|
|
68
|
+
const expectedSize = Object.values(sizes).reduce((total, size) => total + size, 0);
|
|
69
|
+
require(journal.length ===
|
|
70
|
+
expectedSize, "event_journal", "nonempty journal must reference every captured observation");
|
|
71
|
+
};
|
|
38
72
|
const validateLifecycle = (capture, require) => {
|
|
39
73
|
const checkpointNames = capture.filesystem_checkpoints.map(({ name }) => name);
|
|
40
74
|
require(new Set(checkpointNames).size ===
|
|
@@ -89,23 +123,78 @@ const validateShimEvents = (capture, require) => {
|
|
|
89
123
|
maximum, "shim_events", "matched shim events exceed the declared route max_calls");
|
|
90
124
|
}
|
|
91
125
|
};
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
126
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
127
|
+
const machinePlan = (replayPlan) => {
|
|
128
|
+
const parsed = replayMachineSchema.safeParse(replayPlan["machine"]);
|
|
129
|
+
return parsed.success ? parsed.data : undefined;
|
|
130
|
+
};
|
|
131
|
+
const matchesHttpPlan = (value, event) => {
|
|
132
|
+
if (!isRecord(value))
|
|
133
|
+
return false;
|
|
134
|
+
const route = isRecord(value["trigger"]) ? value["trigger"] : value;
|
|
135
|
+
return ((route["protocol"] === undefined || route["protocol"] === "http") &&
|
|
136
|
+
route["method"] === event.method &&
|
|
137
|
+
route["path"] === event.path);
|
|
138
|
+
};
|
|
139
|
+
const validateMatchedHttpEvents = (capture, plans, require) => {
|
|
97
140
|
for (const event of capture.protocol_events) {
|
|
98
141
|
if (event.protocol !== "http" ||
|
|
99
142
|
event.direction !== "request" ||
|
|
100
143
|
event.outcome !== "matched")
|
|
101
144
|
continue;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
145
|
+
require(plans.some((plan) => matchesHttpPlan(plan, event)), "protocol_events", "matched HTTP event has no declared replay route");
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const validateReplayEvents = (capture, require) => {
|
|
149
|
+
const replayPlan = capture.manifest.replay_plan;
|
|
150
|
+
const routes = Array.isArray(replayPlan["http"]) ? replayPlan["http"] : [];
|
|
151
|
+
const machine = machinePlan(replayPlan);
|
|
152
|
+
require(replayPlan["machine"] === undefined ||
|
|
153
|
+
replayPlan["machine"] === null ||
|
|
154
|
+
machine !==
|
|
155
|
+
undefined, "manifest.replay_plan.machine", "replay machine does not satisfy its declared schema");
|
|
156
|
+
validateMatchedHttpEvents(capture, [...routes, ...(machine?.transitions ?? [])], require);
|
|
157
|
+
const plans = new Map((machine?.transitions ?? []).map((transition) => [
|
|
158
|
+
transition.id,
|
|
159
|
+
transition,
|
|
160
|
+
]));
|
|
161
|
+
const transitionUses = new Map();
|
|
162
|
+
const stateVisits = new Map();
|
|
163
|
+
if (machine !== undefined)
|
|
164
|
+
stateVisits.set(machine.initial_state, 1);
|
|
165
|
+
require(machine === undefined ||
|
|
166
|
+
capture.replay_transitions.length <=
|
|
167
|
+
machine.max_transitions, "replay_transitions", "replay transition journal exceeds the declared machine limit");
|
|
168
|
+
for (const [index, transition] of capture.replay_transitions.entries()) {
|
|
169
|
+
const plan = plans.get(transition.transition_id);
|
|
170
|
+
require(plan !==
|
|
171
|
+
undefined, "replay_transitions", "replay transition has no declared machine transition");
|
|
172
|
+
if (plan === undefined)
|
|
173
|
+
continue;
|
|
174
|
+
require(transition.state_before === plan.from &&
|
|
175
|
+
transition.state_after ===
|
|
176
|
+
plan.to, "replay_transitions", "replay transition states do not match the declared transition");
|
|
177
|
+
require(JSON.stringify(transition.sensitive_aliases) ===
|
|
178
|
+
JSON.stringify(plan.captures
|
|
179
|
+
.filter(({ sensitive }) => sensitive)
|
|
180
|
+
.map(({ variable }) => variable)
|
|
181
|
+
.sort()), "replay_transitions", "replay transition sensitive aliases do not match the declared captures");
|
|
182
|
+
const uses = (transitionUses.get(plan.id) ?? 0) + 1;
|
|
183
|
+
transitionUses.set(plan.id, uses);
|
|
184
|
+
require(uses <=
|
|
185
|
+
plan.max_uses, "replay_transitions", "replay transition journal exceeds the transition use limit");
|
|
186
|
+
const visits = (stateVisits.get(transition.state_after) ?? 0) + 1;
|
|
187
|
+
stateVisits.set(transition.state_after, visits);
|
|
188
|
+
const state = machine?.states.find(({ name }) => name === transition.state_after);
|
|
189
|
+
require(state === undefined ||
|
|
190
|
+
visits <=
|
|
191
|
+
state.max_visits, "replay_transitions", "replay transition journal exceeds the state visit limit");
|
|
192
|
+
if (index === 0)
|
|
193
|
+
require(transition.state_before ===
|
|
194
|
+
machine?.initial_state, "replay_transitions", "first replay transition does not start at the declared initial state");
|
|
195
|
+
if (index > 0)
|
|
196
|
+
require(capture.replay_transitions[index - 1].state_after ===
|
|
197
|
+
transition.state_before, "replay_transitions", "replay transition states are not contiguous");
|
|
109
198
|
}
|
|
110
199
|
};
|
|
111
200
|
/** Recompute v4 commitments and cross-field invariants without side effects. */
|
|
@@ -117,8 +206,10 @@ export const validateProcessCapture = (capture) => {
|
|
|
117
206
|
};
|
|
118
207
|
validateCommitments(capture, require);
|
|
119
208
|
validateOrdering(capture, require);
|
|
209
|
+
validateEventJournal(capture, require);
|
|
120
210
|
validateLifecycle(capture, require);
|
|
121
211
|
validateShimEvents(capture, require);
|
|
122
212
|
validateReplayEvents(capture, require);
|
|
213
|
+
validateProcessCaptureReactiveRun(capture, require);
|
|
123
214
|
return issues;
|
|
124
215
|
};
|
|
@@ -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
|
-
|
|
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
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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,162 @@
|
|
|
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
|
+
/** Derive the stable capture-local identity for one normalized observation. */
|
|
64
|
+
export const processObservationSubject = (source, value) => {
|
|
65
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
66
|
+
return null;
|
|
67
|
+
if (source === "process" && "pid" in value && typeof value.pid === "number")
|
|
68
|
+
return `process:${String(value.pid)}`;
|
|
69
|
+
if (source === "filesystem" &&
|
|
70
|
+
"name" in value &&
|
|
71
|
+
typeof value.name === "string")
|
|
72
|
+
return `checkpoint:${value.name}`;
|
|
73
|
+
if (source === "shim" &&
|
|
74
|
+
"command" in value &&
|
|
75
|
+
typeof value.command === "string")
|
|
76
|
+
return "route_index" in value && typeof value.route_index === "number"
|
|
77
|
+
? `shim:${value.command}:route:${String(value.route_index)}`
|
|
78
|
+
: `shim:${value.command}:unmatched`;
|
|
79
|
+
return null;
|
|
80
|
+
};
|
|
81
|
+
const projectedRecord = (capture, location) => {
|
|
82
|
+
const projected = (() => {
|
|
83
|
+
switch (location.collection) {
|
|
84
|
+
case "frames":
|
|
85
|
+
return {
|
|
86
|
+
source: "terminal_raw",
|
|
87
|
+
value: capture.frames[location.index],
|
|
88
|
+
};
|
|
89
|
+
case "rendered_frames":
|
|
90
|
+
return {
|
|
91
|
+
source: "terminal_rendered",
|
|
92
|
+
value: capture.rendered_frames[location.index],
|
|
93
|
+
};
|
|
94
|
+
case "interaction_events":
|
|
95
|
+
return {
|
|
96
|
+
source: "interaction",
|
|
97
|
+
value: capture.interaction_events[location.index],
|
|
98
|
+
};
|
|
99
|
+
case "lifecycle":
|
|
100
|
+
return location.index === 0
|
|
101
|
+
? {
|
|
102
|
+
source: "lifecycle",
|
|
103
|
+
value: { event: "exit", ...capture.exit },
|
|
104
|
+
}
|
|
105
|
+
: location.index === 1
|
|
106
|
+
? {
|
|
107
|
+
source: "lifecycle",
|
|
108
|
+
value: { event: "settlement", ...capture.settlement },
|
|
109
|
+
}
|
|
110
|
+
: null;
|
|
111
|
+
case "process_samples":
|
|
112
|
+
return {
|
|
113
|
+
source: "process",
|
|
114
|
+
value: capture.process_samples[location.index],
|
|
115
|
+
};
|
|
116
|
+
case "filesystem_checkpoints":
|
|
117
|
+
return {
|
|
118
|
+
source: "filesystem",
|
|
119
|
+
value: capture.filesystem_checkpoints[location.index],
|
|
120
|
+
};
|
|
121
|
+
case "shim_events":
|
|
122
|
+
return {
|
|
123
|
+
source: "shim",
|
|
124
|
+
value: capture.shim_events[location.index],
|
|
125
|
+
};
|
|
126
|
+
case "protocol_events": {
|
|
127
|
+
const event = capture.protocol_events[location.index];
|
|
128
|
+
return event === undefined
|
|
129
|
+
? null
|
|
130
|
+
: { source: event.protocol, value: event };
|
|
131
|
+
}
|
|
132
|
+
case "replay_transitions":
|
|
133
|
+
return {
|
|
134
|
+
source: "replay_transition",
|
|
135
|
+
value: capture.replay_transitions[location.index],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
})();
|
|
139
|
+
if (projected === null || projected.value === undefined)
|
|
140
|
+
return null;
|
|
141
|
+
const payload = jsonValueSchema.parse(projected.value);
|
|
142
|
+
return {
|
|
143
|
+
source: projected.source,
|
|
144
|
+
captured_at_ms: atMilliseconds(payload),
|
|
145
|
+
subject_id: processObservationSubject(projected.source, payload),
|
|
146
|
+
payload,
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
/** Project one validated capture-journal reference into the shared observation shape. */
|
|
150
|
+
export const projectProcessObservation = (capture, location) => {
|
|
151
|
+
const projected = projectedRecord(capture, location);
|
|
152
|
+
if (projected === null)
|
|
153
|
+
return null;
|
|
154
|
+
return createProcessObservation({
|
|
155
|
+
source: projected.source,
|
|
156
|
+
source_sequence: location.index,
|
|
157
|
+
captured_at_ms: projected.captured_at_ms,
|
|
158
|
+
subject_id: projected.subject_id,
|
|
159
|
+
location,
|
|
160
|
+
payload: projected.payload,
|
|
161
|
+
});
|
|
162
|
+
};
|