dataiku-sdk 3.8.0 → 4.0.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 (68) hide show
  1. package/README.md +7 -5
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/packages/types/src/index.d.ts +4 -0
  4. package/dist/packages/types/src/index.js +4 -0
  5. package/dist/src/cli/coerce.d.ts +6 -0
  6. package/dist/src/cli/coerce.js +9 -0
  7. package/dist/src/cli/command-syntax.json +36 -0
  8. package/dist/src/cli/commands/app.js +2 -2
  9. package/dist/src/cli/commands/code-env.js +5 -6
  10. package/dist/src/cli/commands/code.js +16 -2
  11. package/dist/src/cli/commands/dashboard.js +1 -2
  12. package/dist/src/cli/commands/dataset.js +2 -2
  13. package/dist/src/cli/commands/flow-zone.js +1 -1
  14. package/dist/src/cli/commands/job.js +101 -22
  15. package/dist/src/cli/commands/plugin.js +7 -7
  16. package/dist/src/cli/commands/project-library.js +2 -2
  17. package/dist/src/cli/commands/project.js +25 -7
  18. package/dist/src/cli/commands/recipe.js +11 -19
  19. package/dist/src/cli/commands/scenario.js +2 -4
  20. package/dist/src/cli/contract.js +29 -10
  21. package/dist/src/cli/env.js +2 -3
  22. package/dist/src/cli/helpers/app-permissions.d.ts +1 -1
  23. package/dist/src/cli/helpers/app-permissions.js +2 -2
  24. package/dist/src/cli/helpers/cleanup.d.ts +2 -0
  25. package/dist/src/cli/helpers/cleanup.js +122 -8
  26. package/dist/src/cli/helpers/flow-zone.d.ts +1 -1
  27. package/dist/src/cli/helpers/flow-zone.js +3 -4
  28. package/dist/src/cli/list-output.d.ts +5 -0
  29. package/dist/src/cli/list-output.js +11 -2
  30. package/dist/src/cli/list-shapes.d.ts +6 -0
  31. package/dist/src/cli/list-shapes.js +7 -1
  32. package/dist/src/cli/output-schemas.d.ts +1 -1
  33. package/dist/src/cli/output-schemas.js +28 -4
  34. package/dist/src/cli/output.js +7 -0
  35. package/dist/src/cli/project-map-output.d.ts +24 -0
  36. package/dist/src/cli/project-map-output.js +21 -0
  37. package/dist/src/cli/read-shapes.d.ts +14 -0
  38. package/dist/src/cli/read-shapes.js +114 -0
  39. package/dist/src/cli/result-shapes.d.ts +6 -0
  40. package/dist/src/cli/result-shapes.js +235 -0
  41. package/dist/src/cli/version.d.ts +2 -2
  42. package/dist/src/cli/version.js +4 -5
  43. package/dist/src/cli.js +77 -33
  44. package/dist/src/client.d.ts +14 -2
  45. package/dist/src/client.js +18 -10
  46. package/dist/src/generated/action-output-schemas.json +242 -35
  47. package/dist/src/index.d.ts +1 -1
  48. package/dist/src/resources/datasets.d.ts +11 -1
  49. package/dist/src/resources/datasets.js +91 -30
  50. package/dist/src/resources/jobs.d.ts +37 -6
  51. package/dist/src/resources/jobs.js +110 -44
  52. package/dist/src/resources/projects.js +2 -2
  53. package/dist/src/resources/recipes.d.ts +12 -1
  54. package/dist/src/resources/recipes.js +39 -12
  55. package/dist/src/resources/scenarios.d.ts +8 -1
  56. package/dist/src/resources/scenarios.js +51 -12
  57. package/dist/src/resources/wiki.js +3 -7
  58. package/dist/src/skill.js +1 -2
  59. package/dist/src/utils/project-archive.d.ts +25 -0
  60. package/dist/src/utils/project-archive.js +216 -5
  61. package/dist/src/utils/response-file.js +15 -8
  62. package/dist/src/utils/secret-sanitize.d.ts +11 -2
  63. package/dist/src/utils/secret-sanitize.js +23 -6
  64. package/package.json +13 -14
  65. package/plugin.json +1 -1
  66. package/skills/dataiku-dss/SKILL.md +1 -1
  67. package/skills/dataiku-dss/references/discovery.md +2 -2
  68. package/skills/dataiku-dss/references/troubleshooting.md +2 -2
@@ -4,10 +4,13 @@ import { inspectProjectArchive, } from "../../utils/project-archive.js";
4
4
  import { projectIncarnationHash, } from "../../utils/project-incarnation.js";
5
5
  import { jsonInput, num, requiredJsonInput, requiredStringFlag, } from "../coerce.js";
6
6
  import { executionMode, } from "../flags.js";
7
- import { CommandResultFailure, } from "../output.js";
7
+ import { CommandResultFailure, enqueueCliWarning, } from "../output.js";
8
+ import { compactProjectMapOutput, } from "../project-map-output.js";
8
9
  import { commandUsage, withUsage, } from "../syntax.js";
9
10
  import { requireArgs, UsageError, } from "../usage.js";
10
11
  const INSPECT_ARCHIVE_USAGE = commandUsage("project", "inspect-archive");
12
+ /** Maximum credential inventory entries echoed in the export warning. */
13
+ const EXPORT_CREDENTIAL_INVENTORY_LIMIT = 100;
11
14
  function flowRenderFormat(value) {
12
15
  if (value === undefined)
13
16
  return undefined;
@@ -28,7 +31,7 @@ export const projectCommands = withUsage("project", {
28
31
  },
29
32
  get: {
30
33
  handler: (c, _a, f) => c.projects.get(f["project-key"]),
31
- description: "Get project settings and metadata.",
34
+ description: "Get project settings and metadata (compact by default: empty values, metrics/version/image metadata dropped, permission flags collapsed to deniedPermissions; --full returns the raw DSS object).",
32
35
  examples: ["dss project get", "dss project get --project-key MYPROJ",],
33
36
  },
34
37
  metadata: {
@@ -42,13 +45,13 @@ export const projectCommands = withUsage("project", {
42
45
  examples: ["dss project flow", "dss project flow --project-key MYPROJ",],
43
46
  },
44
47
  map: {
45
- handler: (c, _a, f) => c.projects.map({
48
+ handler: async (c, _a, f) => compactProjectMapOutput(await c.projects.map({
46
49
  projectKey: f["project-key"],
47
50
  maxNodes: num(f["max-nodes"], "--max-nodes"),
48
51
  maxEdges: num(f["max-edges"], "--max-edges"),
49
52
  includeRaw: f["include-raw"] === true,
50
53
  render: flowRenderFormat(f["render"]),
51
- }),
54
+ })),
52
55
  validate: (_a, f) => {
53
56
  flowRenderFormat(f["render"]);
54
57
  num(f["max-nodes"], "--max-nodes");
@@ -161,9 +164,24 @@ export const projectCommands = withUsage("project", {
161
164
  if (!out)
162
165
  throw new UsageError("--output PATH is required.", "missing_required_flag");
163
166
  await c.projects.exportArchive(a[0], out, jsonInput(f));
164
- return { path: out, };
167
+ const inspection = await inspectProjectArchive(out);
168
+ const findings = inspection.credentialFindings;
169
+ if (findings.length > 0) {
170
+ enqueueCliWarning({
171
+ code: "export_contains_credentials",
172
+ message: `The exported archive '${out}' contains ${findings.length} credential-bearing structure(s) `
173
+ + "(member paths and key names only; values are never printed). "
174
+ + "DSS exports credentials, API keys and secret-named variables verbatim.",
175
+ hint: "Sanitize the archive before sharing or uploading it. Overwriting an object in versioned storage does not erase its retained versions: "
176
+ + "once an unsanitized archive is uploaded, rotate the exposed credentials.",
177
+ total: findings.length,
178
+ shown: Math.min(findings.length, EXPORT_CREDENTIAL_INVENTORY_LIMIT),
179
+ findings: findings.slice(0, EXPORT_CREDENTIAL_INVENTORY_LIMIT),
180
+ });
181
+ }
182
+ return { path: out, credentialFindings: findings.length, };
165
183
  },
166
- description: "Export a project to a local archive (.zip).",
184
+ description: "Export a project to a local archive (.zip). The written archive is inspected locally; credential-bearing structures (apikeys.json, secret-named variables, apiKey/token/password fields) raise an export_contains_credentials warning listing member paths and key names only, and the result reports credentialFindings. The archive is not redacted.",
167
185
  examples: ["dss project export MY_PROJ --output ./my_proj.zip",],
168
186
  },
169
187
  import: {
@@ -201,7 +219,7 @@ export const projectCommands = withUsage("project", {
201
219
  "inspect-archive": {
202
220
  handler: async (_c, a) => inspectArchiveCommand(a),
203
221
  localHandler: inspectArchiveCommand,
204
- description: "Read-only local inspection of a project export archive: member names, sizes, manifest validity, archive issues, and the source project key. Never contacts DSS.",
222
+ description: "Read-only local inspection of a project export archive: member names, sizes, manifest validity, archive issues (including credential_structure and residue_scan_skipped warnings), a credentialFindings inventory of credential-bearing member paths and key names (values are never reported), and the source project key. Never contacts DSS.",
205
223
  examples: ["dss project inspect-archive ./my_proj.zip",],
206
224
  },
207
225
  "permissions-set": {
@@ -1,10 +1,8 @@
1
- import { readFileSync, } from "node:fs";
2
- import { writeFile, } from "node:fs/promises";
3
1
  import { join, } from "node:path";
4
2
  import { ClientValidationError, DataikuError, } from "../../errors.js";
5
3
  import { recipeCreateWarnings, recipeOutputSchemaIsComputable, schemaUpdateComputableId, schemaUpdateIsPending, } from "../../resources/recipe-create.js";
6
- import { deepMerge, } from "../../utils/deep-merge.js";
7
- import { jobLogFilterFromFlag, jsonInput, maxLogLinesFromFlags, normalizeLineEndings, num, recipeInputDatasetsFromFlags, requiredJsonInput, requiredStringFlag, rewritePairsFromFlags, sha256Hex, splitCsvFlag, stableHash, } from "../coerce.js";
4
+ import { buildRecipeUpdate, } from "../../resources/recipes.js";
5
+ import { jobLogFilterFromFlag, jsonInput, maxLogLinesFromFlags, normalizeLineEndings, num, readTextFile, recipeInputDatasetsFromFlags, requiredJsonInput, requiredStringFlag, rewritePairsFromFlags, sha256Hex, splitCsvFlag, stableHash, } from "../coerce.js";
8
6
  import { executionMode, } from "../flags.js";
9
7
  import { moveCreatedItemsToZone, resolveFlowZoneIdFromFlags, } from "../helpers/flow-zone.js";
10
8
  import { ensureRecipeBackupDir, readRecipeBackup, recipeBackupDocument, recipeBackupPath, recipeCodeEnv, recipeGraph, recipeInputItemRef, recipeRoleInputItems, recipeRunShouldWait, writeRecipeBackup, } from "../helpers/recipe.js";
@@ -12,9 +10,9 @@ import { encodedProjectEndpoint, enqueueCliWarning, readIfExists, skipResult, }
12
10
  import { commandUsage, withUsage, } from "../syntax.js";
13
11
  import { requireArgs, UsageError, } from "../usage.js";
14
12
  /** Read a local recipe file before any DSS request, mapping read failures to usage validation. */
15
- function readRecipeFile(filePath, flagName) {
13
+ async function readRecipeFile(filePath, flagName) {
16
14
  try {
17
- return readFileSync(filePath, "utf-8");
15
+ return await readTextFile(filePath);
18
16
  }
19
17
  catch (error) {
20
18
  const reason = error instanceof Error ? error.message : String(error);
@@ -507,7 +505,7 @@ export const recipeCommands = withUsage("recipe", {
507
505
  throw new UsageError(`--file is required. Usage: ${commandUsage("recipe", "diff")}`);
508
506
  }
509
507
  // Read and validate the local file before any DSS request.
510
- const localContent = readRecipeFile(filePath, "--file");
508
+ const localContent = await readRecipeFile(filePath, "--file");
511
509
  const result = await c.recipes.get(a[0], {
512
510
  includePayload: true,
513
511
  projectKey: f["project-key"],
@@ -530,23 +528,17 @@ export const recipeCommands = withUsage("recipe", {
530
528
  const pk = f["project-key"];
531
529
  if (executionMode(f).dryRun) {
532
530
  const current = await c.recipes.get(a[0], { projectKey: pk, includePayload: true, });
533
- const currentRecipe = current.recipe;
534
- const next = {
535
- ...current,
536
- ...data,
537
- recipe: deepMerge(currentRecipe, (data.recipe && typeof data.recipe === "object" && !Array.isArray(data.recipe))
538
- ? data.recipe
539
- : {}),
540
- };
531
+ const next = buildRecipeUpdate(current, data);
541
532
  return { dryRun: true, action: "update", resource: "recipe", name: a[0], current, next, };
542
533
  }
543
534
  await c.recipes.update(a[0], data, pk);
544
535
  return { updated: a[0], resource: "recipe", };
545
536
  },
546
- description: "Update recipe settings via JSON merge. Recipe definition fields must be nested under a top-level recipe key.",
537
+ description: "Update recipe settings via JSON merge. Recipe definition fields must be nested under a top-level recipe key. recipe.params.envSelection, when present, replaces the existing envSelection object (include envMode; no stale envName is kept) and is only accepted under recipe.params.",
547
538
  examples: [
548
539
  "dss recipe update compute_orders --data-file settings.json --dry-run",
549
- 'dss recipe update compute_orders --data \'{"recipe":{"params":{"envSelection":{"envMode":"EXPLICIT_ENV","envName":"python39"}}}}\'',
540
+ 'dss recipe update compute_orders --data \'{"recipe":{"params":{"envSelection":{"envMode":"EXPLICIT_ENV","envName":"py311_env"}}}}\'',
541
+ 'dss recipe update compute_orders --data \'{"recipe":{"params":{"envSelection":{"envMode":"INHERIT"}}}}\'',
550
542
  "cat settings.json | dss recipe update compute_orders --stdin",
551
543
  ],
552
544
  },
@@ -636,7 +628,7 @@ export const recipeCommands = withUsage("recipe", {
636
628
  projectKey: f["project-key"],
637
629
  });
638
630
  if (typeof f["output"] === "string") {
639
- await writeFile(f["output"], payload, "utf-8");
631
+ await Bun.write(f["output"], payload);
640
632
  return f["output"];
641
633
  }
642
634
  return payload;
@@ -663,7 +655,7 @@ export const recipeCommands = withUsage("recipe", {
663
655
  const filePath = f["file"];
664
656
  if (!filePath)
665
657
  throw new UsageError("--file is required.");
666
- const content = readRecipeFile(filePath, "--file");
658
+ const content = await readRecipeFile(filePath, "--file");
667
659
  const pk = f["project-key"];
668
660
  const shouldBackup = f["no-backup"] !== true;
669
661
  const backupDir = shouldBackup
@@ -233,11 +233,9 @@ export const scenarioCommands = withUsage("scenario", {
233
233
  const outputFile = f["output"]
234
234
  ?? f["output-file"];
235
235
  if (outputFile) {
236
- const { mkdir, writeFile, } = await import("node:fs/promises");
237
- const { dirname, resolve, } = await import("node:path");
236
+ const { resolve, } = await import("node:path");
238
237
  const outputPath = resolve(outputFile);
239
- await mkdir(dirname(outputPath), { recursive: true, });
240
- await writeFile(outputPath, result.text, "utf-8");
238
+ await Bun.write(outputPath, result.text);
241
239
  return { path: outputPath, truncated: result.truncated, };
242
240
  }
243
241
  return result.text;
@@ -248,6 +248,13 @@ const ALLOWED_CLEANUP_ACTIONS = new Set([
248
248
  "project-library delete",
249
249
  "streaming-endpoint delete",
250
250
  "workspace delete",
251
+ "saved-model delete",
252
+ "project-folder delete",
253
+ "data-collection delete",
254
+ "plugin delete",
255
+ "connection delete",
256
+ "user delete",
257
+ "group delete",
251
258
  ]);
252
259
  export function isAllowedCleanupAction(resource, action) {
253
260
  return ALLOWED_CLEANUP_ACTIONS.has(`${resource} ${action}`);
@@ -391,7 +398,7 @@ function structuredExamples(examples, examplePayload) {
391
398
  * (`dss commands run`, `dss agent contract`) never loads the TypeBox graph.
392
399
  * The content matches `typeBoxCommandOutputSchemas()` exactly; parity and
393
400
  * freshness are enforced by tests/cli/discovery-output-schemas.test.ts and
394
- * `bun run check`.
401
+ * `bun run verify`.
395
402
  */
396
403
  const actionOutputSchemas = actionOutputSchemasMetadata
397
404
  .schemas;
@@ -764,6 +771,20 @@ const STRING_OUTPUT_ACTIONS = new Set([
764
771
  "log",
765
772
  "log-url",
766
773
  ]);
774
+ /**
775
+ * `resource.action` shapes that differ from the bare-action-name sets above:
776
+ * `contents`/`usages`/`project-timeline` are array-named elsewhere, and
777
+ * `download`/`diff` are string-named elsewhere, but these handlers return the
778
+ * opposite shape. Pinned by tests/cli/output-shapes.test.ts.
779
+ */
780
+ const SCOPED_OUTPUT_SHAPES = {
781
+ "folder.contents": "array",
782
+ "plugin.usages": "object",
783
+ "plugin.download": "object",
784
+ "dataset.download": "object",
785
+ "project-library.get": "string",
786
+ "project-library.diff": "object",
787
+ };
767
788
  function inferOutputShape(resource, action) {
768
789
  if (resource === "project-git") {
769
790
  if (action === "branches" || action === "tags" || action === "list-libraries")
@@ -777,14 +798,15 @@ function inferOutputShape(resource, action) {
777
798
  || resource === "version") {
778
799
  return "object";
779
800
  }
801
+ // Resource-scoped rules come first: the bare-name sets below classify by
802
+ // action alone and would otherwise misfile same-named actions elsewhere.
803
+ const scoped = SCOPED_OUTPUT_SHAPES[`${resource}.${action}`];
804
+ if (scoped !== undefined)
805
+ return scoped;
780
806
  if (/^list(-|$)/.test(action))
781
807
  return "array";
782
808
  if (ARRAY_OUTPUT_ACTIONS.has(action))
783
809
  return "array";
784
- if (resource === "dataset" && action === "download")
785
- return "object";
786
- if (resource === "project-library" && action === "get")
787
- return "string";
788
810
  if (STRING_OUTPUT_ACTIONS.has(action))
789
811
  return "string";
790
812
  return "object";
@@ -1400,11 +1422,10 @@ function commandRegistryJsonSchema() {
1400
1422
  function errorEnvelopeJsonSchema() {
1401
1423
  return {
1402
1424
  type: "object",
1403
- required: ["type", "ok", "error", "code", "category", "exitCode",],
1425
+ required: ["type", "error", "code", "category", "exitCode",],
1404
1426
  additionalProperties: true,
1405
1427
  properties: {
1406
1428
  type: { const: "error", },
1407
- ok: { const: false, },
1408
1429
  error: { type: "string", },
1409
1430
  code: { type: "string", },
1410
1431
  category: { enum: ["usage", "permission_or_environment", "dss", "internal",], },
@@ -1414,8 +1435,6 @@ function errorEnvelopeJsonSchema() {
1414
1435
  retryable: { type: "boolean", },
1415
1436
  requestId: { type: "string", },
1416
1437
  details: { type: "object", additionalProperties: true, },
1417
- resource: { type: "string", },
1418
- action: { type: "string", },
1419
1438
  projectKey: { type: "string", },
1420
1439
  },
1421
1440
  };
@@ -1526,7 +1545,7 @@ export function buildAgentContract() {
1526
1545
  failure: "structured-error-object",
1527
1546
  failureResultDetailLimitBytes: 65_536,
1528
1547
  fieldProjection: "Missing --fields paths: null on stdout; field_projection_missing on stderr.",
1529
- lists: "*.list: compact items; --full/--fields use DSS objects; --contains/--limit filter (list_truncated warning).",
1548
+ compactResults: "*.list, job get, dataset get/info, project get: compact; --full/--fields use DSS objects; lists take --contains/--limit (list_truncated warning).",
1530
1549
  richFailureResults: "doctor/batch/cleanup failures: own compact {ok:false,...} result on stdout and command exit code; no wrapper.",
1531
1550
  },
1532
1551
  stderr: {
@@ -1,6 +1,5 @@
1
1
  import { readFileSync, } from "node:fs";
2
- import { dirname, resolve, } from "node:path";
3
- import { fileURLToPath, } from "node:url";
2
+ import { resolve, } from "node:path";
4
3
  const projectEnvVars = new Set();
5
4
  const installEnvVars = new Set();
6
5
  /**
@@ -42,7 +41,7 @@ export function loadEnvFile() {
42
41
  // CLI. First writer wins below, so cwd must be listed first.
43
42
  const dirs = [
44
43
  process.cwd(),
45
- resolve(dirname(fileURLToPath(import.meta.url)), "..", ".."),
44
+ resolve(import.meta.dir, "..", ".."),
46
45
  ];
47
46
  for (const [index, dir,] of dirs.entries()) {
48
47
  try {
@@ -43,7 +43,7 @@ export declare function buildAppPermissionsSnapshot(projectKey: string, dssUrl:
43
43
  export declare function writeAppPermissionsSnapshot(filePath: string, snapshot: AppPermissionsSnapshot): void;
44
44
  /** Validate envelope shape, version, and hash integrity. Never echoes permission payloads. */
45
45
  export declare function parseAppPermissionsSnapshot(text: string, source: string): AppPermissionsSnapshot;
46
- export declare function readAppPermissionsSnapshot(filePath: string): AppPermissionsSnapshot;
46
+ export declare function readAppPermissionsSnapshot(filePath: string): Promise<AppPermissionsSnapshot>;
47
47
  /**
48
48
  * Refuse to use a snapshot outside the exact project *and* DSS server it was captured
49
49
  * from. Access-control rules are server-scoped: the same project key, users, and groups
@@ -153,10 +153,10 @@ export function parseAppPermissionsSnapshot(text, source) {
153
153
  permissions,
154
154
  };
155
155
  }
156
- export function readAppPermissionsSnapshot(filePath) {
156
+ export async function readAppPermissionsSnapshot(filePath) {
157
157
  let text;
158
158
  try {
159
- text = fs.readFileSync(filePath, "utf-8");
159
+ text = await fs.promises.readFile(filePath, "utf-8");
160
160
  }
161
161
  catch (error) {
162
162
  const message = error instanceof Error ? error.message : String(error);
@@ -1,3 +1,5 @@
1
1
  import type { CleanupLedgerEntry } from "../../utils/cleanup-ledger.js";
2
2
  export declare function projectArg(projectKey: string | undefined): string[];
3
+ /** Input field that identifies the created object, when the result does not. */
4
+ export declare function cleanupInputIdentifierField(resource: string, action: string): string | undefined;
3
5
  export declare function cleanupLedgerEntry(resource: string, action: string, args: string[], flags: Record<string, string | boolean>, result: unknown, projectKey: string | undefined): CleanupLedgerEntry | undefined;
@@ -35,6 +35,30 @@ function createInputFromFlags(flags) {
35
35
  return undefined;
36
36
  }
37
37
  }
38
+ /**
39
+ * Creates whose DSS response carries no identifier: the cleanup ledger pins the
40
+ * created object from this field of the `--data`/`--data-file` input instead.
41
+ * `--stdin` cannot be replayed, so the CLI rejects it with `--record-cleanup`
42
+ * before mutating.
43
+ */
44
+ const CLEANUP_INPUT_IDENTIFIER_FIELDS = {
45
+ "workspace.create": "workspaceKey",
46
+ "connection.create": "name",
47
+ "user.create": "login",
48
+ "group.create": "name",
49
+ };
50
+ /** Input field that identifies the created object, when the result does not. */
51
+ export function cleanupInputIdentifierField(resource, action) {
52
+ return CLEANUP_INPUT_IDENTIFIER_FIELDS[`${resource}.${action}`];
53
+ }
54
+ function inputIdentifier(resource, action, flags) {
55
+ const field = cleanupInputIdentifierField(resource, action);
56
+ if (!field)
57
+ return undefined;
58
+ const input = resultRecord(createInputFromFlags(flags));
59
+ const identifier = stringField(input, [field,]);
60
+ return identifier ? { identifier, input, } : undefined;
61
+ }
38
62
  export function cleanupLedgerEntry(resource, action, args, flags, result, projectKey) {
39
63
  const record = resultRecord(result);
40
64
  // A notebook save is ledger-eligible only when it created the notebook:
@@ -77,6 +101,22 @@ export function cleanupLedgerEntry(resource, action, args, flags, result, projec
77
101
  cleanup: { argv: ["dataset", "delete", name, "--if-exists", ...withProject,], },
78
102
  };
79
103
  }
104
+ case "dataset.create-managed": {
105
+ // The handler returns `created` as the dataset name and the project
106
+ // DSS created it in, which wins over the requested project key.
107
+ const name = stringField(record, ["created", "name",]) ?? flags["name"];
108
+ if (!name)
109
+ return undefined;
110
+ const landedProject = stringField(record, ["projectKey",]) ?? project;
111
+ return {
112
+ ...base,
113
+ ...(landedProject ? { projectKey: landedProject, } : {}),
114
+ name,
115
+ cleanup: {
116
+ argv: ["dataset", "delete", name, "--if-exists", ...projectArg(landedProject),],
117
+ },
118
+ };
119
+ }
80
120
  case "recipe.create": {
81
121
  const name = stringField(record, ["created", "recipeName", "name",])
82
122
  ?? flags["name"];
@@ -199,6 +239,78 @@ export function cleanupLedgerEntry(resource, action, args, flags, result, projec
199
239
  cleanup: { argv: ["ml-task", "delete", analysisId, taskId, ...withProject,], },
200
240
  };
201
241
  }
242
+ case "ml-task.create-for-dataset": {
243
+ // This create opens a new visual analysis around the task, so cleanup
244
+ // removes the whole analysis rather than leaving an empty one behind.
245
+ const analysisId = stringField(record, ["analysisId",]);
246
+ if (!analysisId)
247
+ return undefined;
248
+ return {
249
+ ...base,
250
+ id: analysisId,
251
+ cleanup: { argv: ["analysis", "delete", analysisId, "--if-exists", ...withProject,], },
252
+ };
253
+ }
254
+ case "saved-model.create-external": {
255
+ const id = stringField(record, ["id",]);
256
+ if (!id)
257
+ return undefined;
258
+ const landedProject = stringField(record, ["projectKey",]) ?? project;
259
+ return {
260
+ ...base,
261
+ ...(landedProject ? { projectKey: landedProject, } : {}),
262
+ id,
263
+ name: args[0],
264
+ cleanup: {
265
+ argv: ["saved-model", "delete", id, "--if-exists", ...projectArg(landedProject),],
266
+ },
267
+ };
268
+ }
269
+ case "project-folder.create-child": {
270
+ // `created` falls back to the requested name when DSS omits the id,
271
+ // and a name cannot address a folder delete: only `id` pins it.
272
+ const id = stringField(record, ["id",]);
273
+ if (!id)
274
+ return undefined;
275
+ return {
276
+ ...base,
277
+ id,
278
+ name: flags["name"],
279
+ cleanup: { argv: ["project-folder", "delete", id, "--if-exists",], },
280
+ };
281
+ }
282
+ case "data-collection.create": {
283
+ const id = stringField(record, ["id",]);
284
+ if (!id)
285
+ return undefined;
286
+ return {
287
+ ...base,
288
+ id,
289
+ cleanup: { argv: ["data-collection", "delete", id,], },
290
+ };
291
+ }
292
+ case "plugin.create-dev": {
293
+ const id = stringField(record, ["created",]) ?? args[0];
294
+ if (!id)
295
+ return undefined;
296
+ return {
297
+ ...base,
298
+ id,
299
+ cleanup: { argv: ["plugin", "delete", id,], },
300
+ };
301
+ }
302
+ case "connection.create":
303
+ case "user.create":
304
+ case "group.create": {
305
+ const pinned = inputIdentifier(resource, action, flags);
306
+ if (!pinned)
307
+ return undefined;
308
+ return {
309
+ ...base,
310
+ name: pinned.identifier,
311
+ cleanup: { argv: [resource, "delete", pinned.identifier, "--if-exists",], },
312
+ };
313
+ }
202
314
  case "data-quality.create-rule": {
203
315
  const ruleId = stringField(record, ["id", "created",]);
204
316
  if (!ruleId)
@@ -222,8 +334,11 @@ export function cleanupLedgerEntry(resource, action, args, flags, result, projec
222
334
  cleanup: { argv: ["code-env", "delete", lang, name, "--if-exists",], },
223
335
  };
224
336
  }
225
- case "streaming-endpoint.create": {
226
- const id = args[0];
337
+ case "streaming-endpoint.create":
338
+ case "streaming-endpoint.create-managed": {
339
+ const id = stringField(record, ["created",]) ?? args[0];
340
+ if (!id)
341
+ return undefined;
227
342
  return {
228
343
  ...base,
229
344
  id,
@@ -240,15 +355,14 @@ export function cleanupLedgerEntry(resource, action, args, flags, result, projec
240
355
  };
241
356
  }
242
357
  case "workspace.create": {
243
- const input = resultRecord(createInputFromFlags(flags));
244
- const workspaceKey = stringField(input, ["workspaceKey",]);
245
- if (!workspaceKey)
358
+ const pinned = inputIdentifier(resource, action, flags);
359
+ if (!pinned)
246
360
  return undefined;
247
361
  return {
248
362
  ...base,
249
- id: workspaceKey,
250
- name: stringField(input, ["displayName",]),
251
- cleanup: { argv: ["workspace", "delete", workspaceKey,], },
363
+ id: pinned.identifier,
364
+ name: stringField(pinned.input, ["displayName",]),
365
+ cleanup: { argv: ["workspace", "delete", pinned.identifier,], },
252
366
  };
253
367
  }
254
368
  case "project-library.create-file":
@@ -56,7 +56,7 @@ export declare function dedupeFlowZonePlanItems(items: FlowZoneItemInput[]): Flo
56
56
  export declare function flowZonePlanItemKeys(plan: FlowZoneOrganizePlan): Set<string>;
57
57
  export declare function validateUniqueFlowZoneAssignments(plan: FlowZoneOrganizePlan): void;
58
58
  export declare function parseFlowZoneOrganizePlan(input: Record<string, unknown>): FlowZoneOrganizePlan;
59
- export declare function readFlowZoneOrganizePlan(flags: Record<string, string | boolean>, usage: string): FlowZoneOrganizePlan;
59
+ export declare function readFlowZoneOrganizePlan(flags: Record<string, string | boolean>, usage: string): Promise<FlowZoneOrganizePlan>;
60
60
  export declare function findFlowZoneForPlan(zones: FlowZone[], plan: FlowZoneOrganizeZonePlan): FlowZone | undefined;
61
61
  export declare function ensureFlowZonePlanTarget(plan: FlowZoneOrganizeZonePlan, existing: FlowZone | undefined): void;
62
62
  export declare function flowZoneExplicitItems(zone: FlowZone): FlowZoneItemInput[];
@@ -1,8 +1,7 @@
1
- import { readFileSync, } from "node:fs";
2
1
  import { ClientValidationError, } from "../../errors.js";
3
2
  import { asRecord, } from "../../utils/records.js";
4
3
  import { compareStrings, } from "../../utils/stable-hash.js";
5
- import { finiteNumberField, jsonInput, optionalStringField, parseJsonObject, requiredStringArray, splitCsvFlag, } from "../coerce.js";
4
+ import { finiteNumberField, jsonInput, optionalStringField, parseJsonObject, readTextFile, requiredStringArray, splitCsvFlag, } from "../coerce.js";
6
5
  import { commandUsage, } from "../syntax.js";
7
6
  import { UsageError, } from "../usage.js";
8
7
  export function flowZoneId(value) {
@@ -257,9 +256,9 @@ export function parseFlowZoneOrganizePlan(input) {
257
256
  validateUniqueFlowZoneAssignments(plan);
258
257
  return plan;
259
258
  }
260
- export function readFlowZoneOrganizePlan(flags, usage) {
259
+ export async function readFlowZoneOrganizePlan(flags, usage) {
261
260
  const data = typeof flags["file"] === "string"
262
- ? parseJsonObject(readFileSync(flags["file"], "utf-8"), flags["file"])
261
+ ? parseJsonObject(await readTextFile(flags["file"]), flags["file"])
263
262
  : jsonInput(flags);
264
263
  if (!data) {
265
264
  throw new UsageError(`--data, --data-file, --file, or --stdin is required. Usage: ${usage}`, "missing_required_flag");
@@ -1,3 +1,7 @@
1
+ import { type CompactListField } from "./list-shapes.js";
2
+ type Item = Record<string, unknown>;
3
+ /** One compact field of a DSS list item, as `RESOURCE.list` prints it. */
4
+ export declare function compactValue(item: Item, spec: CompactListField): unknown;
1
5
  /**
2
6
  * Shape a `*.list` result for agents: `--contains` filters on identifier
3
7
  * fields, `--limit` caps the count (a `list_truncated` warning reports the
@@ -5,3 +9,4 @@
5
9
  * `--full` or `--fields` asks for the DSS objects.
6
10
  */
7
11
  export declare function shapeListResult(resource: string, result: unknown, flags: Record<string, string | boolean>): unknown;
12
+ export {};
@@ -4,10 +4,19 @@ import { enqueueCliWarning, } from "./output.js";
4
4
  function record(value) {
5
5
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6
6
  }
7
- function compactValue(item, spec) {
7
+ function valueAt(item, path) {
8
8
  let value = item;
9
- for (const key of spec.path.split("."))
9
+ for (const key of path.split("."))
10
10
  value = record(value)[key];
11
+ return value;
12
+ }
13
+ /** One compact field of a DSS list item, as `RESOURCE.list` prints it. */
14
+ export function compactValue(item, spec) {
15
+ const value = valueAt(item, spec.path);
16
+ if (value === undefined && spec.fallback
17
+ && valueAt(item, spec.fallback.when) === spec.fallback.equals) {
18
+ return spec.fallback.value;
19
+ }
11
20
  if (spec.roleRefs !== true)
12
21
  return value;
13
22
  // Dataset refs across every role of recipe inputs/outputs (`{main:{items:[{ref}]}}`).
@@ -9,6 +9,12 @@ export interface CompactListField {
9
9
  path: string;
10
10
  /** Flatten recipe roles (`{main:{items:[{ref}]}}`) to the dataset refs. */
11
11
  roleRefs?: true;
12
+ /** Value when `path` is absent and the item's `when` path equals `equals`. */
13
+ fallback?: {
14
+ when: string;
15
+ equals: unknown;
16
+ value: unknown;
17
+ };
12
18
  schema: Record<string, unknown>;
13
19
  }
14
20
  /**
@@ -51,7 +51,12 @@ export const COMPACT_LIST_FIELDS = {
51
51
  id: field("def.id", STRING),
52
52
  name: field("def.name", STRING),
53
53
  type: field("def.type", STRING),
54
- state: field("state", STRING),
54
+ // DSS lists in-progress jobs with `stableState: false` and no `state`
55
+ // (COMPUTING_DEPS or RUNNING); `job get` reports the exact phase.
56
+ state: {
57
+ ...field("state", STRING),
58
+ fallback: { when: "stableState", equals: false, value: "RUNNING", },
59
+ },
55
60
  startTime: field("startTime", NUMBER),
56
61
  endTime: field("endTime", NUMBER),
57
62
  },
@@ -83,6 +88,7 @@ export const COMPACT_LIST_FIELDS = {
83
88
  id: field("id", STRING),
84
89
  version: field("version", STRING),
85
90
  label: field("meta.label", STRING),
91
+ isDev: field("isDev", BOOLEAN),
86
92
  },
87
93
  macro: {
88
94
  runnableType: field("runnableType", STRING),
@@ -3,7 +3,7 @@
3
3
  * the single source of truth for `src/generated/action-output-schemas.json`,
4
4
  * which discovery reads as plain data so no TypeBox schema graph loads on the
5
5
  * discovery path. Regenerate with
6
- * `bun scripts/generate-action-output-schemas.mjs`; `bun run check` fails when
6
+ * `bun scripts/generate-action-output-schemas.mjs`; `bun run verify` fails when
7
7
  * the generated file is stale. SDK validation keeps using the TypeBox schema
8
8
  * objects from `packages/types` directly and never depends on the generated file.
9
9
  */