rea-agents 2.0.0 → 2.2.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 +103 -38
- package/bridge/ghidra/ReaGhidraBridge.java +96 -11
- package/dist/application/AndroidApplicationService.js +17 -0
- package/dist/application/AppleApplicationService.js +17 -0
- package/dist/application/ArtifactExtraction.js +1 -1
- package/dist/application/ArtifactInventory/classify.js +42 -0
- package/dist/application/ArtifactInventory/hash.js +43 -0
- package/dist/application/ArtifactInventory/reader.js +41 -0
- package/dist/application/ArtifactInventory/scanCanonical.js +155 -0
- package/dist/application/ArtifactInventory/scanReader.js +124 -0
- package/dist/application/ArtifactInventory/types.js +10 -0
- package/dist/application/ArtifactInventory.js +9 -351
- package/dist/application/AuthorizedArtifactInventory.js +2 -2
- package/dist/application/BinarySession.js +47 -270
- package/dist/application/BinarySessionExecution.js +28 -0
- package/dist/application/BinarySessionOpen.js +39 -0
- package/dist/application/BinarySessionRecords.js +149 -0
- package/dist/application/BinarySessionStatus.js +75 -0
- package/dist/application/CapabilityInventory.js +24 -2
- package/dist/application/ClientRegistrationStatus.js +11 -4
- package/dist/application/CrossVersionInventory.js +8 -2
- package/dist/application/CrossVersionInvestigation.js +1 -23
- package/dist/application/CrossVersionInvestigationCollections.js +23 -0
- package/dist/application/DirectAnalysis.js +47 -44
- package/dist/application/Doctor.js +156 -238
- package/dist/application/DoctorDiagnostics.js +180 -0
- package/dist/application/ElectronBoundaryAnalysis.js +23 -7
- package/dist/application/ElectronBoundaryGraphWindows.js +32 -8
- package/dist/application/EnhancedToolPagination.js +22 -0
- package/dist/application/EnhancedToolTypes.js +4 -0
- package/dist/application/EnhancedTools.js +20 -37
- package/dist/application/InstrumentedJavaScriptReplayHost.js +36 -0
- package/dist/application/InventoryProjectionEvidence.js +41 -0
- package/dist/application/InvestigationProviders.js +12 -0
- package/dist/application/InvestigationWorkspaceStore.js +24 -10
- package/dist/application/JavaScriptArtifactAnalysis.js +45 -6
- package/dist/application/JavaScriptArtifactFiles.js +6 -2
- package/dist/application/JavaScriptArtifactGraphBuilder.js +23 -1
- package/dist/application/JavaScriptArtifactGraphBundlers.js +225 -0
- package/dist/application/JavaScriptArtifactGraphContext.js +37 -36
- package/dist/application/JavaScriptArtifactGraphDocuments.js +27 -4
- package/dist/application/JavaScriptArtifactGraphFindings.js +54 -8
- package/dist/application/JavaScriptArtifactGraphStructure.js +29 -101
- package/dist/application/JavaScriptArtifactPathResolution.js +269 -0
- package/dist/application/JavaScriptArtifactReconstruction.js +1 -1
- package/dist/application/JavaScriptJsonModules.js +39 -0
- package/dist/application/JavaScriptModuleRelationships.js +262 -0
- package/dist/application/JavaScriptReplayPermission.js +16 -0
- package/dist/application/JavaScriptReplayService.js +182 -156
- package/dist/application/LinuxHopper.js +10 -0
- package/dist/application/MacHopper.js +22 -11
- package/dist/application/NodeRuntimeCharacterizationService.js +196 -0
- package/dist/application/PermissionAuthority.js +106 -63
- package/dist/application/ProcessCaptureCapability.js +1 -1
- package/dist/application/ProcessCapturePermission.js +13 -0
- package/dist/application/ProcessCli.js +2 -12
- package/dist/application/ProcessHarness.js +1 -1
- package/dist/application/ProcessPairedExperiment.js +72 -0
- package/dist/application/ProjectPermissionStore.js +48 -9
- package/dist/application/RealHopperAssertions.js +76 -0
- package/dist/application/ReconstructionCoverageService.js +69 -0
- package/dist/application/ReconstructionCoverageWorkspaceStore.js +31 -0
- package/dist/application/RuntimeExecutableDiagnostics.js +172 -0
- package/dist/application/Setup.js +148 -260
- package/dist/application/SetupClientConfiguration.js +224 -0
- package/dist/application/SetupClients.js +24 -2
- package/dist/application/SetupPlan.js +124 -12
- package/dist/application/SetupSkill.js +54 -36
- package/dist/application/SupportedClients.js +1 -0
- package/dist/application/Uninstall.js +12 -3
- package/dist/application/Upgrade.js +16 -1
- package/dist/application/WindowsCapabilities.js +86 -0
- package/dist/artifacts/AsarArtifactReader.js +11 -0
- package/dist/browser/BrowserProcessStartup.js +88 -0
- package/dist/browser/CdpCaptureDocuments.js +12 -8
- package/dist/browser/CdpCaptureEventBodyShapes.js +75 -0
- package/dist/browser/CdpCaptureEventHandlers.js +336 -0
- package/dist/browser/CdpCaptureEventHelpers.js +71 -0
- package/dist/browser/CdpCaptureEventState.js +1 -0
- package/dist/browser/CdpCaptureEventTypes.js +1 -0
- package/dist/browser/CdpCaptureEvents.js +29 -496
- package/dist/browser/CdpObservationSession.js +51 -8
- package/dist/browser/CdpPageCapture.js +12 -133
- package/dist/browser/CdpPageCaptureScripts.js +97 -0
- package/dist/browser/CdpPageCaptureWorkers.js +46 -0
- package/dist/browser/CdpWebMcpDiscovery.js +38 -14
- package/dist/browser/PngVisualDiff.js +9 -2
- package/dist/catalogIdentity.js +4 -0
- package/dist/cli/artifactCommands.js +82 -0
- package/dist/cli/coreAnalysisCommands.js +232 -0
- package/dist/cli/managedCommands.js +397 -0
- package/dist/cli/managedProjectGraphCommand.js +24 -0
- package/dist/cli/options.js +9 -0
- package/dist/cli/setupCommands.js +113 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utilityCommands.js +122 -0
- package/dist/cli.js +10 -768
- package/dist/cliApplicationCommands.js +118 -4
- package/dist/cliBrowserAdvancedCommands.js +73 -16
- package/dist/cliBrowserCommands.js +39 -115
- package/dist/cliCommandNames.js +4 -0
- package/dist/cliElectronCommands.js +33 -62
- package/dist/cliEvidenceCommands.js +13 -2
- package/dist/cliInvestigationCommands.js +53 -10
- package/dist/cliObservationOptions.js +137 -0
- package/dist/cliPolicyCommands.js +80 -46
- package/dist/cliSetup.js +306 -0
- package/dist/config/environment.js +107 -0
- package/dist/config/parseConfig.js +228 -0
- package/dist/config/parsers.js +68 -0
- package/dist/config/permissions.js +30 -0
- package/dist/config/types.js +1 -0
- package/dist/config.js +1 -375
- package/dist/contracts/applicationToolContracts.js +134 -0
- package/dist/contracts/electronToolContracts.js +51 -3
- package/dist/contracts/managedToolContracts.js +8 -3
- package/dist/contracts/managedWorkflowToolContracts.js +3 -3
- package/dist/contracts/providerSelection.js +2 -0
- package/dist/contracts/sessionStatusContract.js +26 -0
- package/dist/contracts/toolContractTypes.js +12 -1
- package/dist/contracts/toolContracts.js +5 -12
- package/dist/contracts/toolEffects.js +14 -0
- package/dist/contracts/toolOutputSchemaGroups.js +274 -0
- package/dist/contracts/toolOutputSchemaPrimitives.js +328 -0
- package/dist/contracts/toolOutputSchemas.js +2 -550
- package/dist/domain/analysisErrorPresentation.js +234 -0
- package/dist/domain/analysisErrorProjection.js +284 -0
- package/dist/domain/androidApplication.js +206 -0
- package/dist/domain/appleApplication.js +221 -0
- package/dist/domain/artifactComparison.js +34 -15
- package/dist/domain/binaryTarget.js +87 -37
- package/dist/domain/boundedCartesianProjection.js +15 -0
- package/dist/domain/browserObservation.js +1 -285
- package/dist/domain/browserObservationSchemas.js +286 -0
- package/dist/domain/electronStaticAnalysisBrowser.js +14 -2
- package/dist/domain/errors.js +4 -486
- package/dist/domain/functionComparison.js +4 -165
- package/dist/domain/functionComparisonDimensions.js +164 -0
- package/dist/domain/javascriptApplicationVersionItems.js +18 -6
- package/dist/domain/javascriptApplicationVersionKeys.js +8 -8
- package/dist/domain/javascriptExportInstrumentation.js +97 -0
- package/dist/domain/javascriptFeatureTraversal.js +19 -7
- package/dist/domain/javascriptRuntimeStaticCandidates.js +3 -1
- package/dist/domain/javascriptSemanticAnalysis.js +386 -0
- package/dist/domain/javascriptSemanticIr.js +30 -0
- package/dist/domain/javascriptSemanticProjection.js +366 -0
- package/dist/domain/javascriptSemanticState.js +40 -0
- package/dist/domain/javascriptSemanticTraversal.js +22 -0
- package/dist/domain/javascriptSemanticValues.js +352 -0
- package/dist/domain/javascriptStaticAnalysis.js +5 -221
- package/dist/domain/javascriptStaticAnalysisBundler.js +209 -0
- package/dist/domain/javascriptStaticAnalysisCalls.js +180 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +71 -2
- package/dist/domain/knownPageCoverage.js +21 -0
- package/dist/domain/managedApplicationGraph.js +14 -364
- package/dist/domain/managedApplicationGraphCoverage.js +170 -0
- package/dist/domain/managedApplicationGraphNodeCoverage.js +6 -0
- package/dist/domain/managedApplicationGraphNodes.js +336 -0
- package/dist/domain/managedArtifact.js +16 -8
- package/dist/domain/managedMemberComparison.js +37 -391
- package/dist/domain/managedMemberComparisonCoverage.js +72 -0
- package/dist/domain/managedMemberComparisonMatch.js +356 -0
- package/dist/domain/managedNativeVerification.js +27 -389
- package/dist/domain/managedNativeVerificationMatch.js +272 -0
- package/dist/domain/managedNativeVerificationSchemas.js +141 -0
- package/dist/domain/managedReconstruction.js +82 -69
- package/dist/domain/managedRuntimeCorrelation.js +12 -3
- package/dist/domain/nodeRuntimeCharacterization.js +66 -0
- package/dist/domain/permissionPolicy.js +2 -0
- package/dist/domain/processComparison.js +19 -0
- package/dist/domain/processPairedExperiment.js +128 -0
- package/dist/domain/reconstructionCoverage.js +387 -0
- package/dist/domain/reconstructionCoverageEvaluation.js +212 -0
- package/dist/domain/reconstructionVerificationSchemas.js +8 -0
- package/dist/domain/replayMachine.js +316 -0
- package/dist/domain/replayMachineRuntime.js +215 -0
- package/dist/domain/runtimeCharacterization.js +85 -0
- package/dist/domain/runtimeIdentification.js +210 -0
- package/dist/domain/webBundleAnalyzer.js +3 -409
- package/dist/domain/webBundleAnalyzerAst.js +77 -0
- package/dist/domain/webBundleAnalyzerInspection.js +307 -0
- package/dist/domain/webBundleAnalyzerResult.js +120 -0
- package/dist/dotnet/ManagedArtifactInspector.js +50 -29
- package/dist/dotnet/ManagedMemberInspector.js +51 -785
- package/dist/dotnet/ManagedMemberInspectorCore.js +297 -0
- package/dist/dotnet/ManagedMemberInstructionDecoder.js +427 -0
- package/dist/dotnet/ManagedMemberRows.js +120 -0
- package/dist/dotnet/ManagedMetadataInventory.js +42 -306
- package/dist/dotnet/ManagedMetadataInventoryRows.js +292 -0
- package/dist/dotnet/ManagedMetadataLayout.js +48 -111
- package/dist/dotnet/ManagedMetadataRowSizes.js +54 -0
- package/dist/dotnet/ManagedMethodBodyReader.js +143 -0
- package/dist/dotnet/ManagedNativeBoundaryHelpers.js +311 -0
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +102 -350
- package/dist/dotnet/ManagedPeReader.js +59 -28
- package/dist/evaluation/CodexAgentEval.js +132 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraClient.js +76 -91
- package/dist/ghidra/GhidraClientStartup.js +29 -0
- package/dist/ghidra/GhidraClientWire.js +34 -0
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraDiagnostics.js +6 -1
- package/dist/ghidra/GhidraInstallation.js +20 -15
- package/dist/ghidra/GhidraLauncher.js +93 -29
- package/dist/ghidra/GhidraProvider.js +61 -161
- package/dist/ghidra/GhidraProviderCapabilities.js +173 -0
- package/dist/ghidra/GhidraSessionValues.js +2 -0
- package/dist/ghidra/GhidraSocketConnection.js +44 -7
- package/dist/ghidra/GhidraTargetSnapshot.js +30 -0
- package/dist/ghidra/GhidraTransport.js +71 -0
- package/dist/identity.js +4 -2
- package/dist/main/messages.js +5 -0
- package/dist/main/reload.js +49 -0
- package/dist/main/shutdown.js +25 -0
- package/dist/main/startup.js +14 -0
- package/dist/main/state.js +26 -0
- package/dist/main/transport.js +61 -0
- package/dist/main/types.js +1 -0
- package/dist/main.js +35 -146
- package/dist/process/PrivateRuntimeRoot.js +3 -2
- package/dist/process/ProcessOwnership.js +35 -0
- package/dist/process/ProviderProcess.js +2 -0
- package/dist/replay/JavaScriptReplayWorker.js +64 -50
- package/dist/replay/LinuxJavaScriptReplayRunner.js +173 -390
- package/dist/replay/LinuxJavaScriptReplaySandbox.js +140 -0
- package/dist/replay/ReplayOutcome.js +95 -0
- package/dist/replay/ReplayProcessLifecycle.js +71 -0
- package/dist/server/ProcessCaptureElicitation.js +157 -0
- package/dist/server/createServer.js +124 -56
- package/dist/server/javascriptApplicationResult.js +85 -0
- package/dist/server/registerApplicationTools/characterization.js +57 -0
- package/dist/server/registerApplicationTools/compareVersions.js +34 -0
- package/dist/server/registerApplicationTools/controlledReplay.js +38 -0
- package/dist/server/registerApplicationTools/coverage.js +84 -0
- package/dist/server/registerApplicationTools/helpers.js +42 -0
- package/dist/server/registerApplicationTools/traceFeature.js +26 -0
- package/dist/server/registerApplicationTools/types.js +1 -0
- package/dist/server/registerApplicationTools.js +10 -112
- package/dist/server/registerBrowserTools.js +59 -83
- package/dist/server/registerElectronTools.js +48 -49
- package/dist/server/registerEnhancedTools.js +34 -42
- package/dist/server/registerEvidenceResources.js +56 -0
- package/dist/server/registerInvestigationTools/helpers.js +74 -0
- package/dist/server/registerInvestigationTools/types.js +1 -0
- package/dist/server/registerInvestigationTools.js +83 -116
- package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
- package/dist/server/registerManagedTools.js +18 -1
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +65 -0
- package/dist/server/registerManagedWorkflowTools/contract.js +8 -0
- package/dist/server/registerManagedWorkflowTools/evidence.js +43 -0
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +39 -0
- package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +39 -0
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +52 -0
- package/dist/server/registerManagedWorkflowTools/types.js +1 -0
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +75 -0
- package/dist/server/registerManagedWorkflowTools.js +10 -274
- package/dist/server/registerOfficialTools.js +73 -59
- package/dist/server/registerSessionRecordTools.js +137 -0
- package/dist/server/registerSessionStatusTool.js +97 -26
- package/dist/server/registerSessionTools.js +60 -164
- package/dist/server/sessionAvailabilityPolicy.js +10 -0
- package/dist/server/toolRegistrationOptions.js +101 -3
- package/dist/server/toolResult.js +15 -4
- package/package.json +11 -11
- package/skills/reverse-engineer-anything/SKILL.md +76 -0
- package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +25 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
- package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
- package/scripts/prepare-node-pty.mjs +0 -35
- package/scripts/rebuild-native.mjs +0 -22
- package/skills/rea-analysis/SKILL.md +0 -222
|
@@ -10,6 +10,16 @@ const RELEASES_URL = "https://www.hopperapp.com/include/files-api.php?request=re
|
|
|
10
10
|
const DOWNLOAD_PREFIX = "https://www.hopperapp.com:443/downloader/public/";
|
|
11
11
|
const MAX_PACKAGE_BYTES = 100_000_000;
|
|
12
12
|
const BUNDLE_IDENTIFIER = "com.cryptic-apps.hopper-web-4";
|
|
13
|
+
/** Stable network and command effects disclosed before macOS Hopper setup. */
|
|
14
|
+
export const macHopperInstallDisclosure = {
|
|
15
|
+
networkOrigins: [RELEASES_URL, DOWNLOAD_PREFIX],
|
|
16
|
+
commands: [
|
|
17
|
+
"hdiutil attach -readonly -nobrowse -mountpoint <private-mount> <verified-hopper.dmg>",
|
|
18
|
+
"codesign --verify --deep --strict <mounted-hopper.app>",
|
|
19
|
+
"ditto <mounted-hopper.app> <user-Applications-stage>",
|
|
20
|
+
"open <installed-hopper.app>",
|
|
21
|
+
],
|
|
22
|
+
};
|
|
13
23
|
/** Download, verify, and install Hopper into the current user's Applications directory. */
|
|
14
24
|
export const installMacHopper = async (options = {}, host = systemMacHopperInstallHost()) => {
|
|
15
25
|
let temporary;
|
|
@@ -66,17 +76,7 @@ export const installMacHopper = async (options = {}, host = systemMacHopperInsta
|
|
|
66
76
|
}
|
|
67
77
|
};
|
|
68
78
|
const systemMacHopperInstallHost = () => ({
|
|
69
|
-
|
|
70
|
-
const response = await fetch(url, {
|
|
71
|
-
headers: { accept: "application/json", "user-agent": "rea-installer" },
|
|
72
|
-
signal: AbortSignal.timeout(120_000),
|
|
73
|
-
});
|
|
74
|
-
const length = Number(response.headers.get("content-length") ?? "0");
|
|
75
|
-
if (length > MAX_PACKAGE_BYTES)
|
|
76
|
-
return { ok: false, bytes: new Uint8Array() };
|
|
77
|
-
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
78
|
-
return { ok: response.ok && bytes.byteLength <= MAX_PACKAGE_BYTES, bytes };
|
|
79
|
-
},
|
|
79
|
+
download: downloadPackage,
|
|
80
80
|
createTemporaryDirectory: () => mkdtemp(join(tmpdir(), "rea-hopper-mac-")),
|
|
81
81
|
createMountDirectory: (path) => mkdir(path),
|
|
82
82
|
writePackage: (path, bytes) => writeFile(path, bytes, { mode: 0o600 }),
|
|
@@ -170,6 +170,17 @@ const systemMacHopperInstallHost = () => ({
|
|
|
170
170
|
cleanup: (path) => rm(path, { recursive: true, force: true }),
|
|
171
171
|
destination: () => join(homedir(), "Applications/Hopper Disassembler.app"),
|
|
172
172
|
});
|
|
173
|
+
const downloadPackage = async (url) => {
|
|
174
|
+
const response = await fetch(url, {
|
|
175
|
+
headers: { accept: "application/json", "user-agent": "rea-installer" },
|
|
176
|
+
signal: AbortSignal.timeout(120_000),
|
|
177
|
+
});
|
|
178
|
+
const length = Number(response.headers.get("content-length") ?? "0");
|
|
179
|
+
if (length > MAX_PACKAGE_BYTES)
|
|
180
|
+
return { ok: false, bytes: new Uint8Array() };
|
|
181
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
182
|
+
return { ok: response.ok && bytes.byteLength <= MAX_PACKAGE_BYTES, bytes };
|
|
183
|
+
};
|
|
173
184
|
const parseJson = (bytes) => JSON.parse(new TextDecoder().decode(bytes));
|
|
174
185
|
const integrityMatches = (bytes, release) => bytes.byteLength === Number(release.file_length) &&
|
|
175
186
|
bytes.byteLength <= MAX_PACKAGE_BYTES &&
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { nodeCharacterizationExecutionInputSchema, nodeCharacterizationExecutionOutputSchema, nodeCharacterizationPreparationInputSchema, nodeCharacterizationPreparationOutputSchema, } from "../domain/nodeRuntimeCharacterization.js";
|
|
4
|
+
import { AnalysisInputError, AnalysisProtocolError, ReplayPlanStaleError, } from "../domain/errors.js";
|
|
5
|
+
import { createEvidence } from "../domain/evidence.js";
|
|
6
|
+
import { controlledReplayOutputSchema } from "../domain/javascriptReplay.js";
|
|
7
|
+
import { createRuntimeCharacterizationPlan, } from "../domain/runtimeCharacterization.js";
|
|
8
|
+
import { err, ok } from "../domain/result.js";
|
|
9
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
10
|
+
import { InstrumentedJavaScriptReplayHost } from "./InstrumentedJavaScriptReplayHost.js";
|
|
11
|
+
import { runControlledReplayValidated, } from "./JavaScriptReplayService.js";
|
|
12
|
+
const PREPARE_OPERATION = "prepare_node_characterization";
|
|
13
|
+
const EXECUTE_OPERATION = "execute_node_characterization";
|
|
14
|
+
const PROVIDER = {
|
|
15
|
+
id: "rea-node-characterization",
|
|
16
|
+
name: "REA Node runtime characterization",
|
|
17
|
+
version: "1",
|
|
18
|
+
};
|
|
19
|
+
/** Prepare exact Node/JavaScript characterization without executing target code. */
|
|
20
|
+
export const prepareNodeCharacterization = async (dependencies, rawInput, options = {}) => {
|
|
21
|
+
const parsed = nodeCharacterizationPreparationInputSchema.safeParse(rawInput);
|
|
22
|
+
if (!parsed.success)
|
|
23
|
+
return err(new AnalysisInputError(PREPARE_OPERATION, { cause: parsed.error }));
|
|
24
|
+
const prepared = await prepareValidated(dependencies, parsed.data, options);
|
|
25
|
+
return prepared.ok
|
|
26
|
+
? ok(jsonValueSchema.parse(nodeCharacterizationPreparationOutputSchema.parse(prepared.value)))
|
|
27
|
+
: prepared;
|
|
28
|
+
};
|
|
29
|
+
/** Execute only a freshly recomputed and separately approved characterization plan. */
|
|
30
|
+
export const executeNodeCharacterization = async (dependencies, rawInput, options = {}) => {
|
|
31
|
+
const parsed = nodeCharacterizationExecutionInputSchema.safeParse(rawInput);
|
|
32
|
+
if (!parsed.success)
|
|
33
|
+
return err(new AnalysisInputError(EXECUTE_OPERATION, { cause: parsed.error }));
|
|
34
|
+
const prepared = await prepareValidated(dependencies, parsed.data.preparation, options);
|
|
35
|
+
if (!prepared.ok)
|
|
36
|
+
return prepared;
|
|
37
|
+
if (prepared.value.plan.plan_sha256 !== parsed.data.approved_plan_sha256)
|
|
38
|
+
return err(new ReplayPlanStaleError(parsed.data.approved_plan_sha256, prepared.value.plan.plan_sha256));
|
|
39
|
+
const plannedReplay = controlledReplayOutputSchema.parse(prepared.value.replay);
|
|
40
|
+
if (plannedReplay.phase !== "plan" || plannedReplay.plan === null)
|
|
41
|
+
return err(new AnalysisProtocolError("Node characterization preparation did not produce a replay plan"));
|
|
42
|
+
const host = new InstrumentedJavaScriptReplayHost(dependencies.host, parsed.data.preparation.instrumentation);
|
|
43
|
+
const executed = await runControlledReplayValidated({ ...dependencies, host }, {
|
|
44
|
+
...parsed.data.preparation.replay,
|
|
45
|
+
mode: "execute",
|
|
46
|
+
approved: true,
|
|
47
|
+
plan_digest: plannedReplay.plan.plan_digest,
|
|
48
|
+
}, options);
|
|
49
|
+
if (!executed.ok)
|
|
50
|
+
return executed;
|
|
51
|
+
if (host.instrumentation === null)
|
|
52
|
+
return err(new AnalysisProtocolError("Node characterization execution omitted instrumentation evidence"));
|
|
53
|
+
return ok(jsonValueSchema.parse(nodeCharacterizationExecutionOutputSchema.parse({
|
|
54
|
+
schema_version: 1,
|
|
55
|
+
phase: "execution",
|
|
56
|
+
plan: prepared.value.plan,
|
|
57
|
+
transformation: host.instrumentation.manifest,
|
|
58
|
+
transformation_evidence: prepared.value.transformation_evidence,
|
|
59
|
+
evidence: createCharacterizationEvidence(prepared.value, controlledReplayOutputSchema.parse(executed.value)),
|
|
60
|
+
replay: controlledReplayOutputSchema.parse(executed.value),
|
|
61
|
+
})));
|
|
62
|
+
};
|
|
63
|
+
const prepareValidated = async (dependencies, input, options) => {
|
|
64
|
+
const host = new InstrumentedJavaScriptReplayHost(dependencies.host, input.instrumentation);
|
|
65
|
+
const replay = await runControlledReplayValidated({ ...dependencies, host }, input.replay, options);
|
|
66
|
+
if (!replay.ok)
|
|
67
|
+
return replay;
|
|
68
|
+
const output = controlledReplayOutputSchema.parse(replay.value);
|
|
69
|
+
if (output.phase !== "plan" ||
|
|
70
|
+
output.plan === null ||
|
|
71
|
+
host.instrumentation === null)
|
|
72
|
+
return err(new AnalysisProtocolError("Node characterization preparation omitted committed plan evidence"));
|
|
73
|
+
const module = output.plan.left.modules.find(({ alias }) => alias === input.selected_alias);
|
|
74
|
+
if (module === undefined)
|
|
75
|
+
return err(new AnalysisProtocolError("Node characterization replay plan omitted selected module"));
|
|
76
|
+
const plan = createCharacterizationPlan(input, host.instrumentation, output.plan);
|
|
77
|
+
return ok({
|
|
78
|
+
schema_version: 1,
|
|
79
|
+
phase: "preparation",
|
|
80
|
+
plan,
|
|
81
|
+
transformation: host.instrumentation.manifest,
|
|
82
|
+
transformation_evidence: createTransformationEvidence(input, host.instrumentation),
|
|
83
|
+
replay: output,
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
const createTransformationEvidence = (input, instrumentation) => createEvidence({
|
|
87
|
+
path: input.instrumentation.artifact_path,
|
|
88
|
+
sha256: input.instrumentation.artifact_sha256,
|
|
89
|
+
format: "javascript-bundle",
|
|
90
|
+
}, PROVIDER, {
|
|
91
|
+
predicateType: "rea.javascript-export-transformation/v1",
|
|
92
|
+
operation: PREPARE_OPERATION,
|
|
93
|
+
parameters: {
|
|
94
|
+
selected_alias: input.selected_alias,
|
|
95
|
+
selected_sha256: input.instrumentation.selection.selected_sha256,
|
|
96
|
+
},
|
|
97
|
+
result: jsonValueSchema.parse(instrumentation.manifest),
|
|
98
|
+
confidence: "derived",
|
|
99
|
+
authority: "shipped-artifact",
|
|
100
|
+
locations: [
|
|
101
|
+
{
|
|
102
|
+
kind: "file-offset-range",
|
|
103
|
+
start: input.instrumentation.selection.byte_start,
|
|
104
|
+
end: input.instrumentation.selection.byte_end,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
limitations: [
|
|
108
|
+
"The transformation is deterministic derived evidence; execution authority is established only by a separately approved characterization run.",
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
const createCharacterizationEvidence = (prepared, replay) => {
|
|
112
|
+
if (replay.phase !== "execute" || replay.evidence === null)
|
|
113
|
+
throw new AnalysisProtocolError("Node characterization execution omitted replay Evidence");
|
|
114
|
+
return createEvidence({
|
|
115
|
+
path: prepared.plan.artifact.path,
|
|
116
|
+
sha256: prepared.plan.artifact.sha256,
|
|
117
|
+
format: "javascript-bundle",
|
|
118
|
+
}, PROVIDER, {
|
|
119
|
+
predicateType: "rea.runtime-characterization/v1",
|
|
120
|
+
operation: EXECUTE_OPERATION,
|
|
121
|
+
parameters: {
|
|
122
|
+
plan_sha256: prepared.plan.plan_sha256,
|
|
123
|
+
replay_evidence_id: replay.evidence.evidence_id,
|
|
124
|
+
},
|
|
125
|
+
result: jsonValueSchema.parse({
|
|
126
|
+
plan: prepared.plan,
|
|
127
|
+
transformation_evidence_id: prepared.transformation_evidence.evidence_id,
|
|
128
|
+
replay_evidence_id: replay.evidence.evidence_id,
|
|
129
|
+
execution: replay.evidence.normalized_result,
|
|
130
|
+
}),
|
|
131
|
+
confidence: "observed",
|
|
132
|
+
authority: "controlled-replay",
|
|
133
|
+
environment: replay.evidence.environment,
|
|
134
|
+
limitations: [
|
|
135
|
+
"This Evidence proves only the finite approved characterization plan and transformed controlled-replay authority, not unmodified shipped-runtime equivalence.",
|
|
136
|
+
...replay.evidence.limitations,
|
|
137
|
+
],
|
|
138
|
+
evidenceLinks: [
|
|
139
|
+
prepared.transformation_evidence.evidence_id,
|
|
140
|
+
replay.evidence.evidence_id,
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
const createCharacterizationPlan = (input, instrumentation, replayPlan) => createRuntimeCharacterizationPlan({
|
|
145
|
+
schema_version: 1,
|
|
146
|
+
preparation_sha256: digestPreparation(instrumentation.manifest.instrumented_sha256, replayPlan.plan_digest),
|
|
147
|
+
artifact: {
|
|
148
|
+
path: input.instrumentation.artifact_path,
|
|
149
|
+
sha256: input.instrumentation.artifact_sha256,
|
|
150
|
+
byte_length: instrumentation.manifest.original_byte_length,
|
|
151
|
+
},
|
|
152
|
+
runtime: {
|
|
153
|
+
family: "ecmascript",
|
|
154
|
+
provider_id: "node-javascript",
|
|
155
|
+
executable_path: replayPlan.runtime.executable.path,
|
|
156
|
+
executable_sha256: replayPlan.runtime.executable.sha256,
|
|
157
|
+
version: replayPlan.runtime.executable.version,
|
|
158
|
+
profile_sha256: replayPlan.policy_sha256,
|
|
159
|
+
},
|
|
160
|
+
callable: {
|
|
161
|
+
callable_id: `callable/${input.instrumentation.selection.selected_sha256}`,
|
|
162
|
+
module_id: stableModuleId(input.selected_alias),
|
|
163
|
+
export_name: input.instrumentation.selection.export_name,
|
|
164
|
+
semantic_evidence_id: null,
|
|
165
|
+
selector_sha256: input.instrumentation.selection.selected_sha256,
|
|
166
|
+
},
|
|
167
|
+
working_directory: "/work",
|
|
168
|
+
isolated_home: "/work",
|
|
169
|
+
expected_effect: input.expected_effect,
|
|
170
|
+
allowed_boundaries: [],
|
|
171
|
+
limits: {
|
|
172
|
+
max_calls: replayPlan.cases.length,
|
|
173
|
+
max_processes: 0,
|
|
174
|
+
max_files: 0,
|
|
175
|
+
max_bytes: replayPlan.limits.output_bytes,
|
|
176
|
+
max_handles: replayPlan.limits.tasks,
|
|
177
|
+
timeout_ms: replayPlan.limits.wall_time_ms,
|
|
178
|
+
idle_timeout_ms: replayPlan.limits.wall_time_ms,
|
|
179
|
+
},
|
|
180
|
+
determinism: {
|
|
181
|
+
clock: "fixed",
|
|
182
|
+
randomness: "seeded",
|
|
183
|
+
identifiers: "deterministic",
|
|
184
|
+
seed: replayPlan.determinism.random_seed,
|
|
185
|
+
},
|
|
186
|
+
authority: {
|
|
187
|
+
preparation_approved: true,
|
|
188
|
+
execution_approved: false,
|
|
189
|
+
network: "none",
|
|
190
|
+
provider_owned_process_only: true,
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
const digestPreparation = (instrumentedSha256, replayPlanDigest) => createHash("sha256")
|
|
194
|
+
.update(`rea.node-characterization-preparation/v1\0${instrumentedSha256}\0${replayPlanDigest}`)
|
|
195
|
+
.digest("hex");
|
|
196
|
+
const stableModuleId = (alias) => `module/${createHash("sha256").update(alias).digest("hex")}`;
|
|
@@ -73,31 +73,52 @@ const errorReason = (error) => isNotFound(error) ? "not_found" : "io";
|
|
|
73
73
|
/** Stateful owner for permission evaluation, consumption, reload, and revoke. */
|
|
74
74
|
export class PermissionAuthority {
|
|
75
75
|
policy;
|
|
76
|
-
|
|
76
|
+
configuredAuthority;
|
|
77
|
+
constructor(policy, configuredAuthority) {
|
|
77
78
|
this.policy = policy;
|
|
79
|
+
this.configuredAuthority = configuredAuthority;
|
|
80
|
+
}
|
|
81
|
+
/** Create a connection-owned overlay for once and session grants. */
|
|
82
|
+
createConnectionAuthority() {
|
|
83
|
+
return new PermissionAuthority(createPermissionPolicy([]), this.configuredAuthority ?? this);
|
|
78
84
|
}
|
|
79
85
|
/** Canonicalize and authorize one exact operation before its side effect. */
|
|
80
86
|
async authorize(request, access, options = {}) {
|
|
81
|
-
const evaluated = await this.
|
|
87
|
+
const evaluated = await this.evaluateRequest(request, access, options);
|
|
82
88
|
if (!evaluated.ok)
|
|
83
89
|
return evaluated;
|
|
84
|
-
const decision = evaluated.value;
|
|
85
|
-
|
|
86
|
-
return ok(decision);
|
|
90
|
+
const { decision, owner, request: canonicalRequest } = evaluated.value;
|
|
91
|
+
owner.policy = consumePermission(owner.policy, decision);
|
|
92
|
+
return ok({ ...decision, request: canonicalRequest });
|
|
87
93
|
}
|
|
88
94
|
/** Dry-run the exact shared evaluation without consuming a once grant. */
|
|
89
95
|
async explain(request, access, options = {}) {
|
|
96
|
+
const evaluated = await this.evaluateRequest(request, access, options);
|
|
97
|
+
return evaluated.ok
|
|
98
|
+
? ok({
|
|
99
|
+
...evaluated.value.decision,
|
|
100
|
+
request: evaluated.value.request,
|
|
101
|
+
})
|
|
102
|
+
: evaluated;
|
|
103
|
+
}
|
|
104
|
+
async evaluateRequest(request, access, options) {
|
|
90
105
|
const canonical = await canonicalizePermissionRequest(request, access);
|
|
91
106
|
if (!canonical.ok)
|
|
92
107
|
return canonical;
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
const evaluated = this.evaluateCanonical(canonical.value, options);
|
|
109
|
+
return evaluated.ok
|
|
110
|
+
? ok({ ...evaluated.value, request: canonical.value })
|
|
111
|
+
: evaluated;
|
|
97
112
|
}
|
|
98
113
|
/** Add authority beneath the administrator ceiling. */
|
|
99
114
|
grant(grant) {
|
|
100
|
-
const
|
|
115
|
+
const policy = this.configuredAuthority === undefined
|
|
116
|
+
? this.policy
|
|
117
|
+
: {
|
|
118
|
+
...this.policy,
|
|
119
|
+
ceilings: this.configuredAuthority.effectivePolicy().ceilings,
|
|
120
|
+
};
|
|
121
|
+
const result = grantPermission(policy, grant);
|
|
101
122
|
if (result.ok)
|
|
102
123
|
this.policy = result.value;
|
|
103
124
|
return result;
|
|
@@ -110,44 +131,61 @@ export class PermissionAuthority {
|
|
|
110
131
|
clearSessionGrants() {
|
|
111
132
|
this.policy = clearSessionPermissions(this.policy);
|
|
112
133
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
effectivePolicy() {
|
|
135
|
+
if (this.configuredAuthority === undefined)
|
|
136
|
+
return this.policy;
|
|
137
|
+
const configured = this.configuredAuthority.effectivePolicy();
|
|
138
|
+
return {
|
|
139
|
+
ceilings: configured.ceilings,
|
|
140
|
+
grants: [...configured.grants, ...this.policy.grants],
|
|
141
|
+
revoked_grant_ids: new Set([
|
|
142
|
+
...configured.revoked_grant_ids,
|
|
143
|
+
...this.policy.revoked_grant_ids,
|
|
144
|
+
]),
|
|
145
|
+
consumed_grant_ids: new Set([
|
|
146
|
+
...configured.consumed_grant_ids,
|
|
147
|
+
...this.policy.consumed_grant_ids,
|
|
148
|
+
]),
|
|
118
149
|
};
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
if (
|
|
125
|
-
return
|
|
126
|
-
cause: added.error,
|
|
127
|
-
}));
|
|
128
|
-
candidate = added.value;
|
|
150
|
+
}
|
|
151
|
+
evaluateCanonical(request, options) {
|
|
152
|
+
const configured = this.configuredAuthority;
|
|
153
|
+
if (configured !== undefined) {
|
|
154
|
+
const configuredDecision = evaluatePermission(configured.effectivePolicy(), request);
|
|
155
|
+
if (configuredDecision.allowed)
|
|
156
|
+
return ok({ decision: configuredDecision, owner: configured });
|
|
129
157
|
}
|
|
130
|
-
|
|
131
|
-
|
|
158
|
+
const localPolicy = configured === undefined
|
|
159
|
+
? this.policy
|
|
160
|
+
: { ...this.policy, ceilings: configured.effectivePolicy().ceilings };
|
|
161
|
+
const localDecision = evaluatePermission(localPolicy, request);
|
|
162
|
+
if (localDecision.allowed)
|
|
163
|
+
return ok({ decision: localDecision, owner: this });
|
|
164
|
+
const effective = this.effectivePolicy();
|
|
165
|
+
const denied = evaluatePermission(effective, request);
|
|
166
|
+
if (denied.allowed)
|
|
167
|
+
return ok({ decision: denied, owner: this });
|
|
168
|
+
return err(new PermissionRequiredError(request, denied.missing, ceilingFor(effective, request.capability), options.elicitationSupported ?? false, options.restartRequired ?? false));
|
|
169
|
+
}
|
|
170
|
+
/** Atomically replace optional persisted project grants. */
|
|
171
|
+
async replaceProjectGrants(grants) {
|
|
172
|
+
return this.replaceGrants(grants, "project");
|
|
132
173
|
}
|
|
133
174
|
/** Replace grants derived from the currently loaded administrator ceiling. */
|
|
134
175
|
async replaceAdministratorGrants(grants) {
|
|
135
|
-
|
|
176
|
+
return this.replaceGrants(grants, "administrator");
|
|
177
|
+
}
|
|
178
|
+
async replaceGrants(grants, lifetime) {
|
|
179
|
+
const canonical = await canonicalizeGrants(grants);
|
|
180
|
+
if (!canonical.ok)
|
|
181
|
+
return canonical;
|
|
182
|
+
const replaced = addGrantsToPolicy({
|
|
136
183
|
...this.policy,
|
|
137
|
-
grants: this.policy.grants.filter((
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return canonical;
|
|
143
|
-
const added = grantPermission(candidate, canonical.value);
|
|
144
|
-
if (!added.ok)
|
|
145
|
-
return err(new PermissionPathError(grant.grant_id, "io", {
|
|
146
|
-
cause: added.error,
|
|
147
|
-
}));
|
|
148
|
-
candidate = added.value;
|
|
149
|
-
}
|
|
150
|
-
this.policy = candidate;
|
|
184
|
+
grants: this.policy.grants.filter((grant) => grant.lifetime !== lifetime),
|
|
185
|
+
}, canonical.value);
|
|
186
|
+
if (!replaced.ok)
|
|
187
|
+
return replaced;
|
|
188
|
+
this.policy = replaced.value;
|
|
151
189
|
return ok(null);
|
|
152
190
|
}
|
|
153
191
|
/** Atomically replace reloadable ceilings and persisted grants. */
|
|
@@ -155,29 +193,20 @@ export class PermissionAuthority {
|
|
|
155
193
|
const ceilings = await canonicalizePermissionCeilings(configuration.ceilings);
|
|
156
194
|
if (!ceilings.ok)
|
|
157
195
|
return ceilings;
|
|
158
|
-
const grants = [
|
|
159
|
-
for (const grant of [
|
|
196
|
+
const grants = await canonicalizeGrants([
|
|
160
197
|
...configuration.administratorGrants,
|
|
161
198
|
...configuration.projectGrants,
|
|
162
|
-
])
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
grants.push(canonical.value);
|
|
167
|
-
}
|
|
168
|
-
let candidate = {
|
|
199
|
+
]);
|
|
200
|
+
if (!grants.ok)
|
|
201
|
+
return grants;
|
|
202
|
+
const candidate = {
|
|
169
203
|
...reloadPermissionCeilings(this.policy, ceilings.value),
|
|
170
204
|
grants: this.policy.grants.filter(({ lifetime }) => lifetime !== "administrator" && lifetime !== "project"),
|
|
171
205
|
};
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
cause: added.error,
|
|
177
|
-
}));
|
|
178
|
-
candidate = added.value;
|
|
179
|
-
}
|
|
180
|
-
this.policy = candidate;
|
|
206
|
+
const replaced = addGrantsToPolicy(candidate, grants.value);
|
|
207
|
+
if (!replaced.ok)
|
|
208
|
+
return replaced;
|
|
209
|
+
this.policy = replaced.value;
|
|
181
210
|
return ok(null);
|
|
182
211
|
}
|
|
183
212
|
/** Replace ceilings without restarting the MCP process. */
|
|
@@ -194,17 +223,31 @@ export const createPermissionAuthority = async (ceilings, grants = []) => {
|
|
|
194
223
|
const canonical = await canonicalizePermissionCeilings(ceilings);
|
|
195
224
|
if (!canonical.ok)
|
|
196
225
|
return canonical;
|
|
197
|
-
|
|
226
|
+
const canonicalGrants = await canonicalizeGrants(grants);
|
|
227
|
+
if (!canonicalGrants.ok)
|
|
228
|
+
return canonicalGrants;
|
|
229
|
+
const policy = addGrantsToPolicy(createPermissionPolicy(canonical.value), canonicalGrants.value);
|
|
230
|
+
return policy.ok ? ok(new PermissionAuthority(policy.value)) : policy;
|
|
231
|
+
};
|
|
232
|
+
const canonicalizeGrants = async (grants) => {
|
|
233
|
+
const canonical = [];
|
|
198
234
|
for (const grant of grants) {
|
|
199
235
|
const canonicalGrant = await canonicalizeGrant(grant);
|
|
200
236
|
if (!canonicalGrant.ok)
|
|
201
237
|
return canonicalGrant;
|
|
202
|
-
|
|
238
|
+
canonical.push(canonicalGrant.value);
|
|
239
|
+
}
|
|
240
|
+
return ok(canonical);
|
|
241
|
+
};
|
|
242
|
+
const addGrantsToPolicy = (initial, grants) => {
|
|
243
|
+
let policy = initial;
|
|
244
|
+
for (const grant of grants) {
|
|
245
|
+
const added = grantPermission(policy, grant);
|
|
203
246
|
if (!added.ok)
|
|
204
247
|
return err(new PermissionPathError(grant.grant_id, "io", { cause: added.error }));
|
|
205
248
|
policy = added.value;
|
|
206
249
|
}
|
|
207
|
-
return ok(
|
|
250
|
+
return ok(policy);
|
|
208
251
|
};
|
|
209
252
|
const canonicalizeGrant = async (grant) => {
|
|
210
253
|
const scope = await canonicalizeScope(grant, "read");
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { tmpdir } from "node:os";
|
|
2
|
-
import { spawn } from "node-pty";
|
|
3
2
|
/** Probe the actual native PTY seam instead of inferring support from the OS name. */
|
|
4
3
|
export const probeProcessCaptureCapability = async () => {
|
|
5
4
|
try {
|
|
5
|
+
const { spawn } = await import("@lydell/node-pty");
|
|
6
6
|
const terminal = spawn(process.platform === "win32" ? "cmd.exe" : "/bin/sh", process.platform === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"], {
|
|
7
7
|
cwd: tmpdir(),
|
|
8
8
|
env: { HOME: tmpdir(), TERM: "xterm-256color" },
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Build the exact permission request shared by process-capture adapters. */
|
|
2
|
+
export const processCapturePermissionRequest = (scenario) => ({
|
|
3
|
+
capability: "process_capture",
|
|
4
|
+
roots: [scenario.working_directory, ...scenario.filesystem_roots],
|
|
5
|
+
executables: [scenario.executable],
|
|
6
|
+
environment_names: [
|
|
7
|
+
...Object.keys(scenario.environment),
|
|
8
|
+
...scenario.inherit_environment,
|
|
9
|
+
],
|
|
10
|
+
network: scenario.network_access === "host" ? "external" : "none",
|
|
11
|
+
mount: false,
|
|
12
|
+
operation_identity: `capture_process_scenario:${scenario.executable}`,
|
|
13
|
+
});
|
|
@@ -5,6 +5,7 @@ import { createEvidence, parseEvidence } from "../domain/evidence.js";
|
|
|
5
5
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
6
6
|
import { compareProcessCaptures, LEGACY_PROCESS_CAPTURE_MESSAGE, parseProcessScenario, parseProcessCapture, } from "../domain/processCapture.js";
|
|
7
7
|
import { captureProcessScenario } from "./ProcessHarness.js";
|
|
8
|
+
import { processCapturePermissionRequest } from "./ProcessCapturePermission.js";
|
|
8
9
|
import { PROCESS_PROVIDER, createProcessCaptureEvidence, } from "./ProcessEvidence.js";
|
|
9
10
|
import { loadConfiguredPermissionAuthority } from "./PermissionConfiguration.js";
|
|
10
11
|
const MAX_INPUT_BYTES = 64 * 1024 * 1024;
|
|
@@ -25,18 +26,7 @@ export const captureProcessScenarioFile = async (path) => {
|
|
|
25
26
|
const authority = await loadConfiguredPermissionAuthority(config.value);
|
|
26
27
|
if (!authority.ok)
|
|
27
28
|
return cliAnalysisError(authority.error);
|
|
28
|
-
const authorized = await authority.value.authorize(
|
|
29
|
-
capability: "process_capture",
|
|
30
|
-
roots: [scenario.working_directory, ...scenario.filesystem_roots],
|
|
31
|
-
executables: [scenario.executable],
|
|
32
|
-
environment_names: [
|
|
33
|
-
...Object.keys(scenario.environment),
|
|
34
|
-
...scenario.inherit_environment,
|
|
35
|
-
],
|
|
36
|
-
network: scenario.network_access === "host" ? "external" : "none",
|
|
37
|
-
mount: false,
|
|
38
|
-
operation_identity: `capture_process_scenario:${scenario.executable}`,
|
|
39
|
-
}, "read");
|
|
29
|
+
const authorized = await authority.value.authorize(processCapturePermissionRequest(scenario), "read");
|
|
40
30
|
if (!authorized.ok)
|
|
41
31
|
return cliAnalysisError(authorized.error instanceof PermissionRequiredError
|
|
42
32
|
? authorized.error
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawn } from "node-pty";
|
|
2
1
|
import { validateProcessCapture } from "../domain/processCapture.js";
|
|
3
2
|
import { err, ok } from "../domain/result.js";
|
|
4
3
|
import { ProcessCaptureError, processCaptureCancelled, } from "./ProcessCaptureError.js";
|
|
@@ -111,6 +110,7 @@ const startCaptureRuntime = async (options) => {
|
|
|
111
110
|
let shimReplay;
|
|
112
111
|
let terminal;
|
|
113
112
|
try {
|
|
113
|
+
const { spawn } = await import("@lydell/node-pty");
|
|
114
114
|
replay = await startLoopbackReplay(scenario);
|
|
115
115
|
const startedAt = new Date();
|
|
116
116
|
const started = Date.now();
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { processCaptureCancelled, } from "./ProcessCaptureError.js";
|
|
2
|
+
import { captureProcessScenario } from "./ProcessHarness.js";
|
|
3
|
+
import { compareProcessCaptures } from "../domain/processComparison.js";
|
|
4
|
+
import { PROCESS_COMPARISON_DIMENSIONS } from "../domain/processComparison.js";
|
|
5
|
+
import { analyzeProcessRepeatability, bindProcessScenario, } from "../domain/processPairedExperiment.js";
|
|
6
|
+
import { err, ok } from "../domain/result.js";
|
|
7
|
+
const productionCapturePort = {
|
|
8
|
+
capture: captureProcessScenario,
|
|
9
|
+
};
|
|
10
|
+
const captureRepeats = async (input) => {
|
|
11
|
+
const captures = [];
|
|
12
|
+
for (let index = 0; index < input.repeatCount; index += 1) {
|
|
13
|
+
if (input.signal?.aborted === true)
|
|
14
|
+
return err(processCaptureCancelled());
|
|
15
|
+
const capture = await input.port.capture(input.scenario, input.policy, input.signal);
|
|
16
|
+
if (!capture.ok)
|
|
17
|
+
return err(capture.error);
|
|
18
|
+
captures.push(capture.value);
|
|
19
|
+
}
|
|
20
|
+
return ok(captures);
|
|
21
|
+
};
|
|
22
|
+
/** Execute repeatability-first authority/candidate captures under one contract. */
|
|
23
|
+
export const runPairedProcessExperiment = async (experiment, policy, options = {}) => {
|
|
24
|
+
const port = options.capturePort ?? productionCapturePort;
|
|
25
|
+
const authorityScenario = bindProcessScenario(experiment.shared_scenario, experiment.authority);
|
|
26
|
+
const candidateScenario = bindProcessScenario(experiment.shared_scenario, experiment.candidate);
|
|
27
|
+
const authorityRuns = await captureRepeats({
|
|
28
|
+
scenario: authorityScenario,
|
|
29
|
+
repeatCount: experiment.repeat_count,
|
|
30
|
+
policy,
|
|
31
|
+
port,
|
|
32
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
33
|
+
});
|
|
34
|
+
if (!authorityRuns.ok)
|
|
35
|
+
return authorityRuns;
|
|
36
|
+
const candidateRuns = await captureRepeats({
|
|
37
|
+
scenario: candidateScenario,
|
|
38
|
+
repeatCount: experiment.repeat_count,
|
|
39
|
+
policy,
|
|
40
|
+
port,
|
|
41
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
42
|
+
});
|
|
43
|
+
if (!candidateRuns.ok)
|
|
44
|
+
return candidateRuns;
|
|
45
|
+
const authorityRepeatability = analyzeProcessRepeatability(authorityRuns.value, experiment.required_dimensions);
|
|
46
|
+
const candidateRepeatability = analyzeProcessRepeatability(candidateRuns.value, experiment.required_dimensions);
|
|
47
|
+
const stable = authorityRepeatability.stable && candidateRepeatability.stable;
|
|
48
|
+
const allDimensionsRequired = PROCESS_COMPARISON_DIMENSIONS.every((dimension) => experiment.required_dimensions.includes(dimension));
|
|
49
|
+
const authorityLatest = authorityRuns.value.at(-1);
|
|
50
|
+
const candidateLatest = candidateRuns.value.at(-1);
|
|
51
|
+
const crossSide = stable &&
|
|
52
|
+
allDimensionsRequired &&
|
|
53
|
+
authorityLatest !== undefined &&
|
|
54
|
+
candidateLatest !== undefined
|
|
55
|
+
? compareProcessCaptures(authorityLatest, candidateLatest, {
|
|
56
|
+
maxCaptureAgeMs: experiment.freshness_policy.max_capture_age_ms,
|
|
57
|
+
...(options.now === undefined ? {} : { now: options.now }),
|
|
58
|
+
})
|
|
59
|
+
: null;
|
|
60
|
+
return ok({
|
|
61
|
+
authority_runs: authorityRuns.value,
|
|
62
|
+
candidate_runs: candidateRuns.value,
|
|
63
|
+
authority_repeatability: authorityRepeatability,
|
|
64
|
+
candidate_repeatability: candidateRepeatability,
|
|
65
|
+
cross_side: crossSide,
|
|
66
|
+
cross_side_blocked_reason: crossSide !== null
|
|
67
|
+
? null
|
|
68
|
+
: !allDimensionsRequired
|
|
69
|
+
? "Cross-side comparison is blocked until every returned dimension is required and repeatable."
|
|
70
|
+
: "Cross-side comparison is blocked because a required same-side dimension is unstable.",
|
|
71
|
+
});
|
|
72
|
+
};
|