rea-agents 2.1.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.
- package/README.md +84 -18
- package/dist/application/AndroidApplicationService.js +17 -0
- package/dist/application/AppleApplicationService.js +17 -0
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +162 -0
- package/dist/application/BinarySession.js +9 -2
- package/dist/application/BinarySessionStatus.js +39 -1
- package/dist/application/CapabilityInventory.js +15 -0
- package/dist/application/ClientRegistrationStatus.js +11 -4
- 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/InventoryProjectionEvidence.js +41 -0
- package/dist/application/InvestigationProviders.js +12 -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/ProcessCaptureJournal.js +95 -0
- package/dist/application/ProcessCaptureLifecycle.js +24 -13
- package/dist/application/ProcessCheckpoints.js +30 -4
- package/dist/application/ProcessCli.js +26 -4
- package/dist/application/ProcessHarness.js +20 -50
- package/dist/application/ProcessNormalization.js +3 -2
- package/dist/application/ProcessPairedExperiment.js +72 -0
- package/dist/application/ProcessReactiveCoordinator.js +218 -0
- package/dist/application/ProcessReactiveEffects.js +133 -0
- package/dist/application/ProcessReactiveObservations.js +20 -0
- package/dist/application/ProcessSampling.js +2 -0
- package/dist/application/SessionProviderRouter.js +7 -6
- package/dist/application/Setup.js +14 -13
- package/dist/application/SetupClientConfiguration.js +10 -6
- package/dist/application/SetupPlan.js +4 -3
- package/dist/application/SetupRegistrationEnvironment.js +12 -0
- package/dist/application/SetupSkill.js +44 -40
- package/dist/application/TerminalRenderer.js +3 -1
- package/dist/application/Uninstall.js +7 -1
- package/dist/application/Upgrade.js +16 -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 +84 -30
- 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 +55 -29
- package/dist/contracts/applicationToolContracts.js +54 -7
- package/dist/contracts/applicationWorkflowInputContracts.js +119 -0
- package/dist/contracts/electronToolContracts.js +73 -4
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +32 -9
- package/dist/contracts/managedToolContracts.js +7 -2
- package/dist/contracts/processCaptureExample.js +1 -0
- package/dist/contracts/replayMachineExample.js +37 -0
- package/dist/contracts/sessionStatusContract.js +26 -0
- package/dist/contracts/toolContractExamples.js +2 -0
- package/dist/contracts/toolContractTypes.js +12 -1
- package/dist/contracts/toolContracts.js +8 -11
- package/dist/contracts/toolEffects.js +3 -0
- package/dist/contracts/toolOutputSchemaGroups.js +53 -9
- package/dist/contracts/toolOutputSchemaPrimitives.js +73 -32
- package/dist/doctorRuntime.js +41 -0
- package/dist/domain/analysisErrorPresentation.js +5 -3
- package/dist/domain/analysisErrorProjection.js +7 -1
- package/dist/domain/androidApplication.js +206 -0
- package/dist/domain/appleApplication.js +221 -0
- package/dist/domain/boundedCartesianProjection.js +15 -0
- package/dist/domain/changedBehavior.js +5 -4
- package/dist/domain/completionLedgerGeneration.js +190 -0
- package/dist/domain/errors.js +4 -2
- 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 +36 -0
- package/dist/domain/processCaptureValidation.js +101 -12
- package/dist/domain/processComparison.js +96 -24
- package/dist/domain/processObservation.js +153 -0
- package/dist/domain/processPairedExperiment.js +128 -0
- package/dist/domain/processReactiveCheckpointDataflow.js +32 -0
- package/dist/domain/processReactiveMatching.js +210 -0
- package/dist/domain/processReactiveRuntime.js +109 -0
- package/dist/domain/processReactiveScenario.js +361 -0
- package/dist/domain/processReactiveScenarioPreflight.js +120 -0
- package/dist/domain/processReactiveTransition.js +159 -0
- package/dist/domain/processScenario.js +12 -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 +332 -0
- package/dist/domain/replayMachineRun.js +216 -0
- package/dist/domain/replayMachineRuntime.js +294 -0
- package/dist/domain/runtimeIdentification.js +210 -0
- package/dist/domain/staticRuntimeCorrelation.js +4 -2
- package/dist/evaluation/CodexAgentEval.js +156 -0
- package/dist/generatedPackageMetadata.js +2 -2
- 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/identity.js +2 -1
- package/dist/main/transport.js +2 -0
- package/dist/mcpDoctor.js +310 -0
- package/dist/process/ProcessOwnership.js +200 -53
- package/dist/process/ProviderRunLineage.js +21 -0
- package/dist/server/createServer.js +8 -2
- package/dist/server/javascriptApplicationResult.js +106 -0
- 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/registerElectronTools.js +19 -5
- package/dist/server/registerEvidenceResources.js +7 -1
- package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
- package/dist/server/registerManagedTools.js +18 -1
- package/dist/server/registerProcessComparisonTool.js +17 -7
- package/dist/server/registerReplayMachineTool.js +20 -0
- package/dist/server/registerSessionStatusTool.js +95 -25
- package/dist/server/registerSessionTools.js +12 -7
- package/dist/server/sessionAvailabilityPolicy.js +2 -1
- package/dist/server/toolResult.js +10 -4
- package/dist/serverIdentity.js +19 -0
- package/package.json +62 -29
- 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 +69 -228
- package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +35 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
- package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
|
@@ -2,6 +2,18 @@ import { z } from "zod";
|
|
|
2
2
|
import { jsonValueSchema } from "./jsonValue.js";
|
|
3
3
|
import { LEGACY_PROCESS_CAPTURE_MESSAGE, normalizationSchema, } from "./processScenario.js";
|
|
4
4
|
export * from "./processScenario.js";
|
|
5
|
+
/** Capture collection whose members participate in global observation order. */
|
|
6
|
+
export const PROCESS_CAPTURE_EVENT_COLLECTIONS = [
|
|
7
|
+
"frames",
|
|
8
|
+
"rendered_frames",
|
|
9
|
+
"interaction_events",
|
|
10
|
+
"lifecycle",
|
|
11
|
+
"process_samples",
|
|
12
|
+
"filesystem_checkpoints",
|
|
13
|
+
"shim_events",
|
|
14
|
+
"protocol_events",
|
|
15
|
+
"replay_transitions",
|
|
16
|
+
];
|
|
5
17
|
const fileStateSchema = z.object({
|
|
6
18
|
path: z.string(),
|
|
7
19
|
type: z.enum(["file", "directory", "symlink", "other"]),
|
|
@@ -115,8 +127,31 @@ export const processCaptureSchema = z.object({
|
|
|
115
127
|
"unmatched",
|
|
116
128
|
"script_exhausted",
|
|
117
129
|
"disconnected",
|
|
130
|
+
"invalid_state",
|
|
131
|
+
"guard_failed",
|
|
132
|
+
"transition_exhausted",
|
|
133
|
+
"invalid_capture",
|
|
134
|
+
"unexpected_reconnect",
|
|
135
|
+
"limit_exhausted",
|
|
118
136
|
]),
|
|
119
137
|
})),
|
|
138
|
+
replay_transitions: z
|
|
139
|
+
.array(z.object({
|
|
140
|
+
sequence: z.number().int().nonnegative(),
|
|
141
|
+
at_ms: z.number().int().nonnegative(),
|
|
142
|
+
transition_id: z.string().min(1).max(64),
|
|
143
|
+
state_before: z.string().min(1).max(64),
|
|
144
|
+
state_after: z.string().min(1).max(64),
|
|
145
|
+
sensitive_aliases: z.array(z.string().min(1).max(64)).max(32),
|
|
146
|
+
}))
|
|
147
|
+
.default([]),
|
|
148
|
+
event_journal: z
|
|
149
|
+
.array(z.object({
|
|
150
|
+
capture_order: z.number().int().nonnegative(),
|
|
151
|
+
collection: z.enum(PROCESS_CAPTURE_EVENT_COLLECTIONS),
|
|
152
|
+
index: z.number().int().nonnegative(),
|
|
153
|
+
}))
|
|
154
|
+
.default([]),
|
|
120
155
|
files_before: z.array(fileStateSchema),
|
|
121
156
|
files_after: z.array(fileStateSchema),
|
|
122
157
|
filesystem_effects: z.array(fileEffectSchema),
|
|
@@ -157,3 +192,4 @@ export const parseProcessCapture = (input) => {
|
|
|
157
192
|
return capture;
|
|
158
193
|
};
|
|
159
194
|
export { compareProcessCaptures, comparisonStatusSchema, deriveProcessComparisonStatus, PROCESS_COMPARISON_DIMENSIONS, processCaptureComparisonSchema, } from "./processComparison.js";
|
|
195
|
+
export { compareProcessTraces, processTraceComparisonResultSchema, processTraceSourceSchema, processTraceSpecificationSchema, } from "./processTraceComparison.js";
|
|
@@ -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
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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) => {
|
|
@@ -94,6 +97,25 @@ const filesystemObservations = (capture) => [
|
|
|
94
97
|
effects: capture.filesystem_effects,
|
|
95
98
|
},
|
|
96
99
|
];
|
|
100
|
+
/** Canonical observations for one process-comparison dimension. */
|
|
101
|
+
export const processDimensionObservations = (capture, dimension) => {
|
|
102
|
+
switch (dimension) {
|
|
103
|
+
case "terminal":
|
|
104
|
+
return terminalObservations(capture);
|
|
105
|
+
case "interaction":
|
|
106
|
+
return capture.interaction_events;
|
|
107
|
+
case "exit":
|
|
108
|
+
return [{ ...capture.exit, settlement: capture.settlement }];
|
|
109
|
+
case "filesystem":
|
|
110
|
+
return filesystemObservations(capture);
|
|
111
|
+
case "protocol":
|
|
112
|
+
return [...capture.protocol_events, ...capture.replay_transitions];
|
|
113
|
+
case "process":
|
|
114
|
+
return capture.process_samples;
|
|
115
|
+
case "shim":
|
|
116
|
+
return capture.shim_events;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
97
119
|
const classifyCollection = (left, right) => {
|
|
98
120
|
if (JSON.stringify(left) === JSON.stringify(right))
|
|
99
121
|
return "unchanged";
|
|
@@ -174,6 +196,40 @@ const truncatedComparison = () => ({
|
|
|
174
196
|
},
|
|
175
197
|
limitations: ["At least one capture is truncated."],
|
|
176
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
|
+
};
|
|
177
233
|
const compareDimensions = (left, right, sameNormalization) => {
|
|
178
234
|
const classify = (scope, leftValues, rightValues) => !sameNormalization || hasUnknown(left, scope) || hasUnknown(right, scope)
|
|
179
235
|
? "unknown"
|
|
@@ -188,12 +244,11 @@ const compareDimensions = (left, right, sameNormalization) => {
|
|
|
188
244
|
? "unchanged"
|
|
189
245
|
: "changed",
|
|
190
246
|
filesystem: classify("filesystem", filesystemObservations(left), filesystemObservations(right)),
|
|
191
|
-
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]),
|
|
192
248
|
process: classify("process", left.process_samples, right.process_samples),
|
|
193
249
|
shim: classify("shim", left.shim_events, right.shim_events),
|
|
194
250
|
};
|
|
195
251
|
};
|
|
196
|
-
/** Compare bounded captures without equating missing or incompatible evidence. */
|
|
197
252
|
/**
|
|
198
253
|
* Compare two Process Capture v4 observations without treating absence as proof.
|
|
199
254
|
*
|
|
@@ -202,33 +257,50 @@ const compareDimensions = (left, right, sameNormalization) => {
|
|
|
202
257
|
*/
|
|
203
258
|
export const compareProcessCaptures = (left, right, options = {}) => {
|
|
204
259
|
assertComparable(left, right, options);
|
|
205
|
-
if (left.truncated || right.truncated)
|
|
206
|
-
|
|
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
|
+
}
|
|
207
269
|
const sameNormalization = JSON.stringify(left.normalization) === JSON.stringify(right.normalization);
|
|
208
|
-
const
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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;
|
|
228
299
|
return {
|
|
229
300
|
status: deriveProcessComparisonStatus(PROCESS_COMPARISON_DIMENSIONS.map((dimension) => dimensions[dimension])),
|
|
230
301
|
...dimensions,
|
|
231
302
|
first_divergence: firstDivergence,
|
|
303
|
+
...(trace === undefined ? {} : { trace }),
|
|
232
304
|
limitations: [
|
|
233
305
|
...left.limitations,
|
|
234
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,128 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { PROCESS_COMPARISON_DIMENSIONS, compareProcessCaptures, processDimensionObservations, } from "./processComparison.js";
|
|
3
|
+
import { digestProcessCommitment, processScenarioSchema, } from "./processScenario.js";
|
|
4
|
+
const sideIdentifierSchema = z
|
|
5
|
+
.string()
|
|
6
|
+
.regex(/^[A-Za-z][A-Za-z0-9._-]{0,127}$/u);
|
|
7
|
+
const environmentNameSchema = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/u);
|
|
8
|
+
const processDimensionSchema = z.enum(PROCESS_COMPARISON_DIMENSIONS);
|
|
9
|
+
const sideBindingSchema = z
|
|
10
|
+
.object({
|
|
11
|
+
id: sideIdentifierSchema,
|
|
12
|
+
executable: z.string().startsWith("/"),
|
|
13
|
+
arguments: z.array(z.string()).max(256).optional(),
|
|
14
|
+
environment: z.record(environmentNameSchema, z.string()).default({}),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
/** Boundary contract for one bounded authority/candidate process experiment. */
|
|
18
|
+
export const processPairedExperimentSchema = z
|
|
19
|
+
.object({
|
|
20
|
+
approved: z.literal(true),
|
|
21
|
+
shared_scenario: processScenarioSchema,
|
|
22
|
+
authority: sideBindingSchema,
|
|
23
|
+
candidate: sideBindingSchema,
|
|
24
|
+
repeat_count: z.number().int().min(2).max(10).default(3),
|
|
25
|
+
freshness_policy: z
|
|
26
|
+
.object({
|
|
27
|
+
max_capture_age_ms: z.number().int().positive().max(86_400_000),
|
|
28
|
+
})
|
|
29
|
+
.strict(),
|
|
30
|
+
required_dimensions: z
|
|
31
|
+
.array(processDimensionSchema)
|
|
32
|
+
.min(1)
|
|
33
|
+
.max(PROCESS_COMPARISON_DIMENSIONS.length)
|
|
34
|
+
.default([...PROCESS_COMPARISON_DIMENSIONS]),
|
|
35
|
+
})
|
|
36
|
+
.strict()
|
|
37
|
+
.superRefine((experiment, context) => {
|
|
38
|
+
if (experiment.authority.id === experiment.candidate.id)
|
|
39
|
+
context.addIssue({
|
|
40
|
+
code: "custom",
|
|
41
|
+
message: "authority and candidate identities must differ",
|
|
42
|
+
path: ["candidate", "id"],
|
|
43
|
+
});
|
|
44
|
+
if (new Set(experiment.required_dimensions).size !==
|
|
45
|
+
experiment.required_dimensions.length)
|
|
46
|
+
context.addIssue({
|
|
47
|
+
code: "custom",
|
|
48
|
+
message: "required process dimensions must be unique",
|
|
49
|
+
path: ["required_dimensions"],
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
/** Apply a side binding without changing the shared observation contract. */
|
|
53
|
+
export const bindProcessScenario = (shared, side) => processScenarioSchema.parse({
|
|
54
|
+
...shared,
|
|
55
|
+
executable: side.executable,
|
|
56
|
+
arguments: side.arguments ?? shared.arguments,
|
|
57
|
+
environment: { ...shared.environment, ...side.environment },
|
|
58
|
+
});
|
|
59
|
+
const classifyInstability = (dimensions) => {
|
|
60
|
+
if (dimensions.has("exit"))
|
|
61
|
+
return "flaky_exit";
|
|
62
|
+
if (dimensions.has("filesystem") || dimensions.has("process"))
|
|
63
|
+
return "environmental_drift";
|
|
64
|
+
return "nondeterministic_output";
|
|
65
|
+
};
|
|
66
|
+
const timingKeys = new Set([
|
|
67
|
+
"at_ms",
|
|
68
|
+
"scheduled_at_ms",
|
|
69
|
+
"dispatched_at_ms",
|
|
70
|
+
"elapsed_ms",
|
|
71
|
+
]);
|
|
72
|
+
const withoutTiming = (value) => typeof value !== "object" || value === null || Array.isArray(value)
|
|
73
|
+
? value
|
|
74
|
+
: Object.fromEntries(Object.entries(value).filter(([key]) => !timingKeys.has(key)));
|
|
75
|
+
const observationsWithoutTiming = (capture, dimension) => dimension === "exit"
|
|
76
|
+
? [
|
|
77
|
+
{
|
|
78
|
+
...capture.exit,
|
|
79
|
+
settlement: {
|
|
80
|
+
state: capture.settlement.state,
|
|
81
|
+
cleanup_outcome: capture.settlement.cleanup_outcome,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
]
|
|
85
|
+
: processDimensionObservations(capture, dimension).map(withoutTiming);
|
|
86
|
+
const hasOnlyTimingVariance = (captures, dimensions) => dimensions.every((dimension) => {
|
|
87
|
+
const first = captures[0];
|
|
88
|
+
if (first === undefined)
|
|
89
|
+
return false;
|
|
90
|
+
const baseline = JSON.stringify(observationsWithoutTiming(first, dimension));
|
|
91
|
+
return captures
|
|
92
|
+
.slice(1)
|
|
93
|
+
.every((capture) => JSON.stringify(observationsWithoutTiming(capture, dimension)) ===
|
|
94
|
+
baseline);
|
|
95
|
+
});
|
|
96
|
+
/** Compare same-side repeats before permitting a cross-side claim. */
|
|
97
|
+
export const analyzeProcessRepeatability = (captures, requiredDimensions) => {
|
|
98
|
+
if (captures.length < 2)
|
|
99
|
+
return {
|
|
100
|
+
classification: "insufficient_samples",
|
|
101
|
+
stable: false,
|
|
102
|
+
digest: digestProcessCommitment({ captures: [], requiredDimensions }),
|
|
103
|
+
comparisons: [],
|
|
104
|
+
unstable_dimensions: [],
|
|
105
|
+
};
|
|
106
|
+
const baseline = captures[0];
|
|
107
|
+
if (baseline === undefined)
|
|
108
|
+
throw new TypeError("Repeatability analysis lost its baseline capture");
|
|
109
|
+
const comparisons = captures
|
|
110
|
+
.slice(1)
|
|
111
|
+
.map((capture) => compareProcessCaptures(baseline, capture));
|
|
112
|
+
const unstable = new Set(requiredDimensions.filter((dimension) => comparisons.some((comparison) => comparison[dimension] !== "unchanged")));
|
|
113
|
+
const unstableDimensions = PROCESS_COMPARISON_DIMENSIONS.filter((dimension) => unstable.has(dimension));
|
|
114
|
+
return {
|
|
115
|
+
classification: unstable.size === 0
|
|
116
|
+
? "stable"
|
|
117
|
+
: hasOnlyTimingVariance(captures, unstableDimensions)
|
|
118
|
+
? "timing_variance"
|
|
119
|
+
: classifyInstability(unstable),
|
|
120
|
+
stable: unstable.size === 0,
|
|
121
|
+
digest: digestProcessCommitment({
|
|
122
|
+
requiredDimensions,
|
|
123
|
+
comparisons,
|
|
124
|
+
}),
|
|
125
|
+
comparisons,
|
|
126
|
+
unstable_dimensions: unstableDimensions,
|
|
127
|
+
};
|
|
128
|
+
};
|