@chengxianglibra/dsh-data-analysis 0.1.1

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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -0
  3. package/cordis.patch.yml +11 -0
  4. package/lib/bin/environment.js +91 -0
  5. package/lib/bridges.js +11 -0
  6. package/lib/client.js +744 -0
  7. package/lib/compatibility.js +66 -0
  8. package/lib/datasource/access.js +60 -0
  9. package/lib/datasource/bridge-programs.js +80 -0
  10. package/lib/datasource/bridge.js +204 -0
  11. package/lib/datasource/credentials.js +24 -0
  12. package/lib/datasource/index.js +4 -0
  13. package/lib/datasource/shell-env.js +386 -0
  14. package/lib/datasource/test.js +65 -0
  15. package/lib/disclosure/activation.js +369 -0
  16. package/lib/disclosure/bridge-program.js +11 -0
  17. package/lib/disclosure/bridge.js +65 -0
  18. package/lib/disclosure/help.js +227 -0
  19. package/lib/disclosure/index.js +3 -0
  20. package/lib/environment/binding.js +273 -0
  21. package/lib/environment/doctor.js +123 -0
  22. package/lib/environment/errors.js +11 -0
  23. package/lib/environment/index.js +7 -0
  24. package/lib/environment/runtime.js +558 -0
  25. package/lib/environment/source.js +6 -0
  26. package/lib/environment/subprocess.js +178 -0
  27. package/lib/environment/summary.js +19 -0
  28. package/lib/environment/workspace.js +49 -0
  29. package/lib/evidence/bridge-program.js +153 -0
  30. package/lib/evidence/bridge.js +166 -0
  31. package/lib/evidence/index.js +2 -0
  32. package/lib/evidence/sources.js +263 -0
  33. package/lib/index.js +7 -0
  34. package/lib/plugin.js +179 -0
  35. package/lib/types/bridges.d.ts +11 -0
  36. package/lib/types/client.d.ts +125 -0
  37. package/lib/types/compatibility.d.ts +29 -0
  38. package/lib/types/datasource/access.d.ts +27 -0
  39. package/lib/types/datasource/bridge.d.ts +53 -0
  40. package/lib/types/datasource/index.d.ts +8 -0
  41. package/lib/types/datasource/shell-env.d.ts +38 -0
  42. package/lib/types/datasource/test.d.ts +32 -0
  43. package/lib/types/disclosure/activation.d.ts +73 -0
  44. package/lib/types/disclosure/bridge.d.ts +28 -0
  45. package/lib/types/disclosure/help.d.ts +64 -0
  46. package/lib/types/disclosure/index.d.ts +5 -0
  47. package/lib/types/environment/binding.d.ts +26 -0
  48. package/lib/types/environment/doctor.d.ts +8 -0
  49. package/lib/types/environment/errors.d.ts +7 -0
  50. package/lib/types/environment/index.d.ts +9 -0
  51. package/lib/types/environment/runtime.d.ts +17 -0
  52. package/lib/types/environment/source.d.ts +7 -0
  53. package/lib/types/environment/subprocess.d.ts +19 -0
  54. package/lib/types/environment/types.d.ts +96 -0
  55. package/lib/types/environment/workspace.d.ts +10 -0
  56. package/lib/types/evidence/bridge.d.ts +40 -0
  57. package/lib/types/evidence/index.d.ts +4 -0
  58. package/lib/types/evidence/sources.d.ts +57 -0
  59. package/lib/types/index.d.ts +9 -0
  60. package/lib/types/plugin.d.ts +43 -0
  61. package/package.json +149 -0
  62. package/python/report-kit/dist/dsh_data_analysis_report_kit-3.0.0-py3-none-any.whl +0 -0
  63. package/skills/dsh-data-analysis-report/SKILL.md +57 -0
  64. package/skills/dsh-data-analysis-report/assets/marivo-artifact.js +113 -0
  65. package/skills/dsh-data-analysis-report/assets/marivo-session-dag.js +1201 -0
  66. package/skills/dsh-data-analysis-report/assets/report-data.js +449 -0
@@ -0,0 +1,178 @@
1
+ import { spawn } from 'node:child_process';
2
+ import process from 'node:process';
3
+ import { SUBPROCESS_POLICY_VERSION } from "../compatibility.js";
4
+ import { MarivoEnvironmentError } from "./errors.js";
5
+ export const SUBPROCESS_POLICY_ID = SUBPROCESS_POLICY_VERSION;
6
+ export const MARIVO_PERSIST_CREDENTIALS_ENV = 'MARIVO_PERSIST_CREDENTIALS';
7
+ export const MARIVO_PERSIST_CREDENTIALS_DISABLED = '0';
8
+ export const DEFAULT_SUBPROCESS_LIMITS = Object.freeze({
9
+ timeoutMs: 30_000,
10
+ stdoutMaxBytes: 1_048_576,
11
+ stderrMaxBytes: 65_536,
12
+ terminateGraceMs: 250,
13
+ });
14
+ function snapshotEnvironment(source) {
15
+ return Object.freeze({
16
+ ...Object.fromEntries(Object.entries(source).filter((entry) => entry[1] !== undefined)),
17
+ [MARIVO_PERSIST_CREDENTIALS_ENV]: MARIVO_PERSIST_CREDENTIALS_DISABLED,
18
+ });
19
+ }
20
+ function overlayEnvironment(snapshot, overlay) {
21
+ if (overlay === undefined)
22
+ return snapshot;
23
+ return Object.freeze({
24
+ ...snapshot,
25
+ ...Object.fromEntries(Object.entries(overlay).filter((entry) => entry[1] !== undefined)),
26
+ // This is plugin policy, not caller-controlled configuration.
27
+ [MARIVO_PERSIST_CREDENTIALS_ENV]: MARIVO_PERSIST_CREDENTIALS_DISABLED,
28
+ });
29
+ }
30
+ function positiveLimit(name, value) {
31
+ if (!Number.isSafeInteger(value) || value < 1) {
32
+ throw new TypeError(`${name} must be a positive safe integer`);
33
+ }
34
+ return value;
35
+ }
36
+ function resolveLimits(overrides) {
37
+ const merged = { ...DEFAULT_SUBPROCESS_LIMITS, ...overrides };
38
+ return {
39
+ timeoutMs: positiveLimit('timeoutMs', merged.timeoutMs),
40
+ stdoutMaxBytes: positiveLimit('stdoutMaxBytes', merged.stdoutMaxBytes),
41
+ stderrMaxBytes: positiveLimit('stderrMaxBytes', merged.stderrMaxBytes),
42
+ terminateGraceMs: positiveLimit('terminateGraceMs', merged.terminateGraceMs),
43
+ };
44
+ }
45
+ /**
46
+ * Direct-argv subprocess runner shared by doctor, inventory, and focused help.
47
+ *
48
+ * The policy captures cwd and the environment projection once. Calls never use a shell. On
49
+ * POSIX, detached children form a process group so timeout, cancellation, and output overflow
50
+ * terminate the whole group.
51
+ */
52
+ export class FixedSubprocessPolicy {
53
+ id = SUBPROCESS_POLICY_ID;
54
+ cwd;
55
+ #environment;
56
+ constructor(cwd, environment = process.env) {
57
+ this.cwd = cwd;
58
+ this.#environment = snapshotEnvironment(environment);
59
+ }
60
+ run(request) {
61
+ const limits = resolveLimits(request.limits);
62
+ const startedAt = performance.now();
63
+ const environment = overlayEnvironment(this.#environment, request.environmentOverlay);
64
+ if (request.signal?.aborted) {
65
+ return Promise.reject(new MarivoEnvironmentError('subprocess-cancelled', 'Marivo subprocess was cancelled before it started'));
66
+ }
67
+ return new Promise((resolve, reject) => {
68
+ const spawnChild = () => spawn(request.executable, request.args, {
69
+ cwd: this.cwd,
70
+ env: environment,
71
+ shell: false,
72
+ windowsHide: true,
73
+ detached: process.platform !== 'win32',
74
+ stdio: ['ignore', 'pipe', 'pipe'],
75
+ });
76
+ let child;
77
+ try {
78
+ child = spawnChild();
79
+ }
80
+ catch (cause) {
81
+ reject(new MarivoEnvironmentError('subprocess-start-failed', 'Could not start Marivo subprocess', {
82
+ executable: request.executable,
83
+ errorCode: cause.code,
84
+ }));
85
+ return;
86
+ }
87
+ const stdout = [];
88
+ const stderr = [];
89
+ let stdoutBytes = 0;
90
+ let stderrBytes = 0;
91
+ let settled = false;
92
+ let failure;
93
+ let killTimer;
94
+ const killTree = (signal) => {
95
+ if (child.pid === undefined)
96
+ return;
97
+ if (process.platform === 'win32') {
98
+ const killer = spawn('taskkill', ['/pid', String(child.pid), '/t', '/f'], {
99
+ cwd: this.cwd,
100
+ env: environment,
101
+ shell: false,
102
+ windowsHide: true,
103
+ stdio: 'ignore',
104
+ });
105
+ killer.once('error', () => child.kill(signal));
106
+ killer.unref();
107
+ return;
108
+ }
109
+ try {
110
+ process.kill(-child.pid, signal);
111
+ }
112
+ catch (error) {
113
+ const code = error.code;
114
+ if (code !== 'ESRCH')
115
+ child.kill(signal);
116
+ }
117
+ };
118
+ const terminate = (error) => {
119
+ if (failure !== undefined || settled)
120
+ return;
121
+ failure = error;
122
+ killTree('SIGTERM');
123
+ killTimer = setTimeout(() => killTree('SIGKILL'), limits.terminateGraceMs);
124
+ killTimer.unref();
125
+ };
126
+ const timeout = setTimeout(() => terminate(new MarivoEnvironmentError('subprocess-timeout', `Marivo subprocess exceeded ${limits.timeoutMs} ms`, { timeoutMs: limits.timeoutMs })), limits.timeoutMs);
127
+ timeout.unref();
128
+ const onAbort = () => terminate(new MarivoEnvironmentError('subprocess-cancelled', 'Marivo subprocess was cancelled'));
129
+ request.signal?.addEventListener('abort', onAbort, { once: true });
130
+ child.stdout.on('data', (chunk) => {
131
+ stdoutBytes += chunk.byteLength;
132
+ if (stdoutBytes > limits.stdoutMaxBytes) {
133
+ terminate(new MarivoEnvironmentError('subprocess-output-limit', `Marivo subprocess stdout exceeded ${limits.stdoutMaxBytes} bytes`, { stream: 'stdout', maxBytes: limits.stdoutMaxBytes, observedBytes: stdoutBytes }));
134
+ return;
135
+ }
136
+ stdout.push(chunk);
137
+ });
138
+ child.stderr.on('data', (chunk) => {
139
+ stderrBytes += chunk.byteLength;
140
+ if (stderrBytes > limits.stderrMaxBytes) {
141
+ terminate(new MarivoEnvironmentError('subprocess-output-limit', `Marivo subprocess stderr exceeded ${limits.stderrMaxBytes} bytes`, { stream: 'stderr', maxBytes: limits.stderrMaxBytes, observedBytes: stderrBytes }));
142
+ return;
143
+ }
144
+ stderr.push(chunk);
145
+ });
146
+ child.once('error', (cause) => {
147
+ if (settled)
148
+ return;
149
+ settled = true;
150
+ clearTimeout(timeout);
151
+ if (killTimer !== undefined)
152
+ clearTimeout(killTimer);
153
+ request.signal?.removeEventListener('abort', onAbort);
154
+ reject(new MarivoEnvironmentError('subprocess-start-failed', 'Could not start Marivo subprocess', { executable: request.executable, errorCode: cause.code }));
155
+ });
156
+ child.once('close', (exitCode, signal) => {
157
+ if (settled)
158
+ return;
159
+ settled = true;
160
+ clearTimeout(timeout);
161
+ if (killTimer !== undefined)
162
+ clearTimeout(killTimer);
163
+ request.signal?.removeEventListener('abort', onAbort);
164
+ if (failure !== undefined) {
165
+ reject(failure);
166
+ return;
167
+ }
168
+ resolve({
169
+ exitCode,
170
+ signal,
171
+ stdout: Buffer.concat(stdout, stdoutBytes),
172
+ stderr: Buffer.concat(stderr, stderrBytes),
173
+ durationMs: Math.max(0, Math.round(performance.now() - startedAt)),
174
+ });
175
+ });
176
+ });
177
+ }
178
+ }
@@ -0,0 +1,19 @@
1
+ /** Serialize only stable Runtime and admitted Environment identity for the operator CLI. */
2
+ export function environmentPayload(runtime, environment) {
3
+ return {
4
+ status: environment.status,
5
+ runtimeRoot: runtime.runtimeRoot,
6
+ skillsRoot: runtime.skillsRoot,
7
+ projectRoot: environment.binding.projectRoot,
8
+ pythonExecutable: environment.binding.pythonExecutable,
9
+ marivo: {
10
+ version: environment.binding.marivoVersion,
11
+ packagePath: environment.binding.packagePath,
12
+ },
13
+ reportKit: {
14
+ version: runtime.reportKitVersion,
15
+ packagePath: runtime.reportKitPackagePath,
16
+ },
17
+ fingerprint: environment.binding.fingerprint,
18
+ };
19
+ }
@@ -0,0 +1,49 @@
1
+ import { realpath, stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { bindMarivoEnvironment } from "./binding.js";
4
+ import { MarivoEnvironmentError } from "./errors.js";
5
+ async function assertDirectory(target, label) {
6
+ try {
7
+ if (!(await stat(target)).isDirectory())
8
+ throw new Error('not a directory');
9
+ }
10
+ catch (cause) {
11
+ throw new MarivoEnvironmentError('project-root-invalid', `Marivo Workspace ${label} path is not a directory: ${target}`, { path: target, label }, { cause });
12
+ }
13
+ }
14
+ async function canonicalWorkspaceRoot(projectRoot) {
15
+ let canonicalRoot;
16
+ try {
17
+ canonicalRoot = await realpath(path.resolve(projectRoot));
18
+ await assertDirectory(canonicalRoot, 'root');
19
+ }
20
+ catch (cause) {
21
+ if (cause instanceof MarivoEnvironmentError)
22
+ throw cause;
23
+ throw new MarivoEnvironmentError('project-root-invalid', `Marivo project root is not an existing directory: ${path.resolve(projectRoot)}`, { projectRoot: path.resolve(projectRoot) }, { cause });
24
+ }
25
+ return canonicalRoot;
26
+ }
27
+ /** Cache one zero-init Workspace binding while retaining project-specific doctor state. */
28
+ export class MarivoWorkspaceEnvironmentManager {
29
+ runtime;
30
+ #bindings = new Map();
31
+ constructor(runtime) {
32
+ this.runtime = runtime;
33
+ }
34
+ async resolve(projectRoot) {
35
+ const canonicalRoot = await canonicalWorkspaceRoot(projectRoot);
36
+ let binding = this.#bindings.get(canonicalRoot);
37
+ if (binding === undefined) {
38
+ binding = bindMarivoEnvironment({
39
+ projectRoot: canonicalRoot,
40
+ pythonExecutable: this.runtime.pythonExecutable,
41
+ });
42
+ this.#bindings.set(canonicalRoot, binding);
43
+ }
44
+ return binding;
45
+ }
46
+ dispose() {
47
+ this.#bindings.clear();
48
+ }
49
+ }
@@ -0,0 +1,153 @@
1
+ export const MARIVO_EVIDENCE_FINDINGS_PROGRAM = String.raw `
2
+ import json
3
+ import sys
4
+ import marivo.analysis as mv
5
+ from marivo.analysis.errors import ArtifactNotFoundError, FindingNotFoundError
6
+
7
+
8
+ session_id = sys.argv[1]
9
+ selections = json.loads(sys.argv[2])
10
+ try:
11
+ session = mv.session.resume(session_id, use_datasources=False)
12
+ except Exception as exc:
13
+ print(json.dumps({
14
+ "kind": "evidence-session-read-failed",
15
+ "exception_type": type(exc).__name__,
16
+ }, sort_keys=True), file=sys.stderr)
17
+ raise SystemExit(70)
18
+
19
+
20
+ def revalidation_projection(artifact):
21
+ try:
22
+ result = session.revalidate(artifact.ref)
23
+ except ArtifactNotFoundError:
24
+ return {
25
+ "status": "unavailable",
26
+ "semantic_status": None,
27
+ "evidence_status": None,
28
+ "dependency_status": None,
29
+ }
30
+ return {
31
+ "status": result.status,
32
+ "semantic_status": result.semantic_status,
33
+ "evidence_status": result.evidence_status,
34
+ "dependency_status": result.dependency_status,
35
+ }
36
+
37
+
38
+ sources = []
39
+ for selection in selections:
40
+ artifact_ref = selection["artifactRef"]
41
+ finding_id = selection["findingId"]
42
+ locator = f"marivo://session/{session.id}/artifact/{artifact_ref}/finding/{finding_id}"
43
+ try:
44
+ artifact = session.artifact(artifact_ref)
45
+ except Exception:
46
+ sources.append({
47
+ "status": "missing",
48
+ "title": f"Missing Marivo Finding {finding_id}",
49
+ "locator": locator,
50
+ "excerpt": None,
51
+ "truncated": False,
52
+ "finding_id": finding_id,
53
+ "finding_type": None,
54
+ "epistemic_kind": None,
55
+ "artifact_ref": artifact_ref,
56
+ "session_id": session.id,
57
+ "canonical_item_key": None,
58
+ "committed_at": None,
59
+ "source_refs": [],
60
+ "revalidation": None,
61
+ })
62
+ continue
63
+ except Exception:
64
+ sources.append({
65
+ "status": "unsupported",
66
+ "title": f"Unavailable Marivo Finding {finding_id}",
67
+ "locator": locator,
68
+ "excerpt": None,
69
+ "truncated": False,
70
+ "finding_id": finding_id,
71
+ "finding_type": None,
72
+ "epistemic_kind": None,
73
+ "artifact_ref": artifact_ref,
74
+ "session_id": session.id,
75
+ "canonical_item_key": None,
76
+ "committed_at": None,
77
+ "source_refs": [],
78
+ "revalidation": None,
79
+ })
80
+ continue
81
+
82
+ revalidation = revalidation_projection(artifact)
83
+ try:
84
+ finding = artifact.finding(finding_id)
85
+ except FindingNotFoundError:
86
+ sources.append({
87
+ "status": "missing",
88
+ "title": f"Missing Marivo Finding {finding_id}",
89
+ "locator": locator,
90
+ "excerpt": None,
91
+ "truncated": False,
92
+ "finding_id": finding_id,
93
+ "finding_type": None,
94
+ "epistemic_kind": None,
95
+ "artifact_ref": artifact.ref,
96
+ "session_id": session.id,
97
+ "canonical_item_key": None,
98
+ "committed_at": None,
99
+ "source_refs": [],
100
+ "revalidation": revalidation,
101
+ })
102
+ continue
103
+ except Exception:
104
+ sources.append({
105
+ "status": "unsupported",
106
+ "title": f"Unavailable Marivo Finding {finding_id}",
107
+ "locator": locator,
108
+ "excerpt": None,
109
+ "truncated": False,
110
+ "finding_id": finding_id,
111
+ "finding_type": None,
112
+ "epistemic_kind": None,
113
+ "artifact_ref": artifact.ref,
114
+ "session_id": session.id,
115
+ "canonical_item_key": None,
116
+ "committed_at": None,
117
+ "source_refs": [],
118
+ "revalidation": revalidation,
119
+ })
120
+ continue
121
+
122
+ if finding.session_id != session.id or finding.artifact_ref != artifact.ref:
123
+ print(json.dumps({"kind": "evidence-identity-mismatch"}, sort_keys=True), file=sys.stderr)
124
+ raise SystemExit(70)
125
+
126
+ try:
127
+ excerpt = finding.render(max_output_bytes=4096)
128
+ status = "available"
129
+ except Exception:
130
+ excerpt = None
131
+ status = "unsupported"
132
+ sources.append({
133
+ "status": status,
134
+ "title": f"{finding.finding_type} Finding: {finding.canonical_item_key}",
135
+ "locator": locator,
136
+ "excerpt": excerpt,
137
+ "truncated": excerpt is not None and "output truncated at " in excerpt,
138
+ "finding_id": finding.finding_id,
139
+ "finding_type": finding.finding_type,
140
+ "epistemic_kind": finding.epistemic_kind,
141
+ "artifact_ref": finding.artifact_ref,
142
+ "session_id": finding.session_id,
143
+ "canonical_item_key": finding.canonical_item_key,
144
+ "committed_at": finding.committed_at.isoformat(),
145
+ "source_refs": list(finding.source_refs),
146
+ "revalidation": revalidation,
147
+ })
148
+
149
+ print(json.dumps({
150
+ "session_id": session.id,
151
+ "sources": sources,
152
+ }, sort_keys=True))
153
+ `.trim();
@@ -0,0 +1,166 @@
1
+ import { MarivoEnvironmentError } from "../environment/errors.js";
2
+ import { resolveMarivoBridgeSource } from "../environment/source.js";
3
+ import { MARIVO_EVIDENCE_FINDINGS_PROGRAM } from "./bridge-program.js";
4
+ const EVIDENCE_LIMITS = Object.freeze({
5
+ timeoutMs: 30_000,
6
+ stdoutMaxBytes: 1_048_576,
7
+ stderrMaxBytes: 65_536,
8
+ });
9
+ function nonEmptyString(value, field, maxChars = 2_048) {
10
+ if (typeof value !== 'string' || value.trim() === '' || value.length > maxChars) {
11
+ throw new TypeError(`${field} must be a bounded non-empty string`);
12
+ }
13
+ return value;
14
+ }
15
+ function boundedText(value, field, maxBytes = 4_096) {
16
+ const result = nonEmptyString(value, field, maxBytes);
17
+ if (Buffer.byteLength(result, 'utf8') > maxBytes) {
18
+ throw new TypeError(`${field} must be at most ${maxBytes} UTF-8 bytes`);
19
+ }
20
+ return result;
21
+ }
22
+ function nullableString(value, field, maxChars = 2_048) {
23
+ return value === null ? null : nonEmptyString(value, field, maxChars);
24
+ }
25
+ function parseRevalidation(value) {
26
+ if (value === null)
27
+ return null;
28
+ const item = jsonObject(value);
29
+ if (item === undefined)
30
+ throw new TypeError('revalidation must be an object or null');
31
+ exactKeys(item, ['status', 'semantic_status', 'evidence_status', 'dependency_status'], 'revalidation');
32
+ return {
33
+ status: nonEmptyString(item.status, 'revalidation.status', 128),
34
+ semanticStatus: nullableString(item.semantic_status, 'revalidation.semantic_status', 128),
35
+ evidenceStatus: nullableString(item.evidence_status, 'revalidation.evidence_status', 128),
36
+ dependencyStatus: nullableString(item.dependency_status, 'revalidation.dependency_status', 128),
37
+ };
38
+ }
39
+ function jsonObject(value) {
40
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
41
+ ? value
42
+ : undefined;
43
+ }
44
+ function exactKeys(value, keys, field) {
45
+ const expected = new Set(keys);
46
+ if (Object.keys(value).some((key) => !expected.has(key)) || keys.some((key) => !(key in value))) {
47
+ throw new TypeError(`${field} must contain exactly the expected fields`);
48
+ }
49
+ }
50
+ function parseFinding(value) {
51
+ const item = jsonObject(value);
52
+ if (item === undefined)
53
+ throw new TypeError('finding must be an object');
54
+ exactKeys(item, [
55
+ 'finding_id',
56
+ 'status',
57
+ 'title',
58
+ 'locator',
59
+ 'excerpt',
60
+ 'truncated',
61
+ 'finding_type',
62
+ 'epistemic_kind',
63
+ 'artifact_ref',
64
+ 'session_id',
65
+ 'canonical_item_key',
66
+ 'committed_at',
67
+ 'source_refs',
68
+ 'revalidation',
69
+ ], 'finding');
70
+ if (!['available', 'missing', 'unsupported'].includes(String(item.status))) {
71
+ throw new TypeError('status must be available, missing, or unsupported');
72
+ }
73
+ if (typeof item.truncated !== 'boolean')
74
+ throw new TypeError('truncated must be boolean');
75
+ if (!Array.isArray(item.source_refs) || item.source_refs.length > 100) {
76
+ throw new TypeError('source_refs must be a bounded array');
77
+ }
78
+ const parsed = {
79
+ status: item.status,
80
+ title: boundedText(item.title, 'title', 2_048),
81
+ locator: boundedText(item.locator, 'locator', 2_048),
82
+ excerpt: item.excerpt === null ? null : boundedText(item.excerpt, 'excerpt'),
83
+ truncated: item.truncated,
84
+ findingId: nonEmptyString(item.finding_id, 'finding_id'),
85
+ findingType: nullableString(item.finding_type, 'finding_type'),
86
+ epistemicKind: nullableString(item.epistemic_kind, 'epistemic_kind'),
87
+ artifactRef: nonEmptyString(item.artifact_ref, 'artifact_ref'),
88
+ sessionId: nonEmptyString(item.session_id, 'finding session_id'),
89
+ canonicalItemKey: nullableString(item.canonical_item_key, 'canonical_item_key'),
90
+ committedAt: nullableString(item.committed_at, 'committed_at'),
91
+ sourceRefs: item.source_refs.map((ref, index) => nonEmptyString(ref, `source_refs[${index}]`, 2_048)),
92
+ revalidation: parseRevalidation(item.revalidation),
93
+ };
94
+ if ((parsed.status === 'available' &&
95
+ (parsed.excerpt === null ||
96
+ parsed.findingType === null ||
97
+ parsed.epistemicKind === null ||
98
+ parsed.canonicalItemKey === null ||
99
+ parsed.committedAt === null)) ||
100
+ (parsed.status === 'missing' &&
101
+ (parsed.excerpt !== null ||
102
+ parsed.truncated ||
103
+ parsed.findingType !== null ||
104
+ parsed.epistemicKind !== null ||
105
+ parsed.canonicalItemKey !== null ||
106
+ parsed.committedAt !== null)) ||
107
+ (parsed.status === 'unsupported' && parsed.excerpt !== null) ||
108
+ (parsed.truncated && parsed.excerpt === null)) {
109
+ throw new TypeError('finding status fields are inconsistent');
110
+ }
111
+ return parsed;
112
+ }
113
+ function parseFindingsPayload(stdout, expectedSessionId, expectedSelections) {
114
+ try {
115
+ const root = jsonObject(JSON.parse(stdout.toString('utf8')));
116
+ if (root !== undefined)
117
+ exactKeys(root, ['session_id', 'sources'], 'root');
118
+ if (root === undefined ||
119
+ root.session_id !== expectedSessionId ||
120
+ !Array.isArray(root.sources)) {
121
+ throw new TypeError('root fields are invalid');
122
+ }
123
+ const findings = root.sources.map(parseFinding);
124
+ if (findings.length !== expectedSelections.length)
125
+ throw new TypeError('finding count differs');
126
+ for (let index = 0; index < findings.length; index++) {
127
+ const finding = findings[index];
128
+ const selection = expectedSelections[index];
129
+ if (finding === undefined ||
130
+ selection === undefined ||
131
+ finding.findingId !== selection.findingId ||
132
+ finding.artifactRef !== selection.artifactRef ||
133
+ finding.sessionId !== expectedSessionId) {
134
+ throw new TypeError('finding identity or order differs');
135
+ }
136
+ }
137
+ return findings;
138
+ }
139
+ catch (cause) {
140
+ throw new MarivoEnvironmentError('subprocess-output-invalid', 'Marivo Evidence reader returned an invalid Finding payload', { stdoutBytes: stdout.byteLength }, { cause });
141
+ }
142
+ }
143
+ /** Identity-checked adapter for exact persisted Marivo Findings. */
144
+ export class MarivoEvidenceBridge {
145
+ binding;
146
+ #runner;
147
+ constructor(runner) {
148
+ this.#runner = runner;
149
+ this.binding = runner.binding;
150
+ }
151
+ async findings(sessionId, selections, signal) {
152
+ const result = await this.#runner.runChecked({
153
+ program: MARIVO_EVIDENCE_FINDINGS_PROGRAM,
154
+ args: [sessionId, JSON.stringify(selections)],
155
+ limits: EVIDENCE_LIMITS,
156
+ signal,
157
+ });
158
+ if (result.exitCode !== 0) {
159
+ throw new MarivoEnvironmentError('subprocess-failed', `Marivo Evidence read failed with exit code ${String(result.exitCode)}`, { exitCode: result.exitCode, stderr: result.stderr.toString('utf8').slice(0, 2_000) });
160
+ }
161
+ return parseFindingsPayload(result.stdout, sessionId, selections);
162
+ }
163
+ }
164
+ export function resolveMarivoEvidenceBridge(source) {
165
+ return resolveMarivoBridgeSource(source);
166
+ }
@@ -0,0 +1,2 @@
1
+ export { MarivoEvidenceBridge, resolveMarivoEvidenceBridge, } from "./bridge.js";
2
+ export { createMarivoEvidenceSourcesTool, evidenceSourcesMeta, installMarivoEvidenceSourcesCodeDelivery, MARIVO_EVIDENCE_SOURCES_DURABLE_CONTENT_KIND, MARIVO_EVIDENCE_SOURCES_MAX_PER_CALL, MARIVO_EVIDENCE_SOURCES_META_KIND, MARIVO_EVIDENCE_SOURCES_META_VERSION, MARIVO_EVIDENCE_SOURCES_TOOL_NAME, registerMarivoEvidenceSourcesTool, } from "./sources.js";