rea-agents 1.5.0 → 1.7.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 +144 -58
- package/bridge/ghidra/ReaGhidraBridge.java +2075 -0
- package/bridge/hopper_bridge.py +13 -3
- package/dist/application/AbortablePromise.js +25 -0
- package/dist/application/AnalysisProvider.js +3 -0
- package/dist/application/AnalysisProviderEvaluation.js +60 -0
- package/dist/application/AnalysisProviderRegistry.js +228 -0
- package/dist/application/AnalysisSnapshotCache.js +41 -21
- package/dist/application/AnalysisSnapshotFiles.js +5 -6
- package/dist/application/BinarySession.js +229 -102
- package/dist/application/BrowserEvidence.js +87 -15
- package/dist/application/BrowserObservationService.js +65 -1
- package/dist/application/CapabilityInventory.js +7 -0
- package/dist/application/ClientConfigPath.js +33 -0
- package/dist/application/CompositeProvider.js +20 -8
- package/dist/application/CrossVersionInventory.js +16 -3
- package/dist/application/CrossVersionInvestigation.js +17 -62
- package/dist/application/CrossVersionInvestigationEvidence.js +64 -0
- package/dist/application/CrossVersionInvestigationReplay.js +170 -0
- package/dist/application/DirectAnalysis.js +49 -35
- package/dist/application/Doctor.js +70 -13
- package/dist/application/ElectronEvidence.js +35 -0
- package/dist/application/ElectronObservationPort.js +1 -0
- package/dist/application/ElectronObservationService.js +46 -0
- package/dist/application/EnhancedTools.js +1 -1
- package/dist/application/EvidenceLedger.js +5 -2
- package/dist/application/InvestigationProviders.js +11 -0
- package/dist/application/JavaScriptArtifactAnalysis.js +299 -0
- package/dist/application/JavaScriptArtifactAnalysisTypes.js +1 -0
- package/dist/application/JavaScriptArtifactFiles.js +231 -0
- package/dist/application/JavaScriptArtifactGraphAccumulator.js +54 -0
- package/dist/application/JavaScriptArtifactGraphBuilder.js +101 -0
- package/dist/application/JavaScriptArtifactGraphContext.js +205 -0
- package/dist/application/JavaScriptArtifactGraphDocuments.js +108 -0
- package/dist/application/JavaScriptArtifactGraphEvidence.js +89 -0
- package/dist/application/JavaScriptArtifactGraphFindings.js +271 -0
- package/dist/application/JavaScriptArtifactGraphStructure.js +409 -0
- package/dist/application/JavaScriptArtifactReconstruction.js +74 -0
- package/dist/application/JavaScriptArtifactReconstructionInput.js +99 -0
- package/dist/application/MacHopper.js +20 -4
- package/dist/application/PermissionAuthority.js +30 -0
- package/dist/application/ProcessCaptureLifecycle.js +2 -6
- package/dist/application/ProcessHarness.js +13 -7
- package/dist/application/ProcessSampling.js +59 -8
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReferenceSourceImport.js +13 -16
- package/dist/application/SessionProviderRouter.js +219 -0
- package/dist/application/Setup.js +153 -70
- package/dist/application/SetupClients.js +8 -5
- package/dist/application/SetupInstallFailure.js +24 -0
- package/dist/application/SetupPlan.js +19 -8
- package/dist/application/SetupSkill.js +20 -3
- package/dist/application/SupportedClients.js +40 -16
- package/dist/application/Uninstall.js +20 -7
- package/dist/application/Upgrade.js +15 -4
- package/dist/application/runtime.js +9 -6
- package/dist/artifacts/AsarArtifactReader.js +23 -3
- package/dist/browser/CdpBrowserProvider.js +158 -53
- package/dist/browser/CdpCaptureCompleteness.js +90 -0
- package/dist/browser/CdpCaptureDocuments.js +180 -21
- package/dist/browser/CdpCaptureEvents.js +342 -43
- package/dist/browser/CdpCaptureValues.js +5 -7
- package/dist/browser/CdpConnection.js +47 -24
- package/dist/browser/CdpElectronInspection.js +330 -0
- package/dist/browser/CdpElectronProvider.js +136 -0
- package/dist/browser/CdpEndpoint.js +77 -5
- package/dist/browser/CdpObservationSession.js +301 -0
- package/dist/browser/CdpPageCapture.js +159 -56
- package/dist/browser/CdpSafeMetadata.js +203 -0
- package/dist/browser/CdpScreenshot.js +101 -0
- package/dist/browser/CdpTargetSession.js +47 -0
- package/dist/browser/CdpWebMcpDiscovery.js +242 -0
- package/dist/browser/ElectronFileScope.js +50 -0
- package/dist/browser/PngVisualDiff.js +177 -0
- package/dist/browser/SensitiveTextCapture.js +25 -0
- package/dist/browser/WebSourceMapFetcher.js +357 -0
- package/dist/catalogIdentity.js +2 -33
- package/dist/cli.js +62 -34
- package/dist/cliBrowserAdvancedCommands.js +170 -0
- package/dist/cliBrowserCommands.js +180 -3
- package/dist/cliCommandNames.js +43 -0
- package/dist/cliElectronCommands.js +115 -0
- package/dist/cliEvidenceCommands.js +4 -3
- package/dist/cliInvestigationCommands.js +17 -9
- package/dist/cliPolicyCommands.js +49 -3
- package/dist/cliProcessCommands.js +3 -2
- package/dist/config.js +58 -7
- package/dist/contracts/browserToolContracts.js +304 -3
- package/dist/contracts/electronToolContracts.js +73 -0
- package/dist/contracts/enhancedInputs.js +2 -2
- package/dist/contracts/providerSelection.js +28 -0
- package/dist/contracts/sessionLifecycleInputs.js +2 -0
- package/dist/contracts/toolContracts.js +24 -20
- package/dist/contracts/toolOutputSchemas.js +117 -22
- package/dist/doctorRuntime.js +6 -0
- package/dist/domain/analysisProfile.js +78 -0
- package/dist/domain/analysisSnapshot.js +92 -46
- package/dist/domain/artifactGraph.js +1 -0
- package/dist/domain/binaryTarget.js +4 -29
- package/dist/domain/browserCompleteness.js +101 -0
- package/dist/domain/browserObservation.js +240 -14
- package/dist/domain/browserSession.js +63 -0
- package/dist/domain/electronObservation.js +162 -0
- package/dist/domain/errors.js +81 -6
- package/dist/domain/evidence.js +44 -5
- package/dist/domain/evidenceBundle.js +9 -7
- package/dist/domain/functionComparison.js +9 -3
- package/dist/domain/functionComparisonNormalization.js +37 -0
- package/dist/domain/functionDossierEvidence.js +4 -1
- package/dist/domain/hopperValues.js +66 -9
- package/dist/domain/investigationWorkspace.js +6 -1
- package/dist/domain/javascriptApplicationEvidenceSchemas.js +361 -0
- package/dist/domain/javascriptApplicationGraph.js +281 -0
- package/dist/domain/javascriptApplicationGraphSchemas.js +132 -0
- package/dist/domain/javascriptAstFingerprint.js +143 -0
- package/dist/domain/javascriptStaticAnalysis.js +358 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +264 -0
- package/dist/domain/javascriptStaticAnalysisState.js +17 -0
- package/dist/domain/javascriptStaticAnalysisTypes.js +1 -0
- package/dist/domain/jsonShape.js +125 -0
- package/dist/domain/referenceSourceGraph.js +3 -1
- package/dist/domain/webBundleAnalysis.js +195 -0
- package/dist/domain/webBundleAnalyzer.js +423 -0
- package/dist/domain/webCaptureDiff.js +184 -0
- package/dist/domain/webContentArtifact.js +47 -0
- package/dist/domain/webInventory.js +61 -0
- package/dist/domain/webMcpDiscovery.js +68 -0
- package/dist/domain/webScreenshot.js +114 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraAnalysisProfile.js +37 -0
- package/dist/ghidra/GhidraClient.js +400 -0
- package/dist/ghidra/GhidraClientTypes.js +1 -0
- package/dist/ghidra/GhidraDefaults.js +20 -0
- package/dist/ghidra/GhidraDiagnostics.js +43 -0
- package/dist/ghidra/GhidraDoctor.js +59 -0
- package/dist/ghidra/GhidraFunctionValues.js +264 -0
- package/dist/ghidra/GhidraInstallation.js +239 -0
- package/dist/ghidra/GhidraInventoryValues.js +217 -0
- package/dist/ghidra/GhidraLauncher.js +139 -0
- package/dist/ghidra/GhidraProvider.js +360 -0
- package/dist/ghidra/GhidraRequestQueue.js +98 -0
- package/dist/ghidra/GhidraResponseBuffer.js +31 -0
- package/dist/ghidra/GhidraResponseRouter.js +24 -0
- package/dist/ghidra/GhidraSessionError.js +39 -0
- package/dist/ghidra/GhidraSessionValues.js +62 -0
- package/dist/ghidra/GhidraSocketConnection.js +46 -0
- package/dist/ghidra/protocol.js +69 -0
- package/dist/hopper/BridgeLauncher.js +21 -57
- package/dist/hopper/HopperAnalysisProfile.js +106 -0
- package/dist/hopper/HopperClient.js +197 -165
- package/dist/hopper/HopperProvider.js +105 -8
- package/dist/hopper/HopperSessionValues.js +27 -0
- package/dist/hopper/HopperSocketConnection.js +31 -0
- package/dist/main.js +36 -31
- package/dist/native/NativeMacOSProvider.js +6 -3
- package/dist/native/NativeMachoInspection.js +21 -6
- package/dist/process/PendingOperations.js +59 -0
- package/dist/process/PrivateRuntimeRoot.js +33 -0
- package/dist/{application → process}/ProcessOwnership.js +40 -20
- package/dist/process/ProviderDeadline.js +95 -0
- package/dist/process/ProviderProcess.js +275 -0
- package/dist/server/createServer.js +8 -0
- package/dist/server/promptCompletion.js +9 -3
- package/dist/server/registerBrowserTools.js +61 -2
- package/dist/server/registerElectronTools.js +36 -0
- package/dist/server/registerEnhancedTools.js +9 -6
- package/dist/server/registerInvestigationTools.js +11 -12
- package/dist/server/registerOfficialTools.js +3 -0
- package/dist/server/registerSessionTools.js +7 -1
- package/package.json +12 -6
- package/scripts/rea.mjs +37 -3
- package/skills/rea-analysis/SKILL.md +19 -9
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isLiteralLoopbackHostname, } from "../domain/browserObservation.js";
|
|
1
2
|
import { AnalysisCapabilityUnavailableError, AnalysisProtocolError, PermissionRequiredError, } from "../domain/errors.js";
|
|
2
3
|
import { err, ok } from "../domain/result.js";
|
|
3
4
|
import { createBrowserEvidence } from "./BrowserEvidence.js";
|
|
@@ -21,6 +22,66 @@ export const inspectWebPage = async (browser, authority, input, options = {}) =>
|
|
|
21
22
|
? ok(createBrowserEvidence("inspect_web_page", input, result.value, ready.value.identity()))
|
|
22
23
|
: result;
|
|
23
24
|
};
|
|
25
|
+
/** Capture approved sources and derive bounded static web-bundle evidence. */
|
|
26
|
+
export const analyzeWebBundle = async (browser, authority, input, options = {}) => {
|
|
27
|
+
const ready = await prepare(browser, authority, input, "analyze_web_bundle");
|
|
28
|
+
if (!ready.ok)
|
|
29
|
+
return ready;
|
|
30
|
+
const analyzed = await ready.value.analyzeBundle(input, options);
|
|
31
|
+
if (!analyzed.ok)
|
|
32
|
+
return analyzed;
|
|
33
|
+
return ok(createBrowserEvidence("analyze_web_bundle", input, analyzed.value, ready.value.identity()));
|
|
34
|
+
};
|
|
35
|
+
/** Observe navigation caused by external user actions during one armed window. */
|
|
36
|
+
export const observeWebSession = async (browser, authority, input, options = {}) => {
|
|
37
|
+
const ready = await prepare(browser, authority, input, "observe_web_session");
|
|
38
|
+
if (!ready.ok)
|
|
39
|
+
return ready;
|
|
40
|
+
const observed = await ready.value.observeSession(input, options);
|
|
41
|
+
return observed.ok
|
|
42
|
+
? ok(createBrowserEvidence("observe_web_session", input, observed.value, ready.value.identity()))
|
|
43
|
+
: observed;
|
|
44
|
+
};
|
|
45
|
+
/** Discover WebMCP declarations without exposing an invocation surface. */
|
|
46
|
+
export const discoverWebMcpTools = async (browser, authority, input, options = {}) => {
|
|
47
|
+
const ready = await prepare(browser, authority, input, "discover_webmcp_tools");
|
|
48
|
+
if (!ready.ok)
|
|
49
|
+
return ready;
|
|
50
|
+
const discovered = await ready.value.discoverWebMcpTools(input, options);
|
|
51
|
+
return discovered.ok
|
|
52
|
+
? ok(createBrowserEvidence("discover_webmcp_tools", input, discovered.value, ready.value.identity()))
|
|
53
|
+
: discovered;
|
|
54
|
+
};
|
|
55
|
+
/** Compare two already-normalized web captures without external access. */
|
|
56
|
+
export const compareWebCaptureEvidence = async (browser, input) => {
|
|
57
|
+
const ready = requireBrowser(browser, "compare_web_captures");
|
|
58
|
+
if (!ready.ok)
|
|
59
|
+
return ready;
|
|
60
|
+
const compared = await ready.value.compareCaptures(input);
|
|
61
|
+
return compared.ok
|
|
62
|
+
? ok(createBrowserEvidence("compare_web_captures", input, compared.value, ready.value.identity()))
|
|
63
|
+
: compared;
|
|
64
|
+
};
|
|
65
|
+
/** Capture one explicitly approved visible-viewport screenshot. */
|
|
66
|
+
export const captureWebScreenshot = async (browser, authority, input, options = {}) => {
|
|
67
|
+
const ready = await prepare(browser, authority, input, "capture_web_screenshot");
|
|
68
|
+
if (!ready.ok)
|
|
69
|
+
return ready;
|
|
70
|
+
const captured = await ready.value.captureScreenshot(input, options);
|
|
71
|
+
return captured.ok
|
|
72
|
+
? ok(createBrowserEvidence("capture_web_screenshot", input, captured.value, ready.value.identity()))
|
|
73
|
+
: captured;
|
|
74
|
+
};
|
|
75
|
+
/** Compare two self-verifying PNG artifacts without external access. */
|
|
76
|
+
export const compareWebScreenshotEvidence = async (browser, input) => {
|
|
77
|
+
const ready = requireBrowser(browser, "compare_web_screenshots");
|
|
78
|
+
if (!ready.ok)
|
|
79
|
+
return ready;
|
|
80
|
+
const compared = await ready.value.compareScreenshots(input);
|
|
81
|
+
return compared.ok
|
|
82
|
+
? ok(createBrowserEvidence("compare_web_screenshots", input, compared.value, ready.value.identity()))
|
|
83
|
+
: compared;
|
|
84
|
+
};
|
|
24
85
|
const prepare = async (browser, authority, input, operation) => {
|
|
25
86
|
if (authority === undefined)
|
|
26
87
|
return err(new AnalysisCapabilityUnavailableError("rea-cdp-browser", operation, "browser observation permission policy is not configured"));
|
|
@@ -44,9 +105,12 @@ const prepare = async (browser, authority, input, operation) => {
|
|
|
44
105
|
? err(new AnalysisCapabilityUnavailableError("rea-cdp-browser", operation, "browser observation provider is not configured"))
|
|
45
106
|
: ok(browser);
|
|
46
107
|
};
|
|
108
|
+
const requireBrowser = (browser, operation) => browser === undefined
|
|
109
|
+
? err(new AnalysisCapabilityUnavailableError("rea-cdp-browser", operation, "browser comparison provider is not configured"))
|
|
110
|
+
: ok(browser);
|
|
47
111
|
const browserNetworkScope = (allowedOrigins) => allowedOrigins.every((origin) => {
|
|
48
112
|
const hostname = new URL(origin).hostname;
|
|
49
|
-
return hostname
|
|
113
|
+
return isLiteralLoopbackHostname(hostname);
|
|
50
114
|
})
|
|
51
115
|
? "loopback"
|
|
52
116
|
: "external";
|
|
@@ -71,6 +71,13 @@ const availabilityFor = (name, kind, targetOpen, targetKind, descriptors, policy
|
|
|
71
71
|
reason: "policy_disabled",
|
|
72
72
|
remediation: "Enable browser observation and configure exact CDP endpoint and page origins.",
|
|
73
73
|
};
|
|
74
|
+
if (kind === "electron-provider")
|
|
75
|
+
return policy.electronObservationEnabled === true
|
|
76
|
+
? { reason: "available", remediation: null }
|
|
77
|
+
: {
|
|
78
|
+
reason: "policy_disabled",
|
|
79
|
+
remediation: "Enable Electron observation and configure a loopback CDP endpoint and canonical file roots.",
|
|
80
|
+
};
|
|
74
81
|
if (kind === "session")
|
|
75
82
|
return { reason: "available", remediation: null };
|
|
76
83
|
if (!targetOpen)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
2
|
+
const systemFileSystem = { lstat, realpath };
|
|
3
|
+
/**
|
|
4
|
+
* Resolve an existing, user-owned config symlink to one stable transaction
|
|
5
|
+
* path. Missing regular paths remain eligible for first-run creation.
|
|
6
|
+
*/
|
|
7
|
+
export const resolveClientConfigTransactionPath = async (requestedPath, fileSystem = systemFileSystem) => {
|
|
8
|
+
let requestedStats;
|
|
9
|
+
try {
|
|
10
|
+
requestedStats = await fileSystem.lstat(requestedPath);
|
|
11
|
+
}
|
|
12
|
+
catch (cause) {
|
|
13
|
+
return isMissing(cause) ? requestedPath : undefined;
|
|
14
|
+
}
|
|
15
|
+
if (!requestedStats.isSymbolicLink())
|
|
16
|
+
return requestedPath;
|
|
17
|
+
const currentUid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
18
|
+
if (currentUid === undefined ||
|
|
19
|
+
requestedStats.uid !== currentUid ||
|
|
20
|
+
fileSystem.realpath === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
try {
|
|
23
|
+
const canonicalPath = await fileSystem.realpath(requestedPath);
|
|
24
|
+
const targetStats = await fileSystem.lstat(canonicalPath);
|
|
25
|
+
return targetStats.isFile?.() === true && targetStats.uid === currentUid
|
|
26
|
+
? canonicalPath
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const isMissing = (cause) => cause instanceof Error && "code" in cause && cause.code === "ENOENT";
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnalysisCapabilityUnavailableError } from "../domain/errors.js";
|
|
2
2
|
import { err, ok } from "../domain/result.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/** Synthetic compatibility identity for a deterministic provider set. */
|
|
4
|
+
export const compositeProviderIdentity = (identities) => ({
|
|
5
|
+
id: `composite:${identities
|
|
6
|
+
.map(({ id }) => id)
|
|
6
7
|
.sort()
|
|
7
8
|
.join("+")}`,
|
|
8
9
|
name: "REA composite analysis provider",
|
|
@@ -14,11 +15,16 @@ export class CompositeProvider {
|
|
|
14
15
|
#identity;
|
|
15
16
|
#capabilities;
|
|
16
17
|
#providerByOperation;
|
|
18
|
+
#profileProvider;
|
|
17
19
|
constructor(providers) {
|
|
18
20
|
this.providers = providers;
|
|
19
21
|
if (providers.length === 0)
|
|
20
22
|
throw new RangeError("CompositeProvider requires at least one provider");
|
|
21
|
-
|
|
23
|
+
const profileProviders = providers.filter(({ resolveAnalysisProfile }) => resolveAnalysisProfile !== undefined);
|
|
24
|
+
if (profileProviders.length > 1)
|
|
25
|
+
throw new TypeError("CompositeProvider supports at most one target-bound analysis profile");
|
|
26
|
+
this.#profileProvider = profileProviders[0];
|
|
27
|
+
this.#identity = Object.freeze(compositeProviderIdentity(providers.map((provider) => provider.identity())));
|
|
22
28
|
const routes = new Map();
|
|
23
29
|
const capabilities = [];
|
|
24
30
|
for (const provider of providers) {
|
|
@@ -39,13 +45,19 @@ export class CompositeProvider {
|
|
|
39
45
|
capabilities() {
|
|
40
46
|
return this.#capabilities;
|
|
41
47
|
}
|
|
42
|
-
|
|
48
|
+
resolveAnalysisProfile(target, options) {
|
|
49
|
+
const resolve = this.#profileProvider?.resolveAnalysisProfile;
|
|
50
|
+
return resolve === undefined
|
|
51
|
+
? Promise.resolve(ok({ profile: null, compatibility: {} }))
|
|
52
|
+
: resolve.call(this.#profileProvider, target, options);
|
|
53
|
+
}
|
|
54
|
+
createClient(target, profile) {
|
|
43
55
|
const clients = new Map();
|
|
44
56
|
const clientFor = (provider) => {
|
|
45
57
|
const existing = clients.get(provider);
|
|
46
58
|
if (existing !== undefined)
|
|
47
59
|
return existing;
|
|
48
|
-
const created = provider.createClient(target);
|
|
60
|
+
const created = provider.createClient(target, profile?.provider.id === provider.identity().id ? profile : undefined);
|
|
49
61
|
clients.set(provider, created);
|
|
50
62
|
return created;
|
|
51
63
|
};
|
|
@@ -62,7 +74,7 @@ export class CompositeProvider {
|
|
|
62
74
|
}));
|
|
63
75
|
const provider = this.#providerByOperation.get(operation);
|
|
64
76
|
return provider === undefined
|
|
65
|
-
? Promise.resolve(err(new
|
|
77
|
+
? Promise.resolve(err(new AnalysisCapabilityUnavailableError(this.#identity.id, operation, "operation is not declared by this provider set")))
|
|
66
78
|
: clientFor(provider).execute(operation, parameters, options);
|
|
67
79
|
},
|
|
68
80
|
close: async () => {
|
|
@@ -7,6 +7,14 @@ import { paginateArtifactInventory, } from "./ArtifactInventory.js";
|
|
|
7
7
|
import { scanAuthorizedArtifactInventory } from "./AuthorizedArtifactInventory.js";
|
|
8
8
|
import { ARTIFACT_GRAPH_PROVIDER } from "./InvestigationProviders.js";
|
|
9
9
|
export const AUTOMATIC_RUN_LIMITATION = "Automatic cross-version runs compare shipped artifact structure only; they do not execute either version.";
|
|
10
|
+
/** Start both independent version scans while preserving left/right identity. */
|
|
11
|
+
export const scanVersionInventories = async (options, scanner = scanAuthorizedArtifactInventory) => {
|
|
12
|
+
const [left, right] = await Promise.all([
|
|
13
|
+
scanner(options.leftPath, options.inputRoots, options.limits, options.signal, options.integrity),
|
|
14
|
+
scanner(options.rightPath, options.inputRoots, options.limits, options.signal, options.integrity),
|
|
15
|
+
]);
|
|
16
|
+
return { left, right };
|
|
17
|
+
};
|
|
10
18
|
/** Authorize and scan both inputs once under the same bounded traversal policy. */
|
|
11
19
|
export const scanVersions = async (input, inputRoots, signal, integrityContinueEnabled = false) => {
|
|
12
20
|
try {
|
|
@@ -19,9 +27,14 @@ export const scanVersions = async (input, inputRoots, signal, integrityContinueE
|
|
|
19
27
|
enabled: integrityContinueEnabled,
|
|
20
28
|
maxMismatches: input.max_integrity_mismatches,
|
|
21
29
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
return ok(await scanVersionInventories({
|
|
31
|
+
leftPath: input.left_path,
|
|
32
|
+
rightPath: input.right_path,
|
|
33
|
+
inputRoots,
|
|
34
|
+
limits,
|
|
35
|
+
...(signal === undefined ? {} : { signal }),
|
|
36
|
+
integrity,
|
|
37
|
+
}));
|
|
25
38
|
}
|
|
26
39
|
catch (cause) {
|
|
27
40
|
if (cause instanceof ArtifactReaderFailure)
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { findChangedBehavior } from "../domain/changedBehavior.js";
|
|
3
|
-
import { createEvidence } from "../domain/evidence.js";
|
|
1
|
+
import {} from "../domain/evidence.js";
|
|
4
2
|
import { createEvidenceBundle, } from "../domain/evidenceBundle.js";
|
|
5
3
|
import { AnalysisCancelledError, AnalysisInputError, EvidenceIntegrityError, InvestigationWorkspaceError, } from "../domain/errors.js";
|
|
6
|
-
import { createLegacyInvestigationRunIdentity, createInvestigationRunIdentity, createInvestigationWorkspace, crossVersionInvestigationInputSchema, investigationRunSchema,
|
|
7
|
-
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
4
|
+
import { createLegacyInvestigationRunIdentity, createInvestigationRunIdentity, createInvestigationWorkspace, crossVersionInvestigationInputSchema, investigationRunSchema, reviseInvestigationWorkspace, } from "../domain/investigationWorkspace.js";
|
|
8
5
|
import { err, ok } from "../domain/result.js";
|
|
9
|
-
import {
|
|
6
|
+
import { createInventoryEvidencePages, runLimitations, scanVersions, targetFor, } from "./CrossVersionInventory.js";
|
|
10
7
|
import { readInvestigationWorkspace, writeInvestigationWorkspace, } from "./InvestigationWorkspaceStore.js";
|
|
11
|
-
import {
|
|
8
|
+
import { createArtifactComparisonEvidence, createChangedBehaviorEvidence, } from "./CrossVersionInvestigationEvidence.js";
|
|
9
|
+
import { selectCompletedInvestigationReplay } from "./CrossVersionInvestigationReplay.js";
|
|
12
10
|
/** Run or resume a deterministic cross-version artifact investigation. */
|
|
13
11
|
export const runCrossVersionInvestigation = async (input, policy, execution) => {
|
|
14
12
|
const parsed = crossVersionInvestigationInputSchema.safeParse(input);
|
|
@@ -24,6 +22,17 @@ export const runCrossVersionInvestigation = async (input, policy, execution) =>
|
|
|
24
22
|
return preflight;
|
|
25
23
|
if (isCancelled(execution.signal))
|
|
26
24
|
return err(new AnalysisCancelledError("find_changed_behavior"));
|
|
25
|
+
if (parsed.data.replay_run_id !== undefined) {
|
|
26
|
+
const replay = selectCompletedInvestigationReplay(initial.value, parsed.data);
|
|
27
|
+
if (!replay.ok)
|
|
28
|
+
return replay;
|
|
29
|
+
return completeOutcome(replay.value.workspace, replay.value.run, true, execution.session);
|
|
30
|
+
}
|
|
31
|
+
if (execution.authorizeInputRead !== undefined) {
|
|
32
|
+
const authorized = await execution.authorizeInputRead();
|
|
33
|
+
if (!authorized.ok)
|
|
34
|
+
return authorized;
|
|
35
|
+
}
|
|
27
36
|
await execution.progress?.report({
|
|
28
37
|
phase: "scan_versions",
|
|
29
38
|
completed: 0,
|
|
@@ -198,31 +207,11 @@ const ensureComparisonCheckpoint = async (input, state, policy) => {
|
|
|
198
207
|
});
|
|
199
208
|
return saved.ok ? ok({ workspace: saved.value, run }) : saved;
|
|
200
209
|
};
|
|
201
|
-
const createArtifactComparisonEvidence = (inventory, limit) => {
|
|
202
|
-
const comparison = compareArtifacts(inventory.left, inventory.right, 0, limit);
|
|
203
|
-
const leftIds = evidenceIds(inventory.left);
|
|
204
|
-
const rightIds = evidenceIds(inventory.right);
|
|
205
|
-
return createEvidence(undefined, ARTIFACT_COMPARISON_PROVIDER, {
|
|
206
|
-
predicateType: "rea.artifact-comparison/v1",
|
|
207
|
-
operation: "compare_artifacts",
|
|
208
|
-
parameters: {
|
|
209
|
-
left_evidence_ids: leftIds,
|
|
210
|
-
right_evidence_ids: rightIds,
|
|
211
|
-
offset: 0,
|
|
212
|
-
limit,
|
|
213
|
-
},
|
|
214
|
-
result: jsonValueSchema.parse(comparison),
|
|
215
|
-
confidence: "derived",
|
|
216
|
-
authority: "analyst-inference",
|
|
217
|
-
limitations: comparison.limitations,
|
|
218
|
-
evidenceLinks: [...leftIds, ...rightIds],
|
|
219
|
-
});
|
|
220
|
-
};
|
|
221
210
|
const finalizeInvestigation = async (input, state, policy, session) => {
|
|
222
211
|
const comparison = comparisonEvidenceFor(state.workspace, state.run);
|
|
223
212
|
if (!comparison.ok)
|
|
224
213
|
return comparison;
|
|
225
|
-
const evidence = createChangedBehaviorEvidence(state, comparison.value);
|
|
214
|
+
const evidence = createChangedBehaviorEvidence(state.workspace, state.run, comparison.value);
|
|
226
215
|
const run = investigationRunSchema.parse({
|
|
227
216
|
...state.run,
|
|
228
217
|
status: "complete",
|
|
@@ -244,40 +233,6 @@ const finalizeInvestigation = async (input, state, policy, session) => {
|
|
|
244
233
|
});
|
|
245
234
|
return saved.ok ? completeOutcome(saved.value, run, false, session) : saved;
|
|
246
235
|
};
|
|
247
|
-
const createChangedBehaviorEvidence = (state, comparison) => {
|
|
248
|
-
const limit = Math.min(state.run.options.change_limit, 100);
|
|
249
|
-
const changed = findChangedBehavior([comparison], 0, limit);
|
|
250
|
-
const summary = investigationRunSummarySchema.parse({
|
|
251
|
-
schema_version: 1,
|
|
252
|
-
workspace_id: state.workspace.workspace_id,
|
|
253
|
-
run_id: state.run.run_id,
|
|
254
|
-
left_manifest_id: state.run.left.manifest_id,
|
|
255
|
-
right_manifest_id: state.run.right.manifest_id,
|
|
256
|
-
inventory_evidence_count: state.run.left_inventory_evidence_ids.length +
|
|
257
|
-
state.run.right_inventory_evidence_ids.length,
|
|
258
|
-
comparison_evidence_id: comparison.evidence_id,
|
|
259
|
-
limitations: state.run.limitations,
|
|
260
|
-
});
|
|
261
|
-
return createEvidence(undefined, CHANGED_BEHAVIOR_PROVIDER, {
|
|
262
|
-
predicateType: "rea.changed-behavior/v1",
|
|
263
|
-
operation: "find_changed_behavior",
|
|
264
|
-
parameters: {
|
|
265
|
-
workspace_id: state.workspace.workspace_id,
|
|
266
|
-
run_id: state.run.run_id,
|
|
267
|
-
comparison_evidence_ids: [comparison.evidence_id],
|
|
268
|
-
offset: 0,
|
|
269
|
-
limit,
|
|
270
|
-
},
|
|
271
|
-
result: jsonValueSchema.parse({
|
|
272
|
-
...changed,
|
|
273
|
-
investigation_run: summary,
|
|
274
|
-
}),
|
|
275
|
-
confidence: "derived",
|
|
276
|
-
authority: "analyst-inference",
|
|
277
|
-
limitations: [...changed.limitations, AUTOMATIC_RUN_LIMITATION],
|
|
278
|
-
evidenceLinks: changed.evidence_links,
|
|
279
|
-
});
|
|
280
|
-
};
|
|
281
236
|
const comparisonEvidenceFor = (workspace, run) => {
|
|
282
237
|
const evidence = workspace.bundle.records.find(({ evidence_id: id }) => id === run.comparison_evidence_id);
|
|
283
238
|
return evidence === undefined
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { compareArtifacts } from "../domain/artifactComparison.js";
|
|
2
|
+
import { findChangedBehavior } from "../domain/changedBehavior.js";
|
|
3
|
+
import { createEvidence } from "../domain/evidence.js";
|
|
4
|
+
import { investigationRunSummarySchema, } from "../domain/investigationWorkspace.js";
|
|
5
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
6
|
+
import { AUTOMATIC_RUN_LIMITATION } from "./CrossVersionInventory.js";
|
|
7
|
+
import { ARTIFACT_COMPARISON_PROVIDER, CHANGED_BEHAVIOR_PROVIDER, } from "./InvestigationProviders.js";
|
|
8
|
+
/** Derive deterministic artifact-comparison Evidence from inventory pages. */
|
|
9
|
+
export const createArtifactComparisonEvidence = (inventory, limit) => {
|
|
10
|
+
const comparison = compareArtifacts(inventory.left, inventory.right, 0, limit);
|
|
11
|
+
const leftIds = evidenceIds(inventory.left);
|
|
12
|
+
const rightIds = evidenceIds(inventory.right);
|
|
13
|
+
return createEvidence(undefined, ARTIFACT_COMPARISON_PROVIDER, {
|
|
14
|
+
predicateType: "rea.artifact-comparison/v1",
|
|
15
|
+
operation: "compare_artifacts",
|
|
16
|
+
parameters: {
|
|
17
|
+
left_evidence_ids: leftIds,
|
|
18
|
+
right_evidence_ids: rightIds,
|
|
19
|
+
offset: 0,
|
|
20
|
+
limit,
|
|
21
|
+
},
|
|
22
|
+
result: jsonValueSchema.parse(comparison),
|
|
23
|
+
confidence: "derived",
|
|
24
|
+
authority: "analyst-inference",
|
|
25
|
+
limitations: comparison.limitations,
|
|
26
|
+
evidenceLinks: [...leftIds, ...rightIds],
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
/** Derive the deterministic final report for one comparison checkpoint. */
|
|
30
|
+
export const createChangedBehaviorEvidence = (workspace, run, comparison) => {
|
|
31
|
+
const limit = Math.min(run.options.change_limit, 100);
|
|
32
|
+
const changed = findChangedBehavior([comparison], 0, limit);
|
|
33
|
+
const summary = investigationRunSummarySchema.parse({
|
|
34
|
+
schema_version: 1,
|
|
35
|
+
workspace_id: workspace.workspace_id,
|
|
36
|
+
run_id: run.run_id,
|
|
37
|
+
left_manifest_id: run.left.manifest_id,
|
|
38
|
+
right_manifest_id: run.right.manifest_id,
|
|
39
|
+
inventory_evidence_count: run.left_inventory_evidence_ids.length +
|
|
40
|
+
run.right_inventory_evidence_ids.length,
|
|
41
|
+
comparison_evidence_id: comparison.evidence_id,
|
|
42
|
+
limitations: run.limitations,
|
|
43
|
+
});
|
|
44
|
+
return createEvidence(undefined, CHANGED_BEHAVIOR_PROVIDER, {
|
|
45
|
+
predicateType: "rea.changed-behavior/v1",
|
|
46
|
+
operation: "find_changed_behavior",
|
|
47
|
+
parameters: {
|
|
48
|
+
workspace_id: workspace.workspace_id,
|
|
49
|
+
run_id: run.run_id,
|
|
50
|
+
comparison_evidence_ids: [comparison.evidence_id],
|
|
51
|
+
offset: 0,
|
|
52
|
+
limit,
|
|
53
|
+
},
|
|
54
|
+
result: jsonValueSchema.parse({
|
|
55
|
+
...changed,
|
|
56
|
+
investigation_run: summary,
|
|
57
|
+
}),
|
|
58
|
+
confidence: "derived",
|
|
59
|
+
authority: "analyst-inference",
|
|
60
|
+
limitations: [...changed.limitations, AUTOMATIC_RUN_LIMITATION],
|
|
61
|
+
evidenceLinks: changed.evidence_links,
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
const evidenceIds = (records) => records.map(({ evidence_id: id }) => id);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { artifactInventoryResultSchema, } from "../domain/artifactGraph.js";
|
|
3
|
+
import { parseArtifactInventoryEvidence } from "../domain/artifactInventoryEvidence.js";
|
|
4
|
+
import { EvidenceIntegrityError, InvestigationWorkspaceError, } from "../domain/errors.js";
|
|
5
|
+
import { err, ok } from "../domain/result.js";
|
|
6
|
+
import { createArtifactComparisonEvidence, createChangedBehaviorEvidence, } from "./CrossVersionInvestigationEvidence.js";
|
|
7
|
+
import { AUTOMATIC_RUN_LIMITATION } from "./CrossVersionInventory.js";
|
|
8
|
+
import { ARTIFACT_GRAPH_PROVIDER } from "./InvestigationProviders.js";
|
|
9
|
+
/** Select and verify one caller-identified complete run without input access. */
|
|
10
|
+
export const selectCompletedInvestigationReplay = (workspace, input) => {
|
|
11
|
+
const run = workspace?.runs.find(({ run_id: runId }) => runId === input.replay_run_id);
|
|
12
|
+
if (workspace === null ||
|
|
13
|
+
run === undefined ||
|
|
14
|
+
run.status !== "complete" ||
|
|
15
|
+
!controlsMatch(run, input))
|
|
16
|
+
return replayConflict();
|
|
17
|
+
const records = new Map(workspace.bundle.records.map((record) => [record.evidence_id, record]));
|
|
18
|
+
const left = inventoryRecords(records, run.left_inventory_evidence_ids);
|
|
19
|
+
const right = inventoryRecords(records, run.right_inventory_evidence_ids);
|
|
20
|
+
if (!left.ok)
|
|
21
|
+
return left;
|
|
22
|
+
if (!right.ok)
|
|
23
|
+
return right;
|
|
24
|
+
if (!pathsMatch(left.value, input.left_path) ||
|
|
25
|
+
!pathsMatch(right.value, input.right_path))
|
|
26
|
+
return replayConflict();
|
|
27
|
+
const validated = validateLinkedEvidence(workspace, run, left.value, right.value);
|
|
28
|
+
return validated.ok ? ok({ workspace, run }) : validated;
|
|
29
|
+
};
|
|
30
|
+
const controlsMatch = (run, input) => isDeepStrictEqual(run.options, input.options) &&
|
|
31
|
+
run.integrity_policy === input.integrity_policy &&
|
|
32
|
+
run.integrity_continue_approved === input.integrity_continue_approved &&
|
|
33
|
+
run.max_integrity_mismatches === input.max_integrity_mismatches;
|
|
34
|
+
const inventoryRecords = (records, evidenceIds) => {
|
|
35
|
+
if (new Set(evidenceIds).size !== evidenceIds.length)
|
|
36
|
+
return replayIntegrity("Investigation replay repeats inventory Evidence");
|
|
37
|
+
const selected = evidenceIds.flatMap((evidenceId) => {
|
|
38
|
+
const evidence = records.get(evidenceId);
|
|
39
|
+
return evidence === undefined ? [] : [evidence];
|
|
40
|
+
});
|
|
41
|
+
return selected.length === evidenceIds.length
|
|
42
|
+
? ok(selected)
|
|
43
|
+
: replayIntegrity("Investigation replay is missing inventory Evidence");
|
|
44
|
+
};
|
|
45
|
+
const pathsMatch = (records, path) => records.every(({ subject }) => subject?.local_path === path);
|
|
46
|
+
const validateLinkedEvidence = (workspace, run, leftRecords, rightRecords) => {
|
|
47
|
+
try {
|
|
48
|
+
const left = validateInventory(leftRecords, run.left, run);
|
|
49
|
+
const right = validateInventory(rightRecords, run.right, run);
|
|
50
|
+
if (!left.ok)
|
|
51
|
+
return left;
|
|
52
|
+
if (!right.ok)
|
|
53
|
+
return right;
|
|
54
|
+
const expectedLimitations = runLimitations(left.value, right.value);
|
|
55
|
+
if (!isDeepStrictEqual(run.limitations, expectedLimitations))
|
|
56
|
+
return replayIntegrity("Investigation replay limitations disagree with inventory Evidence");
|
|
57
|
+
const comparison = createArtifactComparisonEvidence({ left: left.value.records, right: right.value.records }, run.options.change_limit);
|
|
58
|
+
const storedComparison = evidenceFor(workspace, run.comparison_evidence_id);
|
|
59
|
+
if (storedComparison === undefined ||
|
|
60
|
+
!isDeepStrictEqual(storedComparison, comparison))
|
|
61
|
+
return replayIntegrity("Investigation replay comparison Evidence is inconsistent");
|
|
62
|
+
const result = createChangedBehaviorEvidence(workspace, run, comparison);
|
|
63
|
+
const storedResult = evidenceFor(workspace, run.result_evidence_id);
|
|
64
|
+
return storedResult !== undefined && isDeepStrictEqual(storedResult, result)
|
|
65
|
+
? ok(null)
|
|
66
|
+
: replayIntegrity("Investigation replay result Evidence is inconsistent");
|
|
67
|
+
}
|
|
68
|
+
catch (cause) {
|
|
69
|
+
return replayIntegrity("Investigation replay linked Evidence is invalid", cause);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const validateInventory = (records, target, run) => {
|
|
73
|
+
const pages = records.map((record) => artifactInventoryResultSchema.parse(record.normalized_result));
|
|
74
|
+
const expectedPageCount = Math.max(1, Math.ceil((pages[0]?.manifest.node_count ?? 0) / run.options.page_size), Math.ceil((pages[0]?.manifest.occurrence_count ?? 0) / run.options.page_size), Math.ceil((pages[0]?.manifest.edge_count ?? 0) / run.options.page_size));
|
|
75
|
+
if (records.length !== expectedPageCount)
|
|
76
|
+
return replayIntegrity("Investigation replay inventory Evidence pagination is inconsistent");
|
|
77
|
+
if (!sameSnapshotMetadata(pages))
|
|
78
|
+
return replayIntegrity("Investigation replay inventory pages disagree on snapshot metadata");
|
|
79
|
+
for (const [index, record] of records.entries()) {
|
|
80
|
+
const page = pages[index];
|
|
81
|
+
if (page === undefined || !inventoryRecordMatches(record, page, run, index))
|
|
82
|
+
return replayIntegrity("Investigation replay inventory Evidence is inconsistent");
|
|
83
|
+
}
|
|
84
|
+
const result = parseArtifactInventoryEvidence(records);
|
|
85
|
+
if (!result.inventory.complete || !targetMatches(result.inventory, target))
|
|
86
|
+
return replayIntegrity("Investigation replay inventory commitment is inconsistent");
|
|
87
|
+
return ok({ records, result });
|
|
88
|
+
};
|
|
89
|
+
const inventoryRecordMatches = (record, page, run, index) => {
|
|
90
|
+
const offset = index * run.options.page_size;
|
|
91
|
+
const parameters = {
|
|
92
|
+
node_offset: offset,
|
|
93
|
+
node_limit: run.options.page_size,
|
|
94
|
+
occurrence_offset: offset,
|
|
95
|
+
occurrence_limit: run.options.page_size,
|
|
96
|
+
edge_offset: offset,
|
|
97
|
+
edge_limit: run.options.page_size,
|
|
98
|
+
max_entries: run.options.max_entries,
|
|
99
|
+
max_total_bytes: run.options.max_total_bytes,
|
|
100
|
+
max_entry_bytes: run.options.max_entry_bytes,
|
|
101
|
+
max_compression_ratio: run.options.max_compression_ratio,
|
|
102
|
+
max_depth: run.options.max_depth,
|
|
103
|
+
max_path_bytes: run.options.max_path_bytes,
|
|
104
|
+
integrity_policy: run.integrity_policy,
|
|
105
|
+
integrity_continue_approved: run.integrity_continue_approved,
|
|
106
|
+
max_integrity_mismatches: run.max_integrity_mismatches,
|
|
107
|
+
};
|
|
108
|
+
const locations = page.occurrences.items.map(({ logical_path: path }) => ({
|
|
109
|
+
kind: "artifact-path",
|
|
110
|
+
path,
|
|
111
|
+
}));
|
|
112
|
+
return (record.subject !== null &&
|
|
113
|
+
record.subject.name === record.subject.local_path.split("/").at(-1) &&
|
|
114
|
+
record.subject.digest.sha256 === page.manifest.root_sha256 &&
|
|
115
|
+
record.subject.format === page.manifest.root_format &&
|
|
116
|
+
record.subject.architecture === null &&
|
|
117
|
+
isDeepStrictEqual(record.provider, ARTIFACT_GRAPH_PROVIDER) &&
|
|
118
|
+
record.predicate_type === "rea.analysis/v2" &&
|
|
119
|
+
record.operation === "inventory_artifact" &&
|
|
120
|
+
isDeepStrictEqual(record.parameters, parameters) &&
|
|
121
|
+
record.raw_result === null &&
|
|
122
|
+
record.confidence === "observed" &&
|
|
123
|
+
record.authority === "shipped-artifact" &&
|
|
124
|
+
record.environment === null &&
|
|
125
|
+
isDeepStrictEqual(record.limitations, page.limitations) &&
|
|
126
|
+
isDeepStrictEqual(record.locations, locations) &&
|
|
127
|
+
record.evidence_links.length === 0 &&
|
|
128
|
+
isDeepStrictEqual(page.limits, traversalLimits(run)) &&
|
|
129
|
+
pageMatches(page.nodes, offset, run.options.page_size, page.manifest.node_count) &&
|
|
130
|
+
pageMatches(page.occurrences, offset, run.options.page_size, page.manifest.occurrence_count) &&
|
|
131
|
+
pageMatches(page.edges, offset, run.options.page_size, page.manifest.edge_count));
|
|
132
|
+
};
|
|
133
|
+
const sameSnapshotMetadata = (pages) => {
|
|
134
|
+
const first = pages[0];
|
|
135
|
+
return (first !== undefined &&
|
|
136
|
+
pages.every((page) => isDeepStrictEqual(page.manifest, first.manifest) &&
|
|
137
|
+
isDeepStrictEqual(page.limits, first.limits) &&
|
|
138
|
+
isDeepStrictEqual(page.provenance, first.provenance) &&
|
|
139
|
+
isDeepStrictEqual(page.integrity_contradictions, first.integrity_contradictions) &&
|
|
140
|
+
isDeepStrictEqual(page.limitations, first.limitations)));
|
|
141
|
+
};
|
|
142
|
+
const pageMatches = (page, offset, limit, total) => page.offset === offset &&
|
|
143
|
+
page.limit === limit &&
|
|
144
|
+
page.total === total &&
|
|
145
|
+
page.items.length === Math.max(0, Math.min(limit, page.total - offset)) &&
|
|
146
|
+
page.next_offset === (offset + limit < page.total ? offset + limit : null);
|
|
147
|
+
const traversalLimits = (run) => ({
|
|
148
|
+
max_entries: run.options.max_entries,
|
|
149
|
+
max_total_bytes: run.options.max_total_bytes,
|
|
150
|
+
max_entry_bytes: run.options.max_entry_bytes,
|
|
151
|
+
max_compression_ratio: run.options.max_compression_ratio,
|
|
152
|
+
max_depth: run.options.max_depth,
|
|
153
|
+
max_path_bytes: run.options.max_path_bytes,
|
|
154
|
+
});
|
|
155
|
+
const targetMatches = (inventory, target) => inventory.manifest.root_sha256 === target.root_sha256 &&
|
|
156
|
+
inventory.manifest.graph_sha256 === target.graph_sha256 &&
|
|
157
|
+
inventory.manifest.manifest_id === target.manifest_id &&
|
|
158
|
+
inventory.manifest.root_format === target.format;
|
|
159
|
+
const runLimitations = (left, right) => [
|
|
160
|
+
...new Set([
|
|
161
|
+
...left.result.inventory.limitations.map((item) => `Left: ${item}`),
|
|
162
|
+
...right.result.inventory.limitations.map((item) => `Right: ${item}`),
|
|
163
|
+
AUTOMATIC_RUN_LIMITATION,
|
|
164
|
+
]),
|
|
165
|
+
].sort((first, second) => first.localeCompare(second));
|
|
166
|
+
const evidenceFor = (workspace, evidenceId) => evidenceId === null
|
|
167
|
+
? undefined
|
|
168
|
+
: workspace.bundle.records.find(({ evidence_id: current }) => current === evidenceId);
|
|
169
|
+
const replayConflict = () => err(new InvestigationWorkspaceError("read", "revision-conflict"));
|
|
170
|
+
const replayIntegrity = (message, cause) => err(new EvidenceIntegrityError(message, cause === undefined ? undefined : { cause }));
|