dataiku-sdk 0.12.4 → 0.12.6

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 (86) hide show
  1. package/README.md +119 -7
  2. package/dist/packages/types/src/index.d.ts +5 -0
  3. package/dist/packages/types/src/index.js +5 -0
  4. package/dist/src/cli/coerce.d.ts +1 -3
  5. package/dist/src/cli/coerce.js +1 -17
  6. package/dist/src/cli/commands/app.js +709 -14
  7. package/dist/src/cli/commands/code-env.js +2 -2
  8. package/dist/src/cli/commands/dashboard.js +1 -1
  9. package/dist/src/cli/commands/dataset.js +2 -2
  10. package/dist/src/cli/commands/folder.js +1 -1
  11. package/dist/src/cli/commands/insight.js +2 -2
  12. package/dist/src/cli/commands/notebook.js +2 -2
  13. package/dist/src/cli/commands/recipe.js +3 -3
  14. package/dist/src/cli/commands/scenario.js +1 -1
  15. package/dist/src/cli/commands/sql.js +17 -2
  16. package/dist/src/cli/commands/variable.js +1 -1
  17. package/dist/src/cli/commands/wiki.js +1 -1
  18. package/dist/src/cli/contract.d.ts +12 -3
  19. package/dist/src/cli/contract.js +805 -49
  20. package/dist/src/cli/flags.js +9 -0
  21. package/dist/src/cli/helpers/app-permissions.d.ts +63 -0
  22. package/dist/src/cli/helpers/app-permissions.js +239 -0
  23. package/dist/src/cli/helpers/app-successor.d.ts +106 -0
  24. package/dist/src/cli/helpers/app-successor.js +625 -0
  25. package/dist/src/cli/helpers/cleanup.js +46 -12
  26. package/dist/src/cli/output.d.ts +12 -1
  27. package/dist/src/cli/output.js +53 -6
  28. package/dist/src/cli/usage.d.ts +1 -1
  29. package/dist/src/cli/usage.js +5 -2
  30. package/dist/src/cli.js +329 -32
  31. package/dist/src/client.d.ts +13 -1
  32. package/dist/src/client.js +46 -6
  33. package/dist/src/errors.d.ts +1 -1
  34. package/dist/src/resources/applications.d.ts +191 -1
  35. package/dist/src/resources/applications.js +549 -10
  36. package/dist/src/resources/datasets.js +8 -0
  37. package/dist/src/resources/futures.js +7 -2
  38. package/dist/src/resources/sql.d.ts +5 -0
  39. package/dist/src/resources/sql.js +5 -4
  40. package/dist/src/skill.js +78 -19
  41. package/dist/src/utils/cleanup-ledger.d.ts +34 -1
  42. package/dist/src/utils/cleanup-ledger.js +118 -7
  43. package/dist/src/utils/dss-url.d.ts +6 -0
  44. package/dist/src/utils/dss-url.js +17 -0
  45. package/dist/src/utils/project-incarnation.d.ts +6 -0
  46. package/dist/src/utils/project-incarnation.js +27 -0
  47. package/dist/src/utils/stable-hash.d.ts +11 -0
  48. package/dist/src/utils/stable-hash.js +27 -0
  49. package/node_modules/@sinclair/typebox/build/cjs/compiler/compiler.js +26 -46
  50. package/node_modules/@sinclair/typebox/build/cjs/parser/runtime/guard.d.ts +1 -1
  51. package/node_modules/@sinclair/typebox/build/cjs/parser/runtime/token.d.ts +2 -2
  52. package/node_modules/@sinclair/typebox/build/cjs/syntax/mapping.d.ts +1 -1
  53. package/node_modules/@sinclair/typebox/build/cjs/system/evaluate.d.ts +5 -0
  54. package/node_modules/@sinclair/typebox/build/cjs/system/evaluate.js +11 -0
  55. package/node_modules/@sinclair/typebox/build/cjs/system/index.d.ts +1 -0
  56. package/node_modules/@sinclair/typebox/build/cjs/system/index.js +1 -0
  57. package/node_modules/@sinclair/typebox/build/cjs/type/object/object.d.ts +2 -2
  58. package/node_modules/@sinclair/typebox/build/cjs/type/object/object.js +18 -2
  59. package/node_modules/@sinclair/typebox/build/cjs/value/parse/parse.d.ts +1 -1
  60. package/node_modules/@sinclair/typebox/build/esm/compiler/compiler.mjs +27 -47
  61. package/node_modules/@sinclair/typebox/build/esm/parser/runtime/guard.d.mts +1 -1
  62. package/node_modules/@sinclair/typebox/build/esm/parser/runtime/token.d.mts +2 -2
  63. package/node_modules/@sinclair/typebox/build/esm/syntax/mapping.d.mts +1 -1
  64. package/node_modules/@sinclair/typebox/build/esm/system/evaluate.d.mts +5 -0
  65. package/node_modules/@sinclair/typebox/build/esm/system/evaluate.mjs +7 -0
  66. package/node_modules/@sinclair/typebox/build/esm/system/index.d.mts +1 -0
  67. package/node_modules/@sinclair/typebox/build/esm/system/index.mjs +1 -0
  68. package/node_modules/@sinclair/typebox/build/esm/type/composite/composite.mjs +2 -2
  69. package/node_modules/@sinclair/typebox/build/esm/type/const/const.mjs +3 -3
  70. package/node_modules/@sinclair/typebox/build/esm/type/instantiate/instantiate.mjs +2 -2
  71. package/node_modules/@sinclair/typebox/build/esm/type/mapped/mapped.mjs +3 -3
  72. package/node_modules/@sinclair/typebox/build/esm/type/module/compute.mjs +2 -2
  73. package/node_modules/@sinclair/typebox/build/esm/type/object/object.d.mts +2 -2
  74. package/node_modules/@sinclair/typebox/build/esm/type/object/object.mjs +18 -2
  75. package/node_modules/@sinclair/typebox/build/esm/type/omit/omit.mjs +3 -3
  76. package/node_modules/@sinclair/typebox/build/esm/type/partial/partial.mjs +3 -3
  77. package/node_modules/@sinclair/typebox/build/esm/type/pick/pick.mjs +3 -3
  78. package/node_modules/@sinclair/typebox/build/esm/type/record/record.mjs +3 -3
  79. package/node_modules/@sinclair/typebox/build/esm/type/required/required.mjs +3 -3
  80. package/node_modules/@sinclair/typebox/build/esm/type/type/json.mjs +2 -2
  81. package/node_modules/@sinclair/typebox/build/esm/value/delta/delta.mjs +4 -4
  82. package/node_modules/@sinclair/typebox/build/esm/value/parse/parse.d.mts +1 -1
  83. package/node_modules/@sinclair/typebox/package.json +1 -1
  84. package/package.json +9 -5
  85. package/packages/types/dist/index.d.ts +5 -0
  86. package/packages/types/dist/index.js +5 -0
@@ -27,6 +27,7 @@ export const BOOLEAN_FLAGS = new Set([
27
27
  "force-rebuild",
28
28
  "latest",
29
29
  "copy-output-settings",
30
+ "copy-permissions",
30
31
  "continue-on-error",
31
32
  "no-backup",
32
33
  "payload-only",
@@ -38,6 +39,7 @@ export const BOOLEAN_FLAGS = new Set([
38
39
  "full-log",
39
40
  "drop-data",
40
41
  "normalize",
42
+ "unconfirmed-creation",
41
43
  ]);
42
44
  export const SHORT_FLAGS = {
43
45
  v: "verbose",
@@ -82,6 +84,9 @@ export const VALUE_FLAGS = new Set([
82
84
  "active",
83
85
  "deployment-mode",
84
86
  "env-version",
87
+ "expect-hash",
88
+ "expect-project-incarnation",
89
+ "expect-version",
85
90
  "data-file",
86
91
  "database",
87
92
  "dataset",
@@ -104,6 +109,7 @@ export const VALUE_FLAGS = new Set([
104
109
  "package",
105
110
  "packages",
106
111
  "local",
112
+ "manifest-version",
107
113
  "max-edges",
108
114
  "max-lines",
109
115
  "max-log-lines",
@@ -142,6 +148,7 @@ export const VALUE_FLAGS = new Set([
142
148
  "rule-id",
143
149
  "role",
144
150
  "retries",
151
+ "future-id",
145
152
  "poll-interval",
146
153
  "python-interpreter",
147
154
  "replace-input",
@@ -153,6 +160,7 @@ export const VALUE_FLAGS = new Set([
153
160
  "sql",
154
161
  "schema",
155
162
  "sql-file",
163
+ "start-retries",
156
164
  "standard",
157
165
  "state",
158
166
  "streaming-endpoint",
@@ -166,6 +174,7 @@ export const VALUE_FLAGS = new Set([
166
174
  "type",
167
175
  "url",
168
176
  "until",
177
+ "version-notes",
169
178
  "to",
170
179
  "zone",
171
180
  "zone-id",
@@ -0,0 +1,63 @@
1
+ import type { ProjectPermissions } from "../../resources/projects.js";
2
+ import { UsageError } from "../usage.js";
3
+ /**
4
+ * Version 3 binds every snapshot to both its DSS server and the concrete project
5
+ * incarnation present at capture time. Earlier versions are refused.
6
+ */
7
+ export declare const APP_PERMISSIONS_SNAPSHOT_VERSION = 3;
8
+ export interface AppPermissionsSnapshot {
9
+ version: number;
10
+ projectKey: string;
11
+ /** Canonical base URL of the DSS server these rules were read from. */
12
+ dssUrl: string;
13
+ /** Hash of the concrete project incarnation these rules were read from. */
14
+ projectIncarnationHash: string;
15
+ capturedAt: string;
16
+ hash: string;
17
+ permissionsHash: string;
18
+ permissions: ProjectPermissions;
19
+ }
20
+ export type AppPermissionsDifferenceStatus = "added" | "removed" | "changed";
21
+ export interface AppPermissionsDifference {
22
+ path: string;
23
+ status: AppPermissionsDifferenceStatus;
24
+ backup?: unknown;
25
+ current?: unknown;
26
+ }
27
+ export interface AppPermissionsDiff {
28
+ changed: boolean;
29
+ differences: AppPermissionsDifference[];
30
+ }
31
+ /**
32
+ * Canonical hash of a permissions payload. Key order and unknown DSS fields are
33
+ * normalized by `stableJson`, so re-serializing a snapshot never changes the hash.
34
+ */
35
+ export declare function appPermissionsHash(permissions: unknown): string;
36
+ export declare function buildAppPermissionsSnapshot(projectKey: string, dssUrl: string, projectIncarnationHash: string, permissions: ProjectPermissions, capturedAt?: string): AppPermissionsSnapshot;
37
+ /**
38
+ * Write a snapshot as owner-only JSON. Access-control rules must never sit on disk
39
+ * under wider permissions: `writeFileSync`'s `mode` is ignored for an existing file
40
+ * and a trailing `chmod` would publish the payload first, so the descriptor is
41
+ * clamped to 0600 before the file is truncated or written.
42
+ */
43
+ export declare function writeAppPermissionsSnapshot(filePath: string, snapshot: AppPermissionsSnapshot): void;
44
+ /** Validate envelope shape, version, and hash integrity. Never echoes permission payloads. */
45
+ export declare function parseAppPermissionsSnapshot(text: string, source: string): AppPermissionsSnapshot;
46
+ export declare function readAppPermissionsSnapshot(filePath: string): AppPermissionsSnapshot;
47
+ /**
48
+ * Refuse to use a snapshot outside the exact project *and* DSS server it was captured
49
+ * from. Access-control rules are server-scoped: the same project key, users, and groups
50
+ * exist on other instances with different meaning, so replaying a snapshot elsewhere can
51
+ * silently grant or revoke access. Runs before any live permission read or write.
52
+ */
53
+ export declare function assertAppPermissionsSnapshotBinding(snapshot: AppPermissionsSnapshot, targetProjectKey: string, currentDssUrl: string, currentProjectIncarnationHash?: string): void;
54
+ export declare function appPermissionsVerificationError(projectKey: string, hashes: {
55
+ beforeHash: string;
56
+ desiredHash: string;
57
+ verifiedHash: string;
58
+ }): UsageError;
59
+ /**
60
+ * Deterministic, path-addressed differences between a snapshot payload and live
61
+ * permissions. Paths are sorted so repeated runs emit byte-identical output.
62
+ */
63
+ export declare function diffAppPermissions(backup: unknown, current: unknown): AppPermissionsDiff;
@@ -0,0 +1,239 @@
1
+ import * as fs from "node:fs";
2
+ import { canonicalDssUrl, } from "../../utils/dss-url.js";
3
+ import { compareStrings, } from "../../utils/stable-hash.js";
4
+ import { parseJsonObject, plainRecord, stableHash, stableJson, } from "../coerce.js";
5
+ import { UsageError, } from "../usage.js";
6
+ /**
7
+ * Version 3 binds every snapshot to both its DSS server and the concrete project
8
+ * incarnation present at capture time. Earlier versions are refused.
9
+ */
10
+ export const APP_PERMISSIONS_SNAPSHOT_VERSION = 3;
11
+ /** Snapshots hold access-control rules: keep them owner-only on disk. */
12
+ const SNAPSHOT_FILE_MODE = 0o600;
13
+ /**
14
+ * Open for writing *without* `O_TRUNC`: truncation must wait until the descriptor
15
+ * has been narrowed to owner-only. `O_NOFOLLOW`, where the platform defines it,
16
+ * refuses a symlinked destination so a hostile link cannot redirect the snapshot —
17
+ * or the permission change itself — onto another file.
18
+ */
19
+ const SNAPSHOT_OPEN_FLAGS = fs.constants.O_WRONLY | fs.constants.O_CREAT
20
+ | (typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0);
21
+ /**
22
+ * Canonical hash of a permissions payload. Key order and unknown DSS fields are
23
+ * normalized by `stableJson`, so re-serializing a snapshot never changes the hash.
24
+ */
25
+ export function appPermissionsHash(permissions) {
26
+ return stableHash(permissions);
27
+ }
28
+ export function buildAppPermissionsSnapshot(projectKey, dssUrl, projectIncarnationHash, permissions, capturedAt = new Date().toISOString()) {
29
+ const version = APP_PERMISSIONS_SNAPSHOT_VERSION;
30
+ const boundUrl = canonicalDssUrl(dssUrl);
31
+ if (boundUrl.length === 0) {
32
+ throw new UsageError("Cannot capture an app permissions snapshot without the DSS server URL it was read from.", "validation_failed");
33
+ }
34
+ if (!/^[0-9a-f]{64}$/.test(projectIncarnationHash)) {
35
+ throw new UsageError("Cannot capture an app permissions snapshot without a valid project-incarnation hash.", "validation_failed");
36
+ }
37
+ const permissionsHash = appPermissionsHash(permissions);
38
+ const hash = stableHash({
39
+ version,
40
+ projectKey,
41
+ dssUrl: boundUrl,
42
+ projectIncarnationHash,
43
+ capturedAt,
44
+ permissionsHash,
45
+ permissions,
46
+ });
47
+ return {
48
+ version,
49
+ projectKey,
50
+ dssUrl: boundUrl,
51
+ projectIncarnationHash,
52
+ capturedAt,
53
+ hash,
54
+ permissionsHash,
55
+ permissions,
56
+ };
57
+ }
58
+ /** Drain the payload: one `writeSync` may cover only part of the buffer. */
59
+ function writeAllSync(fd, payload) {
60
+ let offset = 0;
61
+ while (offset < payload.length) {
62
+ const written = fs.writeSync(fd, payload, offset, payload.length - offset, offset);
63
+ if (written <= 0) {
64
+ throw new Error(`Stalled writing app permissions snapshot at byte ${offset}.`);
65
+ }
66
+ offset += written;
67
+ }
68
+ }
69
+ /**
70
+ * Write a snapshot as owner-only JSON. Access-control rules must never sit on disk
71
+ * under wider permissions: `writeFileSync`'s `mode` is ignored for an existing file
72
+ * and a trailing `chmod` would publish the payload first, so the descriptor is
73
+ * clamped to 0600 before the file is truncated or written.
74
+ */
75
+ export function writeAppPermissionsSnapshot(filePath, snapshot) {
76
+ // Serialize before touching the filesystem: a serialization fault must not
77
+ // truncate or half-overwrite an existing snapshot.
78
+ const payload = Buffer.from(`${JSON.stringify(snapshot, null, 2)}\n`, "utf-8");
79
+ const fd = fs.openSync(filePath, SNAPSHOT_OPEN_FLAGS, SNAPSHOT_FILE_MODE);
80
+ let wrote = false;
81
+ try {
82
+ fs.fchmodSync(fd, SNAPSHOT_FILE_MODE);
83
+ fs.ftruncateSync(fd, 0);
84
+ writeAllSync(fd, payload);
85
+ wrote = true;
86
+ }
87
+ finally {
88
+ if (wrote) {
89
+ fs.closeSync(fd);
90
+ }
91
+ else {
92
+ try {
93
+ fs.closeSync(fd);
94
+ }
95
+ catch {
96
+ // Keep the original failure: a close fault must not mask it.
97
+ }
98
+ }
99
+ }
100
+ }
101
+ function requiredSnapshotString(record, field, source) {
102
+ const value = record[field];
103
+ if (typeof value !== "string" || value.trim().length === 0) {
104
+ throw new UsageError(`${source} is not an app permissions snapshot: "${field}" must be a non-empty string.`, "validation_failed");
105
+ }
106
+ return value;
107
+ }
108
+ /** Validate envelope shape, version, and hash integrity. Never echoes permission payloads. */
109
+ export function parseAppPermissionsSnapshot(text, source) {
110
+ const record = parseJsonObject(text, source);
111
+ const version = record["version"];
112
+ if (typeof version !== "number" || !Number.isInteger(version)) {
113
+ throw new UsageError(`${source} is not an app permissions snapshot: "version" must be an integer.`, "validation_failed");
114
+ }
115
+ if (version !== APP_PERMISSIONS_SNAPSHOT_VERSION) {
116
+ throw new UsageError(`${source} has unsupported snapshot version ${String(version)} (expected ${String(APP_PERMISSIONS_SNAPSHOT_VERSION)}).`, "validation_failed", version < APP_PERMISSIONS_SNAPSHOT_VERSION
117
+ ? "Older snapshots are not bound to the concrete project incarnation they came from, so they cannot be applied safely after project-key reuse. Re-run `dss app permissions-snapshot` against that project to capture a current snapshot."
118
+ : undefined);
119
+ }
120
+ const projectKey = requiredSnapshotString(record, "projectKey", source);
121
+ const dssUrl = requiredSnapshotString(record, "dssUrl", source);
122
+ const projectIncarnationHash = requiredSnapshotString(record, "projectIncarnationHash", source);
123
+ const capturedAt = requiredSnapshotString(record, "capturedAt", source);
124
+ const hash = requiredSnapshotString(record, "hash", source);
125
+ const permissionsHash = requiredSnapshotString(record, "permissionsHash", source);
126
+ const permissions = plainRecord(record["permissions"]);
127
+ if (!permissions) {
128
+ throw new UsageError(`${source} is not an app permissions snapshot: "permissions" must be a JSON object.`, "validation_failed");
129
+ }
130
+ const actualPermissionsHash = appPermissionsHash(permissions);
131
+ const actualHash = stableHash({
132
+ version,
133
+ projectKey,
134
+ dssUrl,
135
+ projectIncarnationHash,
136
+ capturedAt,
137
+ permissionsHash,
138
+ permissions,
139
+ });
140
+ if (actualPermissionsHash !== permissionsHash || actualHash !== hash) {
141
+ throw new UsageError(`${source} failed its integrity check. Refusing to use a corrupted or hand-edited snapshot.`, "validation_failed", "Re-run `dss app permissions-snapshot` to capture a fresh snapshot.");
142
+ }
143
+ return {
144
+ version,
145
+ projectKey,
146
+ dssUrl,
147
+ projectIncarnationHash,
148
+ capturedAt,
149
+ hash,
150
+ permissionsHash,
151
+ permissions,
152
+ };
153
+ }
154
+ export function readAppPermissionsSnapshot(filePath) {
155
+ let text;
156
+ try {
157
+ text = fs.readFileSync(filePath, "utf-8");
158
+ }
159
+ catch (error) {
160
+ const message = error instanceof Error ? error.message : String(error);
161
+ throw new UsageError(`Cannot read app permissions snapshot ${filePath}: ${message}`, "validation_failed");
162
+ }
163
+ return parseAppPermissionsSnapshot(text, filePath);
164
+ }
165
+ /**
166
+ * Refuse to use a snapshot outside the exact project *and* DSS server it was captured
167
+ * from. Access-control rules are server-scoped: the same project key, users, and groups
168
+ * exist on other instances with different meaning, so replaying a snapshot elsewhere can
169
+ * silently grant or revoke access. Runs before any live permission read or write.
170
+ */
171
+ export function assertAppPermissionsSnapshotBinding(snapshot, targetProjectKey, currentDssUrl, currentProjectIncarnationHash) {
172
+ if (snapshot.projectKey !== targetProjectKey) {
173
+ throw new UsageError(`Snapshot was captured from project ${snapshot.projectKey} but the target project is ${targetProjectKey}. Refusing to restore permissions across projects.`, "validation_failed", `Pass --project-key ${snapshot.projectKey} to restore the project the snapshot came from.`);
174
+ }
175
+ const bound = canonicalDssUrl(snapshot.dssUrl);
176
+ const current = canonicalDssUrl(currentDssUrl);
177
+ if (bound !== current) {
178
+ throw new UsageError(`Snapshot was captured from DSS server ${bound} but the configured server is ${current}. Refusing to use permissions across DSS servers.`, "validation_failed", `Point the CLI at ${bound} to use this snapshot, or capture a fresh snapshot from ${current}.`, { projectKey: snapshot.projectKey, snapshotDssUrl: bound, currentDssUrl: current, });
179
+ }
180
+ if (currentProjectIncarnationHash === undefined)
181
+ return;
182
+ if (snapshot.projectIncarnationHash === currentProjectIncarnationHash)
183
+ return;
184
+ throw new UsageError(`Snapshot was captured from a different incarnation of project ${targetProjectKey}. Refusing to use permissions after project-key reuse.`, "validation_failed", "Capture a fresh permissions snapshot from the current project. Do not restore access-control data from the replaced project.", {
185
+ projectKey: targetProjectKey,
186
+ snapshotProjectIncarnationHash: snapshot.projectIncarnationHash,
187
+ currentProjectIncarnationHash,
188
+ });
189
+ }
190
+ export function appPermissionsVerificationError(projectKey, hashes) {
191
+ return new UsageError(`Permission restore verification failed for project ${projectKey}: expected hash ${hashes.desiredHash} after the update but DSS returned ${hashes.verifiedHash}.`, "validation_failed", "Re-read the live permissions with `dss app permissions-diff` before retrying; DSS may have rejected or rewritten part of the payload.", { projectKey, ...hashes, });
192
+ }
193
+ function differenceStatus(backup, current) {
194
+ if (backup === undefined)
195
+ return "added";
196
+ if (current === undefined)
197
+ return "removed";
198
+ return "changed";
199
+ }
200
+ function pushDifference(out, path, backup, current) {
201
+ out.push({
202
+ path,
203
+ status: differenceStatus(backup, current),
204
+ ...(backup === undefined ? {} : { backup, }),
205
+ ...(current === undefined ? {} : { current, }),
206
+ });
207
+ }
208
+ function collectDifferences(path, backup, current, out) {
209
+ if (stableJson(backup) === stableJson(current))
210
+ return;
211
+ const backupRecord = plainRecord(backup);
212
+ const currentRecord = plainRecord(current);
213
+ if (backupRecord && currentRecord) {
214
+ const keys = [...new Set([...Object.keys(backupRecord), ...Object.keys(currentRecord),]),]
215
+ .sort(compareStrings);
216
+ for (const key of keys) {
217
+ collectDifferences(path ? `${path}.${key}` : key, backupRecord[key], currentRecord[key], out);
218
+ }
219
+ return;
220
+ }
221
+ if (Array.isArray(backup) && Array.isArray(current)) {
222
+ const length = Math.max(backup.length, current.length);
223
+ for (let index = 0; index < length; index += 1) {
224
+ collectDifferences(`${path}[${String(index)}]`, backup[index], current[index], out);
225
+ }
226
+ return;
227
+ }
228
+ pushDifference(out, path || "(root)", backup, current);
229
+ }
230
+ /**
231
+ * Deterministic, path-addressed differences between a snapshot payload and live
232
+ * permissions. Paths are sorted so repeated runs emit byte-identical output.
233
+ */
234
+ export function diffAppPermissions(backup, current) {
235
+ const differences = [];
236
+ collectDifferences("", backup, current, differences);
237
+ differences.sort((a, b) => compareStrings(a.path, b.path));
238
+ return { changed: differences.length > 0, differences, };
239
+ }
@@ -0,0 +1,106 @@
1
+ import type { DataikuClient } from "../../client.js";
2
+ import type { AppManifestValidationResult } from "../../resources/applications.js";
3
+ /**
4
+ * A DSS API key authenticates the public REST API only: the Application UI sits
5
+ * behind the interactive (often SSO) session, so no command in this SDK can
6
+ * observe it. Every app-release result therefore carries this gate explicitly
7
+ * instead of implying that a green API check means the UI works.
8
+ */
9
+ export declare const VISUAL_UI_GATE: {
10
+ readonly gate: "visual-ui";
11
+ readonly status: "required";
12
+ readonly reason: "API key authenticates public REST only; use a pre-authenticated SSO browser session or dedicated UI test identity.";
13
+ readonly requiredAuthentication: "pre-authenticated-sso-or-dedicated-ui-test-identity";
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
+ };
16
+ export type AppInstanceCheckName = "project-type" | "app-registration" | "manifest-version" | "manifest-references";
17
+ export interface AppInstanceCheck {
18
+ check: AppInstanceCheckName;
19
+ status: "ok" | "failed";
20
+ expected: unknown;
21
+ actual: unknown;
22
+ /** Machine-readable cause when the compared values alone do not explain a failure. */
23
+ reason?: string;
24
+ }
25
+ export interface AppManifestReferenceSummary {
26
+ valid: boolean;
27
+ manifestHash: string;
28
+ checks: AppManifestValidationResult["checks"];
29
+ errors: AppManifestValidationResult["errors"];
30
+ }
31
+ export interface AppInstanceVerification {
32
+ appId: string;
33
+ projectKey: string;
34
+ projectAppType: string | null;
35
+ registered: boolean;
36
+ templateVersion: string | null;
37
+ expectedVersion: string | null;
38
+ expectedVersionSource: "expect-version" | "template-manifest";
39
+ instanceVersion: string | null;
40
+ instanceManifestHash: string | null;
41
+ templateManifestHash: string;
42
+ referenceValidation: AppManifestReferenceSummary | null;
43
+ checks: AppInstanceCheck[];
44
+ valid: boolean;
45
+ }
46
+ export interface SuccessorInstanceRequest {
47
+ appId: string;
48
+ from: string;
49
+ to: string;
50
+ name?: string;
51
+ copyPermissions: boolean;
52
+ dryRun: boolean;
53
+ timeoutMs?: number;
54
+ pollIntervalMs?: number;
55
+ }
56
+ /** Never echoes response bodies: only the taxonomy DataikuError already deems safe. */
57
+ export declare function safeErrorSummary(error: unknown): Record<string, unknown>;
58
+ /**
59
+ * Read-only API readiness of one app instance. Only checks that the public REST
60
+ * API can answer authoritatively are performed; the UI gate stays external.
61
+ *
62
+ * The instance manifest is read exactly once: its version, hash and reference
63
+ * validation all describe that one snapshot, so a concurrent manifest edit can
64
+ * never produce a report whose fields contradict each other.
65
+ */
66
+ export declare function verifyAppInstance(client: DataikuClient, appId: string, projectKey: string, expectVersion?: string): Promise<AppInstanceVerification>;
67
+ /** Stage a permission copy reached. Only `copied`/`unchanged` mean the successor holds them. */
68
+ export type AppPermissionCopyState = "not-requested" | "unchanged" | "copied" | "not-attempted" | "rejected" | "unknown" | "completed-unverified";
69
+ /**
70
+ * True only when DSS definitively refused a write: a non-retryable 4xx with a
71
+ * classified client-side taxonomy. Transport failures (status 0), 5xx, retryable
72
+ * 4xx (408/425/429) and unclassified statuses leave the outcome unknown, so the
73
+ * effect of the request must be assumed possible.
74
+ */
75
+ export declare function isDefinitiveRejection(error: unknown): boolean;
76
+ /**
77
+ * Successor identity as reported by the creation future's own result.
78
+ *
79
+ * DSS names the project it created in `targetProjectKey`, and live instances of
80
+ * this future are also observed carrying `projectKey` for the *source template*,
81
+ * so the target-specific field always wins and `projectKey` speaks for the
82
+ * target only when no usable `targetProjectKey` is present. A missing, blank or
83
+ * non-string value is no identity claim at all, exactly as a blank manifest
84
+ * version names no release.
85
+ */
86
+ export declare function futureTargetIdentity(result: unknown): {
87
+ projectKey: string;
88
+ field: "targetProjectKey" | "projectKey";
89
+ } | null;
90
+ /**
91
+ * Creation cleanup is safe only when the target was proven absent before POST.
92
+ * A hidden 403 is not absence: project listing can prove a collision, but it
93
+ * cannot prove that an inaccessible project does not exist.
94
+ */
95
+ export declare function requireAbsentProjectTarget(client: DataikuClient, targetProjectKey: string, targetFlag: "--to" | "targetProjectKey"): Promise<void>;
96
+ /**
97
+ * Create a new app instance from the current template version alongside an
98
+ * existing instance. Classic app instances have no public upgrade endpoint, so
99
+ * the predecessor is never touched: it stays available until it is separately
100
+ * and deliberately retired.
101
+ *
102
+ * After the creation POST this function never throws. A post-POST failure
103
+ * returns a failed-wait result carrying the target project key, so the CLI still
104
+ * exits 4 and the cleanup ledger still records an addressable successor.
105
+ */
106
+ export declare function createSuccessorInstance(client: DataikuClient, request: SuccessorInstanceRequest, usage: string): Promise<Record<string, unknown>>;