@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.
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/cordis.patch.yml +11 -0
- package/lib/bin/environment.js +91 -0
- package/lib/bridges.js +11 -0
- package/lib/client.js +744 -0
- package/lib/compatibility.js +66 -0
- package/lib/datasource/access.js +60 -0
- package/lib/datasource/bridge-programs.js +80 -0
- package/lib/datasource/bridge.js +204 -0
- package/lib/datasource/credentials.js +24 -0
- package/lib/datasource/index.js +4 -0
- package/lib/datasource/shell-env.js +386 -0
- package/lib/datasource/test.js +65 -0
- package/lib/disclosure/activation.js +369 -0
- package/lib/disclosure/bridge-program.js +11 -0
- package/lib/disclosure/bridge.js +65 -0
- package/lib/disclosure/help.js +227 -0
- package/lib/disclosure/index.js +3 -0
- package/lib/environment/binding.js +273 -0
- package/lib/environment/doctor.js +123 -0
- package/lib/environment/errors.js +11 -0
- package/lib/environment/index.js +7 -0
- package/lib/environment/runtime.js +558 -0
- package/lib/environment/source.js +6 -0
- package/lib/environment/subprocess.js +178 -0
- package/lib/environment/summary.js +19 -0
- package/lib/environment/workspace.js +49 -0
- package/lib/evidence/bridge-program.js +153 -0
- package/lib/evidence/bridge.js +166 -0
- package/lib/evidence/index.js +2 -0
- package/lib/evidence/sources.js +263 -0
- package/lib/index.js +7 -0
- package/lib/plugin.js +179 -0
- package/lib/types/bridges.d.ts +11 -0
- package/lib/types/client.d.ts +125 -0
- package/lib/types/compatibility.d.ts +29 -0
- package/lib/types/datasource/access.d.ts +27 -0
- package/lib/types/datasource/bridge.d.ts +53 -0
- package/lib/types/datasource/index.d.ts +8 -0
- package/lib/types/datasource/shell-env.d.ts +38 -0
- package/lib/types/datasource/test.d.ts +32 -0
- package/lib/types/disclosure/activation.d.ts +73 -0
- package/lib/types/disclosure/bridge.d.ts +28 -0
- package/lib/types/disclosure/help.d.ts +64 -0
- package/lib/types/disclosure/index.d.ts +5 -0
- package/lib/types/environment/binding.d.ts +26 -0
- package/lib/types/environment/doctor.d.ts +8 -0
- package/lib/types/environment/errors.d.ts +7 -0
- package/lib/types/environment/index.d.ts +9 -0
- package/lib/types/environment/runtime.d.ts +17 -0
- package/lib/types/environment/source.d.ts +7 -0
- package/lib/types/environment/subprocess.d.ts +19 -0
- package/lib/types/environment/types.d.ts +96 -0
- package/lib/types/environment/workspace.d.ts +10 -0
- package/lib/types/evidence/bridge.d.ts +40 -0
- package/lib/types/evidence/index.d.ts +4 -0
- package/lib/types/evidence/sources.d.ts +57 -0
- package/lib/types/index.d.ts +9 -0
- package/lib/types/plugin.d.ts +43 -0
- package/package.json +149 -0
- package/python/report-kit/dist/dsh_data_analysis_report_kit-3.0.0-py3-none-any.whl +0 -0
- package/skills/dsh-data-analysis-report/SKILL.md +57 -0
- package/skills/dsh-data-analysis-report/assets/marivo-artifact.js +113 -0
- package/skills/dsh-data-analysis-report/assets/marivo-session-dag.js +1201 -0
- package/skills/dsh-data-analysis-report/assets/report-data.js +449 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
export const COMPATIBILITY_SCHEMA = 'dsh-data-analysis-compatibility/v2';
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function requiredString(value, location) {
|
|
7
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
8
|
+
throw new TypeError(`${location} must be a non-empty string`);
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
function requiredLiteral(value, expected, location) {
|
|
13
|
+
if (value !== expected)
|
|
14
|
+
throw new TypeError(`${location} must be ${expected}`);
|
|
15
|
+
return expected;
|
|
16
|
+
}
|
|
17
|
+
function stringRecord(value, location) {
|
|
18
|
+
if (!isRecord(value))
|
|
19
|
+
throw new TypeError(`${location} must be an object`);
|
|
20
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, requiredString(item, `${location}.${key}`)])));
|
|
21
|
+
}
|
|
22
|
+
function parsePackageManifest(value) {
|
|
23
|
+
if (!isRecord(value))
|
|
24
|
+
throw new TypeError('package.json must contain an object');
|
|
25
|
+
const compatibility = value.dshDataAnalysisCompatibility;
|
|
26
|
+
if (!isRecord(compatibility)) {
|
|
27
|
+
throw new TypeError('package.json.dshDataAnalysisCompatibility must be an object');
|
|
28
|
+
}
|
|
29
|
+
const dsh = compatibility.dsh;
|
|
30
|
+
const marivo = compatibility.marivo;
|
|
31
|
+
const contracts = compatibility.contracts;
|
|
32
|
+
if (!isRecord(dsh) || !isRecord(marivo) || !isRecord(contracts)) {
|
|
33
|
+
throw new TypeError('package compatibility dsh, marivo, and contracts must be objects');
|
|
34
|
+
}
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
version: requiredString(value.version, 'package.json.version'),
|
|
37
|
+
peerDependencies: stringRecord(value.peerDependencies, 'package.json.peerDependencies'),
|
|
38
|
+
dshDataAnalysisCompatibility: Object.freeze({
|
|
39
|
+
schema: requiredLiteral(compatibility.schema, COMPATIBILITY_SCHEMA, 'package compatibility schema'),
|
|
40
|
+
dsh: Object.freeze({
|
|
41
|
+
distribution: requiredLiteral(dsh.distribution, '@deepseek-ai/dsh', 'package compatibility dsh.distribution'),
|
|
42
|
+
peerRange: requiredString(dsh.peerRange, 'package compatibility dsh.peerRange'),
|
|
43
|
+
}),
|
|
44
|
+
marivo: Object.freeze({
|
|
45
|
+
version: requiredString(marivo.version, 'package compatibility marivo.version'),
|
|
46
|
+
packageSpec: requiredString(marivo.packageSpec, 'package compatibility marivo.packageSpec'),
|
|
47
|
+
}),
|
|
48
|
+
contracts: Object.freeze({
|
|
49
|
+
runtimeInstallation: requiredLiteral(contracts.runtimeInstallation, 'dsh-data-analysis-runtime/v2', 'package compatibility contracts.runtimeInstallation'),
|
|
50
|
+
subprocessPolicy: requiredLiteral(contracts.subprocessPolicy, 'direct-argv-inherited-env-snapshot-overlay-v2', 'package compatibility contracts.subprocessPolicy'),
|
|
51
|
+
}),
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const packageManifest = parsePackageManifest(createRequire(import.meta.url)('../package.json'));
|
|
56
|
+
/** The package semver and the compatibility contract shipped in this exact installation. */
|
|
57
|
+
export const PLUGIN_VERSION = packageManifest.version;
|
|
58
|
+
export const DSH_DATA_ANALYSIS_COMPATIBILITY = packageManifest.dshDataAnalysisCompatibility;
|
|
59
|
+
/** Every DSH peer is required and must use this one verified release. */
|
|
60
|
+
export const DSH_PEER_RANGE = DSH_DATA_ANALYSIS_COMPATIBILITY.dsh.peerRange;
|
|
61
|
+
export const DSH_DISTRIBUTION = DSH_DATA_ANALYSIS_COMPATIBILITY.dsh.distribution;
|
|
62
|
+
export const DSH_PEER_DEPENDENCIES = Object.freeze(Object.fromEntries(Object.entries(packageManifest.peerDependencies).filter(([name]) => name.startsWith('@deepseek-ai/dsh-'))));
|
|
63
|
+
export const MARIVO_VERSION = DSH_DATA_ANALYSIS_COMPATIBILITY.marivo.version;
|
|
64
|
+
export const MARIVO_PACKAGE_SPEC = DSH_DATA_ANALYSIS_COMPATIBILITY.marivo.packageSpec;
|
|
65
|
+
export const RUNTIME_INSTALLATION_VERSION = DSH_DATA_ANALYSIS_COMPATIBILITY.contracts.runtimeInstallation;
|
|
66
|
+
export const SUBPROCESS_POLICY_VERSION = DSH_DATA_ANALYSIS_COMPATIBILITY.contracts.subprocessPolicy;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
|
+
import { resolveMarivoDatasourceBridge, } from "./bridge.js";
|
|
3
|
+
import { inspectMarivoDatasourceCredentials } from "./credentials.js";
|
|
4
|
+
export const MARIVO_DATASOURCE_ACCESS_TOOL_NAME = 'marivo_datasource_access';
|
|
5
|
+
function datasourceName(value) {
|
|
6
|
+
if (typeof value !== 'string' || value.trim() === '' || value.length > 256) {
|
|
7
|
+
throw new TypeError('marivo_datasource_access name must be a non-empty string of at most 256 characters');
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
function renderValue(value) {
|
|
12
|
+
if (value.status === 'needs-credentials')
|
|
13
|
+
return JSON.stringify(value);
|
|
14
|
+
return [
|
|
15
|
+
`Marivo datasource ${value.name} foreground Shell access is ready for up to ${value.shell_lease.max_uses} executions within ${value.shell_lease.expires_in_ms} ms.`,
|
|
16
|
+
'Copy every line of the matching prelude verbatim before each foreground analysis command.',
|
|
17
|
+
'The first line beginning with "# dsh-marivo-credential-lease:" is a required control marker; do not remove or move it.',
|
|
18
|
+
'Use run_in_background=false (or omit it). If a datasource credential environment variable is missing, reacquire access; never read DSH credential files or ~/.marivo/secrets.toml.',
|
|
19
|
+
`bash prelude:\n${value.shell_lease.bash_prelude}`,
|
|
20
|
+
`pwsh prelude:\n${value.shell_lease.pwsh_prelude}`,
|
|
21
|
+
].join('\n');
|
|
22
|
+
}
|
|
23
|
+
/** Build the scoped datasource access Tool without performing a connection test. */
|
|
24
|
+
export function createMarivoDatasourceAccessTool(bridgeSource, credentials, options) {
|
|
25
|
+
return defineTool({
|
|
26
|
+
name: MARIVO_DATASOURCE_ACCESS_TOOL_NAME,
|
|
27
|
+
description: 'Acquire bounded foreground Shell access for one configured Marivo datasource without testing its connection.',
|
|
28
|
+
parameters: {
|
|
29
|
+
name: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
required: true,
|
|
32
|
+
description: 'Configured Marivo datasource name.',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
output: {
|
|
36
|
+
schema: { type: 'json' },
|
|
37
|
+
render: (_args, value) => [
|
|
38
|
+
{ type: 'text', text: renderValue(value) },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
async execute(args, exec) {
|
|
42
|
+
const name = datasourceName(args.name);
|
|
43
|
+
const bridge = await resolveMarivoDatasourceBridge(bridgeSource);
|
|
44
|
+
options.revokeShellLease(bridge, name);
|
|
45
|
+
const described = await bridge.describe(name, exec.signal);
|
|
46
|
+
const missing = await inspectMarivoDatasourceCredentials(described.refs, credentials, exec.signal);
|
|
47
|
+
if (missing.length > 0) {
|
|
48
|
+
return { status: 'needs-credentials', name: described.name, refs: missing };
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
status: 'ok',
|
|
52
|
+
name: described.name,
|
|
53
|
+
shell_lease: options.issueShellLease(bridge, described.name, described.refs),
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export function registerMarivoDatasourceAccessTool(ctx, bridgeSource, credentials, options) {
|
|
59
|
+
return ctx.tools.register(createMarivoDatasourceAccessTool(bridgeSource, credentials, options));
|
|
60
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export const MARIVO_DATASOURCE_DESCRIBE_PROGRAM = String.raw `
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import marivo
|
|
6
|
+
import marivo.datasource as md
|
|
7
|
+
|
|
8
|
+
description = md.describe(sys.argv[1])
|
|
9
|
+
print(json.dumps({
|
|
10
|
+
"name": description.name,
|
|
11
|
+
"refs": list(description.env_refs.values()),
|
|
12
|
+
}, sort_keys=True))
|
|
13
|
+
`.trim();
|
|
14
|
+
export const MARIVO_DATASOURCE_INVENTORY_PROGRAM = String.raw `
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import marivo
|
|
19
|
+
import marivo.datasource as md
|
|
20
|
+
|
|
21
|
+
print(json.dumps({
|
|
22
|
+
"datasources": [{
|
|
23
|
+
"name": description.name,
|
|
24
|
+
"refs": list(description.env_refs.values()),
|
|
25
|
+
} for description in (md.describe(item.name) for item in md.list())],
|
|
26
|
+
}, sort_keys=True))
|
|
27
|
+
`.trim();
|
|
28
|
+
export const MARIVO_DATASOURCE_TEST_PROGRAM = String.raw `
|
|
29
|
+
import contextlib
|
|
30
|
+
import io
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import sys
|
|
34
|
+
import marivo
|
|
35
|
+
import marivo.datasource as md
|
|
36
|
+
|
|
37
|
+
captured_stdout = io.StringIO()
|
|
38
|
+
captured_stderr = io.StringIO()
|
|
39
|
+
try:
|
|
40
|
+
with contextlib.redirect_stdout(captured_stdout), contextlib.redirect_stderr(captured_stderr):
|
|
41
|
+
description = md.describe(sys.argv[1])
|
|
42
|
+
secret_values = [
|
|
43
|
+
value
|
|
44
|
+
for ref in description.env_refs.values()
|
|
45
|
+
if (value := os.environ.get(ref))
|
|
46
|
+
]
|
|
47
|
+
result = md.test(sys.argv[1])
|
|
48
|
+
except Exception as exc:
|
|
49
|
+
print(json.dumps({"kind": "datasource-test-failed", "exception_type": type(exc).__name__}), file=sys.stderr)
|
|
50
|
+
raise SystemExit(70)
|
|
51
|
+
|
|
52
|
+
def redact(value):
|
|
53
|
+
if isinstance(value, str):
|
|
54
|
+
for secret in secret_values:
|
|
55
|
+
value = value.replace(secret, "[REDACTED]")
|
|
56
|
+
return value
|
|
57
|
+
if isinstance(value, list):
|
|
58
|
+
return [redact(item) for item in value]
|
|
59
|
+
if isinstance(value, tuple):
|
|
60
|
+
return [redact(item) for item in value]
|
|
61
|
+
if isinstance(value, dict):
|
|
62
|
+
return {key: redact(item) for key, item in value.items()}
|
|
63
|
+
return value
|
|
64
|
+
|
|
65
|
+
failure = None if result.failure is None else {
|
|
66
|
+
"code": result.failure.code,
|
|
67
|
+
"exception_type": result.failure.exception_type,
|
|
68
|
+
"backend_code": result.failure.backend_code,
|
|
69
|
+
"backend_name": result.failure.backend_name,
|
|
70
|
+
"message": result.failure.message,
|
|
71
|
+
}
|
|
72
|
+
repair = None if result.repair is None else result.repair.model_dump(mode="json")
|
|
73
|
+
print(json.dumps(redact({
|
|
74
|
+
"name": result.name,
|
|
75
|
+
"ok": result.ok,
|
|
76
|
+
"latency_ms": result.latency_ms,
|
|
77
|
+
"failure": failure,
|
|
78
|
+
"repair": repair,
|
|
79
|
+
}), sort_keys=True))
|
|
80
|
+
`.trim();
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { isCredentialRefName } from '@deepseek-ai/dsh-credentials';
|
|
2
|
+
import { MarivoEnvironmentError } from "../environment/errors.js";
|
|
3
|
+
import { resolveMarivoBridgeSource } from "../environment/source.js";
|
|
4
|
+
import { MARIVO_DATASOURCE_DESCRIBE_PROGRAM, MARIVO_DATASOURCE_INVENTORY_PROGRAM, MARIVO_DATASOURCE_TEST_PROGRAM, } from "./bridge-programs.js";
|
|
5
|
+
const DATASOURCE_LIMITS = Object.freeze({
|
|
6
|
+
timeoutMs: 30_000,
|
|
7
|
+
stdoutMaxBytes: 262_144,
|
|
8
|
+
stderrMaxBytes: 65_536,
|
|
9
|
+
});
|
|
10
|
+
function exactKeys(value, keys, phase) {
|
|
11
|
+
const expected = new Set(keys);
|
|
12
|
+
if (Object.keys(value).some((key) => !expected.has(key)) || keys.some((key) => !(key in value))) {
|
|
13
|
+
throw new MarivoEnvironmentError('subprocess-output-invalid', `Marivo datasource ${phase} returned an unexpected payload shape`, { phase });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function object(value) {
|
|
17
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
18
|
+
? value
|
|
19
|
+
: undefined;
|
|
20
|
+
}
|
|
21
|
+
function string(value) {
|
|
22
|
+
return typeof value === 'string' && value.trim() !== '' ? value : undefined;
|
|
23
|
+
}
|
|
24
|
+
function nullableString(value) {
|
|
25
|
+
return value === null || typeof value === 'string';
|
|
26
|
+
}
|
|
27
|
+
function parseJsonObject(stdout, phase) {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(stdout.toString('utf8'));
|
|
30
|
+
if (object(parsed) === undefined) {
|
|
31
|
+
throw new TypeError('payload must be an object');
|
|
32
|
+
}
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
catch (cause) {
|
|
36
|
+
throw new MarivoEnvironmentError('subprocess-output-invalid', `Marivo datasource ${phase} returned an invalid payload`, { phase, stdoutBytes: stdout.byteLength }, { cause });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function parseDescription(value, phase) {
|
|
40
|
+
const source = object(value);
|
|
41
|
+
if (source === undefined) {
|
|
42
|
+
throw new MarivoEnvironmentError('subprocess-output-invalid', `Marivo datasource ${phase} returned invalid fields`, { phase });
|
|
43
|
+
}
|
|
44
|
+
exactKeys(source, ['name', 'refs'], phase);
|
|
45
|
+
const name = string(source.name);
|
|
46
|
+
if (name === undefined || !Array.isArray(source.refs)) {
|
|
47
|
+
throw new MarivoEnvironmentError('subprocess-output-invalid', `Marivo datasource ${phase} returned invalid fields`, { phase });
|
|
48
|
+
}
|
|
49
|
+
const refs = [];
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
for (const ref of source.refs) {
|
|
52
|
+
if (typeof ref !== 'string' || !isCredentialRefName(ref)) {
|
|
53
|
+
throw new MarivoEnvironmentError('subprocess-output-invalid', `Marivo datasource ${phase} returned an invalid credential reference`, { phase });
|
|
54
|
+
}
|
|
55
|
+
if (!seen.has(ref)) {
|
|
56
|
+
seen.add(ref);
|
|
57
|
+
refs.push(ref);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { name, refs };
|
|
61
|
+
}
|
|
62
|
+
function parseInventory(stdout) {
|
|
63
|
+
const value = parseJsonObject(stdout, 'inventory');
|
|
64
|
+
exactKeys(value, ['datasources'], 'inventory');
|
|
65
|
+
if (!Array.isArray(value.datasources)) {
|
|
66
|
+
throw new MarivoEnvironmentError('subprocess-output-invalid', 'Marivo datasource inventory returned invalid fields', { phase: 'datasource-inventory' });
|
|
67
|
+
}
|
|
68
|
+
return value.datasources.map((item) => parseDescription(item, 'inventory'));
|
|
69
|
+
}
|
|
70
|
+
function parseFailure(value) {
|
|
71
|
+
const source = object(value);
|
|
72
|
+
if (source === undefined)
|
|
73
|
+
throw new TypeError('failure must be an object');
|
|
74
|
+
exactKeys(source, ['code', 'exception_type', 'backend_code', 'backend_name', 'message'], 'test');
|
|
75
|
+
if (string(source.code) === undefined ||
|
|
76
|
+
string(source.exception_type) === undefined ||
|
|
77
|
+
!nullableString(source.backend_code) ||
|
|
78
|
+
!nullableString(source.backend_name) ||
|
|
79
|
+
typeof source.message !== 'string') {
|
|
80
|
+
throw new TypeError('failure fields are invalid');
|
|
81
|
+
}
|
|
82
|
+
return source;
|
|
83
|
+
}
|
|
84
|
+
function parseRepair(value) {
|
|
85
|
+
const source = object(value);
|
|
86
|
+
if (source === undefined)
|
|
87
|
+
throw new TypeError('repair must be an object');
|
|
88
|
+
exactKeys(source, ['kind', 'help_target', 'action', 'snippet', 'candidates', 'preserves_evidence'], 'test');
|
|
89
|
+
if (string(source.kind) === undefined ||
|
|
90
|
+
string(source.help_target) === undefined ||
|
|
91
|
+
typeof source.action !== 'string' ||
|
|
92
|
+
!nullableString(source.snippet) ||
|
|
93
|
+
!Array.isArray(source.candidates) ||
|
|
94
|
+
source.candidates.some((item) => typeof item !== 'string') ||
|
|
95
|
+
(source.preserves_evidence !== null && typeof source.preserves_evidence !== 'boolean')) {
|
|
96
|
+
throw new TypeError('repair fields are invalid');
|
|
97
|
+
}
|
|
98
|
+
return source;
|
|
99
|
+
}
|
|
100
|
+
function redactUnknown(value, secrets) {
|
|
101
|
+
if (typeof value === 'string') {
|
|
102
|
+
let result = value;
|
|
103
|
+
for (const secret of secrets)
|
|
104
|
+
if (secret !== '')
|
|
105
|
+
result = result.split(secret).join('[REDACTED]');
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(value))
|
|
109
|
+
return value.map((item) => redactUnknown(item, secrets));
|
|
110
|
+
if (typeof value !== 'object' || value === null)
|
|
111
|
+
return value;
|
|
112
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactUnknown(item, secrets)]));
|
|
113
|
+
}
|
|
114
|
+
function parseTest(stdout, secrets) {
|
|
115
|
+
const value = redactUnknown(parseJsonObject(stdout, 'test'), secrets);
|
|
116
|
+
exactKeys(value, ['name', 'ok', 'latency_ms', 'failure', 'repair'], 'test');
|
|
117
|
+
if (string(value.name) === undefined ||
|
|
118
|
+
typeof value.ok !== 'boolean' ||
|
|
119
|
+
(value.latency_ms !== null &&
|
|
120
|
+
(typeof value.latency_ms !== 'number' ||
|
|
121
|
+
!Number.isFinite(value.latency_ms) ||
|
|
122
|
+
value.latency_ms < 0))) {
|
|
123
|
+
throw new MarivoEnvironmentError('subprocess-output-invalid', `Marivo datasource test returned invalid fields (name=${typeof value.name}, ok=${typeof value.ok}, latency=${value.latency_ms === null ? 'null' : typeof value.latency_ms})`, { phase: 'test' });
|
|
124
|
+
}
|
|
125
|
+
if (value.ok) {
|
|
126
|
+
if (value.failure !== null || value.repair !== null) {
|
|
127
|
+
throw new MarivoEnvironmentError('subprocess-output-invalid', 'Marivo datasource test success included failure or repair fields', { phase: 'test' });
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
name: value.name,
|
|
131
|
+
ok: true,
|
|
132
|
+
latency_ms: value.latency_ms,
|
|
133
|
+
failure: null,
|
|
134
|
+
repair: null,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (value.failure === null || value.repair === null) {
|
|
138
|
+
throw new MarivoEnvironmentError('subprocess-output-invalid', 'Marivo datasource test failure omitted structured failure or repair', { phase: 'test' });
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
return {
|
|
142
|
+
name: value.name,
|
|
143
|
+
ok: false,
|
|
144
|
+
latency_ms: value.latency_ms,
|
|
145
|
+
failure: parseFailure(value.failure),
|
|
146
|
+
repair: parseRepair(value.repair),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (cause) {
|
|
150
|
+
if (cause instanceof MarivoEnvironmentError)
|
|
151
|
+
throw cause;
|
|
152
|
+
throw new MarivoEnvironmentError('subprocess-output-invalid', 'Marivo datasource test returned invalid failure or repair fields', { phase: 'test' }, { cause });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** Identity-checked adapter for Marivo datasource metadata and connection tests. */
|
|
156
|
+
export class MarivoDatasourceBridge {
|
|
157
|
+
binding;
|
|
158
|
+
#runner;
|
|
159
|
+
constructor(runner) {
|
|
160
|
+
this.#runner = runner;
|
|
161
|
+
this.binding = runner.binding;
|
|
162
|
+
}
|
|
163
|
+
async describe(name, signal) {
|
|
164
|
+
const result = await this.#runner.runChecked({
|
|
165
|
+
program: MARIVO_DATASOURCE_DESCRIBE_PROGRAM,
|
|
166
|
+
args: [name],
|
|
167
|
+
limits: DATASOURCE_LIMITS,
|
|
168
|
+
signal,
|
|
169
|
+
});
|
|
170
|
+
this.#assertSuccess(result, 'describe');
|
|
171
|
+
return parseDescription(parseJsonObject(result.stdout, 'describe'), 'describe');
|
|
172
|
+
}
|
|
173
|
+
async inventory(signal) {
|
|
174
|
+
const result = await this.#runner.runChecked({
|
|
175
|
+
program: MARIVO_DATASOURCE_INVENTORY_PROGRAM,
|
|
176
|
+
limits: DATASOURCE_LIMITS,
|
|
177
|
+
signal,
|
|
178
|
+
});
|
|
179
|
+
this.#assertSuccess(result, 'inventory');
|
|
180
|
+
return parseInventory(result.stdout);
|
|
181
|
+
}
|
|
182
|
+
async test(name, environmentOverlay, signal) {
|
|
183
|
+
const result = await this.#runner.runChecked({
|
|
184
|
+
program: MARIVO_DATASOURCE_TEST_PROGRAM,
|
|
185
|
+
args: [name],
|
|
186
|
+
environmentOverlay,
|
|
187
|
+
limits: DATASOURCE_LIMITS,
|
|
188
|
+
signal,
|
|
189
|
+
});
|
|
190
|
+
this.#assertSuccess(result, 'test');
|
|
191
|
+
return parseTest(result.stdout, Object.values(environmentOverlay).filter((value) => Boolean(value)));
|
|
192
|
+
}
|
|
193
|
+
#assertSuccess(result, phase) {
|
|
194
|
+
if (result.exitCode === 0)
|
|
195
|
+
return;
|
|
196
|
+
throw new MarivoEnvironmentError('subprocess-failed', `Marivo datasource ${phase} failed with exit code ${String(result.exitCode)}`, { phase, exitCode: result.exitCode, stderr: result.stderr.toString('utf8').slice(0, 2_000) });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
export function resolveMarivoDatasourceBridge(source) {
|
|
200
|
+
return resolveMarivoBridgeSource(source);
|
|
201
|
+
}
|
|
202
|
+
export function resolveMarivoDatasourceInventoryBridge(source) {
|
|
203
|
+
return resolveMarivoBridgeSource(source);
|
|
204
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
2
|
+
import { MarivoEnvironmentError } from "../environment/index.js";
|
|
3
|
+
import { assertMarivoCredentialReferences, marivoCredentialStorageRef } from "./shell-env.js";
|
|
4
|
+
/** Resolve mapped credentials once and optionally project values into an operation overlay. */
|
|
5
|
+
export async function inspectMarivoDatasourceCredentials(refs, credentials, signal, accept) {
|
|
6
|
+
const uniqueRefs = [...new Set(refs)];
|
|
7
|
+
assertMarivoCredentialReferences(uniqueRefs);
|
|
8
|
+
const missing = [];
|
|
9
|
+
for (const ref of uniqueRefs) {
|
|
10
|
+
signal.throwIfAborted();
|
|
11
|
+
let resolved;
|
|
12
|
+
try {
|
|
13
|
+
resolved = await credentials.resolve(credentialRef(marivoCredentialStorageRef(ref)));
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new MarivoEnvironmentError('datasource-credential-resolve-failed', 'A mapped Marivo datasource credential could not be resolved', { ref });
|
|
17
|
+
}
|
|
18
|
+
if (resolved === undefined)
|
|
19
|
+
missing.push(ref);
|
|
20
|
+
else
|
|
21
|
+
accept?.(ref, resolved.value);
|
|
22
|
+
}
|
|
23
|
+
return missing;
|
|
24
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createMarivoDatasourceAccessTool, MARIVO_DATASOURCE_ACCESS_TOOL_NAME, registerMarivoDatasourceAccessTool, } from "./access.js";
|
|
2
|
+
export { MarivoDatasourceBridge, resolveMarivoDatasourceBridge, resolveMarivoDatasourceInventoryBridge, } from "./bridge.js";
|
|
3
|
+
export { assertMarivoCredentialReferences, DEFAULT_MARIVO_CREDENTIAL_LEASE_MAX_USES, DEFAULT_MARIVO_CREDENTIAL_LEASE_TTL_MS, MARIVO_CREDENTIAL_LEASE_PREFIX, MARIVO_CREDENTIAL_STORAGE_PREFIX, MarivoShellCredentialLeases, marivoCredentialStorageRef, } from "./shell-env.js";
|
|
4
|
+
export { createMarivoDatasourceTestTool, MARIVO_DATASOURCE_TEST_TOOL_NAME, registerMarivoDatasourceTestTool, } from "./test.js";
|