dataiku-sdk 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +149 -46
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/packages/types/src/index.d.ts +102 -0
  4. package/dist/packages/types/src/index.js +76 -0
  5. package/dist/src/cli/commands/app.js +57 -15
  6. package/dist/src/cli/commands/bundle.js +1 -1
  7. package/dist/src/cli/commands/code-env.js +13 -7
  8. package/dist/src/cli/commands/connection.d.ts +9 -0
  9. package/dist/src/cli/commands/connection.js +267 -1
  10. package/dist/src/cli/commands/data-collection.d.ts +2 -0
  11. package/dist/src/cli/commands/data-collection.js +164 -0
  12. package/dist/src/cli/commands/dataset.js +161 -32
  13. package/dist/src/cli/commands/future.js +2 -2
  14. package/dist/src/cli/commands/group.d.ts +2 -0
  15. package/dist/src/cli/commands/group.js +109 -0
  16. package/dist/src/cli/commands/index.js +151 -78
  17. package/dist/src/cli/commands/knowledge-bank.d.ts +2 -0
  18. package/dist/src/cli/commands/knowledge-bank.js +80 -0
  19. package/dist/src/cli/commands/llm.d.ts +2 -0
  20. package/dist/src/cli/commands/llm.js +93 -0
  21. package/dist/src/cli/commands/macro.d.ts +2 -0
  22. package/dist/src/cli/commands/macro.js +221 -0
  23. package/dist/src/cli/commands/ml-task.js +18 -6
  24. package/dist/src/cli/commands/notebook.js +6 -2
  25. package/dist/src/cli/commands/plugin.d.ts +2 -0
  26. package/dist/src/cli/commands/plugin.js +799 -0
  27. package/dist/src/cli/commands/project-folder.d.ts +2 -0
  28. package/dist/src/cli/commands/project-folder.js +197 -0
  29. package/dist/src/cli/commands/project-git.js +14 -54
  30. package/dist/src/cli/commands/project.js +52 -6
  31. package/dist/src/cli/commands/recipe.js +36 -1
  32. package/dist/src/cli/commands/saved-model.d.ts +5 -0
  33. package/dist/src/cli/commands/saved-model.js +388 -1
  34. package/dist/src/cli/commands/scenario.js +138 -1
  35. package/dist/src/cli/commands/user.d.ts +8 -0
  36. package/dist/src/cli/commands/user.js +296 -0
  37. package/dist/src/cli/contract.d.ts +1 -2
  38. package/dist/src/cli/contract.js +1089 -103
  39. package/dist/src/cli/flags.js +30 -1
  40. package/dist/src/cli/helpers/app-successor.d.ts +19 -4
  41. package/dist/src/cli/helpers/app-successor.js +90 -30
  42. package/dist/src/cli/helpers/cleanup.js +3 -1
  43. package/dist/src/cli/helpers/flow-zone.js +1 -2
  44. package/dist/src/cli/output.d.ts +1 -1
  45. package/dist/src/cli/output.js +23 -10
  46. package/dist/src/cli/usage.d.ts +1 -0
  47. package/dist/src/cli/usage.js +47 -1
  48. package/dist/src/cli.js +147 -41
  49. package/dist/src/client.d.ts +83 -5
  50. package/dist/src/client.js +185 -29
  51. package/dist/src/errors.d.ts +3 -0
  52. package/dist/src/errors.js +2 -0
  53. package/dist/src/index.d.ts +16 -7
  54. package/dist/src/index.js +11 -2
  55. package/dist/src/resources/analyses.js +1 -8
  56. package/dist/src/resources/applications.d.ts +8 -6
  57. package/dist/src/resources/applications.js +21 -21
  58. package/dist/src/resources/base.d.ts +4 -0
  59. package/dist/src/resources/base.js +15 -0
  60. package/dist/src/resources/bundles.d.ts +9 -4
  61. package/dist/src/resources/bundles.js +10 -5
  62. package/dist/src/resources/connections.d.ts +78 -1
  63. package/dist/src/resources/connections.js +132 -1
  64. package/dist/src/resources/data-collections.d.ts +118 -0
  65. package/dist/src/resources/data-collections.js +49 -0
  66. package/dist/src/resources/datasets.d.ts +69 -0
  67. package/dist/src/resources/datasets.js +99 -1
  68. package/dist/src/resources/futures.d.ts +4 -1
  69. package/dist/src/resources/futures.js +38 -6
  70. package/dist/src/resources/groups.d.ts +47 -0
  71. package/dist/src/resources/groups.js +56 -0
  72. package/dist/src/resources/jobs.d.ts +8 -13
  73. package/dist/src/resources/jobs.js +41 -16
  74. package/dist/src/resources/knowledge-banks.d.ts +58 -0
  75. package/dist/src/resources/knowledge-banks.js +61 -0
  76. package/dist/src/resources/llms.d.ts +92 -0
  77. package/dist/src/resources/llms.js +62 -0
  78. package/dist/src/resources/macros.d.ts +121 -0
  79. package/dist/src/resources/macros.js +231 -0
  80. package/dist/src/resources/ml-tasks.d.ts +37 -2
  81. package/dist/src/resources/ml-tasks.js +100 -20
  82. package/dist/src/resources/model-evaluation-stores.js +1 -8
  83. package/dist/src/resources/notebooks.d.ts +43 -7
  84. package/dist/src/resources/notebooks.js +53 -13
  85. package/dist/src/resources/plugins.d.ts +265 -0
  86. package/dist/src/resources/plugins.js +458 -0
  87. package/dist/src/resources/project-folders.d.ts +71 -0
  88. package/dist/src/resources/project-folders.js +57 -0
  89. package/dist/src/resources/project-git.d.ts +6 -0
  90. package/dist/src/resources/project-git.js +69 -42
  91. package/dist/src/resources/project-library.d.ts +0 -1
  92. package/dist/src/resources/project-library.js +1 -17
  93. package/dist/src/resources/projects.d.ts +19 -1
  94. package/dist/src/resources/projects.js +27 -1
  95. package/dist/src/resources/recipes.d.ts +15 -1
  96. package/dist/src/resources/recipes.js +73 -6
  97. package/dist/src/resources/saved-models.d.ts +134 -1
  98. package/dist/src/resources/saved-models.js +214 -6
  99. package/dist/src/resources/scenarios.d.ts +105 -0
  100. package/dist/src/resources/scenarios.js +232 -106
  101. package/dist/src/resources/sql.js +5 -1
  102. package/dist/src/resources/users.d.ts +129 -0
  103. package/dist/src/resources/users.js +128 -0
  104. package/dist/src/resources/webapps.d.ts +15 -2
  105. package/dist/src/resources/webapps.js +18 -4
  106. package/dist/src/schemas.d.ts +2 -2
  107. package/dist/src/schemas.js +1 -1
  108. package/dist/src/utils/deep-merge.d.ts +1 -1
  109. package/dist/src/utils/polling.d.ts +20 -0
  110. package/dist/src/utils/polling.js +27 -0
  111. package/dist/src/utils/response-file.js +1 -1
  112. package/dist/src/utils/secret-sanitize.d.ts +54 -0
  113. package/dist/src/utils/secret-sanitize.js +101 -0
  114. package/package.json +6 -5
  115. package/packages/types/dist/index.d.ts +102 -0
  116. package/packages/types/dist/index.js +76 -0
  117. package/plugin.json +1 -1
  118. package/skills/dataiku-dss/SKILL.md +7 -5
  119. package/skills/dataiku-dss/references/app-releases.md +8 -7
  120. package/skills/dataiku-dss/references/discovery.md +4 -4
@@ -61,6 +61,10 @@ export const BOOLEAN_FLAGS = new Set([
61
61
  "peek",
62
62
  "raw-data",
63
63
  "all",
64
+ "connected",
65
+ "conda",
66
+ "force",
67
+ "partitioning-folder",
64
68
  ]);
65
69
  export const SHORT_FLAGS = {
66
70
  v: "verbose",
@@ -82,9 +86,12 @@ export const VALUE_FLAGS = new Set([
82
86
  "owner",
83
87
  "topic",
84
88
  "reply",
85
- "text",
89
+ "configuration",
86
90
  "partitions",
87
91
  "since",
92
+ "keys",
93
+ "candidates",
94
+ "text",
88
95
  "activity",
89
96
  "agent",
90
97
  "api-key",
@@ -225,6 +232,28 @@ export const VALUE_FLAGS = new Set([
225
232
  "to",
226
233
  "zone",
227
234
  "zone-id",
235
+ "purpose",
236
+ "step-id",
237
+ "max-bytes",
238
+ "type-option-id",
239
+ "format-option-id",
240
+ "copy-partitioning-from",
241
+ "creation-mode",
242
+ "max-dataset-count",
243
+ "logins",
244
+ "max-log-bytes",
245
+ "llm-id",
246
+ "remove-intermediate",
247
+ "code-env",
248
+ "container-exec-config",
249
+ "set-active",
250
+ "binary-classification-threshold",
251
+ "sampling",
252
+ "use-optimal-threshold",
253
+ "skip-expensive-reports",
254
+ "full-class-name",
255
+ "include-libs",
256
+ "archive",
228
257
  ]);
229
258
  export const REPEATABLE_VALUE_FLAGS = new Set([
230
259
  "dataset",
@@ -13,6 +13,14 @@ export declare const VISUAL_UI_GATE: {
13
13
  readonly requiredAuthentication: "pre-authenticated-sso-or-dedicated-ui-test-identity";
14
14
  readonly evidenceRequired: "Open the instance in the Dataiku Apps UI and exercise the affected tiles, forms, and actions to confirm the release behaves as intended.";
15
15
  };
16
+ /**
17
+ * The one generated instance-key shape shared by `app create-instance` and
18
+ * `app create-successor-instance` when no caller-chosen target is supplied:
19
+ * `APP_` plus 122 random bits of UUIDv4 hex, matching the official client's
20
+ * random-key mode. Collisions are negligible, so a generated key is never
21
+ * probed for absence and is never retried under an alternate key.
22
+ */
23
+ export declare function generatedInstanceKey(): string;
16
24
  export type AppInstanceCheckName = "project-type" | "app-registration" | "manifest-version" | "manifest-references";
17
25
  export interface AppInstanceCheck {
18
26
  check: AppInstanceCheckName;
@@ -46,7 +54,11 @@ export interface AppInstanceVerification {
46
54
  export interface SuccessorInstanceRequest {
47
55
  appId: string;
48
56
  from: string;
49
- to: string;
57
+ /**
58
+ * Caller-chosen successor key. Omitted for generated-key mode: the key is
59
+ * generated once during apply and DSS's own creation future names it.
60
+ */
61
+ to?: string;
50
62
  name?: string;
51
63
  copyPermissions: boolean;
52
64
  dryRun: boolean;
@@ -92,9 +104,12 @@ interface TargetAbsenceContext {
92
104
  visibleAppInstanceKeys?: readonly (string | undefined)[];
93
105
  }
94
106
  /**
95
- * Creation cleanup is safe only when the target was proven absent before POST.
96
- * A hidden 403 is not absence: project listing can prove a collision, but it
97
- * cannot prove that an inaccessible project does not exist.
107
+ * Caller-chosen-key guard: when the caller supplies the target key, creation
108
+ * cleanup is safe only when that target was proven absent before POST. A
109
+ * hidden 403 is not absence: project listing can prove a collision, but it
110
+ * cannot prove that an inaccessible project does not exist. Generated keys
111
+ * (app create-instance with targetProjectKey omitted) skip this guard: a
112
+ * freshly generated random key does not need an absence probe.
98
113
  */
99
114
  export declare function requireAbsentProjectTarget(client: DataikuClient, targetProjectKey: string, targetFlag: "--to" | "targetProjectKey", context: TargetAbsenceContext): Promise<void>;
100
115
  /**
@@ -1,3 +1,4 @@
1
+ import { randomUUID, } from "node:crypto";
1
2
  import { DataikuError, } from "../../errors.js";
2
3
  import { projectIncarnationHash, } from "../../utils/project-incarnation.js";
3
4
  import { stableHash, stringField, } from "../coerce.js";
@@ -17,6 +18,16 @@ export const VISUAL_UI_GATE = {
17
18
  requiredAuthentication: "pre-authenticated-sso-or-dedicated-ui-test-identity",
18
19
  evidenceRequired: "Open the instance in the Dataiku Apps UI and exercise the affected tiles, forms, and actions to confirm the release behaves as intended.",
19
20
  };
21
+ /**
22
+ * The one generated instance-key shape shared by `app create-instance` and
23
+ * `app create-successor-instance` when no caller-chosen target is supplied:
24
+ * `APP_` plus 122 random bits of UUIDv4 hex, matching the official client's
25
+ * random-key mode. Collisions are negligible, so a generated key is never
26
+ * probed for absence and is never retried under an alternate key.
27
+ */
28
+ export function generatedInstanceKey() {
29
+ return `APP_${randomUUID().replace(/-/g, "").toUpperCase()}`;
30
+ }
20
31
  /**
21
32
  * DSS answers the app-manifest probe with 400 ("neither an app template nor an
22
33
  * app instance") for ordinary projects and 404 for missing ones. Both mean "not
@@ -183,9 +194,12 @@ export function futureTargetIdentity(result) {
183
194
  return null;
184
195
  }
185
196
  /**
186
- * Creation cleanup is safe only when the target was proven absent before POST.
187
- * A hidden 403 is not absence: project listing can prove a collision, but it
188
- * cannot prove that an inaccessible project does not exist.
197
+ * Caller-chosen-key guard: when the caller supplies the target key, creation
198
+ * cleanup is safe only when that target was proven absent before POST. A
199
+ * hidden 403 is not absence: project listing can prove a collision, but it
200
+ * cannot prove that an inaccessible project does not exist. Generated keys
201
+ * (app create-instance with targetProjectKey omitted) skip this guard: a
202
+ * freshly generated random key does not need an absence probe.
189
203
  */
190
204
  export async function requireAbsentProjectTarget(client, targetProjectKey, targetFlag, context) {
191
205
  if (context.visibleAppInstanceKeys?.includes(targetProjectKey) === true) {
@@ -251,11 +265,14 @@ export async function requireAbsentProjectTarget(client, targetProjectKey, targe
251
265
  creationPostAttempted: false,
252
266
  };
253
267
  if (targetVisibleInProjectList !== true && targetVisibleInAppInstances !== true) {
254
- throw new UsageError(`Could not confirm target project ${targetProjectKey} is absent.`, "target_absence_unverifiable", "DSS exposes no permission-independent project-key availability endpoint, and app-instance creation has no published atomic duplicate-key guarantee. Retry with an identity that has global project visibility; creation was not attempted.", {
268
+ throw new UsageError(`Could not confirm target project ${targetProjectKey} is absent.`, "target_absence_unverifiable", "Creation was not attempted. For a fresh instance with a different key, omit targetProjectKey from `dss app create-instance`. To keep this exact key, retry with an identity that has global project visibility. DSS exposes no permission-independent key-availability endpoint or published atomic duplicate-key guarantee.", {
255
269
  ...probeDetails,
256
270
  targetProbe: "forbidden-and-not-listable",
257
271
  deploymentMasksUnknownProjects: "possible-but-unproven",
258
- supportedRecoveryModes: ["grant-global-project-visibility",],
272
+ supportedRecoveryModes: [
273
+ "create-instance-with-generated-key",
274
+ "grant-global-project-visibility",
275
+ ],
259
276
  unavailableRecoveryModes: [
260
277
  "use-supported-key-availability-endpoint",
261
278
  "server-atomic-create",
@@ -272,7 +289,7 @@ export async function requireAbsentProjectTarget(client, targetProjectKey, targe
272
289
  */
273
290
  async function preflightSuccessor(client, request, usage) {
274
291
  const { appId, from, to, } = request;
275
- if (from === to) {
292
+ if (to !== undefined && from === to) {
276
293
  throw new UsageError("--from and --to must be different project keys: a successor instance is created alongside its predecessor.", "validation_failed", `Usage: ${usage}`, { sourceProjectKey: from, targetProjectKey: to, });
277
294
  }
278
295
  const instances = await client.applications.listInstances(appId);
@@ -290,10 +307,16 @@ async function preflightSuccessor(client, request, usage) {
290
307
  if (source.projectAppType !== "APP_INSTANCE") {
291
308
  throw new UsageError(`Project ${from} is a ${source.projectAppType ?? "non-app"} project, not a classic Dataiku App instance.`, "validation_failed", "--from must name an APP_INSTANCE project; app templates are versioned with `dss app set-manifest-version`.", { sourceProjectKey: from, projectAppType: source.projectAppType ?? null, });
292
309
  }
293
- await requireAbsentProjectTarget(client, to, "--to", {
294
- appId,
295
- visibleAppInstanceKeys: instances.map((instance) => instance.projectKey),
296
- });
310
+ if (to !== undefined) {
311
+ // Caller-chosen keys must be proven absent before the single POST.
312
+ // Generated keys skip the probe entirely: the key does not exist yet,
313
+ // so an absence probe would prove nothing and probe a project the
314
+ // caller never named.
315
+ await requireAbsentProjectTarget(client, to, "--to", {
316
+ appId,
317
+ visibleAppInstanceKeys: instances.map((instance) => instance.projectKey),
318
+ });
319
+ }
297
320
  const templateManifest = await client.applications.getAppManifest(appId);
298
321
  const templateVersion = rawVersion(templateManifest);
299
322
  if (templateVersion === null) {
@@ -317,8 +340,10 @@ async function preflightSuccessor(client, request, usage) {
317
340
  sourceProjectAppType: source.projectAppType,
318
341
  sourceVersion: source.version,
319
342
  sourceManifestHash: source.manifestHash,
320
- targetProjectName: request.name ?? to,
321
- targetExists: false,
343
+ generatedTarget: to === undefined,
344
+ ...(request.name !== undefined || to !== undefined
345
+ ? { targetProjectName: request.name ?? to, }
346
+ : {}),
322
347
  templateManifestHash: stableHash(templateManifest),
323
348
  templateVersion,
324
349
  templateReferenceValidation,
@@ -343,6 +368,7 @@ export async function preflightSuccessorInstance(client, request, usage) {
343
368
  preflightExecuted: true,
344
369
  creationPostAttempted: false,
345
370
  versionMutationAttempted: false,
371
+ ...(preflight.generatedTarget ? { targetProjectKeyGeneratedDuringApply: true, } : {}),
346
372
  appId: request.appId,
347
373
  source: {
348
374
  projectKey: request.from,
@@ -353,12 +379,28 @@ export async function preflightSuccessorInstance(client, request, usage) {
353
379
  ? { permissionsHash: preflight.sourcePermissionsHash, }
354
380
  : {}),
355
381
  },
356
- target: {
357
- projectKey: request.to,
358
- name: preflight.targetProjectName,
359
- exists: false,
360
- },
361
- targetPreflight: "confirmed-absent",
382
+ ...(preflight.generatedTarget
383
+ ? {
384
+ // Generated mode: the target key does not exist yet, so no
385
+ // absence probe ran and none can be claimed. The plan/receipt
386
+ // names no key at all: it is generated once during apply and
387
+ // named by DSS's own terminal creation future.
388
+ target: {
389
+ targetProjectKeyGeneratedDuringApply: true,
390
+ ...(preflight.targetProjectName !== undefined
391
+ ? { name: preflight.targetProjectName, }
392
+ : { targetProjectNameGeneratedDuringApply: true, }),
393
+ },
394
+ targetPreflight: "not-applicable-generated-key",
395
+ }
396
+ : {
397
+ target: {
398
+ projectKey: request.to,
399
+ name: preflight.targetProjectName,
400
+ exists: false,
401
+ },
402
+ targetPreflight: "confirmed-absent",
403
+ }),
362
404
  template: {
363
405
  projectKey: request.appId,
364
406
  version: preflight.templateVersion,
@@ -372,7 +414,7 @@ export async function preflightSuccessorInstance(client, request, usage) {
372
414
  copyPermissions: request.copyPermissions,
373
415
  next: {
374
416
  setVersion: `dss app set-manifest-version --manifest-version <VERSION> --expect-hash ${preflight.templateManifestHash} --project-key ${request.appId}`,
375
- createSuccessor: `dss app create-successor-instance ${request.appId} --from ${request.from} --to ${request.to}${request.name !== undefined ? ` --name ${JSON.stringify(request.name)}` : ""}${request.copyPermissions ? " --copy-permissions" : ""}`,
417
+ createSuccessor: `dss app create-successor-instance ${request.appId} --from ${request.from}${request.to !== undefined ? ` --to ${request.to}` : ""}${request.name !== undefined ? ` --name ${JSON.stringify(request.name)}` : ""}${request.copyPermissions ? " --copy-permissions" : ""}`,
376
418
  },
377
419
  };
378
420
  }
@@ -388,7 +430,8 @@ export async function preflightSuccessorInstance(client, request, usage) {
388
430
  * indeterminate and produces no cleanup entry.
389
431
  */
390
432
  export async function createSuccessorInstance(client, request, usage) {
391
- const { appId, from, to, } = request;
433
+ const { appId, from, } = request;
434
+ const generatedTarget = request.to === undefined;
392
435
  const preflight = await preflightSuccessor(client, request, usage);
393
436
  const source = {
394
437
  projectKey: from,
@@ -408,27 +451,44 @@ export async function createSuccessorInstance(client, request, usage) {
408
451
  sourcePreserved: true,
409
452
  uiPublicationVerified: false,
410
453
  requiredExternalGates: [VISUAL_UI_GATE,],
411
- targetPreflight: "confirmed-absent",
454
+ // Generated mode never probes a target, so it must not claim the
455
+ // probe-backed absence verdict: no target existed to verify.
456
+ targetPreflight: generatedTarget ? "not-applicable-generated-key" : "confirmed-absent",
412
457
  };
413
458
  if (request.dryRun) {
414
459
  return {
415
460
  dryRun: true,
416
461
  action: "create-successor-instance",
417
462
  ...shared,
418
- target: {
419
- projectKey: to,
420
- name: preflight.targetProjectName,
421
- exists: false,
422
- },
463
+ ...(generatedTarget
464
+ ? { targetProjectKeyGeneratedDuringApply: true, }
465
+ : {}),
466
+ target: generatedTarget
467
+ ? {
468
+ targetProjectKeyGeneratedDuringApply: true,
469
+ ...(preflight.targetProjectName !== undefined
470
+ ? { name: preflight.targetProjectName, }
471
+ : { targetProjectNameGeneratedDuringApply: true, }),
472
+ }
473
+ : {
474
+ projectKey: request.to,
475
+ name: preflight.targetProjectName,
476
+ exists: false,
477
+ },
423
478
  copyPermissions: request.copyPermissions,
424
479
  preflight: "passed",
425
480
  };
426
481
  }
482
+ // Generated exactly once, here, immediately before the single POST: plan,
483
+ // preflight and dry-run never allocate. A definitive rejection does not
484
+ // retry and does not fall back to an alternate key.
485
+ const to = request.to ?? generatedInstanceKey();
486
+ const targetProjectName = preflight.targetProjectName ?? to;
427
487
  let created;
428
488
  try {
429
489
  created = await client.applications.createInstance(appId, {
430
490
  targetProjectKey: to,
431
- targetProjectName: preflight.targetProjectName,
491
+ targetProjectName,
432
492
  });
433
493
  }
434
494
  catch (error) {
@@ -436,7 +496,7 @@ export async function createSuccessorInstance(client, request, usage) {
436
496
  return {
437
497
  ...shared,
438
498
  projectKey: to,
439
- target: { projectKey: to, name: preflight.targetProjectName, },
499
+ target: { projectKey: to, name: targetProjectName, },
440
500
  success: false,
441
501
  state: rejected ? "CREATE_FAILED" : "INDETERMINATE",
442
502
  elapsedMs: 0,
@@ -465,7 +525,7 @@ export async function createSuccessorInstance(client, request, usage) {
465
525
  return {
466
526
  ...shared,
467
527
  projectKey: to,
468
- target: { projectKey: to, name: preflight.targetProjectName, },
528
+ target: { projectKey: to, name: targetProjectName, },
469
529
  success: false,
470
530
  state: "VERIFICATION_FAILED",
471
531
  elapsedMs: 0,
@@ -485,7 +545,7 @@ export async function createSuccessorInstance(client, request, usage) {
485
545
  const base = {
486
546
  ...shared,
487
547
  projectKey: to,
488
- target: { projectKey: to, name: preflight.targetProjectName, },
548
+ target: { projectKey: to, name: targetProjectName, },
489
549
  ...(instance ? { instance, } : {}),
490
550
  };
491
551
  let waited;
@@ -280,7 +280,9 @@ export function cleanupLedgerEntry(resource, action, args, flags, result, projec
280
280
  };
281
281
  }
282
282
  case "notebook.save-sql": {
283
- const id = args[0];
283
+ // On create the persisted id is the server receipt (`saved`), not the
284
+ // requested handle, which only becomes the display name.
285
+ const id = stringField(record, ["saved", "id",]) ?? args[0];
284
286
  if (!id)
285
287
  return undefined;
286
288
  return {
@@ -116,8 +116,7 @@ export function flowZonePlanPosition(value, source) {
116
116
  };
117
117
  }
118
118
  export function flowZoneCurrentPosition(zone) {
119
- const record = zone;
120
- const position = plainRecord(record.position);
119
+ const position = plainRecord(zone.position);
121
120
  if (!position)
122
121
  return undefined;
123
122
  const x = position.x;
@@ -10,7 +10,7 @@ export declare function pickResultFields(item: unknown, fields: string[]): unkno
10
10
  * element-wise; scalars and string results pass through untouched. Requested keys
11
11
  * that are absent become null so every row keeps a stable, predictable shape.
12
12
  */
13
- export declare function projectResultFields(result: unknown, fields: string[]): unknown;
13
+ export declare function projectResultFields(result: unknown, fields: string[], availableFieldNames?: () => string[]): unknown;
14
14
  export declare function writeCommandResult(result: unknown): void;
15
15
  export declare function transientBodyWithTargetContext(body: string, target: string, elapsedMs: number): string;
16
16
  export declare function addTransientTargetContext(error: unknown, target: string, elapsedMs: number): never;
@@ -47,14 +47,15 @@ function projectionRecords(result) {
47
47
  const items = Array.isArray(result) ? result : [result,];
48
48
  return items.filter((item) => item !== null && typeof item === "object" && !Array.isArray(item));
49
49
  }
50
- function warnUnknownProjectionFields(result, fields) {
50
+ function warnUnknownProjectionFields(result, fields, availableFieldNames) {
51
51
  const records = projectionRecords(result);
52
52
  if (records.length === 0)
53
53
  return;
54
54
  const unknownFields = fields.filter((field) => !records.some((record) => resolveFieldPath(record, field).found));
55
55
  if (unknownFields.length === 0)
56
56
  return;
57
- const availableFields = [...new Set(records.flatMap((record) => Object.keys(record))),].sort();
57
+ const availableFields = availableFieldNames?.()
58
+ ?? [...new Set(records.flatMap((record) => Object.keys(record))),].sort();
58
59
  enqueueCliWarning({
59
60
  code: "field_projection_missing",
60
61
  fields: unknownFields,
@@ -67,8 +68,8 @@ function warnUnknownProjectionFields(result, fields) {
67
68
  * element-wise; scalars and string results pass through untouched. Requested keys
68
69
  * that are absent become null so every row keeps a stable, predictable shape.
69
70
  */
70
- export function projectResultFields(result, fields) {
71
- warnUnknownProjectionFields(result, fields);
71
+ export function projectResultFields(result, fields, availableFieldNames) {
72
+ warnUnknownProjectionFields(result, fields, availableFieldNames);
72
73
  if (Array.isArray(result))
73
74
  return result.map((item) => pickResultFields(item, fields));
74
75
  return pickResultFields(result, fields);
@@ -98,7 +99,7 @@ export function transientBodyWithTargetContext(body, target, elapsedMs) {
98
99
  }
99
100
  export function addTransientTargetContext(error, target, elapsedMs) {
100
101
  if (error instanceof DataikuError && error.category === "transient") {
101
- throw new DataikuError(error.status, error.statusText, transientBodyWithTargetContext(error.body, target, elapsedMs), error.retry, error.requestId, { target, elapsedMs, });
102
+ throw new DataikuError(error.status, error.statusText, transientBodyWithTargetContext(error.body, target, elapsedMs), error.retry, error.requestId, { target, elapsedMs, bodyTruncated: error.bodyTruncated, });
102
103
  }
103
104
  throw error;
104
105
  }
@@ -106,10 +107,16 @@ export function isFailedWaitResult(result) {
106
107
  if (result === null || typeof result !== "object" || Array.isArray(result))
107
108
  return false;
108
109
  const record = result;
109
- return record.success === false
110
- && typeof record.elapsedMs === "number"
111
- && typeof record.pollCount === "number"
112
- && (typeof record.state === "string" || typeof record.outcome === "string");
110
+ if (record.success !== false)
111
+ return false;
112
+ if (typeof record.elapsedMs !== "number" || typeof record.pollCount !== "number")
113
+ return false;
114
+ // Job/future wait results name the terminal state as a string; macro wait
115
+ // results identify themselves by their runnable identity fields with the
116
+ // poll state attached as an object (or omitted entirely).
117
+ if (typeof record.state === "string" || typeof record.outcome === "string")
118
+ return true;
119
+ return typeof record.runnableType === "string" && typeof record.runId === "string";
113
120
  }
114
121
  export function isAssertionFailureResult(result) {
115
122
  if (result === null || typeof result !== "object" || Array.isArray(result))
@@ -172,7 +179,13 @@ export class CommandResultFailure extends Error {
172
179
  export function commandFailureMessage(result) {
173
180
  if (isFailedWaitResult(result)) {
174
181
  const record = result;
175
- const state = typeof record.state === "string" ? record.state : record.outcome;
182
+ const state = typeof record.state === "string"
183
+ ? record.state
184
+ : typeof record.outcome === "string"
185
+ ? record.outcome
186
+ : typeof record.timedOut === "boolean"
187
+ ? "timed out"
188
+ : undefined;
176
189
  return `Command completed with failed long-running result${state ? `: ${state}` : ""}.`;
177
190
  }
178
191
  if (isAssertionFailureResult(result)) {
@@ -1,5 +1,6 @@
1
1
  import type { StableErrorCode } from "../errors.js";
2
2
  export declare const RESOURCE_NAMES: string[];
3
+ export declare function inferRequiresProject(resource: string, action: string, usage: string): boolean;
3
4
  export declare class UsageError extends Error {
4
5
  readonly code: StableErrorCode;
5
6
  readonly hint?: string;
@@ -9,6 +9,15 @@ export const RESOURCE_NAMES = [
9
9
  "bundle",
10
10
  "project-deployer",
11
11
  "project-git",
12
+ "project-folder",
13
+ "data-collection",
14
+ "llm",
15
+ "knowledge-bank",
16
+ "macro",
17
+ "plugin",
18
+ "user",
19
+ "group",
20
+ "project",
12
21
  "project-library",
13
22
  "streaming-endpoint",
14
23
  "continuous-activity",
@@ -44,10 +53,47 @@ export const RESOURCE_NAMES = [
44
53
  "commands",
45
54
  "fixtures",
46
55
  "install-skill",
47
- "project",
48
56
  "version",
49
57
  ]
50
58
  .sort();
59
+ const PROJECT_SCOPED_RESOURCES = new Set([
60
+ "analysis",
61
+ "data-quality",
62
+ "dashboard",
63
+ "knowledge-bank",
64
+ "dataset",
65
+ "llm",
66
+ "flow-zone",
67
+ "insight",
68
+ "folder",
69
+ "fixtures",
70
+ "job",
71
+ "notebook",
72
+ "macro",
73
+ "ml-task",
74
+ "model-evaluation-store",
75
+ "recipe",
76
+ "scenario",
77
+ "sql",
78
+ "variable",
79
+ "saved-model",
80
+ "wiki",
81
+ ]);
82
+ export function inferRequiresProject(resource, action, usage) {
83
+ if (resource === "agent" || resource === "auth" || resource === "doctor" || resource === "commands"
84
+ || resource === "install-skill" || resource === "version")
85
+ return false;
86
+ if (resource === "sql" && action === "query")
87
+ return false;
88
+ if (PROJECT_SCOPED_RESOURCES.has(resource))
89
+ return true;
90
+ // Plugin project scope is optional, not a requirement.
91
+ if (resource === "plugin")
92
+ return false;
93
+ if (resource === "project-git")
94
+ return !action.startsWith("future-");
95
+ return usage.includes("--project-key");
96
+ }
51
97
  export class UsageError extends Error {
52
98
  code;
53
99
  hint;