dataiku-sdk 3.5.0 → 3.6.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 (44) hide show
  1. package/README.md +1 -1
  2. package/bin/dss.js +50 -9
  3. package/dist/build-metadata.json +1 -1
  4. package/dist/src/cli/coerce.d.ts +0 -1
  5. package/dist/src/cli/coerce.js +0 -5
  6. package/dist/src/cli/commands/app.js +3 -2
  7. package/dist/src/cli/commands/dataset.js +2 -4
  8. package/dist/src/cli/commands/job.js +4 -3
  9. package/dist/src/cli/contract.js +1 -1
  10. package/dist/src/cli/env.js +2 -1
  11. package/dist/src/cli/flags.d.ts +1 -0
  12. package/dist/src/cli/flags.js +4 -9
  13. package/dist/src/cli/helpers/app-permissions.js +5 -4
  14. package/dist/src/cli/helpers/flow-zone.js +6 -5
  15. package/dist/src/cli/version.d.ts +0 -2
  16. package/dist/src/cli/version.js +6 -9
  17. package/dist/src/cli.js +107 -222
  18. package/dist/src/client.d.ts +2 -1
  19. package/dist/src/client.js +25 -11
  20. package/dist/src/resources/base.d.ts +2 -0
  21. package/dist/src/resources/base.js +14 -2
  22. package/dist/src/resources/code-envs.js +1 -3
  23. package/dist/src/resources/data-quality.js +10 -10
  24. package/dist/src/resources/macros.d.ts +1 -1
  25. package/dist/src/resources/macros.js +1 -3
  26. package/dist/src/resources/recipes.js +1 -5
  27. package/dist/src/resources/saved-models.js +1 -13
  28. package/dist/src/resources/scenarios.js +1 -3
  29. package/dist/src/resources/sql.js +1 -5
  30. package/dist/src/resources/statistics.js +1 -3
  31. package/dist/src/utils/deep-merge.js +2 -4
  32. package/dist/src/utils/flow-map.js +1 -6
  33. package/dist/src/utils/project-archive.js +8 -12
  34. package/dist/src/utils/project-incarnation.js +3 -7
  35. package/dist/src/utils/records.d.ts +4 -0
  36. package/dist/src/utils/records.js +8 -0
  37. package/package.json +7 -11
  38. package/plugin.json +1 -1
  39. package/skills/dataiku-dss/SKILL.md +1 -1
  40. package/skills/dataiku-dss/references/authentication.md +2 -2
  41. package/skills/dataiku-dss/references/discovery.md +1 -1
  42. package/skills/dataiku-dss/references/mutations.md +0 -1
  43. package/packages/types/dist/index.d.ts +0 -1062
  44. package/packages/types/dist/index.js +0 -871
package/README.md CHANGED
@@ -267,7 +267,7 @@ DSS_TOKENIZER_MODULE=/absolute/path/to/oh-my-pi/packages/natives/native/index.js
267
267
 
268
268
  When configured, the native module also accelerates ordinary OpenAI token measurements after parity checks against the pinned JavaScript model. The JS fingerprint and independent cross-model reference checks remain authoritative; reserved special-token validation is preserved. Without this variable, measurements use JavaScript and only the native cross-model case is skipped. A configured missing/incompatible module fails instead of silently falling back. The cross-model gate permits 10% growth over recorded text baselines. Claude counts use ctok reconstructions, not an official tokenizer; counts exclude provider framing. No native dependency is added to the CLI.
269
269
 
270
- For a known resource, `dss agent contract --fields commands.actions.dataset` lists action names in **64 tokens**, versus **1,575** for the global index. For known writes, fetch the full action entry directly once: `dataset.create` costs **1,059 tokens**, versus **1,415** for compact discovery followed by the required full entry. Use compact `usage,description,flags,examples` projections for reads when those fields suffice. This is selective disclosure, not lossless compression. Scoped discovery constructs only requested action entries and materializes schemas only when requested; action-name indexes avoid payload schemas, while full registry exports and recovery hints retain their existing content. Metadata-only paths defer SDK runtime/schema imports.
270
+ For a known resource, `dss agent contract --fields commands.actions.dataset` lists only its action names, far smaller than the global index; for known writes, fetch the full action entry directly once rather than compact discovery followed by the full entry. `bun run test:tokens` pins the current figures. Use compact `usage,description,flags,examples` projections for reads when those fields suffice. This is selective disclosure, not lossless compression. Scoped discovery constructs only requested action entries and materializes schemas only when requested; action-name indexes avoid payload schemas, while full registry exports and recovery hints retain their existing content. Metadata-only paths defer SDK runtime/schema imports.
271
271
 
272
272
  Boolean flags accept bare flags or explicit `=true`/`=false` values, including aliases. Invalid boolean values fail before dispatch. Direct, batch, and local commands share execution-mode resolution; `--plan --dry-run` returns a plan with `plannedAndDryRun:true` when both modes are supported. Unreadable JSON/text payload files produce `validation_failed` usage errors (exit 1) with the flag, path, and filesystem cause.
273
273
 
package/bin/dss.js CHANGED
@@ -4,6 +4,7 @@
4
4
  // disabled; `bunx --bun` enters Bun's Node-compatibility mode, which disables
5
5
  // that loading before this module runs.
6
6
  import { spawnSync, } from "node:child_process";
7
+ import { readFileSync, } from "node:fs";
7
8
  import * as path from "node:path";
8
9
  import { fileURLToPath, pathToFileURL, } from "node:url";
9
10
 
@@ -19,23 +20,44 @@ const bunNodeCompatibilityMode = runningUnderBun
19
20
  );
20
21
  const envFileAutoloadDisabled = process.execArgv.includes("--no-env-file",)
21
22
  || bunNodeCompatibilityMode;
23
+ // The one source of the minimum is package.json engines.bun (">=X.Y.Z").
24
+ const MINIMUM_BUN = JSON.parse(
25
+ readFileSync(fileURLToPath(new URL("../package.json", import.meta.url,),), "utf-8",),
26
+ ).engines.bun.replace(/^>=/, "",);
27
+
28
+ /** Whether dotted version `actual` is >= `minimum` (numeric per part). */
29
+ function versionAtLeast(actual, minimum,) {
30
+ // Compare the release part only: canary builds print e.g. 1.4.3-canary.12+abc.
31
+ const a = actual.split(/[-+]/,)[0].split(".",).map(Number,);
32
+ const m = minimum.split(".",).map(Number,);
33
+ for (let i = 0; i < m.length; i++) {
34
+ if ((a[i] ?? 0) !== m[i]) return (a[i] ?? 0) > m[i];
35
+ }
36
+ return true;
37
+ }
22
38
 
23
39
  if (!runningUnderBun) {
24
- const result = spawnSync(
25
- "bun",
26
- ["--no-env-file", fileURLToPath(import.meta.url,), ...process.argv.slice(2,),],
27
- { stdio: "inherit", env: process.env, },
28
- );
40
+ // Check before spawning: an old Bun may reject --no-env-file during argument
41
+ // parsing, before any launcher code could report a JSON error.
42
+ const probe = spawnSync("bun", ["--version",], { encoding: "utf-8", },);
43
+ const found = probe.error ? undefined : probe.stdout.trim();
44
+ const result = found !== undefined && versionAtLeast(found, MINIMUM_BUN,)
45
+ ? spawnSync(
46
+ "bun",
47
+ ["--no-env-file", fileURLToPath(import.meta.url,), ...process.argv.slice(2,),],
48
+ { stdio: "inherit", env: process.env, },
49
+ )
50
+ : { error: new Error(found ? `Bun ${found} is older than ${MINIMUM_BUN}.` : "bun not found",), };
29
51
  if (result.error) {
30
52
  process.stdout.write(`${
31
53
  JSON.stringify({
32
54
  type: "error",
33
55
  ok: false,
34
- error: "Unable to start the required Bun runtime.",
56
+ error: `Unable to start the required Bun runtime: ${result.error.message}`,
35
57
  code: "internal_error",
36
58
  category: "internal",
37
59
  exitCode: 2,
38
- hint: "Install Bun >= 1.4.0 and ensure `bun` is on PATH.",
60
+ hint: `Install Bun >= ${MINIMUM_BUN} and ensure \`bun\` is on PATH.`,
39
61
  },)
40
62
  }\n`,);
41
63
  process.exitCode = 2;
@@ -44,6 +66,19 @@ if (!runningUnderBun) {
44
66
  } else {
45
67
  process.exitCode = result.status ?? 1;
46
68
  }
69
+ } else if (!Bun.semver.satisfies(Bun.version, `>=${MINIMUM_BUN}`,)) {
70
+ process.stdout.write(`${
71
+ JSON.stringify({
72
+ type: "error",
73
+ ok: false,
74
+ error: `Bun ${Bun.version} is older than the required ${MINIMUM_BUN}.`,
75
+ code: "internal_error",
76
+ category: "internal",
77
+ exitCode: 2,
78
+ hint: `Upgrade Bun to >= ${MINIMUM_BUN} (bun upgrade).`,
79
+ },)
80
+ }\n`,);
81
+ process.exitCode = 2;
47
82
  } else if (!envFileAutoloadDisabled) {
48
83
  process.stdout.write(`${
49
84
  JSON.stringify({
@@ -61,7 +96,11 @@ if (!runningUnderBun) {
61
96
  const here = import.meta.dir;
62
97
  const distCliPath = path.resolve(here, "../dist/src/cli.js",);
63
98
  const sourceCliPath = path.resolve(here, "../src/cli.ts",);
64
- const usesDistCli = await Bun.file(distCliPath,).exists();
99
+ // A source checkout always runs its source, like bin/dss; a leftover dist/
100
+ // would otherwise silently shadow every edit. The published package ships
101
+ // no src/, so installs run dist.
102
+ const usesDistCli = !(await Bun.file(sourceCliPath,).exists())
103
+ && await Bun.file(distCliPath,).exists();
65
104
  const cliPath = usesDistCli ? distCliPath : sourceCliPath;
66
105
 
67
106
  // Published dist carries dist/build-metadata.json with the revision it was
@@ -69,7 +108,6 @@ if (!runningUnderBun) {
69
108
  // build revision; only a full lowercase hexadecimal revision is accepted, so an
70
109
  // inherited or corrupt variable never stands in for packaged metadata.
71
110
  if (usesDistCli) {
72
- process.env.DSS_LOAD_SOURCE = "dist";
73
111
  delete process.env.DSS_BUILD_REVISION;
74
112
  try {
75
113
  const metadata = await Bun.file(path.resolve(here, "../dist/build-metadata.json",),).json();
@@ -80,6 +118,9 @@ if (!runningUnderBun) {
80
118
  } catch {
81
119
  // Dist without build metadata: provenance still reports dist source.
82
120
  }
121
+ } else {
122
+ // Only a dist run may carry a build revision; never trust an inherited one.
123
+ delete process.env.DSS_BUILD_REVISION;
83
124
  }
84
125
 
85
126
  // Same process, same argv: the CLI sees process.argv.slice(2) exactly as before.
@@ -1 +1 @@
1
- {"format":1,"buildRevision":"cb8058354cc9c7a71d47d5dc43fded12e3bb4b8c"}
1
+ {"format":1,"buildRevision":"dae7b426ec392ff4a62bcf07fab199e02080a0f6"}
@@ -10,7 +10,6 @@ export declare function normalizeLineEndings(value: string): string;
10
10
  export declare function splitCsvFlag(v: string | boolean | undefined): string[];
11
11
  export declare function recipeInputDatasetsFromFlags(flags: Record<string, string | boolean>): string[] | undefined;
12
12
  export declare function rewritePairsFromFlags(flags: Record<string, string | boolean>, flagName: string): Record<string, string>;
13
- export declare function plainRecord(value: unknown): Record<string, unknown> | undefined;
14
13
  export declare function requiredStringFlag(flags: Record<string, string | boolean>, name: string, usage: string): string;
15
14
  export declare function optionalStringField(record: Record<string, unknown>, keys: string[]): string | undefined;
16
15
  export declare function requiredStringArray(value: unknown, source: string): string[];
@@ -85,11 +85,6 @@ export function rewritePairsFromFlags(flags, flagName) {
85
85
  }
86
86
  return rewrites;
87
87
  }
88
- export function plainRecord(value) {
89
- return value && typeof value === "object" && !Array.isArray(value)
90
- ? value
91
- : undefined;
92
- }
93
88
  export function requiredStringFlag(flags, name, usage) {
94
89
  const value = flags[name];
95
90
  if (typeof value !== "string" || value.trim().length === 0) {
@@ -1,6 +1,7 @@
1
1
  import { ClientValidationError, DataikuError, } from "../../errors.js";
2
2
  import { projectIncarnationHash, } from "../../utils/project-incarnation.js";
3
- import { jsonInput, num, parseBooleanOption, plainRecord, requiredJsonInput, requiredStringFlag, stringField, } from "../coerce.js";
3
+ import { asRecord, } from "../../utils/records.js";
4
+ import { jsonInput, num, parseBooleanOption, requiredJsonInput, requiredStringFlag, stringField, } from "../coerce.js";
4
5
  import { executionMode, } from "../flags.js";
5
6
  import { appPermissionsHash, appPermissionsVerificationError, assertAppPermissionsSnapshotBinding, buildAppPermissionsSnapshot, diffAppPermissions, readAppPermissionsSnapshot, writeAppPermissionsSnapshot, } from "../helpers/app-permissions.js";
6
7
  import { createSuccessorInstance, futureTargetIdentity, generatedInstanceKey, isDefinitiveRejection, preflightSuccessorInstance, requireAbsentProjectTarget, safeErrorSummary, verifyAppInstance, VISUAL_UI_GATE, } from "../helpers/app-successor.js";
@@ -180,7 +181,7 @@ export const appCommands = withUsage("app", {
180
181
  ...safeErrorSummary(error),
181
182
  };
182
183
  }
183
- const instance = plainRecord(created);
184
+ const instance = asRecord(created);
184
185
  if (!instance) {
185
186
  return {
186
187
  success: false,
@@ -1,4 +1,5 @@
1
1
  import { deepMerge, } from "../../utils/deep-merge.js";
2
+ import { isRecord, } from "../../utils/records.js";
2
3
  import { compareStrings, stableHash, } from "../../utils/stable-hash.js";
3
4
  import { jsonInput, num, schemaColumnsInput, unknownJsonInput, } from "../coerce.js";
4
5
  import { executionMode, } from "../flags.js";
@@ -8,9 +9,6 @@ import { enqueueCliWarning, readIfExists, skipResult, } from "../output.js";
8
9
  import { commandUsage, withUsage, } from "../syntax.js";
9
10
  import { requireArgs, UsageError, } from "../usage.js";
10
11
  const MAX_SCHEMA_DIFFERENCES = 50;
11
- function isPlainObject(value) {
12
- return typeof value === "object" && value !== null && !Array.isArray(value);
13
- }
14
12
  function appendSchemaPath(parent, key) {
15
13
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
16
14
  ? (parent ? `${parent}.${key}` : key)
@@ -21,7 +19,7 @@ function collectSchemaDifferences(expected, actual, path, acc) {
21
19
  // subtrees (any key order) match exactly and are never descended into.
22
20
  if (stableHash(expected) === stableHash(actual))
23
21
  return;
24
- if (isPlainObject(expected) && isPlainObject(actual)) {
22
+ if (isRecord(expected) && isRecord(actual)) {
25
23
  const keys = [...new Set([...Object.keys(expected), ...Object.keys(actual),]),].sort(compareStrings);
26
24
  for (const key of keys) {
27
25
  const nextPath = appendSchemaPath(path, key);
@@ -1,6 +1,7 @@
1
1
  import { mkdir, writeFile, } from "node:fs/promises";
2
2
  import { dirname, resolve, } from "node:path";
3
- import { jobBuildTargetTypeFromFlags, jobLogFilterFromFlag, maxLogLinesFromFlags, num, plainRecord, } from "../coerce.js";
3
+ import { asRecord, } from "../../utils/records.js";
4
+ import { jobBuildTargetTypeFromFlags, jobLogFilterFromFlag, maxLogLinesFromFlags, num, } from "../coerce.js";
4
5
  import { executionMode, } from "../flags.js";
5
6
  import { encodedProjectEndpoint, } from "../output.js";
6
7
  import { commandUsage, withUsage, } from "../syntax.js";
@@ -11,7 +12,7 @@ import { requireArgs, UsageError, } from "../usage.js";
11
12
  function nestedValue(value, path) {
12
13
  let current = value;
13
14
  for (const key of path) {
14
- const record = plainRecord(current);
15
+ const record = asRecord(current);
15
16
  if (!record)
16
17
  return undefined;
17
18
  current = record[key];
@@ -95,7 +96,7 @@ function collectWarningCounts(value, inActivity, counts) {
95
96
  collectWarningCounts(item, inActivity, counts);
96
97
  return;
97
98
  }
98
- const record = plainRecord(value);
99
+ const record = asRecord(value);
99
100
  if (!record)
100
101
  return;
101
102
  for (const [key, item,] of Object.entries(record)) {
@@ -734,7 +734,7 @@ function inferSideEffect(resource, action) {
734
734
  // LLM Mesh completions/embeddings invoke the LLM provider (cost-bearing,
735
735
  // arbitrary-prompt execution); the verb shapes match no mutating pattern
736
736
  // and would fall through to read, wrongly rejecting --plan. Knowledge-bank
737
- // search stays a READ per Main's preservation directive.
737
+ // search stays read: it only queries an existing index.
738
738
  if (resource === "llm" && (action === "completions" || action === "embeddings")) {
739
739
  return "write";
740
740
  }
@@ -56,7 +56,8 @@ export function loadEnvFile() {
56
56
  continue;
57
57
  const key = trimmed.slice(0, eq).trim();
58
58
  const val = trimmed.slice(eq + 1).trim().replace(/^['"]|['"]$/g, "");
59
- if (isAllowedEnvKey(key) && !process.env[key]) {
59
+ // A variable set in the environment, even to "", wins over .env ("" clears it).
60
+ if (isAllowedEnvKey(key) && process.env[key] === undefined) {
60
61
  process.env[key] = val;
61
62
  (index === 0 ? projectEnvVars : installEnvVars).add(key);
62
63
  }
@@ -1,3 +1,4 @@
1
+ /** Planning wins dispatch; dryRun remains set for combined plan metadata. */
1
2
  export declare function executionMode(flags: Record<string, string | boolean>): {
2
3
  readonly plan: boolean;
3
4
  readonly dryRun: boolean;
@@ -1,16 +1,11 @@
1
1
  import { parseBooleanOption, } from "./coerce.js";
2
2
  import { unsupportedHelpFlag, UsageError, } from "./usage.js";
3
3
  /** Planning wins dispatch; dryRun remains set for combined plan metadata. */
4
- const EXECUTION_MODES = [
5
- Object.freeze({ plan: false, dryRun: false, }),
6
- Object.freeze({ plan: false, dryRun: true, }),
7
- Object.freeze({ plan: true, dryRun: false, }),
8
- Object.freeze({ plan: true, dryRun: true, }),
9
- ];
10
4
  export function executionMode(flags) {
11
- const plan = parseBooleanOption(flags["plan"], "--plan") ?? false;
12
- const dryRun = parseBooleanOption(flags["dry-run"], "--dry-run") ?? false;
13
- return EXECUTION_MODES[(plan ? 2 : 0) + (dryRun ? 1 : 0)];
5
+ return {
6
+ plan: parseBooleanOption(flags["plan"], "--plan") ?? false,
7
+ dryRun: parseBooleanOption(flags["dry-run"], "--dry-run") ?? false,
8
+ };
14
9
  }
15
10
  export const BOOLEAN_FLAGS = new Set([
16
11
  "verbose",
@@ -1,8 +1,9 @@
1
1
  import * as fs from "node:fs";
2
2
  import { ClientValidationError, } from "../../errors.js";
3
3
  import { canonicalDssUrl, } from "../../utils/dss-url.js";
4
+ import { asRecord, } from "../../utils/records.js";
4
5
  import { compareStrings, } from "../../utils/stable-hash.js";
5
- import { parseJsonObject, plainRecord, stableHash, stableJson, } from "../coerce.js";
6
+ import { parseJsonObject, stableHash, stableJson, } from "../coerce.js";
6
7
  import { UsageError, } from "../usage.js";
7
8
  /**
8
9
  * Version 3 binds every snapshot to both its DSS server and the concrete project
@@ -124,7 +125,7 @@ export function parseAppPermissionsSnapshot(text, source) {
124
125
  const capturedAt = requiredSnapshotString(record, "capturedAt", source);
125
126
  const hash = requiredSnapshotString(record, "hash", source);
126
127
  const permissionsHash = requiredSnapshotString(record, "permissionsHash", source);
127
- const permissions = plainRecord(record["permissions"]);
128
+ const permissions = asRecord(record["permissions"]);
128
129
  if (!permissions) {
129
130
  throw new UsageError(`${source} is not an app permissions snapshot: "permissions" must be a JSON object.`, "validation_failed");
130
131
  }
@@ -209,8 +210,8 @@ function pushDifference(out, path, backup, current) {
209
210
  function collectDifferences(path, backup, current, out) {
210
211
  if (stableJson(backup) === stableJson(current))
211
212
  return;
212
- const backupRecord = plainRecord(backup);
213
- const currentRecord = plainRecord(current);
213
+ const backupRecord = asRecord(backup);
214
+ const currentRecord = asRecord(current);
214
215
  if (backupRecord && currentRecord) {
215
216
  const keys = [...new Set([...Object.keys(backupRecord), ...Object.keys(currentRecord),]),]
216
217
  .sort(compareStrings);
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, } from "node:fs";
2
+ import { asRecord, } from "../../utils/records.js";
2
3
  import { compareStrings, } from "../../utils/stable-hash.js";
3
- import { finiteNumberField, jsonInput, optionalStringField, parseJsonObject, plainRecord, requiredStringArray, splitCsvFlag, } from "../coerce.js";
4
+ import { finiteNumberField, jsonInput, optionalStringField, parseJsonObject, requiredStringArray, splitCsvFlag, } from "../coerce.js";
4
5
  import { commandUsage, } from "../syntax.js";
5
6
  import { UsageError, } from "../usage.js";
6
7
  export function flowZoneId(value) {
@@ -107,7 +108,7 @@ export function flowZonePlanColor(value, source) {
107
108
  export function flowZonePlanPosition(value, source) {
108
109
  if (value === undefined)
109
110
  return undefined;
110
- const record = plainRecord(value);
111
+ const record = asRecord(value);
111
112
  if (!record) {
112
113
  throw new UsageError(`${source} must be an object with x and y.`, "validation_failed");
113
114
  }
@@ -117,7 +118,7 @@ export function flowZonePlanPosition(value, source) {
117
118
  };
118
119
  }
119
120
  export function flowZoneCurrentPosition(zone) {
120
- const position = plainRecord(zone.position);
121
+ const position = asRecord(zone.position);
121
122
  if (!position)
122
123
  return undefined;
123
124
  const x = position.x;
@@ -134,7 +135,7 @@ export function flowZoneSamePosition(a, b) {
134
135
  export function parseFlowZonePlanItem(value, source) {
135
136
  if (typeof value === "string")
136
137
  return parseFlowZoneObject(value);
137
- const record = plainRecord(value);
138
+ const record = asRecord(value);
138
139
  if (!record) {
139
140
  throw new UsageError(`${source} must be TYPE:ID or an object.`, "validation_failed");
140
141
  }
@@ -213,7 +214,7 @@ export function parseFlowZoneOrganizePlan(input) {
213
214
  ...(topologyFingerprint ? { topologyFingerprint, } : {}),
214
215
  zones: zones.map((value, index) => {
215
216
  const source = `zones[${index}]`;
216
- const record = plainRecord(value);
217
+ const record = asRecord(value);
217
218
  if (!record)
218
219
  throw new UsageError(`${source} must be an object.`, "validation_failed");
219
220
  const id = optionalStringField(record, ["id", "zoneId",]);
@@ -37,8 +37,6 @@ export interface CliVersionPayload {
37
37
  buildRevision: string | null;
38
38
  staleBuild: boolean;
39
39
  }
40
- /** Bun is the only supported runtime; the field is retained for payload stability. */
41
- export declare function detectRuntime(): CliRuntime;
42
40
  export declare function buildVersionPayload(input: {
43
41
  packageVersion: string;
44
42
  checkoutRevision: string | undefined;
@@ -182,10 +182,6 @@ export function sourceTreeNewerThanBuild(packageRoot) {
182
182
  return false;
183
183
  }
184
184
  }
185
- /** Bun is the only supported runtime; the field is retained for payload stability. */
186
- export function detectRuntime() {
187
- return "bun";
188
- }
189
185
  export function buildVersionPayload(input) {
190
186
  const isDist = input.loadSource === "dist";
191
187
  // Source execution never claims a build revision, even if the environment
@@ -212,10 +208,11 @@ export const AGENT_CONTRACT_VERSION = 2;
212
208
  export const JSON_SCHEMA_DRAFT = "https://json-schema.org/draft/2020-12/schema";
213
209
  export const AGENT_CONTRACT_SCHEMA_ID = "https://clssck.github.io/dataiku-sdk/schemas/agent-contract-v2.json";
214
210
  export function cliVersionResult() {
215
- /* oxlint-disable dss/no-direct-process-env -- launcher-injected load source and build revision */
216
- const loadSource = process.env["DSS_LOAD_SOURCE"] ?? detectLoadSource();
217
- const envBuildRevision = process.env["DSS_BUILD_REVISION"];
218
- /* oxlint-enable dss/no-direct-process-env */
211
+ // Only code actually loaded from dist/ may report dist provenance; an
212
+ // inherited DSS_LOAD_SOURCE on a source run is ignored.
213
+ const loadSource = detectLoadSource();
214
+ /* oxlint-disable-next-line dss/no-direct-process-env -- launcher-injected build revision */
215
+ const envBuildRevision = loadSource === "dist" ? process.env["DSS_BUILD_REVISION"] : undefined;
219
216
  return buildVersionPayload({
220
217
  packageVersion: CLI_VERSION,
221
218
  checkoutRevision: CLI_GIT_REVISION,
@@ -227,6 +224,6 @@ export function cliVersionResult() {
227
224
  sourceNewerThanBuild: loadSource === "dist"
228
225
  ? sourceTreeNewerThanBuild(PACKAGE_ROOT)
229
226
  : false,
230
- runtime: detectRuntime(),
227
+ runtime: "bun",
231
228
  });
232
229
  }