rea-agents 5.0.0 → 6.0.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 +157 -650
- package/bridge/mitmproxy/capture.py +210 -0
- package/bridge/native/rea_lldb_tracer.py +303 -0
- package/bridge/process/ProcessRunTokenReader.swift +263 -0
- package/bridge/process/ProcessRunTokenReaderMain.swift +35 -0
- package/bridge/pwndbg/core_context.py +101 -0
- package/bridge/pwndbg/launch.py +39 -0
- package/bridge/pwndbg/launch_impl.py +34 -0
- package/bridge/pwntools/decoder_runtime.py +59 -0
- package/bridge/pwntools/layout.py +39 -0
- package/bridge/pwntools/layout_impl.py +309 -0
- package/bridge/pwntools/recorded_crash.py +39 -0
- package/bridge/pwntools/recorded_crash_impl.py +143 -0
- package/dist/android/JadxProvider.js +1 -0
- package/dist/application/CapabilityInventory.js +21 -0
- package/dist/application/DirectAnalysis.js +97 -23
- package/dist/application/EvidenceBundleFiles.js +1 -1
- package/dist/application/ReconstructionObligationCandidates.js +5 -1
- package/dist/application/WebNetworkCapturePort.js +1 -0
- package/dist/application/WebNetworkCaptureService.js +132 -0
- package/dist/application/WindowsCapabilities.js +1 -1
- package/dist/application/binary/AnalysisSnapshotCache.js +49 -6
- package/dist/application/binary/BinarySession.js +38 -10
- package/dist/application/binary/BinarySessionOpen.js +16 -3
- package/dist/application/binary/BinarySessionRecords.js +20 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutPort.js +1 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutService.js +57 -0
- package/dist/application/binaryDiagnostics/RecordedCrashPort.js +1 -0
- package/dist/application/binaryDiagnostics/RecordedCrashService.js +60 -0
- package/dist/application/evm/EvmInterfacePort.js +1 -0
- package/dist/application/evm/EvmInterfaceService.js +62 -0
- package/dist/application/javascript/JavaScriptApplicationEvidence.js +1 -2
- package/dist/application/javascript/JavaScriptApplicationService.js +15 -2
- package/dist/application/javascript/JavaScriptArtifactAnalysis.js +42 -23
- package/dist/application/javascript/JavaScriptArtifactGraphDocuments.js +26 -2
- package/dist/application/javascript/JavaScriptArtifactPathResolution.js +74 -24
- package/dist/application/javascript/JavaScriptArtifactReconstruction.js +23 -10
- package/dist/application/javascript/JavaScriptSemanticGraphAsyncProjection.js +7 -7
- package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +145 -70
- package/dist/application/javascript/JavaScriptSemanticGraphChildProcessProjection.js +14 -15
- package/dist/application/javascript/JavaScriptSemanticGraphConstruction.js +41 -1
- package/dist/application/javascript/JavaScriptSemanticGraphDataProjection.js +7 -7
- package/dist/application/javascript/JavaScriptSemanticGraphFlowProjection.js +5 -9
- package/dist/application/javascript/JavaScriptSemanticGraphObjectProjection.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphProjection.js +118 -12
- package/dist/application/javascript/JavaScriptSemanticGraphResourceProjection.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +5 -5
- package/dist/application/{ProcessCli.js → process/ProcessCli.js} +10 -10
- package/dist/application/{ProcessEvidence.js → process/ProcessEvidence.js} +4 -4
- package/dist/artifacts/ArtifactProvider.js +30 -3
- package/dist/artifacts/ArtifactProviderMetadata.js +2 -1
- package/dist/artifacts/ZipArtifactReader.js +3 -0
- package/dist/artifacts/apple/DylibResolutionReader.js +259 -0
- package/dist/artifacts/apple/MachoLoadCommandReader.js +294 -0
- package/dist/{application → artifacts/extraction}/ArtifactExtraction.js +10 -10
- package/dist/{application → artifacts/inventory}/ArtifactGraphConstruction.js +3 -3
- package/dist/{application → artifacts/inventory}/ArtifactInventory.js +4 -4
- package/dist/{application/ArtifactInventory → artifacts/inventory}/classify.js +2 -2
- package/dist/{application/ArtifactInventory → artifacts/inventory}/reader.js +5 -5
- package/dist/{application/ArtifactInventory → artifacts/inventory}/scanCanonical.js +3 -3
- package/dist/{application/ArtifactInventory → artifacts/inventory}/scanReader.js +5 -5
- package/dist/artifacts/readStableArtifact.js +20 -4
- package/dist/browser/CdpCaptureEventHandlers.js +90 -2
- package/dist/browser/CdpCaptureEvents.js +9 -0
- package/dist/browser/PlaywrightElectronActiveProvider.js +12 -3
- package/dist/browser/history/CaptureFailures.js +101 -0
- package/dist/browser/history/CaptureFormatError.js +10 -0
- package/dist/browser/history/CaptureRedaction.js +65 -0
- package/dist/browser/history/CaptureRelease.js +12 -0
- package/dist/browser/history/HarCapture.js +165 -0
- package/dist/browser/history/HarCaptureAdapter.js +20 -0
- package/dist/browser/history/HarCaptureProcess.js +57 -0
- package/dist/browser/history/HarJson.js +91 -0
- package/dist/browser/history/HarSchema.js +72 -0
- package/dist/browser/history/HistoricalCaptureDecoder.js +186 -0
- package/dist/browser/history/HistoricalCaptureFormatAdapter.js +1 -0
- package/dist/browser/history/MitmproxyCaptureAdapter.js +42 -0
- package/dist/cli/artifactCommands.js +26 -1
- package/dist/cli/binaryDiagnosticsCommands.js +39 -0
- package/dist/cli/coreAnalysisCommands.js +2 -2
- package/dist/cli/coreBinaryCommands.js +8 -8
- package/dist/cli/electronCommands.js +16 -9
- package/dist/cli/evmCommands.js +24 -0
- package/dist/cli/firmwareCommands.js +6 -3
- package/dist/cli/javascriptApplicationAnalysis.js +16 -3
- package/dist/cli/jsonOutput.js +133 -0
- package/dist/cli/nativeCallCommands.js +35 -0
- package/dist/cli/processCommands.js +1 -1
- package/dist/cli/streamedJsonOutput.js +56 -0
- package/dist/cli/utilityCommands.js +2 -0
- package/dist/cli/webNetworkCaptureCommands.js +40 -0
- package/dist/cli.js +9 -3
- package/dist/cliBrowserScenarioCommands.js +2 -2
- package/dist/cliCommandNames.js +6 -0
- package/dist/cliJsonInput.js +30 -1
- package/dist/cliOutput.js +21 -6
- package/dist/composition/binaryDiagnostics.js +8 -0
- package/dist/composition/evm.js +4 -0
- package/dist/composition/webNetworkCaptures.js +6 -0
- package/dist/contracts/artifactToolContracts.js +6 -0
- package/dist/contracts/browserCaptureToolInputSchema.js +43 -0
- package/dist/contracts/browserToolContracts.js +4 -3
- package/dist/contracts/errorSchemas.js +8 -1
- package/dist/contracts/evm/evmToolContracts.js +20 -0
- package/dist/contracts/investigationExamples.js +2 -2
- package/dist/contracts/managed/managedToolContracts.js +7 -3
- package/dist/contracts/native/binaryDiagnosticsToolContracts.js +42 -0
- package/dist/contracts/native/nativeToolContracts.js +17 -1
- package/dist/contracts/officialToolContracts.js +12 -9
- package/dist/contracts/process/processCaptureExample.js +2 -0
- package/dist/contracts/sessionLifecycleInputs.js +22 -5
- package/dist/contracts/sessionStatusContract.js +10 -2
- package/dist/contracts/sessionToolContracts.js +8 -1
- package/dist/contracts/sessionToolSchemas.js +10 -3
- package/dist/contracts/toolContractExamples.js +1 -1
- package/dist/contracts/toolContracts.js +6 -0
- package/dist/contracts/toolEffects.js +30 -0
- package/dist/contracts/toolOutputSchemaGroups.js +5 -1
- package/dist/contracts/toolOutputSchemaPrimitives.js +1 -1
- package/dist/contracts/webNetworkCaptureToolContracts.js +24 -0
- package/dist/domain/analysisErrorBase.js +15 -0
- package/dist/domain/analysisErrorCore.js +59 -5
- package/dist/domain/analysisErrorPresentation.js +29 -1
- package/dist/domain/analysisErrorProjection.js +52 -2
- package/dist/domain/analysisSnapshot.js +166 -2
- package/dist/domain/apple/dyldPaths.js +81 -0
- package/dist/domain/apple/dylibResolution.js +367 -0
- package/dist/domain/apple/dylibResolutionFindings.js +75 -0
- package/dist/domain/artifactPathSyntax.js +1 -1
- package/dist/domain/browserObservation.js +1 -1
- package/dist/domain/browserObservationSchemas.js +13 -0
- package/dist/domain/browserScenarioValues.js +9 -2
- package/dist/domain/callPathSchemas.js +1 -1
- package/dist/domain/canonicalDigest.js +13 -1
- package/dist/domain/changedBehavior.js +3 -3
- package/dist/domain/comparisonStatus.js +2 -30
- package/dist/domain/conformanceTrustGate.js +0 -16
- package/dist/domain/evidence.js +17 -5
- package/dist/domain/evm/evmInterface.js +63 -0
- package/dist/domain/explicitSensitiveFailure.js +92 -0
- package/dist/domain/explicitSensitiveValues.js +10 -0
- package/dist/domain/firmware/firmwareAnalysis.js +8 -1
- package/dist/domain/javascript/electronActiveObservation.js +9 -4
- package/dist/domain/javascript/htmlArtifactReferences.js +40 -0
- package/dist/domain/javascript/javascriptExportShapeComparison.js +21 -5
- package/dist/domain/javascript/javascriptExportShapeVariants.js +0 -10
- package/dist/domain/javascript/javascriptSemanticGraph.js +21 -5
- package/dist/domain/jsonValue.js +4 -0
- package/dist/domain/localPath.js +2 -0
- package/dist/domain/native/binaryLayout.js +357 -0
- package/dist/domain/native/nativeCallObservation.js +164 -0
- package/dist/domain/native/objcSwiftMetadata.js +0 -39
- package/dist/domain/native/recordedCrash.js +266 -0
- package/dist/domain/native/recordedCrashNoteCoverage.js +73 -0
- package/dist/domain/{processCapture.js → process/processCapture.js} +139 -2
- package/dist/domain/{processCaptureExample.js → process/processCaptureExample.js} +5 -0
- package/dist/domain/process/processCaptureParsing.js +54 -0
- package/dist/domain/{processCaptureValidation.js → process/processCaptureValidation.js} +18 -2
- package/dist/domain/{processComparison.js → process/processComparison.js} +1 -1
- package/dist/domain/{processObservation.js → process/processObservation.js} +1 -1
- package/dist/domain/{processTraceSpecification.js → process/processTraceSpecification.js} +1 -1
- package/dist/domain/providerSelectionError.js +4 -2
- package/dist/domain/reconstructionVerification.js +3 -3
- package/dist/domain/reconstructionVerificationSchemas.js +1 -1
- package/dist/domain/residualUnknown.js +3 -2
- package/dist/domain/staticRuntimeCorrelation.js +3 -3
- package/dist/domain/webCaptureDiff.js +1 -0
- package/dist/domain/webNetworkCapture.js +118 -0
- package/dist/domain/webNetworkCaptureCoordinates.js +58 -0
- package/dist/dotnet/ManagedExceptionRegionValidation.js +41 -0
- package/dist/dotnet/ManagedMemberInspector.js +4 -2
- package/dist/dotnet/ManagedMemberInspectorCore.js +38 -32
- package/dist/dotnet/ManagedMemberInstructionDecoder.js +23 -7
- package/dist/dotnet/ManagedMemberRows.js +14 -3
- package/dist/dotnet/ManagedMetadataHeaps.js +20 -2
- package/dist/dotnet/ManagedMetadataInventory.js +38 -28
- package/dist/dotnet/ManagedMetadataInventoryRows.js +24 -10
- package/dist/dotnet/ManagedMethodBodyReader.js +7 -1
- package/dist/dotnet/ManagedNativeBoundaryHelpers.js +15 -3
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +3 -1
- package/dist/dotnet/ManagedPeReader.js +11 -0
- package/dist/evm/EvmBytecodeCarrier.js +21 -0
- package/dist/evm/EvmWorkerLimits.js +40 -0
- package/dist/evm/EvmoleFailures.js +126 -0
- package/dist/evm/EvmoleInterfaceAdapter.js +53 -0
- package/dist/evm/EvmoleInterfaceProvider.js +273 -0
- package/dist/evm/EvmoleInterfaceWorker.js +87 -0
- package/dist/evm/EvmoleRelease.js +16 -0
- package/dist/firmware/FirmwareCommand.js +1 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraLauncher.js +1 -0
- package/dist/ghidra/GhidraProvider.js +2 -2
- package/dist/ghidra/GhidraProviderCapabilities.js +1 -1
- package/dist/hopper/BridgeLauncher.js +2 -0
- package/dist/hopper/LinuxPrivateDisplayProbe.js +4 -2
- package/dist/javascript/recovery/WakaruCommand.js +3 -0
- package/dist/javascript/sourceMaps/SourceMapCommand.js +1 -0
- package/dist/native/CommandRunner.js +2 -1
- package/dist/native/LldbCallTracer.js +278 -0
- package/dist/native/NativeCallObservation.js +135 -0
- package/dist/native/NativeMacOSProvider.js +19 -3
- package/dist/native/NativeMacOSProviderMetadata.js +8 -3
- package/dist/native/pwndbg/PwndbgCoreContext.js +160 -0
- package/dist/native/pwntools/PwntoolsDecoder.js +203 -0
- package/dist/native/pwntools/PwntoolsFailures.js +180 -0
- package/dist/native/pwntools/PwntoolsLayoutProvider.js +36 -0
- package/dist/native/pwntools/PwntoolsRecordedCrashProvider.js +75 -0
- package/dist/native/pwntools/PwntoolsRelease.js +19 -0
- package/dist/native/pwntools/PwntoolsResourceLimits.js +76 -0
- package/dist/native/pwntools/RecordedCrashSourceBindings.js +46 -0
- package/dist/native/pwntools/RecordedCrashStructureBindings.js +101 -0
- package/dist/process/DarwinProcessRunTokenReader.js +294 -0
- package/dist/process/OwnedCommand.js +113 -0
- package/dist/process/ProcessOwnership.js +263 -33
- package/dist/process/ProcessOwnershipObservation.js +161 -24
- package/dist/process/ProviderProcess.js +30 -3
- package/dist/{application → process/capture}/ProcessCaptureCapability.js +15 -0
- package/dist/{application → process/capture}/ProcessCaptureError.js +17 -1
- package/dist/process/capture/ProcessCaptureLifecycle.js +615 -0
- package/dist/{application → process/capture}/ProcessHarness.js +195 -29
- package/dist/{application → process/capture}/ProcessSampling.js +2 -2
- package/dist/server/createServer.js +22 -0
- package/dist/server/promptCompletion.js +10 -3
- package/dist/server/registerBinaryDiagnosticsTools.js +17 -0
- package/dist/server/registerEvidenceTools.js +1 -1
- package/dist/server/registerEvmTools.js +17 -0
- package/dist/server/registerProcessComparisonTool.js +1 -1
- package/dist/server/registerRecordedCrashTools.js +17 -0
- package/dist/server/registerSessionTools.js +2 -2
- package/dist/server/registerWebNetworkCaptureTool.js +28 -0
- package/dist/server/sessionAvailabilityPolicy.js +3 -0
- package/dist/server/sessionToolPolicies.js +1 -1
- package/dist/server/toolResult.js +1 -1
- package/native/windows/build/manifest.json +2 -2
- package/native/windows/build/rea-windows-x64.node +0 -0
- package/package.json +37 -9
- package/scripts/rea.mjs +11 -3
- package/skills/reverse-engineer-anything/SKILL.md +23 -3
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +29 -0
- package/third_party/README.md +60 -0
- package/third_party/evmole/LICENSE +21 -0
- package/third_party/evmole/README.md +22 -0
- package/third_party/pwndbg/LICENSE.md +21 -0
- package/third_party/pwndbg/README.md +23 -0
- package/third_party/pwntools/README.md +40 -0
- package/dist/application/AuthorizedArtifactInventory.js +0 -7
- package/dist/application/ProcessCaptureLifecycle.js +0 -299
- package/dist/application/StoreFileLock.js +0 -124
- package/dist/contracts/processCaptureExample.js +0 -2
- package/dist/domain/bytecodeProvider.js +0 -178
- package/dist/domain/customProtocolCapture.js +0 -137
- package/dist/domain/eventProcessTree.js +0 -225
- package/dist/domain/mobileApplicationInvestigation.js +0 -159
- package/dist/domain/packageAnalysis.js +0 -123
- package/dist/domain/peInspection.js +0 -197
- package/dist/domain/processCaptureParsing.js +0 -21
- package/dist/domain/protocolCapture.js +0 -217
- package/dist/domain/runtimeIdentification.js +0 -212
- package/dist/generatedMcpToolCatalog.js +0 -37
- /package/dist/application/{ArtifactExtractionDestination.js → artifacts/ArtifactExtractionDestination.js} +0 -0
- /package/dist/{application/ArtifactInventory → artifacts/inventory}/policy.js +0 -0
- /package/dist/{application/ArtifactInventory → artifacts/inventory}/types.js +0 -0
- /package/dist/domain/{processEvidenceProvider.js → process/processEvidenceProvider.js} +0 -0
- /package/dist/domain/{processScenario.js → process/processScenario.js} +0 -0
- /package/dist/domain/{processTraceComparison.js → process/processTraceComparison.js} +0 -0
- /package/dist/domain/{processTraceDimensionProjection.js → process/processTraceDimensionProjection.js} +0 -0
- /package/dist/domain/{processTraceEvaluation.js → process/processTraceEvaluation.js} +0 -0
- /package/dist/{application → process/capture}/FilesystemSnapshot.js +0 -0
- /package/dist/{application → process/capture}/ProcessCaptureEnvironment.js +0 -0
- /package/dist/{application → process/capture}/ProcessCaptureJournal.js +0 -0
- /package/dist/{application → process/capture}/ProcessFilesystemEffects.js +0 -0
- /package/dist/{application → process/capture}/ProcessNormalization.js +0 -0
- /package/dist/{application → process/capture}/ProcessScenarioRuntimeValidation.js +0 -0
- /package/dist/{application → process/capture}/ProcessTimer.js +0 -0
- /package/dist/{application → process/capture}/TerminalRenderer.js +0 -0
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { constants, statSync } from "node:fs";
|
|
3
|
+
import { open, mkdtemp, rm, stat } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { digestProcessCommitment, parseProcessCapture, partialProcessCaptureObservationSchema, processComparisonContract, processScenarioCommitment, } from "../../domain/process/processCapture.js";
|
|
7
|
+
import { PRODUCT_IDENTITY } from "../../identity.js";
|
|
8
|
+
import { snapshotRoots } from "./FilesystemSnapshot.js";
|
|
9
|
+
import { classifyFilesystemEffects } from "./ProcessFilesystemEffects.js";
|
|
10
|
+
import { cleanupOwnedProcessGroup, verifyNoTokenOwnedProcesses, } from "../ProcessOwnership.js";
|
|
11
|
+
import { observeOwnedProcessGroup, prepareProcessOwnershipInspection, systemProcessOwnershipHost, } from "../ProcessOwnershipObservation.js";
|
|
12
|
+
import { describeProcessCaptureExecutionFailure, ProcessCaptureError, } from "./ProcessCaptureError.js";
|
|
13
|
+
import { assertNotCancelled } from "./ProcessScenarioRuntimeValidation.js";
|
|
14
|
+
import { normalizeProcessElapsedTime, normalizeProcessSamples, normalizeProcessText, } from "./ProcessNormalization.js";
|
|
15
|
+
import { PROCESS_PROVIDER } from "../../domain/process/processEvidenceProvider.js";
|
|
16
|
+
import { TerminalRenderer } from "./TerminalRenderer.js";
|
|
17
|
+
import { scheduleProcessInterval } from "./ProcessTimer.js";
|
|
18
|
+
/** Seed explicit observation availability before capture completion begins. */
|
|
19
|
+
export const createProcessCaptureObservationBuffer = (options) => ({
|
|
20
|
+
target_pid: {
|
|
21
|
+
state: "unavailable",
|
|
22
|
+
reason: "No PTY process was spawned before the run failed.",
|
|
23
|
+
},
|
|
24
|
+
frames: { state: "available", value: options.frames },
|
|
25
|
+
rendered_frames: {
|
|
26
|
+
state: "unavailable",
|
|
27
|
+
reason: "Rendered terminal frames were not collected before capture completion finished.",
|
|
28
|
+
},
|
|
29
|
+
interaction_events: { state: "available", value: options.interactions },
|
|
30
|
+
exit: {
|
|
31
|
+
state: "unavailable",
|
|
32
|
+
reason: "Terminal exit was not observed before the run failed.",
|
|
33
|
+
},
|
|
34
|
+
settlement: {
|
|
35
|
+
state: "unavailable",
|
|
36
|
+
reason: "Process settlement was not observed before capture completion finished.",
|
|
37
|
+
},
|
|
38
|
+
process_samples: { state: "available", value: options.samples },
|
|
39
|
+
filesystem_snapshots: {
|
|
40
|
+
before: {
|
|
41
|
+
state: "available",
|
|
42
|
+
value: {
|
|
43
|
+
files: options.before.files,
|
|
44
|
+
truncated: options.before.truncated,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
after: {
|
|
48
|
+
state: "unavailable",
|
|
49
|
+
reason: "Final filesystem snapshot was not collected before capture completion finished.",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
event_journal: { state: "available", value: options.eventJournal },
|
|
53
|
+
manifest: {
|
|
54
|
+
state: "unavailable",
|
|
55
|
+
reason: "Capture manifest was not produced before capture completion finished.",
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
const normalizePartialProcessObservations = (observations, context) => {
|
|
59
|
+
const targetPid = observations.target_pid;
|
|
60
|
+
const target_pid = targetPid.state === "available" && context.rootPid !== undefined
|
|
61
|
+
? {
|
|
62
|
+
state: "available",
|
|
63
|
+
value: context.scenario.normalization.pids ? 1 : context.rootPid,
|
|
64
|
+
}
|
|
65
|
+
: targetPid;
|
|
66
|
+
const samples = observations.process_samples;
|
|
67
|
+
const process_samples = samples.state === "available" && context.rootPid !== undefined
|
|
68
|
+
? {
|
|
69
|
+
state: "available",
|
|
70
|
+
value: normalizeProcessSamples(samples.value, context.scenario, context.rootPid),
|
|
71
|
+
}
|
|
72
|
+
: samples;
|
|
73
|
+
return { ...observations, target_pid, process_samples };
|
|
74
|
+
};
|
|
75
|
+
/** Wait for trailing PTY callbacks before the capture becomes immutable. */
|
|
76
|
+
export const settleProcessCaptureJournal = async (journal, quietMs = 25, maxWaitMs = 500) => {
|
|
77
|
+
const deadline = Date.now() + maxWaitMs;
|
|
78
|
+
let observed = journal.length;
|
|
79
|
+
let lastChangeAt = Date.now();
|
|
80
|
+
while (Date.now() < deadline) {
|
|
81
|
+
await new Promise((resolve) => setTimeout(resolve, 4));
|
|
82
|
+
if (journal.length !== observed) {
|
|
83
|
+
observed = journal.length;
|
|
84
|
+
lastChangeAt = Date.now();
|
|
85
|
+
}
|
|
86
|
+
else if (Date.now() - lastChangeAt >= quietMs)
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
export const buildCaptureResult = (options) => {
|
|
91
|
+
const hasSensitiveScriptedInput = options.scenario.events.some((event) => event.type === "input" && event.sensitive);
|
|
92
|
+
const sensitiveInputUnknown = "Sensitive scripted input values are redacted from process capture Evidence.";
|
|
93
|
+
const filesystemObservationUnknown = "No filesystem observation paths were selected; filesystem effects remain unknown.";
|
|
94
|
+
const hasFilesystemObservations = options.scenario.filesystem_observation_paths.length > 0;
|
|
95
|
+
return {
|
|
96
|
+
manifest: options.manifest,
|
|
97
|
+
normalization: options.scenario.normalization,
|
|
98
|
+
frames: options.frames,
|
|
99
|
+
rendered_frames: options.renderedFrames,
|
|
100
|
+
interaction_events: options.interactions,
|
|
101
|
+
exit: {
|
|
102
|
+
code: options.exit.reason === "exited" && options.exit.exitCode >= 0
|
|
103
|
+
? options.exit.exitCode
|
|
104
|
+
: null,
|
|
105
|
+
signal: options.exit.signal ?? null,
|
|
106
|
+
reason: options.exit.reason,
|
|
107
|
+
},
|
|
108
|
+
settlement: options.settlement,
|
|
109
|
+
process_samples: normalizeProcessSamples(options.samples, options.scenario, options.rootPid),
|
|
110
|
+
filesystem_checkpoints: options.checkpoints,
|
|
111
|
+
event_journal: options.eventJournal,
|
|
112
|
+
files_before: options.before.files,
|
|
113
|
+
files_after: options.after.files,
|
|
114
|
+
filesystem_effects: classifyFilesystemEffects(options.before.files, options.after.files),
|
|
115
|
+
truncated: options.truncated,
|
|
116
|
+
limitations: [
|
|
117
|
+
"The executable digest is a prelaunch file sample; matching path metadata immediately after spawn does not prove an atomic operating-system image binding.",
|
|
118
|
+
"Process trees are sampled and may omit short-lived descendants.",
|
|
119
|
+
...(options.samplingPartial
|
|
120
|
+
? ["Process-tree sampling ended with an incomplete observation."]
|
|
121
|
+
: []),
|
|
122
|
+
"Filesystem observations are before/after snapshots, not syscall traces.",
|
|
123
|
+
"Inherited host environment variables are not recorded and may affect results.",
|
|
124
|
+
...(!hasFilesystemObservations ? [filesystemObservationUnknown] : []),
|
|
125
|
+
...(hasSensitiveScriptedInput ? [sensitiveInputUnknown] : []),
|
|
126
|
+
],
|
|
127
|
+
residual_unknowns: [
|
|
128
|
+
{
|
|
129
|
+
scope: "process",
|
|
130
|
+
reason: "Process trees are sampled and may omit short-lived descendants.",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
scope: "process",
|
|
134
|
+
reason: "The executable digest is a prelaunch file sample; operating-system image binding is not atomically observed.",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
scope: "environment",
|
|
138
|
+
reason: "Inherited host environment variables are not recorded.",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
scope: "network",
|
|
142
|
+
reason: "Network activity is not observed by this capture.",
|
|
143
|
+
},
|
|
144
|
+
...(!hasFilesystemObservations
|
|
145
|
+
? [
|
|
146
|
+
{
|
|
147
|
+
scope: "filesystem",
|
|
148
|
+
reason: filesystemObservationUnknown,
|
|
149
|
+
},
|
|
150
|
+
]
|
|
151
|
+
: []),
|
|
152
|
+
...(hasSensitiveScriptedInput
|
|
153
|
+
? [{ scope: "interaction", reason: sensitiveInputUnknown }]
|
|
154
|
+
: []),
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
const identityFromStats = (stats) => ({
|
|
159
|
+
dev: stats.dev,
|
|
160
|
+
ino: stats.ino,
|
|
161
|
+
mode: stats.mode,
|
|
162
|
+
size: stats.size,
|
|
163
|
+
mtimeNs: stats.mtimeNs,
|
|
164
|
+
ctimeNs: stats.ctimeNs,
|
|
165
|
+
});
|
|
166
|
+
const sameExecutableIdentity = (left, right) => left.dev === right.dev &&
|
|
167
|
+
left.ino === right.ino &&
|
|
168
|
+
left.mode === right.mode &&
|
|
169
|
+
left.size === right.size &&
|
|
170
|
+
left.mtimeNs === right.mtimeNs &&
|
|
171
|
+
left.ctimeNs === right.ctimeNs;
|
|
172
|
+
const filesystemFailure = (cause) => {
|
|
173
|
+
if (!(cause instanceof Error) || !("code" in cause) || !("syscall" in cause))
|
|
174
|
+
return undefined;
|
|
175
|
+
return `Executable file observation failed: ${cause.message}`;
|
|
176
|
+
};
|
|
177
|
+
const selectedExecutableFileSystem = {
|
|
178
|
+
open,
|
|
179
|
+
stat: (path) => stat(path, { bigint: true }),
|
|
180
|
+
};
|
|
181
|
+
/** Sample the selected file without launching it; retain cancellation and file identity. */
|
|
182
|
+
export const observeSelectedExecutable = async (path, signal, fileSystem = selectedExecutableFileSystem) => {
|
|
183
|
+
assertNotCancelled(signal);
|
|
184
|
+
let handle;
|
|
185
|
+
try {
|
|
186
|
+
handle = await fileSystem.open(path, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
187
|
+
const openedStats = await handle.stat({ bigint: true });
|
|
188
|
+
if (!openedStats.isFile())
|
|
189
|
+
return {
|
|
190
|
+
sha256: null,
|
|
191
|
+
identity: null,
|
|
192
|
+
reason: "Selected executable is not a regular file.",
|
|
193
|
+
};
|
|
194
|
+
const openedIdentity = identityFromStats(openedStats);
|
|
195
|
+
const selectedPathIdentity = identityFromStats(await fileSystem.stat(path));
|
|
196
|
+
if (!sameExecutableIdentity(openedIdentity, selectedPathIdentity))
|
|
197
|
+
return {
|
|
198
|
+
sha256: null,
|
|
199
|
+
identity: null,
|
|
200
|
+
reason: "Selected executable path changed while it was being opened.",
|
|
201
|
+
};
|
|
202
|
+
// IO above can settle after cancellation. Do not construct a stream with an
|
|
203
|
+
// already-aborted signal: Node can emit a second, unhandled AbortError.
|
|
204
|
+
assertNotCancelled(signal);
|
|
205
|
+
const hash = createHash("sha256");
|
|
206
|
+
for await (const chunk of handle.createReadStream({
|
|
207
|
+
autoClose: false,
|
|
208
|
+
signal,
|
|
209
|
+
})) {
|
|
210
|
+
assertNotCancelled(signal);
|
|
211
|
+
hash.update(chunk);
|
|
212
|
+
}
|
|
213
|
+
assertNotCancelled(signal);
|
|
214
|
+
const finalIdentity = identityFromStats(await handle.stat({ bigint: true }));
|
|
215
|
+
if (!sameExecutableIdentity(openedIdentity, finalIdentity))
|
|
216
|
+
return {
|
|
217
|
+
sha256: null,
|
|
218
|
+
identity: null,
|
|
219
|
+
reason: "Selected executable changed while its contents were sampled.",
|
|
220
|
+
};
|
|
221
|
+
return {
|
|
222
|
+
sha256: hash.digest("hex"),
|
|
223
|
+
identity: openedIdentity,
|
|
224
|
+
reason: null,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
catch (cause) {
|
|
228
|
+
assertNotCancelled(signal);
|
|
229
|
+
const reason = filesystemFailure(cause);
|
|
230
|
+
if (reason !== undefined)
|
|
231
|
+
return { sha256: null, identity: null, reason };
|
|
232
|
+
throw cause;
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
await handle?.close();
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
/** Compare the selected file metadata with the path immediately after PTY spawn. */
|
|
239
|
+
export const observeLaunchedExecutable = (path, selected) => {
|
|
240
|
+
if (selected.identity === null || selected.sha256 === null)
|
|
241
|
+
return {
|
|
242
|
+
selectedSha256: selected.sha256,
|
|
243
|
+
sha256: null,
|
|
244
|
+
state: "unknown",
|
|
245
|
+
reason: selected.reason ?? "Selected executable identity is unavailable.",
|
|
246
|
+
};
|
|
247
|
+
try {
|
|
248
|
+
const currentStats = statSync(path, { bigint: true });
|
|
249
|
+
const currentIdentity = identityFromStats(currentStats);
|
|
250
|
+
if (currentStats.isFile() &&
|
|
251
|
+
sameExecutableIdentity(selected.identity, currentIdentity))
|
|
252
|
+
return {
|
|
253
|
+
selectedSha256: selected.sha256,
|
|
254
|
+
sha256: selected.sha256,
|
|
255
|
+
state: "path_metadata_unchanged",
|
|
256
|
+
reason: null,
|
|
257
|
+
};
|
|
258
|
+
return {
|
|
259
|
+
selectedSha256: selected.sha256,
|
|
260
|
+
sha256: null,
|
|
261
|
+
state: "unknown",
|
|
262
|
+
reason: "Executable path metadata changed between sampling and spawn.",
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
catch (cause) {
|
|
266
|
+
const reason = filesystemFailure(cause);
|
|
267
|
+
if (reason !== undefined)
|
|
268
|
+
return {
|
|
269
|
+
selectedSha256: selected.sha256,
|
|
270
|
+
sha256: null,
|
|
271
|
+
state: "unknown",
|
|
272
|
+
reason,
|
|
273
|
+
};
|
|
274
|
+
throw cause;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
export const createRunManifest = async (scenario, startedAt, completedAt, executable, host = {
|
|
278
|
+
platform: process.platform,
|
|
279
|
+
architecture: process.arch,
|
|
280
|
+
}) => {
|
|
281
|
+
const scenarioCommitment = processScenarioCommitment(scenario, executable.selectedSha256 ?? undefined);
|
|
282
|
+
const comparisonContract = processComparisonContract(scenario);
|
|
283
|
+
return {
|
|
284
|
+
rea_version: PRODUCT_IDENTITY.packageVersion,
|
|
285
|
+
provider_version: PROCESS_PROVIDER.version,
|
|
286
|
+
platform: host.platform,
|
|
287
|
+
architecture: host.architecture,
|
|
288
|
+
pty_backend: "node-pty",
|
|
289
|
+
started_at: startedAt.toISOString(),
|
|
290
|
+
completed_at: completedAt.toISOString(),
|
|
291
|
+
scenario: scenarioCommitment,
|
|
292
|
+
comparison_contract: comparisonContract,
|
|
293
|
+
full_scenario_sha256: digestProcessCommitment(scenarioCommitment),
|
|
294
|
+
comparison_contract_sha256: digestProcessCommitment(comparisonContract),
|
|
295
|
+
selected_executable_sha256: executable.selectedSha256,
|
|
296
|
+
executable_sha256: executable.sha256,
|
|
297
|
+
executable_identity: {
|
|
298
|
+
state: executable.state,
|
|
299
|
+
reason: executable.reason,
|
|
300
|
+
},
|
|
301
|
+
normalization_sha256: digestProcessCommitment(scenario.normalization),
|
|
302
|
+
};
|
|
303
|
+
};
|
|
304
|
+
export const observeSettlement = async (runId, processGroupIds, settleMs, recordEvent = () => undefined, platform = process.platform) => {
|
|
305
|
+
if (platform === "win32") {
|
|
306
|
+
recordEvent("lifecycle", 1);
|
|
307
|
+
return { state: "unverifiable", elapsed_ms: 0 };
|
|
308
|
+
}
|
|
309
|
+
const started = Date.now();
|
|
310
|
+
let consecutiveEmpty = 0;
|
|
311
|
+
let deadlineReached = false;
|
|
312
|
+
while (!deadlineReached) {
|
|
313
|
+
const observations = await Promise.all([...new Set(processGroupIds)].map((processGroupId) => observeOwnedProcessGroup({
|
|
314
|
+
runId,
|
|
315
|
+
leaderPid: processGroupId,
|
|
316
|
+
processGroupId,
|
|
317
|
+
})));
|
|
318
|
+
if (observations.some(({ state }) => state === "unverifiable")) {
|
|
319
|
+
recordEvent("lifecycle", 1);
|
|
320
|
+
return { state: "unverifiable", elapsed_ms: Date.now() - started };
|
|
321
|
+
}
|
|
322
|
+
if (observations.every(({ state }) => state === "empty")) {
|
|
323
|
+
consecutiveEmpty += 1;
|
|
324
|
+
if (consecutiveEmpty >= 2) {
|
|
325
|
+
recordEvent("lifecycle", 1);
|
|
326
|
+
return { state: "quiesced", elapsed_ms: Date.now() - started };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else
|
|
330
|
+
consecutiveEmpty = 0;
|
|
331
|
+
deadlineReached = Date.now() - started >= settleMs;
|
|
332
|
+
if (deadlineReached)
|
|
333
|
+
break;
|
|
334
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
335
|
+
}
|
|
336
|
+
recordEvent("lifecycle", 1);
|
|
337
|
+
return { state: "alive_at_deadline", elapsed_ms: Date.now() - started };
|
|
338
|
+
};
|
|
339
|
+
export const awaitTerminalExit = async ({ terminal, scenario, started, lastOutput, signal, timers, interactions, dispatchedEventIndexes, recordEvent, }) => new Promise((resolveExit) => {
|
|
340
|
+
// The kill caused by a deadline is observed later as an ordinary PTY exit.
|
|
341
|
+
// Keep the initiating lifecycle reason so comparisons distinguish a target
|
|
342
|
+
// exit from harness-owned timeout, idle-timeout, and cancellation cleanup.
|
|
343
|
+
let reason = "exited";
|
|
344
|
+
terminal.onExit((exit) => {
|
|
345
|
+
recordEvent("lifecycle", 0);
|
|
346
|
+
for (const [eventIndex, event] of scenario.events.entries()) {
|
|
347
|
+
if (dispatchedEventIndexes.has(eventIndex))
|
|
348
|
+
continue;
|
|
349
|
+
const sequence = interactions.length;
|
|
350
|
+
interactions.push({
|
|
351
|
+
sequence,
|
|
352
|
+
scheduled_at_ms: event.at_ms,
|
|
353
|
+
dispatched_at_ms: Math.max(0, Date.now() - started),
|
|
354
|
+
type: event.type,
|
|
355
|
+
data: event.type === "input"
|
|
356
|
+
? "<not-dispatched>"
|
|
357
|
+
: event.type === "resize"
|
|
358
|
+
? `${String(event.columns)}x${String(event.rows)}`
|
|
359
|
+
: event.signal,
|
|
360
|
+
outcome: "target_exited",
|
|
361
|
+
});
|
|
362
|
+
recordEvent("interaction_events", sequence);
|
|
363
|
+
}
|
|
364
|
+
for (const timer of timers) {
|
|
365
|
+
timer.cancel();
|
|
366
|
+
}
|
|
367
|
+
timers.clear();
|
|
368
|
+
resolveExit({ ...exit, reason });
|
|
369
|
+
});
|
|
370
|
+
const timeout = scheduleProcessInterval(() => {
|
|
371
|
+
if (signal?.aborted === true) {
|
|
372
|
+
reason = "cancelled";
|
|
373
|
+
terminal.kill("SIGKILL");
|
|
374
|
+
}
|
|
375
|
+
else if (Date.now() - started >= scenario.timeout_ms) {
|
|
376
|
+
reason = "timeout";
|
|
377
|
+
terminal.kill("SIGKILL");
|
|
378
|
+
}
|
|
379
|
+
else if (Date.now() - lastOutput() >= scenario.idle_timeout_ms) {
|
|
380
|
+
reason = "idle_timeout";
|
|
381
|
+
terminal.kill("SIGKILL");
|
|
382
|
+
}
|
|
383
|
+
}, 20);
|
|
384
|
+
timers.add(timeout);
|
|
385
|
+
});
|
|
386
|
+
const processCaptureCleanupHost = {
|
|
387
|
+
platform: process.platform,
|
|
388
|
+
cleanupProcessGroup: cleanupOwnedProcessGroup,
|
|
389
|
+
verifyTokenOwnedProcesses: verifyNoTokenOwnedProcesses,
|
|
390
|
+
removeTemporaryRoot: (path) => rm(path, { recursive: true, force: true }),
|
|
391
|
+
};
|
|
392
|
+
const PROCESS_CLEANUP_VERIFICATION_GRACE_MS = 1_000;
|
|
393
|
+
const PROCESS_CLEANUP_VERIFICATION_INTERVAL_MS = 25;
|
|
394
|
+
const verifyTokenOwnedProcessesUntilSettled = async (verify) => {
|
|
395
|
+
const deadline = Date.now() + PROCESS_CLEANUP_VERIFICATION_GRACE_MS;
|
|
396
|
+
let result = await verify();
|
|
397
|
+
while (!result.cleaned) {
|
|
398
|
+
const remaining = deadline - Date.now();
|
|
399
|
+
if (remaining <= 0)
|
|
400
|
+
return result;
|
|
401
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(PROCESS_CLEANUP_VERIFICATION_INTERVAL_MS, remaining)));
|
|
402
|
+
result = await verify();
|
|
403
|
+
}
|
|
404
|
+
return result;
|
|
405
|
+
};
|
|
406
|
+
export const releaseProcessResources = async (options) => {
|
|
407
|
+
const host = options.host ?? processCaptureCleanupHost;
|
|
408
|
+
for (const timer of options.timers)
|
|
409
|
+
timer.cancel();
|
|
410
|
+
let terminalRenderer = {
|
|
411
|
+
state: options.renderer === undefined ? "not_required" : "cleaned",
|
|
412
|
+
reason: null,
|
|
413
|
+
};
|
|
414
|
+
try {
|
|
415
|
+
await options.renderer?.dispose();
|
|
416
|
+
}
|
|
417
|
+
catch (cause) {
|
|
418
|
+
terminalRenderer = {
|
|
419
|
+
state: "failed",
|
|
420
|
+
reason: cause instanceof Error
|
|
421
|
+
? cause.message || cause.name || "renderer dispose failed"
|
|
422
|
+
: "renderer dispose failed",
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
let ownedProcessGroup = {
|
|
426
|
+
state: options.terminal === undefined ? "not_required" : "cleaned",
|
|
427
|
+
reason: null,
|
|
428
|
+
};
|
|
429
|
+
if (options.terminal !== undefined) {
|
|
430
|
+
if (host.platform === "win32") {
|
|
431
|
+
ownedProcessGroup = {
|
|
432
|
+
state: "unverified",
|
|
433
|
+
reason: "owned process cleanup is unverifiable on Windows without process-job authority",
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
const cleaned = await host.cleanupProcessGroup({
|
|
438
|
+
runId: options.runId,
|
|
439
|
+
leaderPid: options.terminal.pid,
|
|
440
|
+
processGroupId: options.terminal.pid,
|
|
441
|
+
sweepTokenOwnedProcesses: true,
|
|
442
|
+
...(options.sampledProcessGroupIds === undefined
|
|
443
|
+
? {}
|
|
444
|
+
: { sampledProcessGroupIds: options.sampledProcessGroupIds }),
|
|
445
|
+
...(options.captureBaseline === undefined
|
|
446
|
+
? {}
|
|
447
|
+
: { captureBaseline: options.captureBaseline }),
|
|
448
|
+
});
|
|
449
|
+
if (!cleaned.cleaned) {
|
|
450
|
+
ownedProcessGroup = { state: "unverified", reason: cleaned.reason };
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
const verified = await verifyTokenOwnedProcessesUntilSettled(() => host.verifyTokenOwnedProcesses(options.runId, undefined, options.captureBaseline));
|
|
454
|
+
if (!verified.cleaned)
|
|
455
|
+
ownedProcessGroup = { state: "unverified", reason: verified.reason };
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
let temporaryRoot = {
|
|
460
|
+
state: "cleaned",
|
|
461
|
+
reason: null,
|
|
462
|
+
};
|
|
463
|
+
try {
|
|
464
|
+
await host.removeTemporaryRoot(options.temporaryRoot);
|
|
465
|
+
}
|
|
466
|
+
catch (cause) {
|
|
467
|
+
temporaryRoot = {
|
|
468
|
+
state: "failed",
|
|
469
|
+
reason: cause instanceof Error
|
|
470
|
+
? cause.message || cause.name || "temporary root removal failed"
|
|
471
|
+
: "temporary root removal failed",
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
owned_process_group: ownedProcessGroup,
|
|
476
|
+
terminal_renderer: terminalRenderer,
|
|
477
|
+
temporary_root: temporaryRoot,
|
|
478
|
+
};
|
|
479
|
+
};
|
|
480
|
+
export const cleanupReportFailure = (report) => {
|
|
481
|
+
const failures = Object.entries(report).filter(([, outcome]) => outcome.state === "failed" || outcome.state === "unverified");
|
|
482
|
+
return failures.length === 0
|
|
483
|
+
? undefined
|
|
484
|
+
: failures
|
|
485
|
+
.map(([resource, outcome]) => `${resource}: ${outcome.reason ?? outcome.state}`)
|
|
486
|
+
.join("; ");
|
|
487
|
+
};
|
|
488
|
+
export const captureTerminalFrames = (options) => {
|
|
489
|
+
let outputBytes = 0;
|
|
490
|
+
let truncated = false;
|
|
491
|
+
options.terminal.onData((data) => {
|
|
492
|
+
options.onOutput();
|
|
493
|
+
const bytes = Buffer.byteLength(data);
|
|
494
|
+
if (outputBytes + bytes > options.scenario.limits.output_bytes) {
|
|
495
|
+
truncated = true;
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
outputBytes += bytes;
|
|
499
|
+
const atMs = normalizeProcessElapsedTime(Date.now() - options.started, options.scenario.normalization.time_bucket_ms);
|
|
500
|
+
const normalized = normalizeProcessText(data, options.scenario, options.temporaryRoot, options.terminal.pid);
|
|
501
|
+
const sequence = options.frames.length;
|
|
502
|
+
options.frames.push({
|
|
503
|
+
sequence,
|
|
504
|
+
at_ms: atMs,
|
|
505
|
+
data: normalized,
|
|
506
|
+
});
|
|
507
|
+
options.renderer.write(data, atMs);
|
|
508
|
+
options.recordEvent("frames", sequence);
|
|
509
|
+
});
|
|
510
|
+
return () => truncated;
|
|
511
|
+
};
|
|
512
|
+
export const resolveProcessResult = (capture, executionFailure, cleanup, observations, partialContext) => {
|
|
513
|
+
const cleanupFailure = cleanupReportFailure(cleanup);
|
|
514
|
+
if (cleanupFailure !== undefined) {
|
|
515
|
+
const cleanupResources = Object.entries(cleanup)
|
|
516
|
+
.filter(([, outcome]) => outcome.state === "failed" || outcome.state === "unverified")
|
|
517
|
+
.map(([resource]) => resource);
|
|
518
|
+
const executionFailureReason = describeProcessCaptureExecutionFailure(executionFailure);
|
|
519
|
+
let partialObservation;
|
|
520
|
+
if (capture !== undefined || observations !== undefined) {
|
|
521
|
+
const normalizedObservations = observations === undefined || partialContext === undefined
|
|
522
|
+
? observations
|
|
523
|
+
: normalizePartialProcessObservations(observations, partialContext);
|
|
524
|
+
const partialPayload = capture === undefined
|
|
525
|
+
? { observations: normalizedObservations }
|
|
526
|
+
: { capture };
|
|
527
|
+
const pendingCapture = capture === undefined
|
|
528
|
+
? undefined
|
|
529
|
+
: {
|
|
530
|
+
...capture,
|
|
531
|
+
settlement: {
|
|
532
|
+
...capture.settlement,
|
|
533
|
+
cleanup_outcome: "failed",
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
const partialResult = partialProcessCaptureObservationSchema.safeParse({
|
|
537
|
+
...partialPayload,
|
|
538
|
+
...(pendingCapture === undefined
|
|
539
|
+
? {}
|
|
540
|
+
: {
|
|
541
|
+
capture: Object.fromEntries(Object.entries(pendingCapture).filter(([key]) => key !== "cleanup")),
|
|
542
|
+
}),
|
|
543
|
+
cleanup,
|
|
544
|
+
execution_failure: executionFailureReason ?? null,
|
|
545
|
+
});
|
|
546
|
+
if (partialResult.success)
|
|
547
|
+
partialObservation = partialResult.data;
|
|
548
|
+
}
|
|
549
|
+
throw new ProcessCaptureError(cleanupFailure, {
|
|
550
|
+
cause: executionFailure,
|
|
551
|
+
reason: "cleanup_incomplete",
|
|
552
|
+
cleanupResources,
|
|
553
|
+
...(executionFailureReason === undefined
|
|
554
|
+
? {}
|
|
555
|
+
: { executionFailure: executionFailureReason }),
|
|
556
|
+
...(partialObservation === undefined ? {} : { partialObservation }),
|
|
557
|
+
cleanupReport: cleanup,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (executionFailure instanceof ProcessCaptureError)
|
|
561
|
+
throw executionFailure;
|
|
562
|
+
if (executionFailure !== undefined) {
|
|
563
|
+
const executionFailureReason = describeProcessCaptureExecutionFailure(executionFailure);
|
|
564
|
+
throw new ProcessCaptureError("process capture failed", {
|
|
565
|
+
cause: executionFailure,
|
|
566
|
+
...(executionFailureReason === undefined
|
|
567
|
+
? {}
|
|
568
|
+
: { executionFailure: executionFailureReason }),
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
if (capture === undefined)
|
|
572
|
+
throw new ProcessCaptureError("process capture produced no result");
|
|
573
|
+
if ("cleanup" in capture)
|
|
574
|
+
return capture;
|
|
575
|
+
const settlement = capture.settlement.state === "quiesced"
|
|
576
|
+
? { ...capture.settlement, cleanup_outcome: "not_required" }
|
|
577
|
+
: { ...capture.settlement, cleanup_outcome: "cleaned" };
|
|
578
|
+
try {
|
|
579
|
+
return parseProcessCapture({
|
|
580
|
+
...capture,
|
|
581
|
+
settlement,
|
|
582
|
+
cleanup: {
|
|
583
|
+
owned_process_group: "verified",
|
|
584
|
+
temporary_root: "removed",
|
|
585
|
+
},
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
catch (cause) {
|
|
589
|
+
throw new ProcessCaptureError(`process capture validation failed${cause instanceof Error ? `: ${cause.message}` : ""}`, { cause });
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
export const prepareProcessCapture = async (scenario, signal, captureSnapshot = snapshotRoots, host = systemProcessPreparationHost) => {
|
|
593
|
+
assertNotCancelled(signal);
|
|
594
|
+
await host.prepareOwnershipInspector?.(signal);
|
|
595
|
+
assertNotCancelled(signal);
|
|
596
|
+
const before = await captureSnapshot(scenario, signal);
|
|
597
|
+
const temporaryRoot = await host.createTemporaryRoot();
|
|
598
|
+
try {
|
|
599
|
+
assertNotCancelled(signal);
|
|
600
|
+
const runId = randomUUID();
|
|
601
|
+
const ownershipBaseline = (await host.captureOwnershipBaseline?.(signal)) ?? [];
|
|
602
|
+
assertNotCancelled(signal);
|
|
603
|
+
return { temporaryRoot, runId, ownershipBaseline, before };
|
|
604
|
+
}
|
|
605
|
+
catch (cause) {
|
|
606
|
+
await host.cleanup(temporaryRoot);
|
|
607
|
+
throw cause;
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
const systemProcessPreparationHost = {
|
|
611
|
+
prepareOwnershipInspector: prepareProcessOwnershipInspection,
|
|
612
|
+
captureOwnershipBaseline: (signal) => systemProcessOwnershipHost.captureBaseline?.(signal) ?? Promise.resolve([]),
|
|
613
|
+
createTemporaryRoot: () => mkdtemp(join(tmpdir(), "rea-process-")),
|
|
614
|
+
cleanup: (path) => rm(path, { recursive: true, force: true }),
|
|
615
|
+
};
|