rea-agents 1.2.0 → 1.3.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.
Files changed (79) hide show
  1. package/README.md +74 -27
  2. package/bridge/hopper_bridge.py +161 -12
  3. package/dist/application/AnalysisSnapshotCache.js +131 -0
  4. package/dist/application/AnalysisSnapshotFiles.js +31 -0
  5. package/dist/application/ArtifactInventory.js +4 -0
  6. package/dist/application/AuthorizedArtifactInventory.js +16 -0
  7. package/dist/application/BinarySession.js +66 -36
  8. package/dist/application/BinarySessionPort.js +1 -0
  9. package/dist/application/BoundedJsonFiles.js +109 -0
  10. package/dist/application/CrossVersionInventory.js +98 -0
  11. package/dist/application/CrossVersionInvestigation.js +295 -0
  12. package/dist/application/DirectAnalysis.js +115 -22
  13. package/dist/application/Doctor.js +30 -1
  14. package/dist/application/EvidenceBundleFiles.js +15 -108
  15. package/dist/application/EvidenceLedger.js +3 -2
  16. package/dist/application/InvestigationProviders.js +48 -0
  17. package/dist/application/InvestigationWorkspaceStore.js +214 -0
  18. package/dist/application/LinuxHopper.js +72 -48
  19. package/dist/application/ProcessCaptureAuthority.js +2 -2
  20. package/dist/application/ProcessCaptureError.js +12 -0
  21. package/dist/application/ProcessCaptureLifecycle.js +19 -1
  22. package/dist/application/ProcessCli.js +98 -31
  23. package/dist/application/ProcessHarness.js +2 -2
  24. package/dist/application/ReferenceSourceImportEntries.js +18 -3
  25. package/dist/application/ReferenceSourceImportTypes.js +32 -0
  26. package/dist/application/Setup.js +33 -60
  27. package/dist/application/SetupClients.js +19 -0
  28. package/dist/application/SetupInstallFailure.js +53 -0
  29. package/dist/application/SetupPlan.js +31 -0
  30. package/dist/application/Uninstall.js +33 -23
  31. package/dist/application/UnknownEvidence.js +33 -0
  32. package/dist/application/Upgrade.js +1 -1
  33. package/dist/application/runtime.js +1 -1
  34. package/dist/artifacts/ArtifactProvider.js +2 -5
  35. package/dist/cli.js +31 -11
  36. package/dist/cliEvidenceCommands.js +11 -12
  37. package/dist/cliInvestigationCommands.js +86 -0
  38. package/dist/cliOutput.js +41 -0
  39. package/dist/cliProcessCommands.js +14 -4
  40. package/dist/config.js +39 -24
  41. package/dist/contracts/promptContracts.js +256 -0
  42. package/dist/contracts/sessionLifecycleInputs.js +11 -0
  43. package/dist/contracts/toolContractTypes.js +1 -0
  44. package/dist/contracts/toolContracts.js +10 -6
  45. package/dist/contracts/toolOutputSchemas.js +33 -4
  46. package/dist/domain/analysisSnapshot.js +149 -0
  47. package/dist/domain/changedBehavior.js +30 -7
  48. package/dist/domain/errors.js +180 -41
  49. package/dist/domain/evidence.js +4 -2
  50. package/dist/domain/evidenceBundle.js +28 -0
  51. package/dist/domain/hopperStartupFailure.js +55 -0
  52. package/dist/domain/investigationWorkspace.js +214 -0
  53. package/dist/domain/jsonValue.js +2 -0
  54. package/dist/domain/nativeInspection.js +3 -2
  55. package/dist/domain/processCapture.js +5 -4
  56. package/dist/domain/processComparison.js +5 -4
  57. package/dist/hopper/BridgeLauncher.js +47 -20
  58. package/dist/hopper/HopperClient.js +17 -2
  59. package/dist/hopper/HopperProvider.js +1 -0
  60. package/dist/main.js +47 -24
  61. package/dist/server/createServer.js +2 -0
  62. package/dist/server/promptCompletion.js +144 -0
  63. package/dist/server/registerEnhancedTools.js +2 -6
  64. package/dist/server/registerEvidenceTools.js +2 -8
  65. package/dist/server/registerFunctionComparisonTool.js +1 -1
  66. package/dist/server/registerInvestigationTools.js +31 -11
  67. package/dist/server/registerOfficialTools.js +6 -13
  68. package/dist/server/registerProcessComparisonTool.js +1 -7
  69. package/dist/server/registerPrompts.js +67 -0
  70. package/dist/server/registerSessionStatusTool.js +11 -0
  71. package/dist/server/registerSessionTools.js +52 -18
  72. package/dist/server/sessionEvidence.js +2 -8
  73. package/dist/server/sessionToolPolicies.js +1 -42
  74. package/dist/server/toolResult.js +7 -3
  75. package/install.sh +11 -11
  76. package/package.json +5 -3
  77. package/scripts/hopper-demo-x11.py +349 -0
  78. package/scripts/rea.mjs +6 -3
  79. package/skills/rea-analysis/SKILL.md +9 -2
@@ -0,0 +1,31 @@
1
+ import { parseAnalysisSnapshot, serializeAnalysisSnapshot, } from "../domain/analysisSnapshot.js";
2
+ import { EvidenceFileError, EvidenceIntegrityError } from "../domain/errors.js";
3
+ import { err, ok } from "../domain/result.js";
4
+ import { readBoundedJson, writeBoundedText } from "./BoundedJsonFiles.js";
5
+ /** Read and validate a bounded analysis snapshot inside an approved root. */
6
+ export const readAnalysisSnapshot = async (path, policy) => {
7
+ const loaded = await readBoundedJson(path, policy);
8
+ if (!loaded.ok)
9
+ return loaded;
10
+ try {
11
+ return ok(parseAnalysisSnapshot(loaded.value));
12
+ }
13
+ catch (cause) {
14
+ return err(new EvidenceIntegrityError("Analysis snapshot validation failed", {
15
+ cause,
16
+ }));
17
+ }
18
+ };
19
+ /** Atomically write a deterministic analysis snapshot inside an approved root. */
20
+ export const writeAnalysisSnapshot = async (snapshot, path, overwrite, policy) => {
21
+ let encoded;
22
+ try {
23
+ encoded = serializeAnalysisSnapshot(snapshot);
24
+ }
25
+ catch (cause) {
26
+ return err(new EvidenceIntegrityError("Analysis snapshot validation failed", {
27
+ cause,
28
+ }));
29
+ }
30
+ return writeBoundedText(encoded, path, overwrite, policy);
31
+ };
@@ -24,6 +24,10 @@ export const inventoryArtifact = async (inputPath, limits, page, options = {}) =
24
24
  export const scanArtifactInventory = async (inputPath, limits, signal, nativeMount = NATIVE_MOUNT_DISABLED) => {
25
25
  abortIfNeeded(signal);
26
26
  const path = await realpath(inputPath);
27
+ return scanCanonicalArtifactInventory(path, limits, signal, nativeMount);
28
+ };
29
+ /** Scan one already-canonical artifact path without resolving it again. */
30
+ export const scanCanonicalArtifactInventory = async (path, limits, signal, nativeMount = NATIVE_MOUNT_DISABLED) => {
27
31
  const metadata = await lstat(path);
28
32
  const rootFormat = await classifyRoot(path, metadata.isDirectory());
29
33
  const rootDigest = metadata.isDirectory()
@@ -0,0 +1,16 @@
1
+ import { realpath } from "node:fs/promises";
2
+ import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
3
+ import { withinRoot } from "../reference/ReferenceSourceReaderPaths.js";
4
+ import { scanCanonicalArtifactInventory, } from "./ArtifactInventory.js";
5
+ /** Resolve, authorize, and scan one artifact without a second path resolution. */
6
+ export const scanAuthorizedArtifactInventory = async (inputPath, roots, limits, signal) => {
7
+ if (roots.length === 0)
8
+ throw new ArtifactReaderFailure("path", "Artifact input roots are disabled");
9
+ const [path, approved] = await Promise.all([
10
+ realpath(inputPath),
11
+ Promise.all(roots.map(async (root) => realpath(root))),
12
+ ]);
13
+ if (!approved.some((root) => withinRoot(root, path)))
14
+ throw new ArtifactReaderFailure("path", "Artifact path is outside approved input roots");
15
+ return scanCanonicalArtifactInventory(path, limits, signal);
16
+ };
@@ -2,15 +2,14 @@ import { parseBinaryTarget } from "../domain/binaryTarget.js";
2
2
  import { AnalysisCapabilityUnavailableError, EvidenceIntegrityError, EvidenceLimitError, AnalysisCancelledError, NoBinaryOpenError, } from "../domain/errors.js";
3
3
  import { err, ok } from "../domain/result.js";
4
4
  import { createEvidence } from "../domain/evidence.js";
5
+ import { evidenceBundleForTarget } from "../domain/evidenceBundle.js";
5
6
  import { EvidenceLedger } from "./EvidenceLedger.js";
6
7
  import { UnknownRegistryError } from "../domain/errors.js";
7
8
  import { enhancedToolNameSchema } from "../contracts/enhancedInputs.js";
8
9
  import { OFFICIAL_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
9
- const REGISTRY_PROVIDER = {
10
- id: "rea-unknown-registry",
11
- name: "REA residual unknown registry",
12
- version: "1",
13
- };
10
+ import { snapshotMatchesTarget } from "../domain/analysisSnapshot.js";
11
+ import { AnalysisSnapshotCache, isSnapshotCacheable, } from "./AnalysisSnapshotCache.js";
12
+ import { UNKNOWN_REGISTRY_PROVIDER, unknownEvidenceLinks, unknownMutationEvidence, } from "./UnknownEvidence.js";
14
13
  const OFFICIAL_OPERATIONS = new Set(OFFICIAL_TOOL_CONTRACTS.map(({ name }) => name));
15
14
  /**
16
15
  * Owns the single active target shared by CLI and MCP adapters.
@@ -32,8 +31,12 @@ export class BinarySession {
32
31
  maxRecords: 10_000,
33
32
  maxBytes: 64 * 1024 * 1024,
34
33
  });
35
- constructor(provider) {
34
+ #snapshot = new AnalysisSnapshotCache();
35
+ #snapshotsEnabled;
36
+ #snapshotInvalidated = false;
37
+ constructor(provider, options = {}) {
36
38
  this.provider = provider;
39
+ this.#snapshotsEnabled = options.snapshotsEnabled ?? true;
37
40
  this.#createClient =
38
41
  typeof provider === "function"
39
42
  ? provider
@@ -90,6 +93,23 @@ export class BinarySession {
90
93
  importEvidenceBundle(bundle) {
91
94
  return this.#evidence.import(bundle);
92
95
  }
96
+ /** Export immutable cached calls and session evidence for the active target. */
97
+ exportAnalysisSnapshot() {
98
+ if (!this.#snapshotsEnabled)
99
+ return err(new EvidenceIntegrityError("Analysis snapshots are unavailable with custom Hopper loader arguments"));
100
+ if (this.#snapshotInvalidated)
101
+ return err(new EvidenceIntegrityError("Analysis snapshots are unavailable after analysis metadata mutations"));
102
+ const target = this.#active?.target;
103
+ return this.#snapshot.export(target, target === undefined
104
+ ? this.#evidence.export()
105
+ : evidenceBundleForTarget(this.#evidence.export(), target.sha256));
106
+ }
107
+ /** Stage validated cached calls for an identical binary and merge its evidence. */
108
+ importAnalysisSnapshot(snapshot) {
109
+ if (!this.#snapshotsEnabled)
110
+ return err(new EvidenceIntegrityError("Analysis snapshots are unavailable with custom Hopper loader arguments"));
111
+ return this.#snapshot.import(snapshot, this.#active?.target, (bundle) => this.#evidence.import(bundle));
112
+ }
93
113
  /** Create an approved residual unknown and immutable mutation evidence. */
94
114
  recordUnknown(input) {
95
115
  const evidence = unknownMutationEvidence(this.#active?.target, input);
@@ -101,7 +121,7 @@ export class BinarySession {
101
121
  }
102
122
  /** Update one unknown using compare-and-swap revision semantics. */
103
123
  updateUnknown(input) {
104
- const evidence = createEvidence(this.#active?.target, REGISTRY_PROVIDER, {
124
+ const evidence = createEvidence(this.#active?.target, UNKNOWN_REGISTRY_PROVIDER, {
105
125
  predicateType: "rea.residual-unknown-mutation/v1",
106
126
  operation: "update_unknown",
107
127
  parameters: {
@@ -137,10 +157,21 @@ export class BinarySession {
137
157
  const parsed = await parseBinaryTarget(path, process.cwd(), process.arch, options.targetKind);
138
158
  if (!parsed.ok)
139
159
  return parsed;
160
+ if (options.snapshot !== undefined &&
161
+ !snapshotMatchesTarget(options.snapshot.target, parsed.value))
162
+ return err(new EvidenceIntegrityError("Analysis snapshot target does not match the requested binary"));
163
+ if (this.#active === undefined && !this.#snapshot.matches(parsed.value))
164
+ return err(new EvidenceIntegrityError("Analysis snapshot target does not match the requested binary"));
140
165
  if (isAborted(options.signal))
141
166
  return err(new AnalysisCancelledError("open_binary"));
142
- if (this.#active?.target.path === parsed.value.path)
167
+ if (this.#active?.target.path === parsed.value.path) {
168
+ if (options.snapshot !== undefined) {
169
+ const imported = this.importAnalysisSnapshot(options.snapshot);
170
+ if (!imported.ok)
171
+ return imported;
172
+ }
143
173
  return ok(parsed.value);
174
+ }
144
175
  await this.#drainCalls();
145
176
  const previous = this.#active;
146
177
  this.#active = undefined;
@@ -158,6 +189,17 @@ export class BinarySession {
158
189
  return err(new AnalysisCancelledError("open_binary"));
159
190
  }
160
191
  this.#active = { target: parsed.value, client };
192
+ this.#snapshot.select(parsed.value);
193
+ if (options.snapshot !== undefined) {
194
+ const imported = this.importAnalysisSnapshot(options.snapshot);
195
+ if (!imported.ok) {
196
+ this.#active = undefined;
197
+ await client.close();
198
+ await this.#restore(previous);
199
+ return imported;
200
+ }
201
+ }
202
+ this.#snapshotInvalidated = false;
161
203
  return ok(parsed.value);
162
204
  });
163
205
  }
@@ -169,6 +211,8 @@ export class BinarySession {
169
211
  await this.#drainCalls();
170
212
  await previous?.client.close();
171
213
  this.#evidence.clear();
214
+ this.#snapshot.clear();
215
+ this.#snapshotInvalidated = false;
172
216
  return ok(null);
173
217
  });
174
218
  }
@@ -250,10 +294,23 @@ export class BinarySession {
250
294
  const active = this.#active;
251
295
  if (active === undefined)
252
296
  return err(new NoBinaryOpenError());
297
+ const cacheable = isSnapshotCacheable(name, capability, arguments_);
298
+ const cached = cacheable
299
+ ? this.#snapshot.lookup(active.target, name, arguments_, capability.provider)
300
+ : undefined;
301
+ if (cached !== undefined)
302
+ return ok(cached);
253
303
  const call = active.client.execute(name, arguments_, options);
254
304
  this.#calls.add(call);
255
305
  try {
256
- return await call;
306
+ const result = await call;
307
+ if (result.ok && cacheable)
308
+ this.#snapshot.record(active.target, name, arguments_, result.value);
309
+ else if (result.ok && capability?.effects.mutatesArtifact === true) {
310
+ this.#snapshot.clear();
311
+ this.#snapshotInvalidated = true;
312
+ }
313
+ return result;
257
314
  }
258
315
  finally {
259
316
  this.#calls.delete(call);
@@ -303,31 +360,4 @@ export class BinarySession {
303
360
  });
304
361
  }
305
362
  }
306
- const unknownMutationEvidence = (target, input) => createEvidence(target, REGISTRY_PROVIDER, {
307
- predicateType: "rea.residual-unknown-mutation/v1",
308
- operation: "record_unknown",
309
- parameters: {
310
- domain: input.domain,
311
- severity: input.severity,
312
- },
313
- result: {
314
- action: "record",
315
- question: input.question,
316
- required_authority: input.required_authority,
317
- required_confidence: input.required_confidence,
318
- },
319
- confidence: "derived",
320
- authority: "analyst-inference",
321
- evidenceLinks: unknownEvidenceLinks(input),
322
- limitations: [
323
- "Registry mutation evidence records analyst intent, not proof of the answer.",
324
- ],
325
- });
326
- const unknownEvidenceLinks = (input) => [
327
- ...input.supporting_evidence_ids,
328
- ...input.contradicting_evidence_ids,
329
- ...("resolution" in input && input.resolution !== null
330
- ? input.resolution.evidence_ids
331
- : []),
332
- ].filter((id, index, values) => values.indexOf(id) === index);
333
363
  const isAborted = (signal) => signal?.aborted === true;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,109 @@
1
+ import { lstat, readFile, realpath } from "node:fs/promises";
2
+ import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
3
+ import writeFileAtomic from "write-file-atomic";
4
+ import { EvidenceFileError } from "../domain/errors.js";
5
+ import { err, ok } from "../domain/result.js";
6
+ /** Read bounded JSON data from a regular file beneath an approved root. */
7
+ export const readBoundedJson = async (path, policy) => {
8
+ if (policy.roots.length === 0)
9
+ return err(new EvidenceFileError("read", "disabled"));
10
+ try {
11
+ const canonicalPath = await realpath(resolve(path));
12
+ if (!(await isApproved(canonicalPath, policy.roots)))
13
+ return err(new EvidenceFileError("read", "outside-root"));
14
+ const stats = await lstat(canonicalPath);
15
+ if (!stats.isFile())
16
+ return err(new EvidenceFileError("read", "not-file"));
17
+ if (stats.size > policy.maxBytes)
18
+ return err(new EvidenceFileError("read", "too-large"));
19
+ const encoded = await readFile(canonicalPath);
20
+ if (encoded.byteLength > policy.maxBytes)
21
+ return err(new EvidenceFileError("read", "too-large"));
22
+ let decoded;
23
+ try {
24
+ decoded = JSON.parse(encoded.toString("utf8"));
25
+ }
26
+ catch (cause) {
27
+ return err(new EvidenceFileError("read", "invalid-json", { cause }));
28
+ }
29
+ const limits = validateJsonLimits(decoded, policy);
30
+ return limits.ok ? ok(decoded) : limits;
31
+ }
32
+ catch (cause) {
33
+ return err(new EvidenceFileError("read", "io", { cause }));
34
+ }
35
+ };
36
+ /** Atomically write bounded text to a regular file beneath an approved root. */
37
+ export const writeBoundedText = async (encoded, path, overwrite, policy) => {
38
+ if (policy.roots.length === 0)
39
+ return err(new EvidenceFileError("write", "disabled"));
40
+ const bytes = Buffer.byteLength(encoded, "utf8");
41
+ if (bytes > policy.maxBytes)
42
+ return err(new EvidenceFileError("write", "too-large"));
43
+ try {
44
+ const requestedPath = resolve(path);
45
+ const canonicalParent = await realpath(dirname(requestedPath));
46
+ const destination = resolve(canonicalParent, basename(requestedPath));
47
+ if (!(await isApproved(destination, policy.roots)))
48
+ return err(new EvidenceFileError("write", "outside-root"));
49
+ const existing = await lstat(destination).catch((cause) => {
50
+ if (isFileNotFound(cause))
51
+ return undefined;
52
+ throw cause;
53
+ });
54
+ if (existing !== undefined) {
55
+ if (!overwrite)
56
+ return err(new EvidenceFileError("write", "exists"));
57
+ if (!existing.isFile() || existing.isSymbolicLink())
58
+ return err(new EvidenceFileError("write", "not-file"));
59
+ }
60
+ await writeFileAtomic(destination, encoded, {
61
+ encoding: "utf8",
62
+ mode: 0o600,
63
+ fsync: true,
64
+ });
65
+ return ok({ path: requestedPath, bytes });
66
+ }
67
+ catch (cause) {
68
+ return err(new EvidenceFileError("write", "io", { cause }));
69
+ }
70
+ };
71
+ const isApproved = async (candidate, roots) => {
72
+ for (const configuredRoot of roots) {
73
+ const root = await realpath(resolve(configuredRoot));
74
+ const relation = relative(root, candidate);
75
+ if (relation === "" ||
76
+ (!relation.startsWith("..") && !isAbsolute(relation)))
77
+ return true;
78
+ }
79
+ return false;
80
+ };
81
+ const validateJsonLimits = (root, policy) => {
82
+ const pending = [
83
+ { value: root, depth: 0 },
84
+ ];
85
+ let nodes = 0;
86
+ while (pending.length > 0) {
87
+ const current = pending.pop();
88
+ if (current === undefined)
89
+ break;
90
+ nodes += 1;
91
+ if (nodes > policy.maxNodes || current.depth > policy.maxDepth)
92
+ return err(new EvidenceFileError("read", "too-large"));
93
+ if (typeof current.value === "string" &&
94
+ current.value.length > policy.maxStringLength)
95
+ return err(new EvidenceFileError("read", "too-large"));
96
+ if (typeof current.value !== "object" || current.value === null)
97
+ continue;
98
+ for (const [key, value] of Object.entries(current.value)) {
99
+ if (key.length > policy.maxStringLength)
100
+ return err(new EvidenceFileError("read", "too-large"));
101
+ pending.push({ value, depth: current.depth + 1 });
102
+ }
103
+ }
104
+ return ok(null);
105
+ };
106
+ const isFileNotFound = (cause) => typeof cause === "object" &&
107
+ cause !== null &&
108
+ "code" in cause &&
109
+ cause.code === "ENOENT";
@@ -0,0 +1,98 @@
1
+ import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
2
+ import { createEvidence } from "../domain/evidence.js";
3
+ import { AnalysisCancelledError, ArtifactOperationError, } from "../domain/errors.js";
4
+ import { jsonValueSchema } from "../domain/jsonValue.js";
5
+ import { err, ok } from "../domain/result.js";
6
+ import { paginateArtifactInventory, } from "./ArtifactInventory.js";
7
+ import { scanAuthorizedArtifactInventory } from "./AuthorizedArtifactInventory.js";
8
+ import { ARTIFACT_GRAPH_PROVIDER } from "./InvestigationProviders.js";
9
+ export const AUTOMATIC_RUN_LIMITATION = "Automatic cross-version runs compare shipped artifact structure only; they do not execute either version.";
10
+ /** Authorize and scan both inputs once under the same bounded traversal policy. */
11
+ export const scanVersions = async (input, inputRoots, signal) => {
12
+ try {
13
+ if (signal?.aborted === true)
14
+ return err(new AnalysisCancelledError("find_changed_behavior"));
15
+ const limits = artifactLimits(input.options);
16
+ const left = await scanAuthorizedArtifactInventory(input.left_path, inputRoots, limits, signal);
17
+ const right = await scanAuthorizedArtifactInventory(input.right_path, inputRoots, limits, signal);
18
+ return ok({ left, right });
19
+ }
20
+ catch (cause) {
21
+ if (cause instanceof ArtifactReaderFailure)
22
+ return err(new ArtifactOperationError("inventory_artifact", cause.reason, cause.details));
23
+ return err(new ArtifactOperationError("inventory_artifact", "io"));
24
+ }
25
+ };
26
+ /** Project complete snapshots into bounded, comparable Evidence page sets. */
27
+ export const createInventoryEvidencePages = (input, snapshots) => {
28
+ const left = inventoryPages(input.left_path, snapshots.left, input.options);
29
+ const right = inventoryPages(input.right_path, snapshots.right, input.options);
30
+ if (left.length > 100 || right.length > 100)
31
+ return err(new ArtifactOperationError("inventory_artifact", "limit"));
32
+ return ok({ left, right });
33
+ };
34
+ /** Reduce a snapshot to the content commitments used by run identity. */
35
+ export const targetFor = (snapshot) => ({
36
+ root_sha256: snapshot.manifest.root_sha256,
37
+ graph_sha256: snapshot.manifest.graph_sha256,
38
+ manifest_id: snapshot.manifest.manifest_id,
39
+ format: snapshot.manifest.root_format,
40
+ });
41
+ /** Preserve scan and execution limitations in stable order. */
42
+ export const runLimitations = (snapshots) => [
43
+ ...new Set([
44
+ ...snapshots.left.limitations.map((item) => `Left: ${item}`),
45
+ ...snapshots.right.limitations.map((item) => `Right: ${item}`),
46
+ AUTOMATIC_RUN_LIMITATION,
47
+ ]),
48
+ ].sort((left, right) => left.localeCompare(right));
49
+ const inventoryPages = (path, snapshot, options) => {
50
+ const count = Math.max(1, Math.ceil(snapshot.nodes.length / options.page_size), Math.ceil(snapshot.occurrences.length / options.page_size), Math.ceil(snapshot.edges.length / options.page_size));
51
+ return Array.from({ length: count }, (_, index) => inventoryPage(path, snapshot, options, index * options.page_size));
52
+ };
53
+ const inventoryPage = (path, snapshot, options, offset) => {
54
+ const result = paginateArtifactInventory(snapshot, {
55
+ nodeOffset: offset,
56
+ nodeLimit: options.page_size,
57
+ occurrenceOffset: offset,
58
+ occurrenceLimit: options.page_size,
59
+ edgeOffset: offset,
60
+ edgeLimit: options.page_size,
61
+ });
62
+ return createEvidence({
63
+ path,
64
+ sha256: snapshot.manifest.root_sha256,
65
+ format: snapshot.manifest.root_format,
66
+ }, ARTIFACT_GRAPH_PROVIDER, {
67
+ operation: "inventory_artifact",
68
+ parameters: {
69
+ node_offset: offset,
70
+ node_limit: options.page_size,
71
+ occurrence_offset: offset,
72
+ occurrence_limit: options.page_size,
73
+ edge_offset: offset,
74
+ edge_limit: options.page_size,
75
+ max_entries: options.max_entries,
76
+ max_total_bytes: options.max_total_bytes,
77
+ max_entry_bytes: options.max_entry_bytes,
78
+ max_compression_ratio: options.max_compression_ratio,
79
+ max_depth: options.max_depth,
80
+ max_path_bytes: options.max_path_bytes,
81
+ },
82
+ result: jsonValueSchema.parse(result),
83
+ rawResult: null,
84
+ limitations: result.limitations,
85
+ locations: result.occurrences.items.map(({ logical_path: logicalPath }) => ({
86
+ kind: "artifact-path",
87
+ path: logicalPath,
88
+ })),
89
+ });
90
+ };
91
+ const artifactLimits = (options) => ({
92
+ maxEntries: options.max_entries,
93
+ maxTotalBytes: options.max_total_bytes,
94
+ maxEntryBytes: options.max_entry_bytes,
95
+ maxCompressionRatio: options.max_compression_ratio,
96
+ maxDepth: options.max_depth,
97
+ maxPathBytes: options.max_path_bytes,
98
+ });