rea-agents 3.0.0 → 3.1.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 +14 -16
- package/dist/application/AnalysisContextQueries.js +21 -9
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +19 -11
- package/dist/application/ArtifactInventory/policy.js +17 -0
- package/dist/application/ArtifactInventory/reader.js +1 -4
- package/dist/application/ArtifactInventory/scanCanonical.js +0 -3
- package/dist/application/ArtifactInventory/scanReader.js +7 -7
- package/dist/application/ArtifactInventory/types.js +1 -5
- package/dist/application/BinarySession.js +14 -16
- package/dist/application/BinarySessionRecords.js +31 -5
- package/dist/application/BinarySessionStatus.js +11 -5
- package/dist/application/BrowserEvidence.js +3 -5
- package/dist/application/CapabilityClientRequirements.js +26 -0
- package/dist/application/CapabilityInventory.js +134 -63
- package/dist/application/CrossVersionInventory.js +7 -6
- package/dist/application/ElectronActiveEvidence.js +55 -0
- package/dist/application/ElectronActiveObservationPort.js +1 -0
- package/dist/application/ElectronActiveObservationService.js +81 -0
- package/dist/application/EnhancedTools.js +22 -20
- package/dist/application/EvidenceBundleCommands.js +5 -1
- package/dist/application/EvidenceLedger.js +8 -1
- package/dist/application/JavaScriptReplayPermission.js +1 -1
- package/dist/application/JavaScriptReplayService.js +23 -15
- package/dist/application/ManagedRuntimeCorrelationService.js +9 -5
- package/dist/application/NodeRuntimeCharacterizationService.js +8 -9
- package/dist/application/ProcessCaptureLifecycle.js +5 -0
- package/dist/application/ProcessCli.js +2 -1
- package/dist/application/ProcessHarness.js +8 -3
- package/dist/application/ProcessNormalization.js +2 -1
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReferenceSourceImport.js +2 -2
- package/dist/application/SetupClientConfiguration.js +6 -1
- package/dist/application/WindowsCapabilities.js +5 -4
- package/dist/artifacts/ArtifactProvider.js +8 -10
- package/dist/artifacts/SafeOutputTree.js +7 -8
- package/dist/browser/CdpCaptureStorageFingerprints.js +10 -7
- package/dist/browser/CdpElectronInspection.js +3 -1
- package/dist/browser/JavaScriptRuntimeScope.js +5 -0
- package/dist/browser/PlaywrightElectronActiveActions.js +324 -0
- package/dist/browser/PlaywrightElectronActiveProvider.js +294 -0
- package/dist/browser/PlaywrightScenarioBrowser.js +4 -4
- package/dist/browser/PlaywrightScenarioSession.js +4 -4
- package/dist/browser/SensitiveTextCapture.js +1 -1
- package/dist/browser/V8InspectorCaptureProjection.js +1 -0
- package/dist/browser/V8InspectorProvider.js +3 -3
- package/dist/catalogIdentity.js +12 -6
- package/dist/cli/managedCommands.js +1 -1
- package/dist/cliApplicationCommands.js +2 -2
- package/dist/cliBrowserAdvancedCommands.js +11 -5
- package/dist/cliBrowserCommands.js +14 -7
- package/dist/cliCommandNames.js +1 -0
- package/dist/cliElectronCommands.js +46 -4
- package/dist/cliJavaScriptRuntimeCommands.js +14 -6
- package/dist/cliObservationOptions.js +4 -0
- package/dist/cliPolicyCommands.js +1 -0
- package/dist/config/browserScenario.js +77 -0
- package/dist/config/electronAutomation.js +35 -0
- package/dist/config/environment.js +6 -0
- package/dist/config/javascriptReplay.js +41 -0
- package/dist/config/managedRuntime.js +25 -0
- package/dist/config/parseConfig.js +56 -200
- package/dist/config/parsers.js +10 -1
- package/dist/config/passiveObservation.js +157 -0
- package/dist/config/processCapture.js +47 -0
- package/dist/contracts/applicationToolContracts.js +2 -5
- package/dist/contracts/applicationWorkflowInputContracts.js +86 -96
- package/dist/contracts/artifactToolContracts.js +27 -34
- package/dist/contracts/browserToolContracts.js +2 -2
- package/dist/contracts/electronToolContracts.js +30 -1
- package/dist/contracts/enhancedInputs.js +13 -29
- package/dist/contracts/functionWorkflowToolContracts.js +2 -3
- package/dist/contracts/managedToolContracts.js +4 -9
- package/dist/contracts/managedWorkflowExamples.js +12 -9
- package/dist/contracts/managedWorkflowToolContracts.js +30 -27
- package/dist/contracts/nativeToolContracts.js +2 -3
- package/dist/contracts/sessionToolSchemas.js +61 -0
- package/dist/contracts/toolContractExamples.js +1 -0
- package/dist/contracts/toolContractHelpers.js +23 -0
- package/dist/contracts/toolContracts.js +5 -77
- package/dist/contracts/toolEffects.js +15 -0
- package/dist/contracts/toolOutputSchemaGroups.js +17 -2
- package/dist/contracts/toolOutputSchemaPrimitives.js +76 -26
- package/dist/contracts/toolSchemaMetadata.js +128 -0
- package/dist/doctorRuntime.js +1 -1
- package/dist/domain/androidApplication.js +2 -0
- package/dist/domain/appleApplication.js +2 -0
- package/dist/domain/binaryTarget.js +7 -5
- package/dist/domain/binaryTargetTypes.js +1 -0
- package/dist/domain/browserCaptureComparison.js +21 -69
- package/dist/domain/browserObservation.js +22 -9
- package/dist/domain/bytecodeProvider.js +33 -35
- package/dist/domain/callPath.js +3 -2
- package/dist/domain/conformanceReplay.js +3 -3
- package/dist/domain/conformanceTrustGate.js +151 -16
- package/dist/domain/customProtocolCapture.js +3 -3
- package/dist/domain/electronActiveObservation.js +277 -0
- package/dist/domain/electronObservation.js +15 -11
- package/dist/domain/electronStaticAnalysisValues.js +4 -1
- package/dist/domain/eventProcessTree.js +82 -79
- package/dist/domain/functionComparisonNormalization.js +15 -8
- package/dist/domain/functionDossierEvidence.js +9 -4
- package/dist/domain/hopperValues.js +68 -71
- package/dist/domain/inputIssueProjection.js +4 -1
- package/dist/domain/investigationWorkspace.js +16 -15
- package/dist/domain/javascriptExportShapeComparisonSchemas.js +2 -2
- package/dist/domain/javascriptReplay.js +79 -63
- package/dist/domain/javascriptRuntimeObservation.js +1 -0
- package/dist/domain/javascriptRuntimeReconciliationActive.js +89 -0
- package/dist/domain/javascriptRuntimeReconciliationGraph.js +11 -10
- package/dist/domain/javascriptRuntimeReconciliationMatching.js +57 -49
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +5 -1
- package/dist/domain/javascriptRuntimeReconciliationResult.js +21 -14
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
- package/dist/domain/knownPageCoverage.js +15 -7
- package/dist/domain/nativeApiBoundary.js +15 -7
- package/dist/domain/nodeRuntimeCharacterization.js +14 -13
- package/dist/domain/processCaptureReactiveValidation.js +8 -3
- package/dist/domain/processCaptureValidation.js +7 -3
- package/dist/domain/processComparison.js +12 -7
- package/dist/domain/processObservation.js +5 -6
- package/dist/domain/processReactiveMatching.js +14 -7
- package/dist/domain/processReactiveTransition.js +9 -3
- package/dist/domain/processScenario.js +6 -3
- package/dist/domain/processTraceComparison.js +3 -2
- package/dist/domain/protocolCapture.js +8 -6
- package/dist/domain/reconstructionReadinessSchemas.js +30 -2
- package/dist/domain/referenceSourceImportParsing.js +2 -2
- package/dist/domain/replayMachineRuntime.js +5 -3
- package/dist/domain/sourceToBundleComparison.js +2 -2
- package/dist/domain/sourceToBundleComparisonSchemas.js +8 -8
- package/dist/domain/sourceToBundleSignals.js +7 -1
- package/dist/domain/staticRuntimeCorrelation.js +7 -5
- package/dist/domain/webBundleAnalysis.js +14 -14
- package/dist/domain/webCaptureDiff.js +15 -12
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +2 -3
- package/dist/generatedMcpToolCatalog.js +1 -1
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraClient.js +1 -1
- package/dist/ghidra/GhidraClientStartup.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +17 -15
- package/dist/ghidra/GhidraInventoryValues.js +57 -47
- package/dist/ghidra/GhidraProvider.js +18 -10
- package/dist/ghidra/GhidraProviderCapabilities.js +5 -1
- package/dist/ghidra/GhidraSocketConnection.js +1 -1
- package/dist/ghidra/protocol.js +19 -42
- package/dist/hopper/BridgeLauncher.js +3 -1
- package/dist/hopper/HopperAnalysisProfile.js +2 -4
- package/dist/hopper/HopperCleanup.js +5 -6
- package/dist/hopper/HopperClient.js +1 -1
- package/dist/hopper/HopperProvider.js +0 -14
- package/dist/hopper/LinuxPrivateDisplayProbe.js +16 -10
- package/dist/main/reload.js +1 -6
- package/dist/main/state.js +0 -14
- package/dist/main/transport.js +30 -37
- package/dist/main.js +17 -15
- package/dist/mcpDoctor.js +2 -24
- package/dist/process/PrivateRuntimeRoot.js +17 -0
- package/dist/process/ProcessOwnership.js +64 -128
- package/dist/process/ProcessOwnershipIdentity.js +13 -0
- package/dist/process/ProcessOwnershipObservation.js +88 -0
- package/dist/process/ProcessOwnershipProcessTree.js +22 -0
- package/dist/process/ProviderDeadline.js +8 -12
- package/dist/process/WindowsAuthority.js +32 -0
- package/dist/reference/ReferenceSourceReader.js +3 -3
- package/dist/reference/ReferenceSourceReaderEntries.js +2 -2
- package/dist/replay/JavaScriptReplayWorker.js +95 -11
- package/dist/replay/JavaScriptReplayWorkerTypes.js +1 -0
- package/dist/replay/LinuxJavaScriptReplayRunner.js +10 -15
- package/dist/replay/ReplayOutcome.js +26 -15
- package/dist/replay/ReplayWorkerProtocol.js +34 -29
- package/dist/server/createServer.js +158 -94
- package/dist/server/mcpClientMetadata.js +32 -0
- package/dist/server/registerApplicationTools/characterization.js +6 -13
- package/dist/server/registerApplicationTools/compareExportShapes.js +2 -7
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +2 -7
- package/dist/server/registerApplicationTools/compareVersions.js +3 -8
- package/dist/server/registerApplicationTools/controlledReplay.js +6 -11
- package/dist/server/registerApplicationTools/coverage.js +13 -19
- package/dist/server/registerApplicationTools/obligationLedger.js +1 -6
- package/dist/server/registerApplicationTools/readiness.js +1 -6
- package/dist/server/registerApplicationTools/traceFeature.js +3 -8
- package/dist/server/registerApplicationTools/traceSemantics.js +1 -6
- package/dist/server/registerArtifactComparisonTool.js +12 -16
- package/dist/server/registerBrowserScenarioTool.js +1 -6
- package/dist/server/registerBrowserTools.js +37 -79
- package/dist/server/registerBundleComparisonTool.js +10 -14
- package/dist/server/registerCloseLifecycleTool.js +6 -14
- package/dist/server/registerElectronTools.js +30 -47
- package/dist/server/registerEnhancedTools.js +114 -122
- package/dist/server/registerEvidenceTools.js +5 -9
- package/dist/server/registerFunctionComparisonTool.js +11 -15
- package/dist/server/registerInvestigationTools.js +41 -57
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +13 -27
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +4 -9
- package/dist/server/registerManagedWorkflowTools/contract.js +2 -3
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +3 -8
- package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +3 -8
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +8 -13
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +4 -9
- package/dist/server/registerOfficialTools.js +8 -9
- package/dist/server/registerProcessComparisonTool.js +19 -24
- package/dist/server/registerReplayMachineTool.js +4 -10
- package/dist/server/registerSessionRecordTools.js +69 -40
- package/dist/server/registerSessionStatusTool.js +13 -28
- package/dist/server/registerSessionTools.js +36 -52
- package/dist/server/sessionAvailabilityPolicy.js +2 -1
- package/dist/server/sessionToolPolicies.js +1 -5
- package/dist/server/toolRegistrationOptions.js +4 -192
- package/dist/server/toolResult.js +2 -18
- package/package.json +22 -14
- package/scripts/electron-active-hook-boundaries.cjs +394 -0
- package/scripts/electron-active-hook.cjs +501 -0
- package/scripts/rea.mjs +1 -11
- package/skills/reverse-engineer-anything/SKILL.md +2 -2
- package/dist/server/DynamicToolAvailability.js +0 -70
- package/dist/server/LazyToolCatalog.js +0 -134
- package/dist/server/hydrateServerTools.js +0 -191
- package/dist/server/toolInputValidation.js +0 -77
- package/scripts/package-runner-bootstrap.mjs +0 -72
package/dist/main/transport.js
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
import { createServer, } from "../server/createServer.js";
|
|
2
2
|
import { MCP_CONNECTION_LOST, MCP_CONNECTION_START_FAILED, } from "./messages.js";
|
|
3
3
|
export const startMcpTransport = async (dependencies, session, serverContext) => {
|
|
4
|
-
const liveServers = new Set();
|
|
5
4
|
const { serverLogger } = serverContext;
|
|
5
|
+
let optionalProviders = {};
|
|
6
|
+
try {
|
|
7
|
+
optionalProviders = await serverContext.loadOptionalProviders();
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
serverLogger.warn("Optional MCP providers could not load; affected tools remain unavailable");
|
|
11
|
+
}
|
|
6
12
|
let handle;
|
|
7
13
|
try {
|
|
8
|
-
handle = dependencies.serve(() => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
artifactIntegrityContinueEnabled
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
23
|
-
liveServers.add(server);
|
|
24
|
-
return server;
|
|
25
|
-
}, {
|
|
14
|
+
handle = dependencies.serve(() => (dependencies.createServer ?? createServer)(session, session, {
|
|
15
|
+
logger: serverContext.logger,
|
|
16
|
+
processPolicy: () => serverContext.runtimeState.currentConfig.processExecutionPolicy,
|
|
17
|
+
evidenceFilePolicy: serverContext.runtimeState.evidencePolicy,
|
|
18
|
+
investigationInputRoots: serverContext.runtimeState.investigationRoots,
|
|
19
|
+
analysisSnapshotFilePolicy: serverContext.runtimeState.snapshotPolicy,
|
|
20
|
+
permissionAuthority: serverContext.permissionAuthority,
|
|
21
|
+
...optionalProviders,
|
|
22
|
+
artifactIntegrityContinueEnabled: () => serverContext.runtimeState.currentConfig
|
|
23
|
+
.artifactIntegrityContinueEnabled,
|
|
24
|
+
javascriptReplayPolicy: () => serverContext.runtimeState.currentConfig.javascriptReplayPolicy,
|
|
25
|
+
managedRuntimePolicy: () => serverContext.runtimeState.currentConfig.managedRuntimePolicy,
|
|
26
|
+
availabilityPolicy: () => runtimeAvailability(serverContext.runtimeState),
|
|
27
|
+
}), {
|
|
26
28
|
onerror: () => {
|
|
27
29
|
serverLogger.error(MCP_CONNECTION_LOST);
|
|
28
30
|
dependencies.writeStderr(`${MCP_CONNECTION_LOST}\n`);
|
|
@@ -35,26 +37,17 @@ export const startMcpTransport = async (dependencies, session, serverContext) =>
|
|
|
35
37
|
dependencies.writeStderr(`${MCP_CONNECTION_START_FAILED}\n`);
|
|
36
38
|
return { ok: false };
|
|
37
39
|
}
|
|
38
|
-
return { ok: true, handle
|
|
40
|
+
return { ok: true, handle };
|
|
39
41
|
};
|
|
40
42
|
const runtimeAvailability = (state) => ({
|
|
41
|
-
processCaptureEnabled: state.currentConfig.processExecutionPolicy.enabled,
|
|
43
|
+
processCaptureEnabled: state.currentConfig.processExecutionPolicy.status === "enabled",
|
|
42
44
|
evidenceFileRoots: state.currentConfig.evidenceFilePolicy.roots.length,
|
|
43
45
|
investigationInputRoots: state.currentConfig.investigationInputRoots.length,
|
|
44
|
-
browserObservationEnabled: state.currentConfig.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
electronObservationEnabled: state.currentConfig.electronObservationEnabled &&
|
|
52
|
-
state.currentConfig.electronCdpEndpoints.length > 0 &&
|
|
53
|
-
state.currentConfig.electronFileRoots.length > 0,
|
|
54
|
-
v8InspectorObservationEnabled: state.currentConfig.v8InspectorObservationEnabled &&
|
|
55
|
-
state.currentConfig.v8InspectorEndpoints.length > 0 &&
|
|
56
|
-
(state.currentConfig.v8InspectorFileRoots.length > 0 ||
|
|
57
|
-
state.currentConfig.v8InspectorAllowedOrigins.length > 0),
|
|
58
|
-
javascriptReplayEnabled: state.currentConfig.javascriptReplayPolicy.enabled,
|
|
59
|
-
managedRuntimeEnabled: state.currentConfig.managedRuntimePolicy.enabled,
|
|
46
|
+
browserObservationEnabled: state.currentConfig.browserObservationPolicy.status === "enabled",
|
|
47
|
+
browserScenarioEnabled: state.currentConfig.browserScenarioPolicy.status === "enabled",
|
|
48
|
+
electronObservationEnabled: state.currentConfig.electronObservationPolicy.status === "enabled",
|
|
49
|
+
electronAutomationEnabled: state.currentConfig.electronAutomationPolicy.status === "enabled",
|
|
50
|
+
v8InspectorObservationEnabled: state.currentConfig.v8InspectorObservationPolicy.status === "enabled",
|
|
51
|
+
javascriptReplayEnabled: state.currentConfig.javascriptReplayPolicy.status === "enabled",
|
|
52
|
+
managedRuntimeEnabled: state.currentConfig.managedRuntimePolicy.status === "enabled",
|
|
60
53
|
});
|
package/dist/main.js
CHANGED
|
@@ -63,20 +63,23 @@ export const run = async (dependencies = runtimeDependencies()) => {
|
|
|
63
63
|
const transport = await startMcpTransport(dependencies, session, {
|
|
64
64
|
logger,
|
|
65
65
|
serverLogger,
|
|
66
|
-
loadOptionalProviders:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
66
|
+
loadOptionalProviders: dependencies.loadOptionalProviders ??
|
|
67
|
+
(async () => {
|
|
68
|
+
const [{ CdpBrowserProvider }, { PlaywrightBrowserScenarioProvider }, { CdpElectronProvider }, { PlaywrightElectronActiveProvider }, { V8InspectorProvider },] = await Promise.all([
|
|
69
|
+
import("./browser/CdpBrowserProvider.js"),
|
|
70
|
+
import("./browser/PlaywrightBrowserScenarioProvider.js"),
|
|
71
|
+
import("./browser/CdpElectronProvider.js"),
|
|
72
|
+
import("./browser/PlaywrightElectronActiveProvider.js"),
|
|
73
|
+
import("./browser/V8InspectorProvider.js"),
|
|
74
|
+
]);
|
|
75
|
+
return {
|
|
76
|
+
browserObservation: new CdpBrowserProvider(),
|
|
77
|
+
browserScenarioCapture: new PlaywrightBrowserScenarioProvider(),
|
|
78
|
+
electronObservation: new CdpElectronProvider(),
|
|
79
|
+
electronActiveObservation: new PlaywrightElectronActiveProvider(),
|
|
80
|
+
javascriptRuntimeObservation: new V8InspectorProvider(),
|
|
81
|
+
};
|
|
82
|
+
}),
|
|
80
83
|
permissionAuthority: permissionAuthority.value,
|
|
81
84
|
runtimeState,
|
|
82
85
|
});
|
|
@@ -86,7 +89,6 @@ export const run = async (dependencies = runtimeDependencies()) => {
|
|
|
86
89
|
dependencies,
|
|
87
90
|
permissionAuthority: permissionAuthority.value,
|
|
88
91
|
runtimeState,
|
|
89
|
-
liveServers: transport.liveServers,
|
|
90
92
|
serverLogger,
|
|
91
93
|
});
|
|
92
94
|
createShutdown({
|
package/dist/mcpDoctor.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Formatter } from "incur";
|
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import { CATALOG_IDENTITY, MCP_RESOURCE_CATALOG, MCP_RESOURCE_TEMPLATE_CATALOG, } from "./catalogIdentity.js";
|
|
6
6
|
import { PROMPT_CONTRACTS } from "./contracts/promptContracts.js";
|
|
7
|
+
import { TOOL_CONTRACTS } from "./contracts/toolContracts.js";
|
|
7
8
|
import { PRODUCT_IDENTITY } from "./identity.js";
|
|
8
9
|
import { MCP_STARTUP_POLICY } from "./mcpStartupPolicy.js";
|
|
9
10
|
const STDERR_LIMIT_BYTES = 64 * 1_024;
|
|
@@ -63,9 +64,8 @@ const inspectProductionMcpSession = async (client, deadline, signal) => {
|
|
|
63
64
|
client.readResource({ uri: "rea://server/identity" }, request),
|
|
64
65
|
client.callTool({ name: "binary_session", arguments: { detail: "full" } }, request),
|
|
65
66
|
]);
|
|
66
|
-
const expectedToolNames = availableToolNames(requestFlow.structuredContent) ?? [];
|
|
67
67
|
const inventory = {
|
|
68
|
-
tools: compareInventory(
|
|
68
|
+
tools: compareInventory(TOOL_CONTRACTS.map(({ name }) => name), tools.tools.map(({ name }) => name)),
|
|
69
69
|
prompts: compareInventory(PROMPT_CONTRACTS.map(({ name }) => name), prompts.prompts.map(({ name }) => name)),
|
|
70
70
|
resources: compareInventory(MCP_RESOURCE_CATALOG.map(({ name }) => name), resources.resources.map(({ name }) => name)),
|
|
71
71
|
resource_templates: compareInventory(MCP_RESOURCE_TEMPLATE_CATALOG.map(({ name }) => name), templates.resourceTemplates.map(({ name }) => name)),
|
|
@@ -117,28 +117,6 @@ const inspectProductionMcpSession = async (client, deadline, signal) => {
|
|
|
117
117
|
checks,
|
|
118
118
|
};
|
|
119
119
|
};
|
|
120
|
-
const availableToolNames = (structuredContent) => {
|
|
121
|
-
if (typeof structuredContent !== "object" || structuredContent === null)
|
|
122
|
-
return undefined;
|
|
123
|
-
const result = Reflect.get(structuredContent, "result");
|
|
124
|
-
if (typeof result !== "object" || result === null)
|
|
125
|
-
return undefined;
|
|
126
|
-
const availability = Reflect.get(result, "tool_availability");
|
|
127
|
-
if (!Array.isArray(availability))
|
|
128
|
-
return undefined;
|
|
129
|
-
const names = [];
|
|
130
|
-
for (const item of availability) {
|
|
131
|
-
if (typeof item !== "object" || item === null)
|
|
132
|
-
return undefined;
|
|
133
|
-
const name = Reflect.get(item, "name");
|
|
134
|
-
const available = Reflect.get(item, "available");
|
|
135
|
-
if (typeof name !== "string" || typeof available !== "boolean")
|
|
136
|
-
return undefined;
|
|
137
|
-
if (available)
|
|
138
|
-
names.push(name);
|
|
139
|
-
}
|
|
140
|
-
return names;
|
|
141
|
-
};
|
|
142
120
|
/**
|
|
143
121
|
* Dispatcher entry for `rea mcp doctor`, kept separate from Incur MCP setup.
|
|
144
122
|
* @public
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { chmod, mkdtemp, rm } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { windowsNativeCapabilities, } from "./WindowsAuthority.js";
|
|
5
|
+
/** Report whether this process can establish a private runtime root. */
|
|
6
|
+
export const privateRuntimeRootCapability = (platform = process.platform) => platform === "win32"
|
|
7
|
+
? windowsNativeCapabilities(platform).private_runtime_dacl
|
|
8
|
+
: { available: true, reason: null, proof: "posix-mode-0700" };
|
|
9
|
+
/** Expected failure when a runtime root's privacy boundary is unavailable. */
|
|
10
|
+
export class PrivateRuntimeRootUnavailableError extends Error {
|
|
11
|
+
code = "private-runtime-root-authority-unavailable";
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "PrivateRuntimeRootUnavailableError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
4
17
|
/**
|
|
5
18
|
* Owns one POSIX mode-0700 temporary runtime root and removes it idempotently.
|
|
6
19
|
*
|
|
@@ -16,6 +29,10 @@ export class PrivateRuntimeRoot {
|
|
|
16
29
|
}
|
|
17
30
|
/** Allocate a new private runtime root without performing protocol work. */
|
|
18
31
|
static async create(options = {}) {
|
|
32
|
+
const platform = options.platform ?? process.platform;
|
|
33
|
+
const capability = privateRuntimeRootCapability(platform);
|
|
34
|
+
if (!capability.available)
|
|
35
|
+
throw new PrivateRuntimeRootUnavailableError(capability.reason);
|
|
19
36
|
const path = await mkdtemp(join(options.parent ?? tmpdir(), options.prefix ?? "rea-provider-"));
|
|
20
37
|
try {
|
|
21
38
|
await chmod(path, 0o700);
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
|
-
import {
|
|
4
|
+
import { observeOwnedProcessGroupWithHost, observeOwnedProcessLineageWithHost, } from "./ProcessOwnershipObservation.js";
|
|
5
|
+
import { launcherIdentityFailure } from "./ProcessOwnershipIdentity.js";
|
|
6
|
+
import { descendantsOf, liveProcesses } from "./ProcessOwnershipProcessTree.js";
|
|
5
7
|
const execFileAsync = promisify(execFile);
|
|
6
8
|
/**
|
|
7
9
|
* Terminate one Windows process tree through the platform utility.
|
|
8
10
|
*
|
|
9
11
|
* This is a bounded P0 cleanup mechanism, not Job Object ownership proof. The
|
|
10
|
-
* caller-visible Windows
|
|
12
|
+
* caller-visible Windows capability report remains unavailable until a native
|
|
13
|
+
* authority verifies Job Object creation, membership, and cleanup semantics.
|
|
11
14
|
*/
|
|
12
15
|
export const cleanupWindowsProcessTree = async (rootPid, host = systemWindowsProcessTreeHost) => {
|
|
13
16
|
if (!Number.isSafeInteger(rootPid) || rootPid <= 0)
|
|
@@ -70,8 +73,11 @@ const systemHost = {
|
|
|
70
73
|
}
|
|
71
74
|
const { stdout } = await execFileAsync("ps", ["eww", "-p", String(pid)]);
|
|
72
75
|
const environment = {};
|
|
73
|
-
for (const match of stdout.matchAll(/(?:^|\s)([A-Za-z_][A-Za-z0-9_]*)=([^\s]*)/gu))
|
|
74
|
-
|
|
76
|
+
for (const match of stdout.matchAll(/(?:^|\s)([A-Za-z_][A-Za-z0-9_]*)=([^\s]*)/gu)) {
|
|
77
|
+
const name = match[1];
|
|
78
|
+
if (name !== undefined)
|
|
79
|
+
environment[name] = match[2] ?? "";
|
|
80
|
+
}
|
|
75
81
|
return environment;
|
|
76
82
|
},
|
|
77
83
|
signalGroup(processGroupId, signal) {
|
|
@@ -97,13 +103,9 @@ const systemWindowsProcessTreeHost = {
|
|
|
97
103
|
export const readProcessRunId = async (pid, host = systemHost) => (await host.environment(pid)).REA_PROCESS_RUN_ID;
|
|
98
104
|
/** Token-validate every rooted POSIX process group before signaling any. */
|
|
99
105
|
export const cleanupOwnedProcessGroup = async (ownership, host = systemHost) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
processes = liveProcesses(await host.listProcesses());
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
106
|
+
const processes = await readLiveProcessTable(host);
|
|
107
|
+
if (processes === undefined)
|
|
105
108
|
return { cleaned: false, reason: "process table could not be inspected" };
|
|
106
|
-
}
|
|
107
109
|
const plan = await createOwnedCleanupPlan(ownership, processes, host);
|
|
108
110
|
if ("cleaned" in plan)
|
|
109
111
|
return plan;
|
|
@@ -126,10 +128,38 @@ export const cleanupOwnedProcessGroup = async (ownership, host = systemHost) =>
|
|
|
126
128
|
}
|
|
127
129
|
return { cleaned: true, signaled };
|
|
128
130
|
};
|
|
131
|
+
/** Verify that no live process still carries an owned capture run token. */
|
|
132
|
+
export const verifyNoTokenOwnedProcesses = async (runId, host = systemHost) => {
|
|
133
|
+
const processes = await readLiveProcessTable(host);
|
|
134
|
+
if (processes === undefined)
|
|
135
|
+
return { cleaned: false, reason: "process table could not be inspected" };
|
|
136
|
+
const scan = await scanTokenOwnedProcesses(processes, runId, host);
|
|
137
|
+
if (scan.failures.length > 0)
|
|
138
|
+
return cleanupValidationFailure(scan.failures);
|
|
139
|
+
return scan.owned.length === 0
|
|
140
|
+
? { cleaned: true, signaled: false }
|
|
141
|
+
: { cleaned: false, reason: "token-owned process remained after cleanup" };
|
|
142
|
+
};
|
|
143
|
+
const readLiveProcessTable = async (host) => {
|
|
144
|
+
try {
|
|
145
|
+
return liveProcesses(await host.listProcesses());
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
129
151
|
const createOwnedCleanupPlan = async (ownership, processes, host) => {
|
|
152
|
+
const tokenOwned = ownership.sweepTokenOwnedProcesses === true
|
|
153
|
+
? await scanTokenOwnedProcesses(processes, ownership.runId, host)
|
|
154
|
+
: { owned: [], failures: [] };
|
|
155
|
+
if (tokenOwned.failures.length > 0)
|
|
156
|
+
return cleanupValidationFailure(tokenOwned.failures);
|
|
157
|
+
const tokenOwnedGroupIds = new Set(tokenOwned.owned.map(({ processGroupId }) => processGroupId));
|
|
130
158
|
const launcher = processes.find(({ pid }) => pid === ownership.leaderPid);
|
|
131
159
|
const rootMembers = processes.filter(({ processGroupId }) => processGroupId === ownership.processGroupId);
|
|
132
|
-
if (launcher === undefined &&
|
|
160
|
+
if (launcher === undefined &&
|
|
161
|
+
rootMembers.length === 0 &&
|
|
162
|
+
tokenOwned.owned.length === 0)
|
|
133
163
|
return { cleaned: true, signaled: false };
|
|
134
164
|
let descendants = [];
|
|
135
165
|
if (launcher !== undefined) {
|
|
@@ -142,11 +172,14 @@ const createOwnedCleanupPlan = async (ownership, processes, host) => {
|
|
|
142
172
|
const processGroupIds = new Set([ownership.processGroupId]);
|
|
143
173
|
for (const descendant of descendants)
|
|
144
174
|
processGroupIds.add(descendant.processGroupId);
|
|
175
|
+
for (const process of tokenOwned.owned)
|
|
176
|
+
processGroupIds.add(process.processGroupId);
|
|
145
177
|
for (const processGroupId of processGroupIds) {
|
|
146
178
|
if (processGroupId === ownership.processGroupId)
|
|
147
179
|
continue;
|
|
148
180
|
const groupLeader = processes.find(({ pid, processGroupId: observedGroupId }) => pid === processGroupId && observedGroupId === processGroupId);
|
|
149
|
-
if (groupLeader === undefined || !descendantPids.has(groupLeader.pid))
|
|
181
|
+
if ((groupLeader === undefined || !descendantPids.has(groupLeader.pid)) &&
|
|
182
|
+
!tokenOwnedGroupIds.has(processGroupId))
|
|
150
183
|
return {
|
|
151
184
|
cleaned: false,
|
|
152
185
|
reason: "descendant process-group leader identity could not be verified",
|
|
@@ -162,17 +195,6 @@ const createOwnedCleanupPlan = async (ownership, processes, host) => {
|
|
|
162
195
|
.sort((left, right) => left - right)),
|
|
163
196
|
};
|
|
164
197
|
};
|
|
165
|
-
const launcherIdentityFailure = (launcher, ownership) => {
|
|
166
|
-
if (launcher.processGroupId !== ownership.processGroupId)
|
|
167
|
-
return "owned launcher process-group identity did not match";
|
|
168
|
-
if (ownership.expectedParentPid !== undefined &&
|
|
169
|
-
launcher.parentPid !== ownership.expectedParentPid)
|
|
170
|
-
return "owned launcher parent identity did not match";
|
|
171
|
-
if (ownership.expectedCommand !== undefined &&
|
|
172
|
-
!matchesOwnedProcessCommand(launcher.command, ownership.expectedCommand))
|
|
173
|
-
return `owned launcher command identity did not match (observed=${launcher.command}; expected=${ownership.expectedCommand})`;
|
|
174
|
-
return null;
|
|
175
|
-
};
|
|
176
198
|
const revalidateOwnedProcessGroup = async (processGroupId, ownership, host) => {
|
|
177
199
|
let members;
|
|
178
200
|
try {
|
|
@@ -197,7 +219,8 @@ const revalidateOwnedProcessGroup = async (processGroupId, ownership, host) => {
|
|
|
197
219
|
}
|
|
198
220
|
if (processGroupId !== ownership.processGroupId &&
|
|
199
221
|
members.length > 0 &&
|
|
200
|
-
!members.some(({ pid }) => pid === processGroupId)
|
|
222
|
+
!members.some(({ pid }) => pid === processGroupId) &&
|
|
223
|
+
ownership.sweepTokenOwnedProcesses !== true)
|
|
201
224
|
return {
|
|
202
225
|
cleaned: false,
|
|
203
226
|
reason: "descendant process-group leader identity could not be revalidated",
|
|
@@ -230,39 +253,28 @@ const processOwnershipFailures = async (members, runId, host) => {
|
|
|
230
253
|
}
|
|
231
254
|
return failures;
|
|
232
255
|
};
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
members = (await host.listProcesses()).filter(({ processGroupId }) => processGroupId === ownership.processGroupId);
|
|
238
|
-
}
|
|
239
|
-
catch {
|
|
240
|
-
return {
|
|
241
|
-
state: "unverifiable",
|
|
242
|
-
reason: "process group could not be inspected",
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
const liveMembers = liveProcesses(members);
|
|
246
|
-
if (liveMembers.length === 0)
|
|
247
|
-
return { state: "empty" };
|
|
248
|
-
for (const member of liveMembers) {
|
|
256
|
+
const scanTokenOwnedProcesses = async (processes, runId, host) => {
|
|
257
|
+
const owned = [];
|
|
258
|
+
const failures = [];
|
|
259
|
+
for (const process of processes) {
|
|
249
260
|
try {
|
|
250
|
-
if ((await host.environment(
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
state: "unverifiable",
|
|
254
|
-
reason: "process ownership did not match",
|
|
255
|
-
};
|
|
261
|
+
if ((await host.environment(process.pid)).REA_PROCESS_RUN_ID === runId)
|
|
262
|
+
owned.push(process);
|
|
256
263
|
}
|
|
257
264
|
catch {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
265
|
+
try {
|
|
266
|
+
const live = liveProcesses(await host.listProcesses());
|
|
267
|
+
if (!live.some(({ pid }) => pid === process.pid))
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
catch { }
|
|
271
|
+
failures.push({ pid: process.pid, reason: "environment-unreadable" });
|
|
262
272
|
}
|
|
263
273
|
}
|
|
264
|
-
return {
|
|
274
|
+
return { owned, failures };
|
|
265
275
|
};
|
|
276
|
+
/** Observe one group without signaling it, failing closed on identity doubt. */
|
|
277
|
+
export const observeOwnedProcessGroup = async (ownership, host = systemHost) => observeOwnedProcessGroupWithHost(ownership, host);
|
|
266
278
|
/**
|
|
267
279
|
* Record the live launcher and descendant lineage after run-token validation.
|
|
268
280
|
*
|
|
@@ -270,80 +282,4 @@ export const observeOwnedProcessGroup = async (ownership, host = systemHost) =>
|
|
|
270
282
|
* list means no descendants were live during this observation, not that the
|
|
271
283
|
* run never created a short-lived child.
|
|
272
284
|
*/
|
|
273
|
-
export const observeOwnedProcessLineage = async (ownership, host = systemHost) =>
|
|
274
|
-
let processes;
|
|
275
|
-
try {
|
|
276
|
-
processes = liveProcesses(await host.listProcesses());
|
|
277
|
-
}
|
|
278
|
-
catch {
|
|
279
|
-
return unavailableLineage(ownership, "process table could not be inspected");
|
|
280
|
-
}
|
|
281
|
-
const launcher = processes.find(({ pid }) => pid === ownership.leaderPid);
|
|
282
|
-
if (launcher === undefined)
|
|
283
|
-
return unavailableLineage(ownership, "owned launcher is not live");
|
|
284
|
-
if (launcher.processGroupId !== ownership.processGroupId)
|
|
285
|
-
return unavailableLineage(ownership, "owned launcher process-group identity did not match");
|
|
286
|
-
if (ownership.expectedParentPid !== undefined &&
|
|
287
|
-
launcher.parentPid !== ownership.expectedParentPid)
|
|
288
|
-
return unavailableLineage(ownership, "owned launcher parent identity did not match");
|
|
289
|
-
if (ownership.expectedCommand !== undefined &&
|
|
290
|
-
!matchesOwnedProcessCommand(launcher.command, ownership.expectedCommand))
|
|
291
|
-
return unavailableLineage(ownership, `owned launcher command identity did not match (observed=${launcher.command}; expected=${ownership.expectedCommand})`);
|
|
292
|
-
const descendants = descendantsOf(launcher.pid, processes);
|
|
293
|
-
for (const member of [launcher, ...descendants]) {
|
|
294
|
-
try {
|
|
295
|
-
if ((await host.environment(member.pid)).REA_PROCESS_RUN_ID !==
|
|
296
|
-
ownership.runId)
|
|
297
|
-
return unavailableLineage(ownership, "process lineage contains an unowned or PID-reused process");
|
|
298
|
-
}
|
|
299
|
-
catch {
|
|
300
|
-
return unavailableLineage(ownership, "process ownership could not be revalidated");
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return {
|
|
304
|
-
status: "verified",
|
|
305
|
-
observedAt: new Date().toISOString(),
|
|
306
|
-
lineage: {
|
|
307
|
-
schemaVersion: 1,
|
|
308
|
-
runId: ownership.runId,
|
|
309
|
-
launcherPid: launcher.pid,
|
|
310
|
-
launcherParentPid: launcher.parentPid,
|
|
311
|
-
processGroupId: launcher.processGroupId,
|
|
312
|
-
descendants: descendants
|
|
313
|
-
.sort((left, right) => left.pid - right.pid)
|
|
314
|
-
.map(({ pid, parentPid, processGroupId }) => ({
|
|
315
|
-
pid,
|
|
316
|
-
parentPid,
|
|
317
|
-
processGroupId,
|
|
318
|
-
})),
|
|
319
|
-
},
|
|
320
|
-
};
|
|
321
|
-
};
|
|
322
|
-
const descendantsOf = (launcherPid, processes) => {
|
|
323
|
-
const childrenByParent = new Map();
|
|
324
|
-
for (const process of processes) {
|
|
325
|
-
const siblings = childrenByParent.get(process.parentPid) ?? [];
|
|
326
|
-
siblings.push(process);
|
|
327
|
-
childrenByParent.set(process.parentPid, siblings);
|
|
328
|
-
}
|
|
329
|
-
const descendants = [];
|
|
330
|
-
const pending = [...(childrenByParent.get(launcherPid) ?? [])];
|
|
331
|
-
const visited = new Set([launcherPid]);
|
|
332
|
-
for (const process of pending) {
|
|
333
|
-
if (visited.has(process.pid))
|
|
334
|
-
continue;
|
|
335
|
-
visited.add(process.pid);
|
|
336
|
-
descendants.push(process);
|
|
337
|
-
pending.push(...(childrenByParent.get(process.pid) ?? []));
|
|
338
|
-
}
|
|
339
|
-
return descendants;
|
|
340
|
-
};
|
|
341
|
-
const unavailableLineage = (ownership, reason) => ({
|
|
342
|
-
status: "unavailable",
|
|
343
|
-
observedAt: new Date().toISOString(),
|
|
344
|
-
runId: ownership.runId,
|
|
345
|
-
launcherPid: ownership.leaderPid,
|
|
346
|
-
processGroupId: ownership.processGroupId,
|
|
347
|
-
reason,
|
|
348
|
-
});
|
|
349
|
-
const liveProcesses = (members) => members.filter(({ state }) => !state.startsWith("Z"));
|
|
285
|
+
export const observeOwnedProcessLineage = async (ownership, host = systemHost) => observeOwnedProcessLineageWithHost(ownership, host);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { matchesOwnedProcessCommand } from "./ProcessCommandIdentity.js";
|
|
2
|
+
/** Compare a live launcher against the identity committed at process launch. */
|
|
3
|
+
export const launcherIdentityFailure = (launcher, ownership) => {
|
|
4
|
+
if (launcher.processGroupId !== ownership.processGroupId)
|
|
5
|
+
return "owned launcher process-group identity did not match";
|
|
6
|
+
if (ownership.expectedParentPid !== undefined &&
|
|
7
|
+
launcher.parentPid !== ownership.expectedParentPid)
|
|
8
|
+
return "owned launcher parent identity did not match";
|
|
9
|
+
if (ownership.expectedCommand !== undefined &&
|
|
10
|
+
!matchesOwnedProcessCommand(launcher.command, ownership.expectedCommand))
|
|
11
|
+
return `owned launcher command identity did not match (observed=${launcher.command}; expected=${ownership.expectedCommand})`;
|
|
12
|
+
return null;
|
|
13
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { launcherIdentityFailure } from "./ProcessOwnershipIdentity.js";
|
|
2
|
+
import { descendantsOf, liveProcesses } from "./ProcessOwnershipProcessTree.js";
|
|
3
|
+
/** Observe one group without signaling it, failing closed on identity doubt. */
|
|
4
|
+
export const observeOwnedProcessGroupWithHost = async (ownership, host) => {
|
|
5
|
+
let members;
|
|
6
|
+
try {
|
|
7
|
+
members = (await host.listProcesses()).filter(({ processGroupId }) => processGroupId === ownership.processGroupId);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return {
|
|
11
|
+
state: "unverifiable",
|
|
12
|
+
reason: "process group could not be inspected",
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const liveMembers = liveProcesses(members);
|
|
16
|
+
if (liveMembers.length === 0)
|
|
17
|
+
return { state: "empty" };
|
|
18
|
+
for (const member of liveMembers) {
|
|
19
|
+
try {
|
|
20
|
+
if ((await host.environment(member.pid)).REA_PROCESS_RUN_ID !==
|
|
21
|
+
ownership.runId)
|
|
22
|
+
return {
|
|
23
|
+
state: "unverifiable",
|
|
24
|
+
reason: "process ownership did not match",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return {
|
|
29
|
+
state: "unverifiable",
|
|
30
|
+
reason: "process ownership could not be revalidated",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { state: "alive" };
|
|
35
|
+
};
|
|
36
|
+
/** Record the live launcher and descendant lineage after run-token validation. */
|
|
37
|
+
export const observeOwnedProcessLineageWithHost = async (ownership, host) => {
|
|
38
|
+
let processes;
|
|
39
|
+
try {
|
|
40
|
+
processes = liveProcesses(await host.listProcesses());
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return unavailableLineage(ownership, "process table could not be inspected");
|
|
44
|
+
}
|
|
45
|
+
const launcher = processes.find(({ pid }) => pid === ownership.leaderPid);
|
|
46
|
+
if (launcher === undefined)
|
|
47
|
+
return unavailableLineage(ownership, "owned launcher is not live");
|
|
48
|
+
const identityFailure = launcherIdentityFailure(launcher, ownership);
|
|
49
|
+
if (identityFailure !== null)
|
|
50
|
+
return unavailableLineage(ownership, identityFailure);
|
|
51
|
+
const descendants = descendantsOf(launcher.pid, processes);
|
|
52
|
+
for (const member of [launcher, ...descendants]) {
|
|
53
|
+
try {
|
|
54
|
+
if ((await host.environment(member.pid)).REA_PROCESS_RUN_ID !==
|
|
55
|
+
ownership.runId)
|
|
56
|
+
return unavailableLineage(ownership, "process lineage contains an unowned or PID-reused process");
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return unavailableLineage(ownership, "process ownership could not be revalidated");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
status: "verified",
|
|
64
|
+
observedAt: new Date().toISOString(),
|
|
65
|
+
lineage: {
|
|
66
|
+
schemaVersion: 1,
|
|
67
|
+
runId: ownership.runId,
|
|
68
|
+
launcherPid: launcher.pid,
|
|
69
|
+
launcherParentPid: launcher.parentPid,
|
|
70
|
+
processGroupId: launcher.processGroupId,
|
|
71
|
+
descendants: descendants
|
|
72
|
+
.sort((left, right) => left.pid - right.pid)
|
|
73
|
+
.map(({ pid, parentPid, processGroupId }) => ({
|
|
74
|
+
pid,
|
|
75
|
+
parentPid,
|
|
76
|
+
processGroupId,
|
|
77
|
+
})),
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
const unavailableLineage = (ownership, reason) => ({
|
|
82
|
+
status: "unavailable",
|
|
83
|
+
observedAt: new Date().toISOString(),
|
|
84
|
+
runId: ownership.runId,
|
|
85
|
+
launcherPid: ownership.leaderPid,
|
|
86
|
+
processGroupId: ownership.processGroupId,
|
|
87
|
+
reason,
|
|
88
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Exclude process-table entries that the operating system has already reaped. */
|
|
2
|
+
export const liveProcesses = (members) => members.filter(({ state }) => !state.startsWith("Z"));
|
|
3
|
+
/** Return every live-table descendant of a launcher in breadth-first order. */
|
|
4
|
+
export const descendantsOf = (launcherPid, processes) => {
|
|
5
|
+
const childrenByParent = new Map();
|
|
6
|
+
for (const process of processes) {
|
|
7
|
+
const siblings = childrenByParent.get(process.parentPid) ?? [];
|
|
8
|
+
siblings.push(process);
|
|
9
|
+
childrenByParent.set(process.parentPid, siblings);
|
|
10
|
+
}
|
|
11
|
+
const descendants = [];
|
|
12
|
+
const pending = [...(childrenByParent.get(launcherPid) ?? [])];
|
|
13
|
+
const visited = new Set([launcherPid]);
|
|
14
|
+
for (const process of pending) {
|
|
15
|
+
if (visited.has(process.pid))
|
|
16
|
+
continue;
|
|
17
|
+
visited.add(process.pid);
|
|
18
|
+
descendants.push(process);
|
|
19
|
+
pending.push(...(childrenByParent.get(process.pid) ?? []));
|
|
20
|
+
}
|
|
21
|
+
return descendants;
|
|
22
|
+
};
|
|
@@ -26,8 +26,7 @@ export class ProviderStartupDeadline {
|
|
|
26
26
|
#externalSignal;
|
|
27
27
|
#onExternalAbort;
|
|
28
28
|
#timer;
|
|
29
|
-
#
|
|
30
|
-
#cancelled = false;
|
|
29
|
+
#interruption;
|
|
31
30
|
constructor(timeoutMs, signal) {
|
|
32
31
|
this.timeoutMs = timeoutMs;
|
|
33
32
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
@@ -38,11 +37,11 @@ export class ProviderStartupDeadline {
|
|
|
38
37
|
if (this.#controller.signal.aborted)
|
|
39
38
|
return;
|
|
40
39
|
if (Date.now() >= this.#deadlineAt) {
|
|
41
|
-
this.#
|
|
40
|
+
this.#interruption = "timeout";
|
|
42
41
|
this.#abort(new DOMException("Provider startup deadline elapsed", "TimeoutError"));
|
|
43
42
|
return;
|
|
44
43
|
}
|
|
45
|
-
this.#
|
|
44
|
+
this.#interruption = "cancelled";
|
|
46
45
|
this.#abort(signal?.reason);
|
|
47
46
|
};
|
|
48
47
|
if (signal?.aborted === true) {
|
|
@@ -53,7 +52,7 @@ export class ProviderStartupDeadline {
|
|
|
53
52
|
this.#timer = setTimeout(() => {
|
|
54
53
|
if (this.#controller.signal.aborted)
|
|
55
54
|
return;
|
|
56
|
-
this.#
|
|
55
|
+
this.#interruption = "timeout";
|
|
57
56
|
this.#abort(new DOMException("Provider startup deadline elapsed", "TimeoutError"));
|
|
58
57
|
}, timeoutMs);
|
|
59
58
|
}
|
|
@@ -61,13 +60,10 @@ export class ProviderStartupDeadline {
|
|
|
61
60
|
get signal() {
|
|
62
61
|
return this.#controller.signal;
|
|
63
62
|
}
|
|
64
|
-
/**
|
|
65
|
-
get
|
|
66
|
-
return this.#
|
|
67
|
-
|
|
68
|
-
/** True when the absolute startup deadline has elapsed. */
|
|
69
|
-
get timedOut() {
|
|
70
|
-
return (this.#timedOut || (!this.#cancelled && Date.now() >= this.#deadlineAt));
|
|
63
|
+
/** First control path that interrupted startup, if any. */
|
|
64
|
+
get interruption() {
|
|
65
|
+
return (this.#interruption ??
|
|
66
|
+
(Date.now() >= this.#deadlineAt ? "timeout" : undefined));
|
|
71
67
|
}
|
|
72
68
|
/** Milliseconds still available to the next startup phase. */
|
|
73
69
|
remainingMs() {
|