rea-agents 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -18
- package/bridge/ghidra/ReaGhidraBridge.java +48 -1
- package/bridge/hopper_bridge.py +259 -24
- package/dist/application/AnalysisClientCleanup.js +14 -0
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
- package/dist/application/ArtifactExtraction.js +5 -1
- package/dist/application/ArtifactGraphConstruction.js +3 -15
- package/dist/application/ArtifactInventory/classify.js +5 -1
- package/dist/application/ArtifactInventory/reader.js +2 -0
- package/dist/application/BinarySession.js +15 -4
- package/dist/application/BinarySessionStatus.js +32 -1
- package/dist/application/BrowserEvidence.js +14 -0
- package/dist/application/BrowserScenarioCapturePort.js +1 -0
- package/dist/application/BrowserScenarioCaptureService.js +53 -0
- package/dist/application/BrowserScenarioEvidence.js +40 -0
- package/dist/application/CallPathTracing.js +160 -0
- package/dist/application/CapabilityInventory.js +79 -15
- package/dist/application/CompositeProvider.js +10 -1
- package/dist/application/DirectAnalysis.js +7 -2
- package/dist/application/EnhancedLiteralTracing.js +118 -0
- package/dist/application/EnhancedTools.js +35 -121
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
- package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
- package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
- package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
- package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
- package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
- package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
- package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
- package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
- package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
- package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
- package/dist/application/ProcessCaptureEnvironment.js +26 -0
- package/dist/application/ProcessCaptureLifecycle.js +1 -0
- package/dist/application/ProcessCheckpoints.js +5 -0
- package/dist/application/ProcessHarness.js +45 -34
- package/dist/application/ProcessNormalization.js +6 -0
- package/dist/application/ProcessReactiveCoordinator.js +10 -3
- package/dist/application/ProcessReactiveEffects.js +15 -1
- package/dist/application/ProcessReactiveHarness.js +100 -0
- package/dist/application/ProcessReactiveObservations.js +101 -16
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReconstructionObligationAuthority.js +72 -0
- package/dist/application/ReconstructionObligationCandidates.js +265 -0
- package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
- package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
- package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
- package/dist/application/ReconstructionObligationLedgerService.js +72 -0
- package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
- package/dist/application/ReconstructionObligationPolicies.js +132 -0
- package/dist/application/ReconstructionReadinessService.js +48 -0
- package/dist/artifacts/ArtifactProvider.js +87 -22
- package/dist/browser/BrowserScenarioSecrets.js +53 -0
- package/dist/browser/BrowserScenarioSessionPort.js +1 -0
- package/dist/browser/CdpBrowserProvider.js +2 -2
- package/dist/browser/CdpCaptureDocuments.js +57 -0
- package/dist/browser/CdpCaptureStorage.js +71 -29
- package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
- package/dist/browser/CdpEndpoint.js +8 -6
- package/dist/browser/CdpPageCapture.js +2 -0
- package/dist/browser/JavaScriptRuntimeScope.js +38 -0
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
- package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
- package/dist/browser/PlaywrightScenarioActions.js +62 -0
- package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
- package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
- package/dist/browser/PlaywrightScenarioEvents.js +301 -0
- package/dist/browser/PlaywrightScenarioSession.js +300 -0
- package/dist/browser/V8InspectorCaptureProjection.js +110 -0
- package/dist/browser/V8InspectorEndpoint.js +72 -0
- package/dist/browser/V8InspectorProvider.js +267 -0
- package/dist/catalogIdentity.js +8 -0
- package/dist/cli/artifactCommands.js +39 -0
- package/dist/cli/coreAnalysisCommands.js +37 -0
- package/dist/cli.js +4 -0
- package/dist/cliApplicationCommands.js +30 -2
- package/dist/cliBrowserAdvancedCommands.js +17 -5
- package/dist/cliBrowserCommands.js +1 -0
- package/dist/cliBrowserScenarioCommands.js +43 -0
- package/dist/cliCommandNames.js +8 -0
- package/dist/cliJavaScriptRuntimeCommands.js +164 -0
- package/dist/cliObservationOptions.js +4 -0
- package/dist/cliPolicyCommands.js +1 -0
- package/dist/config/environment.js +12 -0
- package/dist/config/parseConfig.js +88 -17
- package/dist/contracts/analysisSearchContract.js +14 -0
- package/dist/contracts/applicationToolContracts.js +86 -2
- package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
- package/dist/contracts/artifactToolContracts.js +22 -0
- package/dist/contracts/browserProviderToolContracts.js +7 -0
- package/dist/contracts/browserScenarioToolContracts.js +81 -0
- package/dist/contracts/browserToolContracts.js +88 -98
- package/dist/contracts/enhancedInputs.js +25 -6
- package/dist/contracts/functionInstructionContract.js +8 -0
- package/dist/contracts/hopperMemoryContracts.js +22 -0
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
- package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
- package/dist/contracts/reconstructionReadinessExample.js +436 -0
- package/dist/contracts/toolContractExamples.js +5 -0
- package/dist/contracts/toolContractTypes.js +1 -0
- package/dist/contracts/toolContracts.js +13 -21
- package/dist/contracts/toolEffects.js +21 -0
- package/dist/contracts/toolOutputSchemaGroups.js +70 -20
- package/dist/contracts/toolOutputSchemaPrimitives.js +40 -0
- package/dist/domain/analysisErrorProjection.js +8 -0
- package/dist/domain/analysisSnapshot.js +2 -0
- package/dist/domain/artifactGraph.js +2 -0
- package/dist/domain/artifactInspection.js +347 -0
- package/dist/domain/binaryTarget.js +3 -6
- package/dist/domain/browserCaptureComparison.js +82 -0
- package/dist/domain/browserObservation.js +8 -1
- package/dist/domain/browserObservationErrors.js +1 -0
- package/dist/domain/browserObservationSchemas.js +26 -0
- package/dist/domain/browserScenario.js +165 -0
- package/dist/domain/browserScenarioCapture.js +57 -0
- package/dist/domain/browserScenarioCaptureValues.js +204 -0
- package/dist/domain/browserScenarioDiff.js +324 -0
- package/dist/domain/browserScenarioDiffValues.js +157 -0
- package/dist/domain/browserScenarioNormalization.js +45 -0
- package/dist/domain/browserScenarioValues.js +372 -0
- package/dist/domain/evidence.js +2 -0
- package/dist/domain/hopperValues.js +10 -0
- package/dist/domain/javascriptRuntimeObservation.js +170 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
- package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
- package/dist/domain/javascriptSemanticAnalysis.js +5 -4
- package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
- package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
- package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
- package/dist/domain/javascriptSemanticDataEffects.js +284 -0
- package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
- package/dist/domain/javascriptSemanticFingerprints.js +174 -0
- package/dist/domain/javascriptSemanticIr.js +21 -1
- package/dist/domain/javascriptSemanticObjects.js +115 -0
- package/dist/domain/javascriptSemanticPromises.js +290 -0
- package/dist/domain/javascriptSemanticResources.js +136 -0
- package/dist/domain/javascriptStaticAnalysis.js +4 -3
- package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
- package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
- package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
- package/dist/domain/processCapture.js +71 -1
- package/dist/domain/processCaptureReactiveValidation.js +175 -0
- package/dist/domain/processCaptureValidation.js +2 -0
- package/dist/domain/processObservation.js +15 -6
- package/dist/domain/processReactiveRuntime.js +21 -10
- package/dist/domain/processReactiveScenario.js +1 -0
- package/dist/domain/processReactiveTransition.js +17 -10
- package/dist/domain/processScenario.js +21 -0
- package/dist/domain/providerCleanupError.js +11 -0
- package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
- package/dist/domain/reconstructionReadiness.js +121 -0
- package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
- package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
- package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
- package/dist/domain/reconstructionReadinessSchemas.js +240 -0
- package/dist/domain/sourceToBundleComparison.js +335 -0
- package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
- package/dist/domain/sourceToBundleSignals.js +202 -0
- package/dist/domain/webCaptureDiff.js +141 -27
- package/dist/domain/webScreenshot.js +1 -1
- package/dist/domain/zipPackageFormat.js +20 -0
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +13 -1
- package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
- package/dist/hopper/HopperCleanup.js +158 -0
- package/dist/hopper/HopperClient.js +69 -99
- package/dist/hopper/HopperProvider.js +20 -1
- package/dist/hopper/HopperRequestQueue.js +187 -0
- package/dist/hopper/HopperResponseStream.js +46 -0
- package/dist/hopper/protocol.js +1 -0
- package/dist/main/transport.js +24 -24
- package/dist/main.js +6 -0
- package/dist/mcpDoctor.js +25 -2
- package/dist/native/NativeMacOSProvider.js +1 -0
- package/dist/process/ProcessCommandIdentity.js +19 -0
- package/dist/process/ProcessOwnership.js +11 -8
- package/dist/server/DynamicToolAvailability.js +70 -0
- package/dist/server/createServer.js +46 -1
- package/dist/server/lifecycleProgress.js +10 -0
- package/dist/server/permissionFailure.js +12 -0
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
- package/dist/server/registerApplicationTools/helpers.js +11 -7
- package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
- package/dist/server/registerApplicationTools/readiness.js +55 -0
- package/dist/server/registerApplicationTools.js +6 -0
- package/dist/server/registerArtifactTools.js +9 -1
- package/dist/server/registerBrowserScenarioTool.js +25 -0
- package/dist/server/registerBrowserTools.js +2 -2
- package/dist/server/registerCloseLifecycleTool.js +57 -0
- package/dist/server/registerEnhancedTools.js +16 -5
- package/dist/server/registerEvidenceResources.js +4 -0
- package/dist/server/registerEvidenceTools.js +9 -2
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
- package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
- package/dist/server/registerReconstructionReadinessResource.js +39 -0
- package/dist/server/registerSessionStatusTool.js +11 -8
- package/dist/server/registerSessionTools.js +11 -57
- package/dist/server/sessionAvailabilityPolicy.js +10 -8
- package/package.json +12 -6
- package/skills/reverse-engineer-anything/SKILL.md +4 -3
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Project internal provider routing state into the caller-visible session status. */
|
|
2
|
-
export const binarySessionStatus = ({ target, route, router, runtimeAvailability, runId, runtimeLineageSnapshots, }) => {
|
|
2
|
+
export const binarySessionStatus = ({ target, route, router, runtimeAvailability, runId, runtimeLineageSnapshots, requestActivitySnapshots, }) => {
|
|
3
3
|
const configuredProvider = router.configuredIdentity();
|
|
4
4
|
const provider = providerSummary(configuredProvider);
|
|
5
5
|
const providers = router.providerIdentities(route).map(providerSummary);
|
|
@@ -41,6 +41,7 @@ export const binarySessionStatus = ({ target, route, router, runtimeAvailability
|
|
|
41
41
|
run_id: runId,
|
|
42
42
|
process_lineage: processLineageStatus(runtimeLineageSnapshots),
|
|
43
43
|
},
|
|
44
|
+
analysis_activity: requestActivityStatus(requestActivitySnapshots),
|
|
44
45
|
};
|
|
45
46
|
return target === undefined
|
|
46
47
|
? { open: false, ...common }
|
|
@@ -54,6 +55,35 @@ export const binarySessionStatus = ({ target, route, router, runtimeAvailability
|
|
|
54
55
|
architecture: target.architecture ?? null,
|
|
55
56
|
};
|
|
56
57
|
};
|
|
58
|
+
const requestActivityStatus = (snapshots) => {
|
|
59
|
+
const providers = snapshots
|
|
60
|
+
.slice()
|
|
61
|
+
.sort((left, right) => left.provider.id.localeCompare(right.provider.id))
|
|
62
|
+
.map(({ provider, active, queuedRequests }) => ({
|
|
63
|
+
provider: providerSummary(provider),
|
|
64
|
+
active: active === null
|
|
65
|
+
? null
|
|
66
|
+
: {
|
|
67
|
+
request_id: active.requestId,
|
|
68
|
+
operation: active.operation,
|
|
69
|
+
elapsed_ms: active.elapsedMs,
|
|
70
|
+
timeout_ms: active.timeoutMs,
|
|
71
|
+
caller_state: active.callerState,
|
|
72
|
+
},
|
|
73
|
+
queued_requests: queuedRequests,
|
|
74
|
+
}));
|
|
75
|
+
const active = snapshots.flatMap((snapshot) => snapshot.active === null ? [] : [snapshot.active]);
|
|
76
|
+
return {
|
|
77
|
+
status: snapshots.length === 0
|
|
78
|
+
? "not_observed"
|
|
79
|
+
: active.some(({ callerState }) => callerState === "timed_out")
|
|
80
|
+
? "timed_out_busy"
|
|
81
|
+
: active.length > 0
|
|
82
|
+
? "busy"
|
|
83
|
+
: "idle",
|
|
84
|
+
providers,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
57
87
|
const providerSummary = ({ id, name, version, }) => ({ id, name, version });
|
|
58
88
|
const processLineageStatus = (snapshots) => snapshots.length === 0
|
|
59
89
|
? { status: "not_observed" }
|
|
@@ -91,6 +121,7 @@ const capabilityStatus = (descriptor) => ({
|
|
|
91
121
|
operation: descriptor.operation,
|
|
92
122
|
available: descriptor.available,
|
|
93
123
|
reason: descriptor.reason,
|
|
124
|
+
availability_code: descriptor.availabilityCode ?? null,
|
|
94
125
|
input_contract_version: descriptor.inputContractVersion,
|
|
95
126
|
output_contract_version: descriptor.outputContractVersion,
|
|
96
127
|
pagination: descriptor.pagination,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createEvidence, } from "../domain/evidence.js";
|
|
2
2
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
3
|
+
import { commitBrowserScenarioNormalization } from "../domain/browserScenarioNormalization.js";
|
|
3
4
|
/** Create Evidence v2 for a policy-scoped external browser observation. */
|
|
4
5
|
export const createBrowserEvidence = (operation, input, result, provider) => createEvidence(undefined, provider, {
|
|
5
6
|
predicateType: browserPredicate(operation),
|
|
@@ -20,6 +21,18 @@ export const createBrowserEvidence = (operation, input, result, provider) => cre
|
|
|
20
21
|
});
|
|
21
22
|
const browserParameters = (input) => {
|
|
22
23
|
if (!("cdp_endpoint" in input)) {
|
|
24
|
+
if ("before_scenario" in input &&
|
|
25
|
+
input.before_scenario !== undefined &&
|
|
26
|
+
input.after_scenario !== undefined)
|
|
27
|
+
return {
|
|
28
|
+
comparison_kind: "browser_scenario",
|
|
29
|
+
before_browser: input.before_scenario.browser,
|
|
30
|
+
after_browser: input.after_scenario.browser,
|
|
31
|
+
before_start_origin: input.before_scenario.scenario.start_origin,
|
|
32
|
+
after_start_origin: input.after_scenario.scenario.start_origin,
|
|
33
|
+
max_changes: input.max_changes,
|
|
34
|
+
normalization_sha256: commitBrowserScenarioNormalization(input.normalization).sha256,
|
|
35
|
+
};
|
|
23
36
|
if ("max_changes" in input)
|
|
24
37
|
return {
|
|
25
38
|
before_target_id: input.before.inspection.target.target_id,
|
|
@@ -74,6 +87,7 @@ const browserParameters = (input) => {
|
|
|
74
87
|
include_websocket_shapes: input.include_websocket_shapes,
|
|
75
88
|
include_script_sources: input.include_script_sources,
|
|
76
89
|
include_storage_keys: input.include_storage_keys,
|
|
90
|
+
include_storage_fingerprints: input.include_storage_fingerprints,
|
|
77
91
|
limits: input.limits,
|
|
78
92
|
...(input.include_script_sources && "source_capture_approved" in input
|
|
79
93
|
? {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createBrowserScenarioEvidence } from "./BrowserScenarioEvidence.js";
|
|
2
|
+
import { digestJson } from "./JavaScriptReplayPlanning.js";
|
|
3
|
+
import { AnalysisCapabilityUnavailableError, AnalysisProtocolError, PermissionRequiredError, } from "../domain/errors.js";
|
|
4
|
+
import { isLiteralLoopbackHostname } from "../domain/browserObservation.js";
|
|
5
|
+
import { err } from "../domain/result.js";
|
|
6
|
+
const OPERATION = "capture_browser_scenario";
|
|
7
|
+
const PROVIDER_ID = "rea-playwright-browser-scenario";
|
|
8
|
+
const networkScope = (origins) => origins.every((origin) => isLiteralLoopbackHostname(new URL(origin).hostname))
|
|
9
|
+
? "loopback"
|
|
10
|
+
: "external";
|
|
11
|
+
const authorizeScenario = async (authority, scenario) => {
|
|
12
|
+
if (authority === undefined)
|
|
13
|
+
return err(new AnalysisCapabilityUnavailableError(PROVIDER_ID, OPERATION, "browser scenario permission policy is not configured"));
|
|
14
|
+
const authorized = await authority.authorize({
|
|
15
|
+
capability: "browser_automate",
|
|
16
|
+
roots: [],
|
|
17
|
+
executables: scenario.browser.mode === "launch"
|
|
18
|
+
? [scenario.browser.executable_path]
|
|
19
|
+
: [],
|
|
20
|
+
environment_names: scenario.secrets.map(({ environment_variable: name }) => name),
|
|
21
|
+
origins: [
|
|
22
|
+
...(scenario.browser.mode === "connect"
|
|
23
|
+
? [scenario.browser.cdp_endpoint]
|
|
24
|
+
: []),
|
|
25
|
+
...scenario.allowed_origins,
|
|
26
|
+
],
|
|
27
|
+
network: networkScope(scenario.allowed_origins),
|
|
28
|
+
mount: false,
|
|
29
|
+
operation_identity: `${OPERATION}:${digestJson(scenario)}`,
|
|
30
|
+
}, "write");
|
|
31
|
+
if (!authorized.ok)
|
|
32
|
+
return err(authorized.error instanceof PermissionRequiredError
|
|
33
|
+
? authorized.error
|
|
34
|
+
: new AnalysisProtocolError(authorized.error.message, {
|
|
35
|
+
cause: authorized.error,
|
|
36
|
+
}));
|
|
37
|
+
return { ok: true, value: true };
|
|
38
|
+
};
|
|
39
|
+
/** Authorize and execute one controlled browser scenario. */
|
|
40
|
+
export const captureBrowserScenario = async (provider, authority, scenario, options = {}) => {
|
|
41
|
+
const authorized = await authorizeScenario(authority, scenario);
|
|
42
|
+
if (!authorized.ok)
|
|
43
|
+
return authorized;
|
|
44
|
+
if (provider === undefined)
|
|
45
|
+
return err(new AnalysisCapabilityUnavailableError(PROVIDER_ID, OPERATION, "browser scenario provider is not configured"));
|
|
46
|
+
const captured = await provider.captureScenario(scenario, options);
|
|
47
|
+
return captured.ok
|
|
48
|
+
? {
|
|
49
|
+
ok: true,
|
|
50
|
+
value: createBrowserScenarioEvidence(scenario, captured.value, provider.identity()),
|
|
51
|
+
}
|
|
52
|
+
: captured;
|
|
53
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { sanitizeBrowserUrl } from "../domain/browserObservation.js";
|
|
2
|
+
import { createEvidence, } from "../domain/evidence.js";
|
|
3
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
4
|
+
import { digestJson } from "./JavaScriptReplayPlanning.js";
|
|
5
|
+
const browserScenarioParameters = (scenario) => ({
|
|
6
|
+
scenario_sha256: digestJson(scenario),
|
|
7
|
+
browser_mode: scenario.browser.mode,
|
|
8
|
+
start_url: sanitizeBrowserUrl(scenario.start_url.url),
|
|
9
|
+
allowed_origins: scenario.allowed_origins,
|
|
10
|
+
actions: scenario.actions.map(({ step_id, action }) => ({
|
|
11
|
+
step_id,
|
|
12
|
+
action,
|
|
13
|
+
})),
|
|
14
|
+
secret_declarations: scenario.secrets.map(({ secret_id, environment_variable, purpose, redaction }) => ({
|
|
15
|
+
secret_id,
|
|
16
|
+
environment_variable,
|
|
17
|
+
purpose,
|
|
18
|
+
redaction,
|
|
19
|
+
})),
|
|
20
|
+
capture: scenario.capture,
|
|
21
|
+
limits: scenario.limits,
|
|
22
|
+
});
|
|
23
|
+
/** Create Evidence v2 without retaining resolved scenario secret values. */
|
|
24
|
+
export const createBrowserScenarioEvidence = (scenario, capture, provider) => createEvidence(undefined, provider, {
|
|
25
|
+
predicateType: "rea.browser-scenario-capture/v1",
|
|
26
|
+
operation: "capture_browser_scenario",
|
|
27
|
+
parameters: browserScenarioParameters(scenario),
|
|
28
|
+
result: jsonValueSchema.parse(capture),
|
|
29
|
+
confidence: "observed",
|
|
30
|
+
authority: "controlled-replay",
|
|
31
|
+
environment: {
|
|
32
|
+
id: `${capture.browser.product}@${capture.browser.version}`,
|
|
33
|
+
platform: process.platform,
|
|
34
|
+
architecture: process.arch,
|
|
35
|
+
isolation: capture.browser.process_ownership === "provider-owned"
|
|
36
|
+
? "process"
|
|
37
|
+
: "none",
|
|
38
|
+
},
|
|
39
|
+
limitations: capture.limitations,
|
|
40
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { projectAnalysisError } from "../domain/errors.js";
|
|
2
|
+
import { parseRelatedAddresses } from "../domain/hopperValues.js";
|
|
3
|
+
import { ok } from "../domain/result.js";
|
|
4
|
+
/** Trace one deterministic bounded direct caller/callee path. */
|
|
5
|
+
export const traceCallPath = async (call, input, signal) => {
|
|
6
|
+
const state = createTraceState(input);
|
|
7
|
+
while (state.queueIndex < state.queue.length &&
|
|
8
|
+
state.traversalPath.length === 0) {
|
|
9
|
+
const address = state.queue[state.queueIndex++];
|
|
10
|
+
if (address === undefined)
|
|
11
|
+
break;
|
|
12
|
+
const current = state.visited.get(address);
|
|
13
|
+
if (current === undefined)
|
|
14
|
+
continue;
|
|
15
|
+
if (current.depth >= input.max_depth) {
|
|
16
|
+
state.frontier.add(address);
|
|
17
|
+
state.residual.add("Call-path traversal reached the depth limit.");
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (state.operations >= input.max_operations) {
|
|
21
|
+
recordPendingFrontier(state, input);
|
|
22
|
+
state.residual.add("Call-path traversal reached the operation budget.");
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
state.operations += 1;
|
|
26
|
+
const found = await expandAddress(call, {
|
|
27
|
+
input,
|
|
28
|
+
state,
|
|
29
|
+
address,
|
|
30
|
+
current,
|
|
31
|
+
...(signal === undefined ? {} : { signal }),
|
|
32
|
+
});
|
|
33
|
+
if (found)
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
return ok(projectTrace(input, state));
|
|
37
|
+
};
|
|
38
|
+
const createTraceState = (input) => ({
|
|
39
|
+
visited: new Map([[input.start, { depth: 0, path: [input.start] }]]),
|
|
40
|
+
queue: [input.start],
|
|
41
|
+
edges: new Map(),
|
|
42
|
+
failures: [],
|
|
43
|
+
frontier: new Set(),
|
|
44
|
+
residual: new Set(),
|
|
45
|
+
operations: 0,
|
|
46
|
+
queueIndex: 0,
|
|
47
|
+
traversalPath: input.goal === input.start ? [input.start] : [],
|
|
48
|
+
});
|
|
49
|
+
const expandAddress = async (call, expansion) => {
|
|
50
|
+
const { input, state, address, current, signal } = expansion;
|
|
51
|
+
const relation = input.direction === "forward" ? "callees" : "callers";
|
|
52
|
+
const tool = input.direction === "forward" ? "procedure_callees" : "procedure_callers";
|
|
53
|
+
const result = await call(tool, { procedure: address }, signal);
|
|
54
|
+
if (!result.ok) {
|
|
55
|
+
state.failures.push({
|
|
56
|
+
address,
|
|
57
|
+
error: projectAnalysisError(result.error),
|
|
58
|
+
});
|
|
59
|
+
state.residual.add(`Call relationships were unavailable for ${address}.`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const related = parseRelatedAddresses(result.value, relation);
|
|
63
|
+
if (!related.ok) {
|
|
64
|
+
state.failures.push({
|
|
65
|
+
address,
|
|
66
|
+
error: projectAnalysisError(related.error),
|
|
67
|
+
});
|
|
68
|
+
state.residual.add(`Call relationships were unreadable for ${address}.`);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
for (const relatedAddress of sortedUniqueAddresses(related.value)) {
|
|
72
|
+
recordEdge(input, state, {
|
|
73
|
+
address,
|
|
74
|
+
relatedAddress,
|
|
75
|
+
discoveryDepth: current.depth + 1,
|
|
76
|
+
});
|
|
77
|
+
const path = state.visited.get(relatedAddress)?.path ?? [
|
|
78
|
+
...current.path,
|
|
79
|
+
relatedAddress,
|
|
80
|
+
];
|
|
81
|
+
if (state.visited.has(relatedAddress)) {
|
|
82
|
+
if (relatedAddress === input.goal) {
|
|
83
|
+
state.traversalPath = path;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (state.visited.size >= input.max_nodes) {
|
|
89
|
+
if (state.frontier.size < input.max_nodes)
|
|
90
|
+
state.frontier.add(relatedAddress);
|
|
91
|
+
state.residual.add("Call-path traversal reached the node limit.");
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
state.visited.set(relatedAddress, {
|
|
95
|
+
depth: current.depth + 1,
|
|
96
|
+
path,
|
|
97
|
+
});
|
|
98
|
+
if (relatedAddress === input.goal) {
|
|
99
|
+
state.traversalPath = path;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
state.queue.push(relatedAddress);
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
};
|
|
106
|
+
const recordEdge = (input, state, edge) => {
|
|
107
|
+
const sourceAddress = input.direction === "forward" ? edge.address : edge.relatedAddress;
|
|
108
|
+
const targetAddress = input.direction === "forward" ? edge.relatedAddress : edge.address;
|
|
109
|
+
const edgeKey = `${sourceAddress}\u0000${targetAddress}`;
|
|
110
|
+
if (state.edges.has(edgeKey))
|
|
111
|
+
return;
|
|
112
|
+
if (state.edges.size >= input.max_nodes) {
|
|
113
|
+
state.residual.add("Call-path traversal reached the edge limit.");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
state.edges.set(edgeKey, {
|
|
117
|
+
source_address: sourceAddress,
|
|
118
|
+
target_address: targetAddress,
|
|
119
|
+
discovery_depth: edge.discoveryDepth,
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
const recordPendingFrontier = (state, input) => {
|
|
123
|
+
for (const pending of state.queue.slice(state.queueIndex - 1))
|
|
124
|
+
if (state.frontier.size < input.max_nodes)
|
|
125
|
+
state.frontier.add(pending);
|
|
126
|
+
};
|
|
127
|
+
const projectTrace = (input, state) => ({
|
|
128
|
+
start: input.start,
|
|
129
|
+
goal: input.goal ?? null,
|
|
130
|
+
direction: input.direction,
|
|
131
|
+
goal_status: input.goal === undefined
|
|
132
|
+
? "not_requested"
|
|
133
|
+
: state.traversalPath.length > 0
|
|
134
|
+
? "reached"
|
|
135
|
+
: "not_reached",
|
|
136
|
+
nodes: [...state.visited.entries()]
|
|
137
|
+
.map(([address, { depth }]) => ({ address, depth }))
|
|
138
|
+
.sort((left, right) => left.depth - right.depth || compareAddress(left.address, right.address)),
|
|
139
|
+
edges: [...state.edges.values()].sort((left, right) => left.discovery_depth - right.discovery_depth ||
|
|
140
|
+
compareAddress(left.source_address, right.source_address) ||
|
|
141
|
+
compareAddress(left.target_address, right.target_address)),
|
|
142
|
+
traversal_path: state.traversalPath,
|
|
143
|
+
failures: state.failures,
|
|
144
|
+
frontier: [...state.frontier].sort(compareAddress),
|
|
145
|
+
limits: {
|
|
146
|
+
max_depth: input.max_depth,
|
|
147
|
+
max_nodes: input.max_nodes,
|
|
148
|
+
max_operations: input.max_operations,
|
|
149
|
+
nodes_visited: state.visited.size,
|
|
150
|
+
operations_used: state.operations,
|
|
151
|
+
},
|
|
152
|
+
truncated: state.residual.size > 0,
|
|
153
|
+
residual_unknowns: [...state.residual],
|
|
154
|
+
limitations: [
|
|
155
|
+
"Direct provider call relationships may omit unresolved indirect calls.",
|
|
156
|
+
"A not_reached goal is only absent from this bounded traversal, not from the complete program.",
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
const compareAddress = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
160
|
+
const sortedUniqueAddresses = (addresses) => [...new Set(addresses)].sort(compareAddress);
|
|
@@ -8,8 +8,21 @@ const statusSchema = z.object({
|
|
|
8
8
|
operation: z.string(),
|
|
9
9
|
available: z.boolean(),
|
|
10
10
|
reason: z.string().nullable(),
|
|
11
|
+
availability_code: z.string().nullable().optional(),
|
|
11
12
|
})),
|
|
12
13
|
});
|
|
14
|
+
const NO_CLIENT_FEATURES = {
|
|
15
|
+
elicitation_form: false,
|
|
16
|
+
elicitation_url: false,
|
|
17
|
+
roots: false,
|
|
18
|
+
sampling: false,
|
|
19
|
+
};
|
|
20
|
+
const CLIENT_FEATURE_REQUIREMENTS = {
|
|
21
|
+
capture_process_scenario: {
|
|
22
|
+
required: [],
|
|
23
|
+
optional: ["elicitation_form"],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
13
26
|
const ENHANCED_REQUIREMENTS = {
|
|
14
27
|
swift_classes: ["list_procedures"],
|
|
15
28
|
get_objc_classes: ["list_names"],
|
|
@@ -32,10 +45,17 @@ const ENHANCED_REQUIREMENTS = {
|
|
|
32
45
|
"resolve_containing_procedure",
|
|
33
46
|
],
|
|
34
47
|
};
|
|
35
|
-
/** Build stable per-operation availability
|
|
36
|
-
export const buildCapabilityInventory = (sessionStatus, policy) => {
|
|
48
|
+
/** Build stable per-operation availability for discovery and tool visibility. */
|
|
49
|
+
export const buildCapabilityInventory = (sessionStatus, policy, clientFeatures = NO_CLIENT_FEATURES) => {
|
|
37
50
|
const status = statusSchema.parse(sessionStatus);
|
|
38
|
-
const descriptors = new Map(status.capabilities.map((descriptor) => [
|
|
51
|
+
const descriptors = new Map(status.capabilities.map((descriptor) => [
|
|
52
|
+
descriptor.operation,
|
|
53
|
+
{
|
|
54
|
+
available: descriptor.available,
|
|
55
|
+
reason: descriptor.reason,
|
|
56
|
+
availability_code: descriptor.availability_code ?? null,
|
|
57
|
+
},
|
|
58
|
+
]));
|
|
39
59
|
return TOOL_CONTRACTS.map((contract) => {
|
|
40
60
|
const availability = availabilityFor({
|
|
41
61
|
name: contract.name,
|
|
@@ -45,12 +65,19 @@ export const buildCapabilityInventory = (sessionStatus, policy) => {
|
|
|
45
65
|
descriptors,
|
|
46
66
|
policy,
|
|
47
67
|
});
|
|
68
|
+
const clientRequirements = clientRequirementsFor(contract.name, clientFeatures);
|
|
69
|
+
const clientBlocked = clientRequirements.missing_required.length > 0;
|
|
48
70
|
return {
|
|
49
71
|
name: contract.name,
|
|
50
72
|
surface: contract.kind,
|
|
51
|
-
available: availability.reason === "available",
|
|
52
|
-
reason:
|
|
53
|
-
|
|
73
|
+
available: availability.reason === "available" && !clientBlocked,
|
|
74
|
+
reason: clientBlocked
|
|
75
|
+
? "client_capability_missing"
|
|
76
|
+
: availability.reason,
|
|
77
|
+
remediation: clientBlocked
|
|
78
|
+
? `Use an MCP client that supports: ${clientRequirements.missing_required.join(", ")}.`
|
|
79
|
+
: availability.remediation,
|
|
80
|
+
client_requirements: clientRequirements,
|
|
54
81
|
effects: { ...contract.effects },
|
|
55
82
|
annotations: {
|
|
56
83
|
read_only: contract.annotations.readOnlyHint ?? false,
|
|
@@ -61,6 +88,18 @@ export const buildCapabilityInventory = (sessionStatus, policy) => {
|
|
|
61
88
|
};
|
|
62
89
|
}).sort((left, right) => left.name.localeCompare(right.name));
|
|
63
90
|
};
|
|
91
|
+
const clientRequirementsFor = (name, clientFeatures) => {
|
|
92
|
+
const requirements = CLIENT_FEATURE_REQUIREMENTS[name] ?? {
|
|
93
|
+
required: [],
|
|
94
|
+
optional: [],
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
required: [...requirements.required],
|
|
98
|
+
optional: [...requirements.optional],
|
|
99
|
+
missing_required: requirements.required.filter((feature) => !clientFeatures[feature]),
|
|
100
|
+
missing_optional: requirements.optional.filter((feature) => !clientFeatures[feature]),
|
|
101
|
+
};
|
|
102
|
+
};
|
|
64
103
|
const availabilityFor = (context) => {
|
|
65
104
|
const javascriptApplication = javascriptApplicationAvailability(context);
|
|
66
105
|
if (javascriptApplication !== null)
|
|
@@ -86,6 +125,9 @@ const javascriptApplicationAvailability = ({ name, policy, }) => {
|
|
|
86
125
|
: null;
|
|
87
126
|
};
|
|
88
127
|
const policyAvailability = ({ name, kind, policy, }) => {
|
|
128
|
+
const browser = browserPolicyAvailability(name, kind, policy);
|
|
129
|
+
if (browser !== null)
|
|
130
|
+
return browser;
|
|
89
131
|
if (name === "capture_process_scenario" && !policy.processCaptureEnabled)
|
|
90
132
|
return {
|
|
91
133
|
reason: "policy_disabled",
|
|
@@ -113,26 +155,43 @@ const policyAvailability = ({ name, kind, policy, }) => {
|
|
|
113
155
|
reason: "policy_disabled",
|
|
114
156
|
remediation: "Configure or grant an evidence_read root.",
|
|
115
157
|
};
|
|
116
|
-
if (kind === "
|
|
117
|
-
return policy.
|
|
158
|
+
if (kind === "electron-provider")
|
|
159
|
+
return policy.electronObservationEnabled === true
|
|
118
160
|
? { reason: "available", remediation: null }
|
|
119
161
|
: {
|
|
120
162
|
reason: "policy_disabled",
|
|
121
|
-
remediation: "Enable
|
|
163
|
+
remediation: "Enable Electron observation and configure a loopback CDP endpoint and canonical file roots.",
|
|
122
164
|
};
|
|
123
|
-
if (kind === "
|
|
124
|
-
return policy.
|
|
165
|
+
if (kind === "runtime-provider")
|
|
166
|
+
return policy.v8InspectorObservationEnabled === true
|
|
125
167
|
? { reason: "available", remediation: null }
|
|
126
168
|
: {
|
|
127
169
|
reason: "policy_disabled",
|
|
128
|
-
remediation: "Enable
|
|
170
|
+
remediation: "Enable V8 Inspector observation and configure exact loopback endpoints plus canonical file roots or exact origins.",
|
|
129
171
|
};
|
|
130
172
|
if (kind === "session")
|
|
131
173
|
return { reason: "available", remediation: null };
|
|
132
174
|
return null;
|
|
133
175
|
};
|
|
176
|
+
const browserPolicyAvailability = (name, kind, policy) => {
|
|
177
|
+
if (name === "capture_browser_scenario")
|
|
178
|
+
return policy.browserScenarioEnabled === true
|
|
179
|
+
? { reason: "available", remediation: null }
|
|
180
|
+
: {
|
|
181
|
+
reason: "policy_disabled",
|
|
182
|
+
remediation: "Enable browser scenarios and configure exact executable roots or loopback CDP endpoints, page origins, and environment secret names.",
|
|
183
|
+
};
|
|
184
|
+
if (kind !== "browser-provider")
|
|
185
|
+
return null;
|
|
186
|
+
return policy.browserObservationEnabled === true
|
|
187
|
+
? { reason: "available", remediation: null }
|
|
188
|
+
: {
|
|
189
|
+
reason: "policy_disabled",
|
|
190
|
+
remediation: "Enable browser observation and configure exact CDP endpoint and page origins.",
|
|
191
|
+
};
|
|
192
|
+
};
|
|
134
193
|
const targetAvailability = ({ kind, targetKind, targetOpen, }) => {
|
|
135
|
-
if (!targetOpen)
|
|
194
|
+
if (!targetOpen && (kind === "official-proxy" || kind === "enhanced"))
|
|
136
195
|
return {
|
|
137
196
|
reason: "target_required",
|
|
138
197
|
remediation: "Call open_binary with a supported local target.",
|
|
@@ -151,13 +210,18 @@ const providerAvailability = ({ descriptors, kind, name, }) => {
|
|
|
151
210
|
if (kind === "enhanced")
|
|
152
211
|
return composedAvailability(name, descriptors);
|
|
153
212
|
const descriptor = descriptors.get(name);
|
|
213
|
+
if (descriptor === undefined &&
|
|
214
|
+
(kind === "artifact-provider" ||
|
|
215
|
+
kind === "managed-provider" ||
|
|
216
|
+
kind === "native-provider"))
|
|
217
|
+
return { reason: "available", remediation: null };
|
|
154
218
|
if (descriptor === undefined)
|
|
155
219
|
return {
|
|
156
220
|
reason: "provider_missing",
|
|
157
221
|
remediation: "Install or configure a provider that declares this operation.",
|
|
158
222
|
};
|
|
159
223
|
if (!descriptor.available &&
|
|
160
|
-
descriptor.
|
|
224
|
+
descriptor.availability_code === "unsupported_host")
|
|
161
225
|
return {
|
|
162
226
|
reason: "unsupported_host",
|
|
163
227
|
remediation: descriptor.reason,
|
|
@@ -187,7 +251,7 @@ const composedAvailability = (name, descriptors) => {
|
|
|
187
251
|
.find((descriptor) => descriptor?.available === false);
|
|
188
252
|
if (unavailable !== undefined)
|
|
189
253
|
return {
|
|
190
|
-
reason: unavailable.
|
|
254
|
+
reason: unavailable.availability_code === "unsupported_host"
|
|
191
255
|
? "unsupported_host"
|
|
192
256
|
: "provider_unavailable",
|
|
193
257
|
remediation: unavailable.reason ?? "Choose another target or configured provider.",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AnalysisCapabilityUnavailableError } from "../domain/errors.js";
|
|
2
2
|
import { err, ok } from "../domain/result.js";
|
|
3
|
+
import { closeAnalysisClient } from "./AnalysisClientCleanup.js";
|
|
3
4
|
/** Synthetic compatibility identity for a deterministic provider set. */
|
|
4
5
|
export const compositeProviderIdentity = (identities) => ({
|
|
5
6
|
id: `composite:${identities
|
|
@@ -61,6 +62,10 @@ export class CompositeProvider {
|
|
|
61
62
|
clients.set(provider, created);
|
|
62
63
|
return created;
|
|
63
64
|
};
|
|
65
|
+
const closeWithOutcome = async (options) => {
|
|
66
|
+
const outcomes = await Promise.all([...clients.entries()].map(([provider, client]) => closeAnalysisClient(client, provider.identity().id, options)));
|
|
67
|
+
return outcomes.find((outcome) => !outcome.ok) ?? ok(null);
|
|
68
|
+
};
|
|
64
69
|
return {
|
|
65
70
|
execute: (operation, parameters, options) => {
|
|
66
71
|
if (operation === "health")
|
|
@@ -80,8 +85,12 @@ export class CompositeProvider {
|
|
|
80
85
|
runtimeLineageSnapshots: () => [...clients.values()]
|
|
81
86
|
.flatMap((client) => client.runtimeLineageSnapshots?.() ?? [])
|
|
82
87
|
.sort((left, right) => left.provider.id.localeCompare(right.provider.id)),
|
|
88
|
+
requestActivitySnapshots: () => [...clients.values()]
|
|
89
|
+
.flatMap((client) => client.requestActivitySnapshots?.() ?? [])
|
|
90
|
+
.sort((left, right) => left.provider.id.localeCompare(right.provider.id)),
|
|
91
|
+
closeWithOutcome,
|
|
83
92
|
close: async () => {
|
|
84
|
-
await
|
|
93
|
+
await closeWithOutcome();
|
|
85
94
|
},
|
|
86
95
|
};
|
|
87
96
|
}
|
|
@@ -12,6 +12,7 @@ import { err, ok } from "../domain/result.js";
|
|
|
12
12
|
import { loadConfiguredPermissionAuthority } from "./PermissionConfiguration.js";
|
|
13
13
|
import { authorizeFileReadWithDeferredWrite, } from "./DeferredFileAuthorization.js";
|
|
14
14
|
import { REA_WORKFLOW_PROVIDER, workflowAnalysisProfile, } from "./InvestigationProviders.js";
|
|
15
|
+
import { artifactInspectionResultSchema } from "../domain/artifactInspection.js";
|
|
15
16
|
export { runCapabilityStatus, runProviderStatus, } from "./DirectAnalysisStatus.js";
|
|
16
17
|
/**
|
|
17
18
|
* Open one binary, execute one tool, and always release provider resources.
|
|
@@ -60,7 +61,7 @@ const authorizeAnalysis = async (authority, tool, arguments_) => {
|
|
|
60
61
|
cause: result.error,
|
|
61
62
|
}));
|
|
62
63
|
}
|
|
63
|
-
if (
|
|
64
|
+
if (["inventory_artifact", "inspect_artifact"].includes(tool) &&
|
|
64
65
|
arguments_.native_mount_approved === true) {
|
|
65
66
|
const result = await authority.authorize({
|
|
66
67
|
capability: "native_mount",
|
|
@@ -69,7 +70,7 @@ const authorizeAnalysis = async (authority, tool, arguments_) => {
|
|
|
69
70
|
environment_names: [],
|
|
70
71
|
network: "none",
|
|
71
72
|
mount: true,
|
|
72
|
-
operation_identity:
|
|
73
|
+
operation_identity: `${tool}:native_mount`,
|
|
73
74
|
}, "read");
|
|
74
75
|
if (!result.ok)
|
|
75
76
|
return err(result.error instanceof PermissionRequiredError
|
|
@@ -212,6 +213,10 @@ const executeAnalysisTool = async (input) => {
|
|
|
212
213
|
rawResult: result.value.rawResult,
|
|
213
214
|
limitations: result.value.limitations,
|
|
214
215
|
locations: result.value.locations,
|
|
216
|
+
evidenceLinks: tool === "inspect_artifact"
|
|
217
|
+
? artifactInspectionResultSchema.parse(result.value.result)
|
|
218
|
+
.evidence_links
|
|
219
|
+
: [],
|
|
215
220
|
});
|
|
216
221
|
return { output: evidence, evidence };
|
|
217
222
|
};
|