rea-agents 2.3.0 → 2.5.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 +80 -18
- package/bridge/ghidra/ReaGhidraBridge.java +48 -1
- package/bridge/hopper_bridge.py +259 -24
- package/dist/application/AnalysisClientCleanup.js +14 -0
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
- 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 +15 -4
- package/dist/application/BinarySessionStatus.js +32 -1
- package/dist/application/BrowserEvidence.js +14 -0
- package/dist/application/BrowserScenarioCapturePort.js +1 -0
- package/dist/application/BrowserScenarioCaptureService.js +53 -0
- package/dist/application/BrowserScenarioEvidence.js +40 -0
- package/dist/application/CallPathTracing.js +160 -0
- package/dist/application/CapabilityInventory.js +79 -15
- package/dist/application/CompositeProvider.js +10 -1
- package/dist/application/DirectAnalysis.js +7 -2
- package/dist/application/EnhancedLiteralTracing.js +118 -0
- package/dist/application/EnhancedTools.js +35 -121
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
- package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
- package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
- package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
- package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
- package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
- package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
- package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
- package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
- package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
- package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
- package/dist/application/ProcessCaptureEnvironment.js +26 -0
- package/dist/application/ProcessCaptureLifecycle.js +1 -0
- package/dist/application/ProcessCheckpoints.js +5 -0
- package/dist/application/ProcessHarness.js +45 -34
- package/dist/application/ProcessNormalization.js +6 -0
- package/dist/application/ProcessReactiveCoordinator.js +10 -3
- package/dist/application/ProcessReactiveEffects.js +15 -1
- package/dist/application/ProcessReactiveHarness.js +100 -0
- package/dist/application/ProcessReactiveObservations.js +101 -16
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReconstructionObligationAuthority.js +72 -0
- package/dist/application/ReconstructionObligationCandidates.js +265 -0
- package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
- package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
- package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
- package/dist/application/ReconstructionObligationLedgerService.js +72 -0
- package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
- package/dist/application/ReconstructionObligationPolicies.js +132 -0
- package/dist/application/ReconstructionReadinessService.js +48 -0
- package/dist/artifacts/ArtifactProvider.js +87 -22
- package/dist/browser/BrowserScenarioSecrets.js +53 -0
- package/dist/browser/BrowserScenarioSessionPort.js +1 -0
- package/dist/browser/CdpBrowserProvider.js +2 -2
- package/dist/browser/CdpCaptureDocuments.js +57 -0
- package/dist/browser/CdpCaptureStorage.js +71 -29
- package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
- package/dist/browser/CdpEndpoint.js +8 -6
- package/dist/browser/CdpPageCapture.js +2 -0
- package/dist/browser/JavaScriptRuntimeScope.js +38 -0
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
- package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
- package/dist/browser/PlaywrightScenarioActions.js +62 -0
- package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
- package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
- package/dist/browser/PlaywrightScenarioEvents.js +301 -0
- package/dist/browser/PlaywrightScenarioSession.js +300 -0
- package/dist/browser/V8InspectorCaptureProjection.js +110 -0
- package/dist/browser/V8InspectorEndpoint.js +72 -0
- package/dist/browser/V8InspectorProvider.js +267 -0
- package/dist/catalogIdentity.js +8 -0
- package/dist/cli/artifactCommands.js +39 -0
- package/dist/cli/coreAnalysisCommands.js +37 -0
- package/dist/cli.js +4 -0
- package/dist/cliApplicationCommands.js +30 -2
- package/dist/cliBrowserAdvancedCommands.js +17 -5
- package/dist/cliBrowserCommands.js +1 -0
- package/dist/cliBrowserScenarioCommands.js +43 -0
- package/dist/cliCommandNames.js +8 -0
- package/dist/cliJavaScriptRuntimeCommands.js +164 -0
- package/dist/cliObservationOptions.js +4 -0
- package/dist/cliPolicyCommands.js +1 -0
- package/dist/config/environment.js +12 -0
- package/dist/config/parseConfig.js +88 -17
- package/dist/contracts/analysisSearchContract.js +14 -0
- package/dist/contracts/applicationToolContracts.js +86 -2
- package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
- package/dist/contracts/artifactToolContracts.js +22 -0
- package/dist/contracts/browserProviderToolContracts.js +7 -0
- package/dist/contracts/browserScenarioToolContracts.js +81 -0
- package/dist/contracts/browserToolContracts.js +88 -98
- package/dist/contracts/enhancedInputs.js +25 -6
- package/dist/contracts/functionInstructionContract.js +8 -0
- package/dist/contracts/hopperMemoryContracts.js +22 -0
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
- package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
- package/dist/contracts/reconstructionReadinessExample.js +436 -0
- package/dist/contracts/toolContractExamples.js +5 -0
- package/dist/contracts/toolContractTypes.js +1 -0
- package/dist/contracts/toolContracts.js +13 -21
- package/dist/contracts/toolEffects.js +21 -0
- package/dist/contracts/toolOutputSchemaGroups.js +70 -20
- package/dist/contracts/toolOutputSchemaPrimitives.js +40 -0
- package/dist/domain/analysisErrorProjection.js +8 -0
- package/dist/domain/analysisSnapshot.js +2 -0
- package/dist/domain/artifactGraph.js +2 -0
- package/dist/domain/artifactInspection.js +347 -0
- package/dist/domain/binaryTarget.js +3 -6
- package/dist/domain/browserCaptureComparison.js +82 -0
- package/dist/domain/browserObservation.js +8 -1
- package/dist/domain/browserObservationErrors.js +1 -0
- package/dist/domain/browserObservationSchemas.js +26 -0
- package/dist/domain/browserScenario.js +165 -0
- package/dist/domain/browserScenarioCapture.js +57 -0
- package/dist/domain/browserScenarioCaptureValues.js +204 -0
- package/dist/domain/browserScenarioDiff.js +324 -0
- package/dist/domain/browserScenarioDiffValues.js +157 -0
- package/dist/domain/browserScenarioNormalization.js +45 -0
- package/dist/domain/browserScenarioValues.js +372 -0
- package/dist/domain/evidence.js +2 -0
- package/dist/domain/hopperValues.js +10 -0
- package/dist/domain/javascriptRuntimeObservation.js +170 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
- package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
- package/dist/domain/javascriptSemanticAnalysis.js +5 -4
- package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
- package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
- package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
- package/dist/domain/javascriptSemanticDataEffects.js +284 -0
- package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
- package/dist/domain/javascriptSemanticFingerprints.js +174 -0
- package/dist/domain/javascriptSemanticIr.js +21 -1
- package/dist/domain/javascriptSemanticObjects.js +115 -0
- package/dist/domain/javascriptSemanticPromises.js +290 -0
- package/dist/domain/javascriptSemanticResources.js +136 -0
- package/dist/domain/javascriptStaticAnalysis.js +4 -3
- package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
- package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
- package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
- package/dist/domain/processCapture.js +71 -1
- package/dist/domain/processCaptureReactiveValidation.js +175 -0
- package/dist/domain/processCaptureValidation.js +2 -0
- package/dist/domain/processObservation.js +15 -6
- package/dist/domain/processReactiveRuntime.js +21 -10
- package/dist/domain/processReactiveScenario.js +1 -0
- package/dist/domain/processReactiveTransition.js +17 -10
- package/dist/domain/processScenario.js +21 -0
- package/dist/domain/providerCleanupError.js +11 -0
- package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
- package/dist/domain/reconstructionReadiness.js +121 -0
- package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
- package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
- package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
- package/dist/domain/reconstructionReadinessSchemas.js +240 -0
- package/dist/domain/sourceToBundleComparison.js +335 -0
- package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
- package/dist/domain/sourceToBundleSignals.js +202 -0
- package/dist/domain/webCaptureDiff.js +141 -27
- package/dist/domain/webScreenshot.js +1 -1
- package/dist/domain/zipPackageFormat.js +20 -0
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +13 -1
- package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
- package/dist/hopper/HopperCleanup.js +158 -0
- package/dist/hopper/HopperClient.js +69 -99
- package/dist/hopper/HopperProvider.js +20 -1
- package/dist/hopper/HopperRequestQueue.js +187 -0
- package/dist/hopper/HopperResponseStream.js +46 -0
- package/dist/hopper/protocol.js +1 -0
- package/dist/main/transport.js +24 -24
- package/dist/main.js +6 -0
- package/dist/mcpDoctor.js +25 -2
- package/dist/native/NativeMacOSProvider.js +1 -0
- package/dist/process/ProcessCommandIdentity.js +19 -0
- package/dist/process/ProcessOwnership.js +11 -8
- package/dist/server/DynamicToolAvailability.js +70 -0
- package/dist/server/createServer.js +46 -1
- package/dist/server/lifecycleProgress.js +10 -0
- package/dist/server/permissionFailure.js +12 -0
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
- package/dist/server/registerApplicationTools/helpers.js +11 -7
- package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
- package/dist/server/registerApplicationTools/readiness.js +55 -0
- package/dist/server/registerApplicationTools.js +6 -0
- package/dist/server/registerArtifactTools.js +9 -1
- package/dist/server/registerBrowserScenarioTool.js +25 -0
- package/dist/server/registerBrowserTools.js +2 -2
- package/dist/server/registerCloseLifecycleTool.js +57 -0
- package/dist/server/registerEnhancedTools.js +16 -5
- package/dist/server/registerEvidenceResources.js +4 -0
- package/dist/server/registerEvidenceTools.js +9 -2
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
- package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
- package/dist/server/registerReconstructionReadinessResource.js +39 -0
- package/dist/server/registerSessionStatusTool.js +11 -8
- package/dist/server/registerSessionTools.js +11 -57
- package/dist/server/sessionAvailabilityPolicy.js +10 -8
- package/package.json +12 -6
- package/skills/reverse-engineer-anything/SKILL.md +4 -3
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as t from "@babel/types";
|
|
2
|
+
import { semanticStaticPropertyName } from "./javascriptSemanticProjection.js";
|
|
3
|
+
import { resolveSemanticBindingState, } from "./javascriptSemanticState.js";
|
|
4
|
+
import { dataEffectLimitReached, outerDataEffectBinding, traverseDataEffects, } from "./javascriptSemanticDataEffectHelpers.js";
|
|
5
|
+
import { range } from "./javascriptStaticAnalysisHelpers.js";
|
|
6
|
+
const ACQUIRE_METHODS = [
|
|
7
|
+
"open",
|
|
8
|
+
"openSync",
|
|
9
|
+
"createReadStream",
|
|
10
|
+
"createWriteStream",
|
|
11
|
+
"connect",
|
|
12
|
+
"createConnection",
|
|
13
|
+
];
|
|
14
|
+
const RELEASE_METHODS = ["close", "destroy", "end"];
|
|
15
|
+
/** Recover built-in filesystem/network acquisition and local release handles. */
|
|
16
|
+
export const collectJavaScriptSemanticResources = (program, state, callables) => {
|
|
17
|
+
const context = {
|
|
18
|
+
state,
|
|
19
|
+
callableIds: new Set(callables.map(({ callableId }) => callableId)),
|
|
20
|
+
callableStack: [],
|
|
21
|
+
ancestors: [],
|
|
22
|
+
};
|
|
23
|
+
const acquisitions = collectAcquisitions(program, context);
|
|
24
|
+
return [
|
|
25
|
+
...acquisitions.map(immutableAcquisition),
|
|
26
|
+
...collectReleases(program, context, acquisitions),
|
|
27
|
+
];
|
|
28
|
+
};
|
|
29
|
+
const collectAcquisitions = (program, context) => {
|
|
30
|
+
const output = [];
|
|
31
|
+
traverseDataEffects(program, context, (node) => {
|
|
32
|
+
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
|
|
33
|
+
return;
|
|
34
|
+
const method = acquisitionMethod(node.callee, context.state);
|
|
35
|
+
if (method === null)
|
|
36
|
+
return;
|
|
37
|
+
if (resourceLimitReached(output.length, context.state))
|
|
38
|
+
return;
|
|
39
|
+
output.push({
|
|
40
|
+
node,
|
|
41
|
+
method,
|
|
42
|
+
ownerCallableId: context.callableStack.at(-1) ?? null,
|
|
43
|
+
resultBindingId: outerDataEffectBinding(node, context),
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
return output;
|
|
47
|
+
};
|
|
48
|
+
const collectReleases = (program, context, acquisitions) => {
|
|
49
|
+
const byBinding = new Map();
|
|
50
|
+
for (const acquisition of acquisitions) {
|
|
51
|
+
if (acquisition.resultBindingId === null)
|
|
52
|
+
continue;
|
|
53
|
+
const existing = byBinding.get(acquisition.resultBindingId) ?? [];
|
|
54
|
+
existing.push(acquisition);
|
|
55
|
+
byBinding.set(acquisition.resultBindingId, existing);
|
|
56
|
+
}
|
|
57
|
+
const output = [];
|
|
58
|
+
traverseDataEffects(program, context, (node) => {
|
|
59
|
+
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
|
|
60
|
+
return;
|
|
61
|
+
const member = memberCallee(node);
|
|
62
|
+
if (member === null || !t.isIdentifier(member.object))
|
|
63
|
+
return;
|
|
64
|
+
const method = RELEASE_METHODS.find((candidate) => candidate ===
|
|
65
|
+
semanticStaticPropertyName(member.property, member.computed));
|
|
66
|
+
if (method === undefined)
|
|
67
|
+
return;
|
|
68
|
+
const binding = resolveSemanticBindingState(context.state, member.object, member.object.name);
|
|
69
|
+
const linked = binding === undefined ? [] : (byBinding.get(binding.bindingId) ?? []);
|
|
70
|
+
if (linked.length === 0)
|
|
71
|
+
return;
|
|
72
|
+
if (resourceLimitReached(acquisitions.length + output.length, context.state))
|
|
73
|
+
return;
|
|
74
|
+
output.push({
|
|
75
|
+
resourceId: `resource:release:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
|
|
76
|
+
kind: "release",
|
|
77
|
+
method,
|
|
78
|
+
location: range(node),
|
|
79
|
+
ownerCallableId: context.callableStack.at(-1) ?? null,
|
|
80
|
+
resultBindingId: binding?.bindingId ?? null,
|
|
81
|
+
linkedResourceIds: linked.map(resourceCandidateId).sort(),
|
|
82
|
+
resolution: linked.length === 1 ? "complete" : "partial",
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
return output;
|
|
86
|
+
};
|
|
87
|
+
const immutableAcquisition = (candidate) => ({
|
|
88
|
+
resourceId: resourceCandidateId(candidate),
|
|
89
|
+
kind: "acquire",
|
|
90
|
+
method: candidate.method,
|
|
91
|
+
location: range(candidate.node),
|
|
92
|
+
ownerCallableId: candidate.ownerCallableId,
|
|
93
|
+
resultBindingId: candidate.resultBindingId,
|
|
94
|
+
linkedResourceIds: [],
|
|
95
|
+
resolution: "complete",
|
|
96
|
+
});
|
|
97
|
+
const acquisitionMethod = (callee, state) => {
|
|
98
|
+
if (t.isIdentifier(callee)) {
|
|
99
|
+
const binding = resolveSemanticBindingState(state, callee, callee.name);
|
|
100
|
+
return methodFromBinding(binding);
|
|
101
|
+
}
|
|
102
|
+
if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
|
|
103
|
+
return null;
|
|
104
|
+
const method = ACQUIRE_METHODS.find((candidate) => candidate ===
|
|
105
|
+
semanticStaticPropertyName(callee.property, callee.computed));
|
|
106
|
+
if (method === undefined || !t.isIdentifier(callee.object))
|
|
107
|
+
return null;
|
|
108
|
+
const binding = resolveSemanticBindingState(state, callee.object, callee.object.name);
|
|
109
|
+
return resourceNamespace(binding) ? method : null;
|
|
110
|
+
};
|
|
111
|
+
const methodFromBinding = (binding) => {
|
|
112
|
+
for (const { specifier, importedPath } of binding?.directOrigins ?? []) {
|
|
113
|
+
if (!resourceModule(specifier))
|
|
114
|
+
continue;
|
|
115
|
+
const method = ACQUIRE_METHODS.find((candidate) => candidate === importedPath.at(-1));
|
|
116
|
+
if (method !== undefined)
|
|
117
|
+
return method;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
};
|
|
121
|
+
const resourceNamespace = (binding) => binding?.directOrigins.some(({ specifier, importedPath }) => resourceModule(specifier) && importedPath.length === 0) ?? false;
|
|
122
|
+
const resourceModule = (specifier) => [
|
|
123
|
+
"fs",
|
|
124
|
+
"fs/promises",
|
|
125
|
+
"net",
|
|
126
|
+
"node:fs",
|
|
127
|
+
"node:fs/promises",
|
|
128
|
+
"node:net",
|
|
129
|
+
"node:tls",
|
|
130
|
+
"tls",
|
|
131
|
+
].includes(specifier);
|
|
132
|
+
const resourceLimitReached = (retained, state) => dataEffectLimitReached(retained, state.limits.maxResourceOperations, "maxResourceOperations", state);
|
|
133
|
+
const resourceCandidateId = (candidate) => `resource:acquire:${String(candidate.node.start ?? -1)}:${String(candidate.node.end ?? -1)}`;
|
|
134
|
+
const memberCallee = (node) => t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)
|
|
135
|
+
? node.callee
|
|
136
|
+
: null;
|
|
@@ -3,7 +3,7 @@ import * as t from "@babel/types";
|
|
|
3
3
|
import { inspectElectronStaticNode } from "./electronStaticAnalysis.js";
|
|
4
4
|
import { detectVendors, failedJavaScriptStaticAnalysis, registrationKey, sortedUnique, stringValue, } from "./javascriptStaticAnalysisHelpers.js";
|
|
5
5
|
import { addReference, addSourceMapDirectives, inspectCall, inspectRouteProperty, inspectRoleProperty, } from "./javascriptStaticAnalysisCalls.js";
|
|
6
|
-
import { inspectBundlerRegistration } from "./javascriptStaticAnalysisBundler.js";
|
|
6
|
+
import { inspectBundlerRegistration, inspectEsbuildWrapper, } from "./javascriptStaticAnalysisBundler.js";
|
|
7
7
|
import { finalizeLocatedFindings } from "./javascriptStaticAnalysisFindings.js";
|
|
8
8
|
import { createJavaScriptAnalysisAccumulator, } from "./javascriptStaticAnalysisState.js";
|
|
9
9
|
/** Parse one bounded JavaScript artifact and recover static structure only. */
|
|
@@ -100,10 +100,11 @@ const inspectNode = (source, node, accumulator, limits) => {
|
|
|
100
100
|
inspectElectronStaticNode(node, findings);
|
|
101
101
|
if (t.isCallExpression(node)) {
|
|
102
102
|
inspectBundlerRegistration(source, node, accumulator, limits);
|
|
103
|
-
|
|
103
|
+
inspectEsbuildWrapper(source, node, accumulator, limits);
|
|
104
|
+
inspectCall(source, node, findings);
|
|
104
105
|
}
|
|
105
106
|
else if (t.isNewExpression(node))
|
|
106
|
-
inspectCall(node, findings);
|
|
107
|
+
inspectCall(source, node, findings);
|
|
107
108
|
if ((t.isImportDeclaration(node) || t.isExportAllDeclaration(node)) &&
|
|
108
109
|
node.source !== undefined)
|
|
109
110
|
addReference(findings, {
|
|
@@ -61,6 +61,44 @@ export const inspectBundlerRegistration = (source, call, accumulator, limits) =>
|
|
|
61
61
|
};
|
|
62
62
|
addBoundedFinding(accumulator, `registration\0${registrationKey(registration)}`, limits.maxFindings, () => accumulator.registrations.push(registration));
|
|
63
63
|
};
|
|
64
|
+
/** Inspect esbuild helper wrappers and recover their literal module tables. */
|
|
65
|
+
export const inspectEsbuildWrapper = (source, call, accumulator, limits) => {
|
|
66
|
+
const name = calleeName(call.callee);
|
|
67
|
+
const wrapperKind = name === "__commonJS" || name.endsWith(".__commonJS")
|
|
68
|
+
? "commonjs"
|
|
69
|
+
: name === "__esm" || name.endsWith(".__esm")
|
|
70
|
+
? "esm"
|
|
71
|
+
: null;
|
|
72
|
+
const table = call.arguments[0];
|
|
73
|
+
if (wrapperKind === null || !t.isObjectExpression(table))
|
|
74
|
+
return;
|
|
75
|
+
const recovered = recoverBundlerModules(source, table, accumulator, limits);
|
|
76
|
+
if (recovered.modules.length === 0)
|
|
77
|
+
return;
|
|
78
|
+
if (recovered.omittedAsyncChunkKeys > 0) {
|
|
79
|
+
accumulator.droppedFindings += recovered.omittedAsyncChunkKeys;
|
|
80
|
+
accumulator.structuralTruncation = true;
|
|
81
|
+
}
|
|
82
|
+
accumulator.unknownFindings += recovered.unknownAsyncChunkKeys;
|
|
83
|
+
const registration = {
|
|
84
|
+
bundler: "esbuild",
|
|
85
|
+
runtime: `esbuild-${wrapperKind}`,
|
|
86
|
+
chunk_keys: [`${wrapperKind}@${String(call.start ?? 0)}`],
|
|
87
|
+
omitted_chunk_keys: 0,
|
|
88
|
+
unknown_chunk_keys: 0,
|
|
89
|
+
runtime_require_name: null,
|
|
90
|
+
runtime_module_cache_status: "not-observed",
|
|
91
|
+
entry_module_keys: [],
|
|
92
|
+
omitted_entry_module_keys: 0,
|
|
93
|
+
unknown_entry_module_keys: 0,
|
|
94
|
+
async_chunk_keys: recovered.asyncChunkKeys,
|
|
95
|
+
omitted_async_chunk_keys: recovered.omittedAsyncChunkKeys,
|
|
96
|
+
unknown_async_chunk_keys: recovered.unknownAsyncChunkKeys,
|
|
97
|
+
modules: recovered.modules.sort((left, right) => compareCodePoints(left.module_key, right.module_key)),
|
|
98
|
+
location: range(call),
|
|
99
|
+
};
|
|
100
|
+
addBoundedFinding(accumulator, `registration\0${registrationKey(registration)}`, limits.maxFindings, () => accumulator.registrations.push(registration));
|
|
101
|
+
};
|
|
64
102
|
const recoverBundlerModules = (source, table, accumulator, limits) => {
|
|
65
103
|
const modules = [];
|
|
66
104
|
const asyncChunkKeys = [];
|
|
@@ -2,7 +2,7 @@ import * as t from "@babel/types";
|
|
|
2
2
|
import { addBoundedFinding, addLocatedFinding, moduleAtOffset, } from "./javascriptStaticAnalysisFindings.js";
|
|
3
3
|
import { STATIC_ROUTE_CALL_NAMES, argumentValue, calleeName, endpointArgument as endpointArgumentHelper, prefixedArgument, propertyName, range, rangeForOffsets, sanitizeCandidate, sourceSlice, staticPath, staticPathResolutionContext, storageKind, stringValue, } from "./javascriptStaticAnalysisHelpers.js";
|
|
4
4
|
/** Inspect one call or new expression for references, endpoints, storage, and roles. */
|
|
5
|
-
export const inspectCall = (node, context) => {
|
|
5
|
+
export const inspectCall = (source, node, context) => {
|
|
6
6
|
const { accumulator } = context;
|
|
7
7
|
const name = calleeName(node.callee);
|
|
8
8
|
const first = stringValue(node.arguments[0]);
|
|
@@ -22,6 +22,8 @@ export const inspectCall = (node, context) => {
|
|
|
22
22
|
kind: "dynamic-import",
|
|
23
23
|
specifier: chunkLoad.specifier,
|
|
24
24
|
});
|
|
25
|
+
for (const specifier of vitePreloadDependencySpecifiers(source, node, name))
|
|
26
|
+
addReference(context, { node, kind: "dynamic-import", specifier });
|
|
25
27
|
if (name === "Worker" || name.endsWith(".Worker"))
|
|
26
28
|
addReference(context, { node, kind: "worker", specifier: first });
|
|
27
29
|
if (name.endsWith("serviceWorker.register"))
|
|
@@ -73,6 +75,26 @@ const chunkLoadSpecifier = (node, name, moduleRequireName) => {
|
|
|
73
75
|
return null;
|
|
74
76
|
return { specifier: prefixedArgument("chunk:", node.arguments[0]) };
|
|
75
77
|
};
|
|
78
|
+
const vitePreloadDependencySpecifiers = (source, node, name) => {
|
|
79
|
+
if (name !== "__vite__mapDeps" || !t.isArrayExpression(node.arguments[0]))
|
|
80
|
+
return [];
|
|
81
|
+
const indexes = node.arguments[0].elements.flatMap((element) => t.isNumericLiteral(element) ? [element.value] : []);
|
|
82
|
+
if (indexes.length !== node.arguments[0].elements.length)
|
|
83
|
+
return [];
|
|
84
|
+
const table = vitePreloadDependencyTable(source);
|
|
85
|
+
return indexes.flatMap((index) => table[index] ?? []);
|
|
86
|
+
};
|
|
87
|
+
const vitePreloadDependencyTable = (source) => {
|
|
88
|
+
const match = /\.\s*f\s*\|\|\s*\(\s*[^)]*?\.f\s*=\s*\[([\s\S]*?)\]\s*\)/u.exec(source);
|
|
89
|
+
const body = match?.[1];
|
|
90
|
+
if (body === undefined)
|
|
91
|
+
return [];
|
|
92
|
+
return [
|
|
93
|
+
...body.matchAll(/"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gu),
|
|
94
|
+
]
|
|
95
|
+
.map((literal) => (literal[1] ?? literal[2] ?? "").slice(0, 4_096))
|
|
96
|
+
.slice(0, 256);
|
|
97
|
+
};
|
|
76
98
|
const inspectEndpointCall = (node, name, first, context) => {
|
|
77
99
|
const route = STATIC_ROUTE_CALL_NAMES.some((candidate) => name === candidate || name.endsWith(`.${candidate}`));
|
|
78
100
|
if (route && first !== undefined)
|
|
@@ -294,6 +294,12 @@ export const rangeForOffsets = (source, start, end) => ({
|
|
|
294
294
|
start: pointForOffset(source, start),
|
|
295
295
|
end: pointForOffset(source, end),
|
|
296
296
|
});
|
|
297
|
+
/** Compare two exact source ranges. */
|
|
298
|
+
export const sourceRangesEqual = (left, right) => left !== null &&
|
|
299
|
+
left.start.line === right.start.line &&
|
|
300
|
+
left.start.column === right.start.column &&
|
|
301
|
+
left.end.line === right.end.line &&
|
|
302
|
+
left.end.column === right.end.column;
|
|
297
303
|
const pointForOffset = (source, offset) => {
|
|
298
304
|
const before = source.slice(0, offset);
|
|
299
305
|
const lines = before.split("\n");
|
|
@@ -335,6 +341,8 @@ const vendorPatterns = [
|
|
|
335
341
|
{ name: "webpack", patterns: ["__webpack_require__", "webpackChunk"] },
|
|
336
342
|
{ name: "Rspack", patterns: ["__webpack_require__.f", "rspackChunk"] },
|
|
337
343
|
{ name: "Vite", patterns: ["__vite__", "import.meta.hot"] },
|
|
344
|
+
{ name: "Rollup", patterns: ["ROLLUP_FILE_URL", "import.meta.ROLLUP"] },
|
|
345
|
+
{ name: "esbuild", patterns: ["__commonJS", "__esm"] },
|
|
338
346
|
{ name: "React", patterns: ["React.createElement", "react/jsx-runtime"] },
|
|
339
347
|
{ name: "Vue", patterns: ["__VUE__", "createApp("] },
|
|
340
348
|
{ name: "Next.js", patterns: ["__NEXT_DATA__", "/_next/"] },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { jsonValueSchema } from "./jsonValue.js";
|
|
3
3
|
import { LEGACY_PROCESS_CAPTURE_MESSAGE, normalizationSchema, } from "./processScenario.js";
|
|
4
|
+
import { PROCESS_REACTIVE_LIMITS } from "./processReactiveScenario.js";
|
|
4
5
|
export * from "./processScenario.js";
|
|
5
6
|
/** Capture collection whose members participate in global observation order. */
|
|
6
7
|
export const PROCESS_CAPTURE_EVENT_COLLECTIONS = [
|
|
@@ -76,7 +77,7 @@ export const processCaptureSchema = z.object({
|
|
|
76
77
|
sequence: z.number().int().nonnegative(),
|
|
77
78
|
scheduled_at_ms: z.number().int().nonnegative(),
|
|
78
79
|
dispatched_at_ms: z.number().int().nonnegative(),
|
|
79
|
-
type: z.enum(["input", "resize", "signal"]),
|
|
80
|
+
type: z.enum(["input", "resize", "stdin_close", "signal"]),
|
|
80
81
|
data: z.string(),
|
|
81
82
|
outcome: z.enum(["dispatched", "target_exited", "failed"]),
|
|
82
83
|
})),
|
|
@@ -145,6 +146,75 @@ export const processCaptureSchema = z.object({
|
|
|
145
146
|
sensitive_aliases: z.array(z.string().min(1).max(64)).max(32),
|
|
146
147
|
}))
|
|
147
148
|
.default([]),
|
|
149
|
+
reactive_run: z
|
|
150
|
+
.object({
|
|
151
|
+
status: z.enum(["running", "finished"]),
|
|
152
|
+
outcome: z
|
|
153
|
+
.enum([
|
|
154
|
+
"passed",
|
|
155
|
+
"predicate_timeout",
|
|
156
|
+
"scenario_deadline",
|
|
157
|
+
"ambiguous_match",
|
|
158
|
+
"action_rejected",
|
|
159
|
+
"target_lost",
|
|
160
|
+
"capture_incomplete",
|
|
161
|
+
"cancelled",
|
|
162
|
+
"cleanup_failed",
|
|
163
|
+
])
|
|
164
|
+
.nullable(),
|
|
165
|
+
active_state: z.string().min(1).max(64),
|
|
166
|
+
controls: z
|
|
167
|
+
.array(z.object({
|
|
168
|
+
sequence: z.number().int().nonnegative(),
|
|
169
|
+
kind: z.enum([
|
|
170
|
+
"state_deadline",
|
|
171
|
+
"scenario_deadline",
|
|
172
|
+
"target_lost",
|
|
173
|
+
"cancelled",
|
|
174
|
+
"cleanup_failed",
|
|
175
|
+
]),
|
|
176
|
+
after_capture_order: z.number().int().min(-1),
|
|
177
|
+
}))
|
|
178
|
+
.default([]),
|
|
179
|
+
transitions: z
|
|
180
|
+
.array(z.object({
|
|
181
|
+
sequence: z.number().int().nonnegative(),
|
|
182
|
+
transition_id: z.string().min(1).max(64),
|
|
183
|
+
state_before: z.string().min(1).max(64),
|
|
184
|
+
state_after: z.string().min(1).max(64).nullable(),
|
|
185
|
+
outcome: z
|
|
186
|
+
.enum([
|
|
187
|
+
"passed",
|
|
188
|
+
"predicate_timeout",
|
|
189
|
+
"scenario_deadline",
|
|
190
|
+
"ambiguous_match",
|
|
191
|
+
"action_rejected",
|
|
192
|
+
"target_lost",
|
|
193
|
+
"capture_incomplete",
|
|
194
|
+
"cancelled",
|
|
195
|
+
"cleanup_failed",
|
|
196
|
+
])
|
|
197
|
+
.nullable(),
|
|
198
|
+
trigger_event_ids: z
|
|
199
|
+
.array(z.string().min(1))
|
|
200
|
+
.max(PROCESS_REACTIVE_LIMITS.predicates),
|
|
201
|
+
action_event_ids: z
|
|
202
|
+
.array(z.string().min(1))
|
|
203
|
+
.max(PROCESS_REACTIVE_LIMITS.actionsPerTransition),
|
|
204
|
+
action_types: z
|
|
205
|
+
.array(z.enum([
|
|
206
|
+
"send_input",
|
|
207
|
+
"resize",
|
|
208
|
+
"close_stdin",
|
|
209
|
+
"send_signal",
|
|
210
|
+
"checkpoint",
|
|
211
|
+
]))
|
|
212
|
+
.max(PROCESS_REACTIVE_LIMITS.actionsPerTransition),
|
|
213
|
+
}))
|
|
214
|
+
.max(PROCESS_REACTIVE_LIMITS.transitions),
|
|
215
|
+
})
|
|
216
|
+
.nullable()
|
|
217
|
+
.default(null),
|
|
148
218
|
event_journal: z
|
|
149
219
|
.array(z.object({
|
|
150
220
|
capture_order: z.number().int().nonnegative(),
|
|
@@ -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,4 @@
|
|
|
1
|
+
import { validateProcessCaptureReactiveRun } from "./processCaptureReactiveValidation.js";
|
|
1
2
|
import { replayMachineSchema } from "./replayMachine.js";
|
|
2
3
|
import { digestProcessCommitment } from "./processScenario.js";
|
|
3
4
|
const orderedTimestamps = (values) => values.every((value, index) => index === 0 || value.at_ms >= values[index - 1].at_ms);
|
|
@@ -209,5 +210,6 @@ export const validateProcessCapture = (capture) => {
|
|
|
209
210
|
validateLifecycle(capture, require);
|
|
210
211
|
validateShimEvents(capture, require);
|
|
211
212
|
validateReplayEvents(capture, require);
|
|
213
|
+
validateProcessCaptureReactiveRun(capture, require);
|
|
212
214
|
return issues;
|
|
213
215
|
};
|
|
@@ -60,13 +60,22 @@ const atMilliseconds = (value) => {
|
|
|
60
60
|
}
|
|
61
61
|
return null;
|
|
62
62
|
};
|
|
63
|
-
|
|
63
|
+
/** Derive the stable capture-local identity for one normalized observation. */
|
|
64
|
+
export const processObservationSubject = (source, value) => {
|
|
64
65
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
65
66
|
return null;
|
|
66
|
-
if (source === "process" && typeof value
|
|
67
|
-
return `process:${String(value
|
|
68
|
-
if (source === "filesystem" &&
|
|
69
|
-
|
|
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`;
|
|
70
79
|
return null;
|
|
71
80
|
};
|
|
72
81
|
const projectedRecord = (capture, location) => {
|
|
@@ -133,7 +142,7 @@ const projectedRecord = (capture, location) => {
|
|
|
133
142
|
return {
|
|
134
143
|
source: projected.source,
|
|
135
144
|
captured_at_ms: atMilliseconds(payload),
|
|
136
|
-
subject_id:
|
|
145
|
+
subject_id: processObservationSubject(projected.source, payload),
|
|
137
146
|
payload,
|
|
138
147
|
};
|
|
139
148
|
};
|
|
@@ -2,6 +2,22 @@ import { matchProcessReactiveTrigger } from "./processReactiveMatching.js";
|
|
|
2
2
|
import { PROCESS_REACTIVE_LIMITS, } from "./processReactiveScenario.js";
|
|
3
3
|
export { commitProcessReactiveProposal } from "./processReactiveTransition.js";
|
|
4
4
|
const counterValue = (counters, id) => counters.find((counter) => counter.id === id)?.count ?? 0;
|
|
5
|
+
const observeInput = (snapshot, observation) => {
|
|
6
|
+
const duplicate = snapshot.observations.find(({ event_id }) => event_id === observation.event_id);
|
|
7
|
+
if (duplicate !== undefined)
|
|
8
|
+
return { kind: "invalid" };
|
|
9
|
+
const prior = snapshot.observations.at(-1);
|
|
10
|
+
if ((prior !== undefined && observation.capture_order <= prior.capture_order) ||
|
|
11
|
+
snapshot.observations.length >= PROCESS_REACTIVE_LIMITS.retainedObservations)
|
|
12
|
+
return { kind: "invalid" };
|
|
13
|
+
return {
|
|
14
|
+
kind: "new",
|
|
15
|
+
snapshot: {
|
|
16
|
+
...snapshot,
|
|
17
|
+
observations: [...snapshot.observations, observation],
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
};
|
|
5
21
|
/** Create the initial immutable reducer state for one validated scenario. */
|
|
6
22
|
export const createProcessReactiveSnapshot = (scenario) => ({
|
|
7
23
|
status: "running",
|
|
@@ -35,6 +51,8 @@ const reduceControlInput = (snapshot, input) => {
|
|
|
35
51
|
: { kind: "waiting", snapshot, evaluations: [] };
|
|
36
52
|
if (input.kind === "scenario_deadline")
|
|
37
53
|
return finishedDecision(snapshot, "scenario_deadline");
|
|
54
|
+
if (input.kind === "target_lost")
|
|
55
|
+
return finishedDecision(snapshot, "target_lost");
|
|
38
56
|
return finishedDecision(snapshot, "cancelled");
|
|
39
57
|
};
|
|
40
58
|
/** Advance one validated scenario using only an observation or explicit deadline. */
|
|
@@ -44,17 +62,10 @@ export const reduceProcessReactiveScenario = (scenario, snapshot, input) => {
|
|
|
44
62
|
return controlDecision;
|
|
45
63
|
if (input.kind !== "observation")
|
|
46
64
|
return finishedDecision(snapshot, "capture_incomplete");
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
input.observation.capture_order <= prior.capture_order) ||
|
|
50
|
-
snapshot.observations.some(({ event_id }) => event_id === input.observation.event_id))
|
|
51
|
-
return finishedDecision(snapshot, "capture_incomplete");
|
|
52
|
-
if (snapshot.observations.length >= PROCESS_REACTIVE_LIMITS.retainedObservations)
|
|
65
|
+
const admitted = observeInput(snapshot, input.observation);
|
|
66
|
+
if (admitted.kind === "invalid")
|
|
53
67
|
return finishedDecision(snapshot, "capture_incomplete");
|
|
54
|
-
const observed =
|
|
55
|
-
...snapshot,
|
|
56
|
-
observations: [...snapshot.observations, input.observation],
|
|
57
|
-
};
|
|
68
|
+
const observed = admitted.snapshot;
|
|
58
69
|
const state = scenario.states.find(({ id }) => id === observed.active_state);
|
|
59
70
|
if (state === undefined)
|
|
60
71
|
return finishedDecision(observed, "target_lost");
|
|
@@ -128,6 +128,7 @@ const processReactiveActionSchema = z.discriminatedUnion("type", [
|
|
|
128
128
|
columns: z.number().int().min(1).max(1_000),
|
|
129
129
|
rows: z.number().int().min(1).max(1_000),
|
|
130
130
|
}),
|
|
131
|
+
z.strictObject({ type: z.literal("close_stdin") }),
|
|
131
132
|
z.strictObject({
|
|
132
133
|
type: z.literal("send_signal"),
|
|
133
134
|
target: signalTargetSchema,
|