@chengxianglibra/dsh-data-analysis 0.1.1 → 0.1.2

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 (195) hide show
  1. package/README.md +83 -34
  2. package/lib/bin/environment.js +1 -1
  3. package/lib/bin/presentation-lint.js +21 -0
  4. package/lib/bridges.js +2 -2
  5. package/lib/client/credentials/create-datasource.js +67 -0
  6. package/lib/client/credentials/install.js +191 -0
  7. package/lib/client/credentials/model.js +494 -0
  8. package/lib/client/credentials/styles.js +17 -0
  9. package/lib/client/workspace-header-action.js +11 -0
  10. package/lib/client.js +1133 -736
  11. package/lib/compatibility.js +1 -1
  12. package/lib/datasource/authoring.js +70 -0
  13. package/lib/datasource/bridge-programs.js +30 -18
  14. package/lib/datasource/bridge.js +100 -8
  15. package/lib/datasource/index.js +3 -2
  16. package/lib/datasource/python.js +115 -0
  17. package/lib/datasource/resolver-program.js +112 -0
  18. package/lib/datasource/rpc.js +88 -0
  19. package/lib/datasource/service.js +687 -0
  20. package/lib/datasource/shell-env.js +2 -333
  21. package/lib/datasource/test.js +24 -51
  22. package/lib/environment/binding.js +54 -9
  23. package/lib/environment/runtime.js +89 -78
  24. package/lib/environment/subprocess.js +6 -1
  25. package/lib/environment/summary.js +3 -3
  26. package/lib/environment/workspace.js +5 -0
  27. package/lib/index.js +1 -1
  28. package/lib/plugin.js +168 -51
  29. package/lib/presentation/assets/portable.js +606 -0
  30. package/lib/presentation/assets/static.js +945 -0
  31. package/lib/presentation/build/index.js +71 -0
  32. package/lib/presentation/commit.js +156 -0
  33. package/lib/presentation/contracts/charts.js +476 -0
  34. package/lib/presentation/contracts/editing.js +58 -0
  35. package/lib/presentation/contracts/index.js +651 -0
  36. package/lib/presentation/contracts/interaction.js +62 -0
  37. package/lib/presentation/contracts/types.js +15 -0
  38. package/lib/presentation/contracts/values.js +28 -0
  39. package/lib/presentation/delivery.js +56 -0
  40. package/lib/presentation/files.js +83 -0
  41. package/lib/presentation/index.js +6 -0
  42. package/lib/presentation/lint.js +46 -0
  43. package/lib/presentation/projection/files.js +85 -0
  44. package/lib/presentation/projection/index.js +195 -0
  45. package/lib/presentation/projection/program.js +144 -0
  46. package/lib/presentation/projection/sql-code.js +79 -0
  47. package/lib/presentation/receipt.js +34 -0
  48. package/lib/presentation/reports.js +122 -0
  49. package/lib/presentation/rpc.js +148 -0
  50. package/lib/presentation/tool.js +122 -0
  51. package/lib/python-execution-contracts.js +1 -0
  52. package/lib/python-execution.js +232 -0
  53. package/lib/semantic-browser/contracts.js +80 -0
  54. package/lib/semantic-browser/definition.js +64 -0
  55. package/lib/semantic-browser/program.js +119 -0
  56. package/lib/semantic-browser/service.js +72 -0
  57. package/lib/semantic-reference/bridge.js +118 -0
  58. package/lib/semantic-reference/contracts.js +113 -0
  59. package/lib/semantic-reference/index.js +3 -0
  60. package/lib/semantic-reference/rpc.js +122 -0
  61. package/lib/semantic-reference/search.js +94 -0
  62. package/lib/semantic-reference/usage.js +143 -0
  63. package/lib/types/bridges.d.ts +2 -2
  64. package/lib/types/client/credentials/create-datasource.d.ts +5 -0
  65. package/lib/types/client/credentials/install.d.ts +1 -0
  66. package/lib/types/client/credentials/model.d.ts +59 -0
  67. package/lib/types/client/credentials/styles.d.ts +1 -0
  68. package/lib/types/client/presentation/ask-dsh.d.ts +9 -0
  69. package/lib/types/client/presentation/chart-explorer.d.ts +12 -0
  70. package/lib/types/client/presentation/chart-geometry.d.ts +30 -0
  71. package/lib/types/client/presentation/chart-renderer.d.ts +10 -0
  72. package/lib/types/client/presentation/chart-specials.d.ts +10 -0
  73. package/lib/types/client/presentation/chart-styles.d.ts +1 -0
  74. package/lib/types/client/presentation/chart-tooltip.d.ts +13 -0
  75. package/lib/types/client/presentation/chart-view.d.ts +23 -0
  76. package/lib/types/client/presentation/copy-context.d.ts +5 -0
  77. package/lib/types/client/presentation/delivery-model.d.ts +54 -0
  78. package/lib/types/client/presentation/delivery.d.ts +77 -0
  79. package/lib/types/client/presentation/editor-controls.d.ts +12 -0
  80. package/lib/types/client/presentation/global-filters.d.ts +6 -0
  81. package/lib/types/client/presentation/host-entry.d.ts +12 -0
  82. package/lib/types/client/presentation/icons.d.ts +4 -0
  83. package/lib/types/client/presentation/install.d.ts +15 -0
  84. package/lib/types/client/presentation/markdown.d.ts +5 -0
  85. package/lib/types/client/presentation/model.d.ts +62 -0
  86. package/lib/types/client/presentation/reader.d.ts +11 -0
  87. package/lib/types/client/presentation/source-code-model.d.ts +16 -0
  88. package/lib/types/client/presentation/source-code.d.ts +11 -0
  89. package/lib/types/client/presentation/source-dialog.d.ts +13 -0
  90. package/lib/types/client/presentation/source-facts.d.ts +20 -0
  91. package/lib/types/client/presentation/source-format.d.ts +5 -0
  92. package/lib/types/client/presentation/source-highlight.d.ts +7 -0
  93. package/lib/types/client/presentation/sources.d.ts +13 -0
  94. package/lib/types/client/presentation/styles.d.ts +1 -0
  95. package/lib/types/client/presentation/table.d.ts +12 -0
  96. package/lib/types/client/presentation/use-element-width.d.ts +5 -0
  97. package/lib/types/client/workspace-header-action.d.ts +7 -0
  98. package/lib/types/client.d.ts +4 -122
  99. package/lib/types/compatibility.d.ts +2 -2
  100. package/lib/types/datasource/authoring.d.ts +25 -0
  101. package/lib/types/datasource/bridge.d.ts +21 -3
  102. package/lib/types/datasource/index.d.ts +3 -5
  103. package/lib/types/datasource/python.d.ts +4 -0
  104. package/lib/types/datasource/resolver-program.d.ts +6 -0
  105. package/lib/types/datasource/rpc.d.ts +4 -0
  106. package/lib/types/datasource/service.d.ts +112 -0
  107. package/lib/types/datasource/shell-env.d.ts +0 -29
  108. package/lib/types/datasource/test.d.ts +6 -29
  109. package/lib/types/environment/binding.d.ts +5 -1
  110. package/lib/types/environment/errors.d.ts +1 -1
  111. package/lib/types/environment/runtime.d.ts +5 -5
  112. package/lib/types/environment/types.d.ts +13 -2
  113. package/lib/types/index.d.ts +1 -1
  114. package/lib/types/plugin.d.ts +11 -5
  115. package/lib/types/presentation/build/index.d.ts +11 -0
  116. package/lib/types/presentation/commit.d.ts +4 -0
  117. package/lib/types/presentation/contracts/charts.d.ts +13 -0
  118. package/lib/types/presentation/contracts/editing.d.ts +8 -0
  119. package/lib/types/presentation/contracts/index.d.ts +11 -0
  120. package/lib/types/presentation/contracts/interaction.d.ts +8 -0
  121. package/lib/types/presentation/contracts/types.d.ts +252 -0
  122. package/lib/types/presentation/contracts/values.d.ts +9 -0
  123. package/lib/types/presentation/delivery.d.ts +3 -0
  124. package/lib/types/presentation/files.d.ts +7 -0
  125. package/lib/types/presentation/index.d.ts +6 -0
  126. package/lib/types/presentation/lint.d.ts +13 -0
  127. package/lib/types/presentation/projection/files.d.ts +2 -0
  128. package/lib/types/presentation/projection/index.d.ts +20 -0
  129. package/lib/types/presentation/projection/program.d.ts +2 -0
  130. package/lib/types/presentation/projection/sql-code.d.ts +2 -0
  131. package/lib/types/presentation/receipt.d.ts +14 -0
  132. package/lib/types/presentation/reports.d.ts +5 -0
  133. package/lib/types/presentation/rpc.d.ts +24 -0
  134. package/lib/types/presentation/tool.d.ts +12 -0
  135. package/lib/types/python-execution-contracts.d.ts +10 -0
  136. package/lib/types/python-execution.d.ts +10 -0
  137. package/lib/types/semantic-browser/contracts.d.ts +40 -0
  138. package/lib/types/semantic-browser/definition.d.ts +20 -0
  139. package/lib/types/semantic-browser/program.d.ts +2 -0
  140. package/lib/types/semantic-browser/service.d.ts +20 -0
  141. package/lib/types/semantic-reference/bridge.d.ts +11 -0
  142. package/lib/types/semantic-reference/contracts.d.ts +51 -0
  143. package/lib/types/semantic-reference/index.d.ts +3 -0
  144. package/lib/types/semantic-reference/rpc.d.ts +15 -0
  145. package/lib/types/semantic-reference/search.d.ts +10 -0
  146. package/lib/types/semantic-reference/usage.d.ts +43 -0
  147. package/package.json +43 -19
  148. package/python/presentation-kit/dist/dsh_data_analysis_presentation_kit-1.1.0-py3-none-any.whl +0 -0
  149. package/skills/dsh-data-analysis-presentation/SKILL.md +24 -0
  150. package/skills/dsh-data-analysis-presentation/references/chart-examples.md +18 -0
  151. package/skills/dsh-data-analysis-presentation/references/charts.md +55 -0
  152. package/skills/dsh-data-analysis-presentation/references/examples/artifact.draft.json +27 -0
  153. package/skills/dsh-data-analysis-presentation/references/examples/charts-bins.dataset.json +33 -0
  154. package/skills/dsh-data-analysis-presentation/references/examples/charts-boxes.dataset.json +45 -0
  155. package/skills/dsh-data-analysis-presentation/references/examples/charts-composition.dataset.json +33 -0
  156. package/skills/dsh-data-analysis-presentation/references/examples/charts-funnel.dataset.json +27 -0
  157. package/skills/dsh-data-analysis-presentation/references/examples/charts-precision.dataset.json +21 -0
  158. package/skills/dsh-data-analysis-presentation/references/examples/charts-series.dataset.json +58 -0
  159. package/skills/dsh-data-analysis-presentation/references/examples/charts-shares.dataset.json +33 -0
  160. package/skills/dsh-data-analysis-presentation/references/examples/charts-waterfall.dataset.json +44 -0
  161. package/skills/dsh-data-analysis-presentation/references/examples/charts.draft.json +392 -0
  162. package/skills/dsh-data-analysis-presentation/references/examples/comparison-selected.dataset.json +24 -0
  163. package/skills/dsh-data-analysis-presentation/references/examples/comparison-summary.dataset.json +22 -0
  164. package/skills/dsh-data-analysis-presentation/references/examples/comparison.dataset.json +18 -0
  165. package/skills/dsh-data-analysis-presentation/references/examples/comparison.draft.json +79 -0
  166. package/skills/dsh-data-analysis-presentation/references/examples/computed-sources.draft.json +35 -0
  167. package/skills/dsh-data-analysis-presentation/references/examples/computed.dataset.json +12 -0
  168. package/skills/dsh-data-analysis-presentation/references/examples/computed.draft.json +57 -0
  169. package/skills/dsh-data-analysis-presentation/references/examples/interaction-detail.dataset.json +30 -0
  170. package/skills/dsh-data-analysis-presentation/references/examples/interaction-summary.dataset.json +22 -0
  171. package/skills/dsh-data-analysis-presentation/references/examples/interaction.draft.json +278 -0
  172. package/skills/dsh-data-analysis-presentation/references/examples/source-only.draft.json +19 -0
  173. package/skills/dsh-data-analysis-presentation/references/examples/write-charts.py +73 -0
  174. package/skills/dsh-data-analysis-presentation/references/examples/write-comparison.py +42 -0
  175. package/skills/dsh-data-analysis-presentation/references/examples/write-computed.py +21 -0
  176. package/skills/dsh-data-analysis-presentation/references/examples/write-interaction.py +33 -0
  177. package/skills/dsh-data-analysis-presentation/references/examples.md +42 -0
  178. package/skills/dsh-data-analysis-presentation/references/interaction.md +56 -0
  179. package/skills/dsh-data-analysis-presentation/references/narrative.md +37 -0
  180. package/skills/dsh-data-analysis-presentation/references/schema.md +138 -0
  181. package/lib/datasource/access.js +0 -60
  182. package/lib/datasource/credentials.js +0 -24
  183. package/lib/evidence/bridge-program.js +0 -153
  184. package/lib/evidence/bridge.js +0 -166
  185. package/lib/evidence/index.js +0 -2
  186. package/lib/evidence/sources.js +0 -263
  187. package/lib/types/datasource/access.d.ts +0 -27
  188. package/lib/types/evidence/bridge.d.ts +0 -40
  189. package/lib/types/evidence/index.d.ts +0 -4
  190. package/lib/types/evidence/sources.d.ts +0 -57
  191. package/python/report-kit/dist/dsh_data_analysis_report_kit-3.0.0-py3-none-any.whl +0 -0
  192. package/skills/dsh-data-analysis-report/SKILL.md +0 -57
  193. package/skills/dsh-data-analysis-report/assets/marivo-artifact.js +0 -113
  194. package/skills/dsh-data-analysis-report/assets/marivo-session-dag.js +0 -1201
  195. package/skills/dsh-data-analysis-report/assets/report-data.js +0 -449
@@ -46,7 +46,7 @@ function parsePackageManifest(value) {
46
46
  packageSpec: requiredString(marivo.packageSpec, 'package compatibility marivo.packageSpec'),
47
47
  }),
48
48
  contracts: Object.freeze({
49
- runtimeInstallation: requiredLiteral(contracts.runtimeInstallation, 'dsh-data-analysis-runtime/v2', 'package compatibility contracts.runtimeInstallation'),
49
+ runtimeInstallation: requiredLiteral(contracts.runtimeInstallation, 'dsh-data-analysis-runtime/v3', 'package compatibility contracts.runtimeInstallation'),
50
50
  subprocessPolicy: requiredLiteral(contracts.subprocessPolicy, 'direct-argv-inherited-env-snapshot-overlay-v2', 'package compatibility contracts.subprocessPolicy'),
51
51
  }),
52
52
  }),
@@ -0,0 +1,70 @@
1
+ import { z } from 'zod';
2
+ export const datasourceAuthoringSchema = z.object({
3
+ fingerprint: z.string(),
4
+ backends: z.array(z.object({
5
+ name: z.string(),
6
+ fields: z.array(z.object({
7
+ name: z.string(),
8
+ type: z.enum(['string', 'number', 'boolean', 'json']),
9
+ required: z.boolean(),
10
+ description: z.string(),
11
+ default: z.unknown().optional(),
12
+ })),
13
+ })),
14
+ });
15
+ // The public DatasourceSpec union and dataclass fields are read from the checked Runtime.
16
+ // No backend registry or field schema is duplicated in the plugin.
17
+ export const DATASOURCE_AUTHORING_PROGRAM = String.raw `
18
+ import contextlib
19
+ import dataclasses
20
+ import json
21
+ import sys
22
+ import typing
23
+ import marivo.datasource as md
24
+
25
+ class Discard:
26
+ def write(self, value): return len(value)
27
+ def flush(self): pass
28
+
29
+ def spec_fields(cls):
30
+ hints = typing.get_type_hints(cls)
31
+ result = []
32
+ for field in dataclasses.fields(cls):
33
+ if not field.init or field.name == "ai_context":
34
+ continue
35
+ annotation = hints[field.name]
36
+ types = set(typing.get_args(annotation) or [annotation]) - {type(None)}
37
+ kind = "string" if types == {str} else "boolean" if types == {bool} else "number" if types <= {int, float} else "json"
38
+ item = {"name": field.name, "type": kind,
39
+ "required": field.default is dataclasses.MISSING and field.default_factory is dataclasses.MISSING,
40
+ "description": field.metadata.get("description", "")}
41
+ if field.default is not dataclasses.MISSING:
42
+ item["default"] = field.default
43
+ result.append(item)
44
+ return result
45
+
46
+ try:
47
+ payload = json.load(sys.stdin)
48
+ with contextlib.redirect_stdout(Discard()), contextlib.redirect_stderr(Discard()):
49
+ specs = {cls.backend_type: cls for cls in typing.get_args(md.DatasourceSpec)}
50
+ if payload["action"] == "schema":
51
+ result = {"backends": [{"name": key, "fields": spec_fields(cls)} for key, cls in specs.items()]}
52
+ else:
53
+ cls = specs[payload["backend"]]
54
+ fields = payload["fields"]
55
+ allowed = {field["name"] for field in spec_fields(cls)}
56
+ if set(fields) - allowed:
57
+ raise ValueError("unknown-field")
58
+ spec = cls(**fields)
59
+ if any(item.name == spec.name for item in md.list()):
60
+ result = {"error": "datasource-already-exists"}
61
+ else:
62
+ # Validate references before writing; the Host applies its own reserved-name check too.
63
+ if sorted(spec.env_refs.values()) != sorted(payload["refs"]):
64
+ raise ValueError("credential-reference-mismatch")
65
+ md.register(spec)
66
+ result = {"name": spec.name}
67
+ except Exception:
68
+ result = {"error": "datasource-definition-invalid"}
69
+ print(json.dumps(result))
70
+ `.trim();
@@ -1,3 +1,4 @@
1
+ import { RESOLVER_PROGRAM } from "./resolver-program.js";
1
2
  export const MARIVO_DATASOURCE_DESCRIBE_PROGRAM = String.raw `
2
3
  import json
3
4
  import os
@@ -5,11 +6,17 @@ import sys
5
6
  import marivo
6
7
  import marivo.datasource as md
7
8
 
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))
9
+ import hashlib
10
+
11
+ def project_description(description):
12
+ payload = {"name": description.name, "backend": description.backend_type,
13
+ "fields": description.literal_fields, "refs": description.env_refs}
14
+ return {"name": description.name, "backend": description.backend_type,
15
+ "properties": description.literal_fields, "refs": list(description.env_refs.values()),
16
+ "fields": description.env_refs,
17
+ "definition": hashlib.sha256(json.dumps(payload, sort_keys=True, default=str).encode()).hexdigest()}
18
+
19
+ print(json.dumps(project_description(md.describe(sys.argv[1])), sort_keys=True))
13
20
  `.trim();
14
21
  export const MARIVO_DATASOURCE_INVENTORY_PROGRAM = String.raw `
15
22
  import json
@@ -18,14 +25,21 @@ import sys
18
25
  import marivo
19
26
  import marivo.datasource as md
20
27
 
28
+ import hashlib
29
+
30
+ def project_description(description):
31
+ payload = {"name": description.name, "backend": description.backend_type,
32
+ "fields": description.literal_fields, "refs": description.env_refs}
33
+ return {"name": description.name, "backend": description.backend_type,
34
+ "properties": description.literal_fields, "refs": list(description.env_refs.values()),
35
+ "fields": description.env_refs,
36
+ "definition": hashlib.sha256(json.dumps(payload, sort_keys=True, default=str).encode()).hexdigest()}
37
+
21
38
  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())],
39
+ "datasources": [project_description(md.describe(item.name)) for item in md.list()],
26
40
  }, sort_keys=True))
27
41
  `.trim();
28
- export const MARIVO_DATASOURCE_TEST_PROGRAM = String.raw `
42
+ export const MARIVO_DATASOURCE_TEST_PROGRAM = `${RESOLVER_PROGRAM}\n${String.raw `
29
43
  import contextlib
30
44
  import io
31
45
  import json
@@ -34,17 +48,15 @@ import sys
34
48
  import marivo
35
49
  import marivo.datasource as md
36
50
 
51
+ payload = json.load(sys.stdin)
52
+ resolver = SnapshotResolver(payload)
53
+ secret_values = list(payload["values"].values())
37
54
  captured_stdout = io.StringIO()
38
55
  captured_stderr = io.StringIO()
39
56
  try:
40
57
  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])
58
+ with md.credential_scope(resolver=resolver):
59
+ result = md.test(sys.argv[1])
48
60
  except Exception as exc:
49
61
  print(json.dumps({"kind": "datasource-test-failed", "exception_type": type(exc).__name__}), file=sys.stderr)
50
62
  raise SystemExit(70)
@@ -77,4 +89,4 @@ print(json.dumps(redact({
77
89
  "failure": failure,
78
90
  "repair": repair,
79
91
  }), sort_keys=True))
80
- `.trim();
92
+ `.trim()}`;
@@ -1,7 +1,9 @@
1
1
  import { isCredentialRefName } from '@deepseek-ai/dsh-credentials';
2
2
  import { MarivoEnvironmentError } from "../environment/errors.js";
3
3
  import { resolveMarivoBridgeSource } from "../environment/source.js";
4
+ import { DATASOURCE_AUTHORING_PROGRAM, datasourceAuthoringSchema, } from "./authoring.js";
4
5
  import { MARIVO_DATASOURCE_DESCRIBE_PROGRAM, MARIVO_DATASOURCE_INVENTORY_PROGRAM, MARIVO_DATASOURCE_TEST_PROGRAM, } from "./bridge-programs.js";
6
+ import { marivoCredentialStorageRef } from "./shell-env.js";
5
7
  const DATASOURCE_LIMITS = Object.freeze({
6
8
  timeoutMs: 30_000,
7
9
  stdoutMaxBytes: 262_144,
@@ -24,6 +26,16 @@ function string(value) {
24
26
  function nullableString(value) {
25
27
  return value === null || typeof value === 'string';
26
28
  }
29
+ function jsonValue(value) {
30
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
31
+ return true;
32
+ if (typeof value === 'number')
33
+ return Number.isFinite(value);
34
+ if (Array.isArray(value))
35
+ return value.every(jsonValue);
36
+ const fields = object(value);
37
+ return fields !== undefined && Object.values(fields).every(jsonValue);
38
+ }
27
39
  function parseJsonObject(stdout, phase) {
28
40
  try {
29
41
  const parsed = JSON.parse(stdout.toString('utf8'));
@@ -41,15 +53,24 @@ function parseDescription(value, phase) {
41
53
  if (source === undefined) {
42
54
  throw new MarivoEnvironmentError('subprocess-output-invalid', `Marivo datasource ${phase} returned invalid fields`, { phase });
43
55
  }
44
- exactKeys(source, ['name', 'refs'], phase);
56
+ exactKeys(source, ['name', 'backend', 'properties', 'refs', 'fields', 'definition'], phase);
45
57
  const name = string(source.name);
46
- if (name === undefined || !Array.isArray(source.refs)) {
58
+ const backend = string(source.backend);
59
+ const properties = object(source.properties);
60
+ if (name === undefined ||
61
+ name.length > 256 ||
62
+ backend === undefined ||
63
+ backend.length > 256 ||
64
+ properties === undefined ||
65
+ !jsonValue(properties) ||
66
+ !Array.isArray(source.refs) ||
67
+ source.refs.length > 128) {
47
68
  throw new MarivoEnvironmentError('subprocess-output-invalid', `Marivo datasource ${phase} returned invalid fields`, { phase });
48
69
  }
49
70
  const refs = [];
50
71
  const seen = new Set();
51
72
  for (const ref of source.refs) {
52
- if (typeof ref !== 'string' || !isCredentialRefName(ref)) {
73
+ if (typeof ref !== 'string' || ref.length > 256 || !isCredentialRefName(ref)) {
53
74
  throw new MarivoEnvironmentError('subprocess-output-invalid', `Marivo datasource ${phase} returned an invalid credential reference`, { phase });
54
75
  }
55
76
  if (!seen.has(ref)) {
@@ -57,7 +78,21 @@ function parseDescription(value, phase) {
57
78
  refs.push(ref);
58
79
  }
59
80
  }
60
- return { name, refs };
81
+ const fields = object(source.fields);
82
+ if (!fields ||
83
+ typeof source.definition !== 'string' ||
84
+ !/^[a-f0-9]{64}$/.test(source.definition) ||
85
+ Object.values(fields).some((ref) => typeof ref !== 'string' || !refs.includes(ref)) ||
86
+ refs.some((ref) => !Object.values(fields).includes(ref)))
87
+ throw new Error('Invalid datasource definition projection');
88
+ return {
89
+ name,
90
+ backend,
91
+ properties: properties,
92
+ refs,
93
+ fields: fields,
94
+ definition: source.definition,
95
+ };
61
96
  }
62
97
  function parseInventory(stdout) {
63
98
  const value = parseJsonObject(stdout, 'inventory');
@@ -87,7 +122,9 @@ function parseRepair(value) {
87
122
  throw new TypeError('repair must be an object');
88
123
  exactKeys(source, ['kind', 'help_target', 'action', 'snippet', 'candidates', 'preserves_evidence'], 'test');
89
124
  if (string(source.kind) === undefined ||
90
- string(source.help_target) === undefined ||
125
+ object(source.help_target) === undefined ||
126
+ string(object(source.help_target)?.surface) === undefined ||
127
+ !nullableString(object(source.help_target)?.canonical_id) ||
91
128
  typeof source.action !== 'string' ||
92
129
  !nullableString(source.snippet) ||
93
130
  !Array.isArray(source.candidates) ||
@@ -160,6 +197,55 @@ export class MarivoDatasourceBridge {
160
197
  this.#runner = runner;
161
198
  this.binding = runner.binding;
162
199
  }
200
+ async authoring(signal) {
201
+ const result = await this.#runner.runChecked({
202
+ program: DATASOURCE_AUTHORING_PROGRAM,
203
+ stdin: JSON.stringify({ action: 'schema' }),
204
+ limits: DATASOURCE_LIMITS,
205
+ signal,
206
+ });
207
+ this.#assertSuccess(result, 'inventory');
208
+ return datasourceAuthoringSchema.parse({
209
+ ...parseJsonObject(result.stdout, 'inventory'),
210
+ fingerprint: this.binding.fingerprint,
211
+ });
212
+ }
213
+ async create(input, signal) {
214
+ const refs = Object.entries(input.fields)
215
+ .filter(([key]) => key.endsWith('_env'))
216
+ .flatMap(([, value]) => typeof value === 'string'
217
+ ? [value]
218
+ : value && typeof value === 'object' && !Array.isArray(value)
219
+ ? Object.values(value)
220
+ : []);
221
+ for (const ref of refs) {
222
+ if (typeof ref !== 'string')
223
+ return { error: 'datasource-credential-ref-invalid' };
224
+ try {
225
+ marivoCredentialStorageRef(ref);
226
+ }
227
+ catch (error) {
228
+ if (error instanceof MarivoEnvironmentError &&
229
+ error.code === 'datasource-credential-ref-invalid')
230
+ return { error: 'datasource-credential-ref-invalid' };
231
+ throw error;
232
+ }
233
+ }
234
+ const result = await this.#runner.runChecked({
235
+ program: DATASOURCE_AUTHORING_PROGRAM,
236
+ stdin: JSON.stringify({ action: 'create', ...input, refs }),
237
+ limits: DATASOURCE_LIMITS,
238
+ signal,
239
+ });
240
+ this.#assertSuccess(result, 'inventory');
241
+ const value = parseJsonObject(result.stdout, 'inventory');
242
+ if (value.error === 'datasource-already-exists' ||
243
+ value.error === 'datasource-definition-invalid')
244
+ return { error: value.error };
245
+ if (typeof value.name !== 'string')
246
+ throw new Error('Invalid datasource creation result');
247
+ return { name: value.name };
248
+ }
163
249
  async describe(name, signal) {
164
250
  const result = await this.#runner.runChecked({
165
251
  program: MARIVO_DATASOURCE_DESCRIBE_PROGRAM,
@@ -179,16 +265,22 @@ export class MarivoDatasourceBridge {
179
265
  this.#assertSuccess(result, 'inventory');
180
266
  return parseInventory(result.stdout);
181
267
  }
182
- async test(name, environmentOverlay, signal) {
268
+ async test(description, values, signal) {
269
+ const { name } = description;
183
270
  const result = await this.#runner.runChecked({
184
271
  program: MARIVO_DATASOURCE_TEST_PROGRAM,
185
272
  args: [name],
186
- environmentOverlay,
273
+ stdin: JSON.stringify({
274
+ project_root: this.binding.projectRoot,
275
+ grants: { [name]: description },
276
+ values,
277
+ }),
278
+ secretValues: Object.values(values),
187
279
  limits: DATASOURCE_LIMITS,
188
280
  signal,
189
281
  });
190
282
  this.#assertSuccess(result, 'test');
191
- return parseTest(result.stdout, Object.values(environmentOverlay).filter((value) => Boolean(value)));
283
+ return parseTest(result.stdout, Object.values(values));
192
284
  }
193
285
  #assertSuccess(result, phase) {
194
286
  if (result.exitCode === 0)
@@ -1,4 +1,5 @@
1
- export { createMarivoDatasourceAccessTool, MARIVO_DATASOURCE_ACCESS_TOOL_NAME, registerMarivoDatasourceAccessTool, } from "./access.js";
2
1
  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";
2
+ export { registerMarivoPythonTool } from "./python.js";
3
+ export { MarivoCredentialService } from "./service.js";
4
+ export { assertMarivoCredentialReferences, MARIVO_CREDENTIAL_STORAGE_PREFIX, marivoCredentialStorageRef, } from "./shell-env.js";
4
5
  export { createMarivoDatasourceTestTool, MARIVO_DATASOURCE_TEST_TOOL_NAME, registerMarivoDatasourceTestTool, } from "./test.js";
@@ -0,0 +1,115 @@
1
+ import process from 'node:process';
2
+ import { defineTool } from '@deepseek-ai/dsh-tools';
3
+ import { savePythonExecution } from "../python-execution.js";
4
+ import { resolveMarivoDatasourceBridge } from "./bridge.js";
5
+ import { PYTHON_LAUNCHER, PYTHON_WORKER } from "./resolver-program.js";
6
+ import { datasourceToolValue } from "./test.js";
7
+ function quote(value) {
8
+ return process.platform === 'win32'
9
+ ? `'${value.replaceAll("'", "''")}'`
10
+ : `'${value.replaceAll("'", "'\\''")}'`;
11
+ }
12
+ export function registerMarivoPythonTool(ctx, source, service) {
13
+ return ctx.tools.register(defineTool({
14
+ name: 'marivo_python',
15
+ description: 'Execute foreground Python in the bound Marivo Workspace using Host-injected credentials. This call waits for missing credentials for every listed datasource before starting Python once; configured credentials do not trigger a connection test. Create/resume Sessions and readers inside this execution; close Sessions in finally. No background execution or secret environment variables. Nonzero exits are reported without replay. Successful execution saves the exact submitted code and returns codeRef; explicitly associate it with presentation datasets through draft codeRefs. A codeCaptureError does not change the execution outcome and must not trigger replay.',
16
+ parameters: {
17
+ code: {
18
+ type: 'string',
19
+ required: true,
20
+ description: 'Python code, never credential values. Use try/finally for per-call Session resource cleanup; consult current Runtime Help for Session APIs.',
21
+ },
22
+ datasources: {
23
+ type: 'array',
24
+ required: true,
25
+ items: { type: 'string' },
26
+ description: 'All exact datasource names this execution may access through data or metadata connections, including inspection and datasources without passwords; pass [] only when no datasource will be accessed. Catalog-only definition reads need no datasource connection.',
27
+ },
28
+ },
29
+ output: {
30
+ schema: { type: 'json' },
31
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
32
+ },
33
+ async execute(args, exec) {
34
+ const code = args.code;
35
+ if (!code.trim() || Buffer.byteLength(code) > 131072)
36
+ throw new Error('Invalid Python code size');
37
+ const shell = ctx.get('shell');
38
+ if (!shell)
39
+ throw new Error('DSH Shell execution service is required');
40
+ const shellEnv = ctx.get('shellEnv');
41
+ if (!shellEnv)
42
+ throw new Error('DSH Shell environment service is required');
43
+ const policyService = ctx.get('sandboxPolicy');
44
+ if (shell.sandboxMode !== undefined && !policyService)
45
+ throw new Error('DSH sandbox policy is required');
46
+ const prepared = await service.track(service.prepareExecution(exec, () => resolveMarivoDatasourceBridge(source), args.datasources));
47
+ if (!('status' in prepared) || prepared.status !== 'ready')
48
+ return datasourceToolValue(prepared);
49
+ try {
50
+ const { binding } = prepared.bridge;
51
+ const policy = policyService?.resolve(exec.agent ? { session: exec.agent.session } : {});
52
+ const spec = shell.resolve({
53
+ command: `${process.platform === 'win32' ? '& ' : ''}${quote(binding.pythonExecutable)} -c ${quote(PYTHON_LAUNCHER)}`,
54
+ workdir: binding.projectRoot,
55
+ timeoutMs: 120_000,
56
+ signal: prepared.signal,
57
+ stdin: JSON.stringify({
58
+ identity: binding,
59
+ project_root: binding.projectRoot,
60
+ grants: prepared.grants,
61
+ values: prepared.values,
62
+ code,
63
+ worker: PYTHON_WORKER,
64
+ }),
65
+ env: { MARIVO_PERSIST_CREDENTIALS: '0' },
66
+ dshEnv: shellEnv.collect(exec),
67
+ ...(policy ? { sandboxPolicy: policy } : {}),
68
+ });
69
+ // Keep this guard adjacent to the only launch, after synchronous Host hooks.
70
+ prepared.assertCurrent();
71
+ const startedAt = new Date().toISOString();
72
+ const result = await service.track(shell.run(spec));
73
+ const finishedAt = new Date().toISOString();
74
+ // Defense at the result seam as well as before Harness collection/spill.
75
+ const redact = (text) => Object.values(prepared.values)
76
+ .filter(Boolean)
77
+ .sort((a, b) => b.length - a.length)
78
+ .reduce((out, value) => out.split(value).join('[REDACTED]'), text);
79
+ let codeRef;
80
+ let codeCaptureError;
81
+ if (result.exitCode === 0 && !result.timedOut && !result.aborted) {
82
+ try {
83
+ prepared.assertCurrent();
84
+ if (Object.values(prepared.values).some((value) => value && code.includes(value)))
85
+ throw new Error('Host credential in submitted code');
86
+ codeRef = await service.track(savePythonExecution(binding.projectRoot, { text: code, startedAt, finishedAt }, prepared.signal));
87
+ prepared.assertCurrent();
88
+ }
89
+ catch {
90
+ codeRef = undefined;
91
+ codeCaptureError =
92
+ 'Python completed successfully, but its source code could not be saved. Do not rerun the code to recover this snapshot; inspect its existing effects.';
93
+ }
94
+ }
95
+ return JSON.parse(JSON.stringify({
96
+ exitCode: result.exitCode,
97
+ timedOut: result.timedOut,
98
+ aborted: result.aborted,
99
+ stdout: redact(result.stdout.text),
100
+ stderr: redact(result.stderr.text),
101
+ truncated: result.stdout.truncated || result.stderr.truncated,
102
+ sandbox: result.sandbox ?? null,
103
+ ...(codeRef ? { codeRef } : {}),
104
+ ...(codeCaptureError ? { codeCaptureError } : {}),
105
+ }));
106
+ }
107
+ catch {
108
+ throw new Error('Marivo Python execution failed or was cancelled; inspect the execution policy and retry only after checking effects');
109
+ }
110
+ finally {
111
+ prepared.release();
112
+ }
113
+ },
114
+ }));
115
+ }
@@ -0,0 +1,112 @@
1
+ /** Shared Python adapter. All inputs below arrive on a Host-owned pipe. */
2
+ export const RESOLVER_PROGRAM = String.raw `
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import sys
7
+ from pathlib import Path
8
+ import marivo
9
+ import marivo.datasource as md
10
+
11
+ if not all(hasattr(md, name) for name in ("credential_scope", "CredentialRequest", "SecretValue", "DatasourceCredentialError")):
12
+ raise RuntimeError("Marivo credential_scope capability is required; upgrade the bound Runtime")
13
+
14
+ def definition(description):
15
+ return hashlib.sha256(json.dumps({
16
+ "name": description.name, "backend": description.backend_type,
17
+ "fields": description.literal_fields, "refs": description.env_refs,
18
+ }, sort_keys=True, default=str).encode()).hexdigest()
19
+
20
+ class SnapshotResolver:
21
+ def __init__(self, payload):
22
+ self.root = Path(payload["project_root"]).resolve()
23
+ self.grants = payload["grants"]
24
+ self.values = payload["values"]
25
+
26
+ def resolve(self, request):
27
+ grant = self.grants.get(request.datasource)
28
+ if request.cancelled:
29
+ reason = "timeout"
30
+ elif Path(request.project_root).resolve() != self.root or grant is None:
31
+ reason = "denied"
32
+ elif any(grant["fields"].get(field) != request.reference for field in request.fields):
33
+ reason = "denied"
34
+ elif not request.fields or request.reference not in grant["fields"].values():
35
+ reason = "denied"
36
+ elif definition(md.describe(request.datasource)) != grant["definition"]:
37
+ reason = "denied"
38
+ elif not self.values.get(request.reference):
39
+ reason = "missing"
40
+ else:
41
+ return md.SecretValue(self.values[request.reference])
42
+ raise md.DatasourceCredentialError(reason=reason, reference=request.reference,
43
+ datasource=request.datasource, fields=request.fields)
44
+ `.trim();
45
+ /** The worker checks the exact binding and enters scope before any user code. */
46
+ export const PYTHON_WORKER = `${RESOLVER_PROGRAM}\n${String.raw `
47
+ payload = json.load(sys.stdin)
48
+ expected = payload["identity"]
49
+ if (os.path.abspath(sys.executable) != expected["pythonExecutable"] or
50
+ marivo.__version__ != expected["marivoVersion"] or
51
+ os.path.abspath(marivo.__file__) != expected["packagePath"] or
52
+ str(Path.cwd().resolve()) != payload["project_root"]):
53
+ raise RuntimeError("Marivo execution identity changed; rebind required")
54
+ resolver = SnapshotResolver(payload)
55
+ code = payload.pop("code")
56
+ sys.stdin = open(os.devnull)
57
+ try:
58
+ with md.credential_scope(resolver=resolver):
59
+ for name, grant in resolver.grants.items():
60
+ description = md.describe(name)
61
+ if grant["name"] != name or description.name != name:
62
+ raise RuntimeError("Marivo datasource identity changed; execution cancelled")
63
+ if definition(description) != grant["definition"]:
64
+ raise RuntimeError("Marivo datasource definition changed; execution cancelled")
65
+ exec(compile(code, "<marivo_python>", "exec"), {"__name__": "__main__"})
66
+ finally:
67
+ resolver.values.clear()
68
+ payload.clear()
69
+ `}`;
70
+ /** Capture raw fd output before Harness collection/spill, including driver writes. */
71
+ export const PYTHON_LAUNCHER = String.raw `
72
+ import json, subprocess, sys, threading
73
+ payload = json.load(sys.stdin)
74
+ worker = payload.pop("worker")
75
+ values = sorted(set(payload["values"].values()), key=len, reverse=True)
76
+ child = subprocess.Popen([sys.executable, "-c", worker], stdin=subprocess.PIPE,
77
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
78
+ streams = [bytearray(), bytearray()]
79
+ overflow = threading.Event()
80
+ def collect(pipe, target):
81
+ while True:
82
+ chunk = pipe.read(8192)
83
+ if not chunk:
84
+ break
85
+ if len(target) + len(chunk) > 1048576:
86
+ overflow.set()
87
+ child.kill()
88
+ elif not overflow.is_set():
89
+ target.extend(chunk)
90
+ threads = [threading.Thread(target=collect, args=(pipe, buf), daemon=True)
91
+ for pipe, buf in zip((child.stdout, child.stderr), streams)]
92
+ for thread in threads:
93
+ thread.start()
94
+ try:
95
+ child.stdin.write(json.dumps(payload).encode())
96
+ child.stdin.close()
97
+ except BrokenPipeError:
98
+ pass
99
+ child.wait()
100
+ for thread in threads:
101
+ thread.join()
102
+ if overflow.is_set():
103
+ print("Marivo Python output limit exceeded", file=sys.stderr)
104
+ raise SystemExit(74)
105
+ for stream, raw in zip((sys.stdout, sys.stderr), streams):
106
+ text = raw.decode("utf-8", errors="replace")
107
+ for value in values:
108
+ if value:
109
+ text = text.replace(value, "[REDACTED]")
110
+ stream.write(text)
111
+ raise SystemExit(child.returncode if child.returncode >= 0 else 1)
112
+ `.trim();
@@ -0,0 +1,88 @@
1
+ import { z } from 'zod';
2
+ import { CREDENTIAL_CHANNEL, credentialError } from "./service.js";
3
+ const text = z.string().min(1).max(256);
4
+ const handle = { generation: z.string().uuid(), id: z.string().uuid(), scope: z.string().uuid() };
5
+ const mutation = z
6
+ .object({
7
+ ...handle,
8
+ action: z.enum(['save', 'update', 'delete', 'test', 'submit', 'diagnose']),
9
+ version: z.string().max(65536),
10
+ requestId: z.string().uuid().optional(),
11
+ reference: text.optional(),
12
+ changes: z.record(text, z.string().min(1).max(65536)).optional(),
13
+ })
14
+ .strict();
15
+ export function registerCredentialRpc(connection, service, workspace) {
16
+ const unregister = connection.rpc.handle(CREDENTIAL_CHANNEL, async (endpoint, payload, signal) => {
17
+ try {
18
+ if (Buffer.byteLength(JSON.stringify(payload) ?? '') > 1_048_576)
19
+ throw new Error('invalid-request');
20
+ let value;
21
+ if (endpoint === 'overview') {
22
+ const input = z.object({ workspaceId: text }).strict().parse(payload);
23
+ value = {
24
+ generation: service.generation,
25
+ datasources: await service.overview(input.workspaceId, () => workspace(input.workspaceId), signal),
26
+ };
27
+ }
28
+ else if (endpoint === 'authoring') {
29
+ const input = z.object({ workspaceId: text }).strict().parse(payload);
30
+ const bridge = await workspace(input.workspaceId);
31
+ if (!bridge.authoring)
32
+ throw new Error('authoring-unavailable');
33
+ value = { generation: service.generation, ...(await bridge.authoring(signal)) };
34
+ }
35
+ else if (endpoint === 'create-datasource') {
36
+ const input = z
37
+ .object({
38
+ workspaceId: text,
39
+ generation: z.string().uuid(),
40
+ fingerprint: z.string().min(1).max(256),
41
+ backend: text,
42
+ fields: z.record(text, z.unknown()),
43
+ })
44
+ .strict()
45
+ .parse(payload);
46
+ value = await service.createDatasource(input.generation, input.fingerprint, input, () => workspace(input.workspaceId), signal);
47
+ }
48
+ else if (endpoint === 'watch') {
49
+ const input = z
50
+ .object({ sessionId: text, cursor: z.string().max(256).optional() })
51
+ .strict()
52
+ .parse(payload);
53
+ value = await service.waitWatch(input.sessionId, input.cursor, signal);
54
+ }
55
+ else if (endpoint === 'start') {
56
+ value = service.start(mutation.parse(payload));
57
+ }
58
+ else if (endpoint === 'operation') {
59
+ const input = z.object(handle).strict().parse(payload);
60
+ value = service.operation(input.generation, input.id, input.scope) ?? null;
61
+ }
62
+ else if (endpoint === 'cancel-operation') {
63
+ const input = z.object(handle).strict().parse(payload);
64
+ service.cancelOperation(input.generation, input.id, input.scope);
65
+ value = { cancelled: true };
66
+ }
67
+ else if (endpoint === 'cancel-request') {
68
+ const input = z.object({ requestId: z.string().uuid() }).strict().parse(payload);
69
+ service.cancelRequest(input.requestId);
70
+ value = { cancelled: true };
71
+ }
72
+ else
73
+ throw new Error('unknown-endpoint');
74
+ return { ok: true, value };
75
+ }
76
+ catch (error) {
77
+ return {
78
+ ok: false,
79
+ error: { code: 'internal', message: credentialError(error), details: {} },
80
+ };
81
+ }
82
+ }, { authority: 'trusted-host' });
83
+ return async () => {
84
+ const draining = unregister();
85
+ await service.close();
86
+ await draining;
87
+ };
88
+ }