@camunda/ccma-shared-types 1.2.0 → 3.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 (59) hide show
  1. package/dist/_tests/permissions/session-permissions.spec.d.ts +1 -0
  2. package/dist/approvals/policy.d.ts +94 -5
  3. package/dist/approvals/policy.js +64 -14
  4. package/dist/approvals/policy.js.map +2 -2
  5. package/dist/approvals/use-cases.const.d.ts +41 -0
  6. package/dist/approvals/use-cases.const.js +12 -0
  7. package/dist/approvals/use-cases.const.js.map +7 -0
  8. package/dist/config/camundi-permission-rules.js +7 -1
  9. package/dist/config/camundi-permission-rules.js.map +2 -2
  10. package/dist/constants/version-gates.const.d.ts +9 -2
  11. package/dist/constants/version-gates.const.js +1 -1
  12. package/dist/constants/version-gates.const.js.map +2 -2
  13. package/dist/dtos/AlertSubscription.dto.d.ts +6 -0
  14. package/dist/dtos/AlertSubscription.dto.js.map +2 -2
  15. package/dist/dtos/ApprovalJob.dto.d.ts +258 -4
  16. package/dist/dtos/ApprovalJob.dto.js +42 -1
  17. package/dist/dtos/ApprovalJob.dto.js.map +2 -2
  18. package/dist/dtos/ClusterCreationDefaults.dto.d.ts +96 -0
  19. package/dist/dtos/ClusterCreationDefaults.dto.js +93 -0
  20. package/dist/dtos/ClusterCreationDefaults.dto.js.map +7 -0
  21. package/dist/dtos/ClusterOwnerMove.dto.d.ts +81 -0
  22. package/dist/dtos/ClusterOwnerMove.dto.js +1 -0
  23. package/dist/dtos/ClusterOwnerMove.dto.js.map +7 -0
  24. package/dist/dtos/CommonCluster.dto.d.ts +1 -0
  25. package/dist/dtos/ControlV2.dto.d.ts +25 -0
  26. package/dist/dtos/FeatureFlags.dto.d.ts +3 -3
  27. package/dist/dtos/Generations.dto.d.ts +17 -0
  28. package/dist/dtos/Generations.dto.js +7 -0
  29. package/dist/dtos/Generations.dto.js.map +2 -2
  30. package/dist/dtos/Generations.dto.spec.d.ts +1 -0
  31. package/dist/dtos/Organization.dto.d.ts +9 -0
  32. package/dist/dtos/Organization.dto.js.map +2 -2
  33. package/dist/dtos/ReleaseTrainSanity.dto.d.ts +6 -0
  34. package/dist/enums/AuditEntity.enum.d.ts +2 -0
  35. package/dist/enums/AuditEntity.enum.js +2 -0
  36. package/dist/enums/AuditEntity.enum.js.map +2 -2
  37. package/dist/enums/SystemAuditEntity.enum.d.ts +1 -0
  38. package/dist/enums/SystemAuditEntity.enum.js +1 -0
  39. package/dist/enums/SystemAuditEntity.enum.js.map +2 -2
  40. package/dist/index.d.ts +12 -7
  41. package/dist/index.js +53 -3
  42. package/dist/index.js.map +2 -2
  43. package/dist/permissions/combo.d.ts +9 -0
  44. package/dist/permissions/combo.js +57 -1
  45. package/dist/permissions/combo.js.map +2 -2
  46. package/dist/permissions/combo.optionalTelemetry.spec.d.ts +1 -0
  47. package/dist/permissions/console.permissions.d.ts +2 -0
  48. package/dist/permissions/console.permissions.js +13 -1
  49. package/dist/permissions/console.permissions.js.map +2 -2
  50. package/dist/types/ClusterBulkOperation.type.d.ts +10 -2
  51. package/dist/types/ClusterPlanTypeAvailabilityClass.type.d.ts +11 -0
  52. package/dist/types/ClusterPlanTypeAvailabilityClass.type.js +4 -0
  53. package/dist/types/ClusterPlanTypeAvailabilityClass.type.js.map +2 -2
  54. package/dist/types/Roles.d.ts +1 -1
  55. package/dist/types/Roles.js.map +2 -2
  56. package/dist/utils/versionCheck.utils.d.ts +48 -0
  57. package/dist/utils/versionCheck.utils.js +50 -4
  58. package/dist/utils/versionCheck.utils.js.map +2 -2
  59. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/dtos/AlertSubscription.dto.ts"],
4
- "sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nimport type {\n AlertSubscriptionReportType,\n AlertSubscriptionType,\n} from '../enums/AlertSubscription.enum.js'\n\nexport interface AlertSubscriptionDto {\n uuid: string\n userIdOrWebhook: string\n type: AlertSubscriptionType\n report: AlertSubscriptionReportType\n webhookAuth?: AlertSubscriptionWebhookAuth\n createdBy: string\n createdByName: string\n created: Date\n updatedBy: string\n updatedByName: string\n updated: Date\n lastTimeTriggered?: Date\n numberOfTimesTriggered: number\n}\n\nexport interface AlertSubscriptionEntryDto {\n errorMessage?: string\n errorType?: string\n flowNodeId?: string\n jobKey?: string\n creationTime?: string\n processInstanceId?: string\n processName?: string\n processVersion?: number\n bpmnProcessId?: string\n}\n\nexport const HMAC_SUPPORTED_ALGORITHM = 'sha256'\nexport const HMAC_HEADER_POSITION = 'X-Camunda-Signature-256'\n\n/**\n * Webhook targets a customer may not point an alert at, to limit SSRF.\n *\n * Despite the name, each entry is a URL prefix, scheme included: callers match\n * with `url.href.startsWith(entry)`. So add entries as `https://<host>`, with\n * no trailing slash and no path.\n */\nexport const FORBIDDEN_HOSTNAMES_FOR_WEBHOOK_ALERTS = [\n 'https://camunda.com',\n 'https://console.cloud.camunda.io',\n 'https://accounts.cloud.camunda.io',\n 'https://console.camunda.io',\n 'https://accounts.camunda.io',\n 'https://console.ultrawombat.com',\n 'https://console.cloud.ultrawombat.com',\n 'https://accounts.cloud.ultrawombat.com',\n 'https://accounts.ultrawombat.com',\n 'https://console.cloud.dev.ultrawombat.com',\n 'https://console.dev.ultrawombat.com',\n 'https://accounts.cloud.dev.ultrawombat.com',\n 'https://accounts.dev.ultrawombat.com',\n // Hub and consolelegacy, all stages.\n // Note: This list contains URL prefixes (`https://<host>`), not bare hostnames.\n // Callers compare with `url.href.startsWith(...)`.\n 'https://hub.dev.ultrawombat.com',\n 'https://hub.ultrawombat.com',\n 'https://hub.camunda.io',\n 'https://consolelegacy.dev.ultrawombat.com',\n 'https://consolelegacy.ultrawombat.com',\n 'https://consolelegacy.camunda.io',\n]\n\nexport type ALERT_SUBSCRIPTION_TYPES = 'ProcessAlert'\nexport type ALERT_SUBSCRIPTION_REPORT_TYPES = 'Email' | 'Webhook'\n\nexport interface AlertSubscriptionWebhookAuth {\n hmac?: {\n secret: string\n }\n}\n\nexport type CheckFailedReason =\n | 'urlEmpty'\n | 'urlNoHead'\n | 'generatedHmacNotAccepted'\n | 'urlBad'\n | 'urlPrivate'\n"],
5
- "mappings": "AAuCO,MAAM,2BAA2B;AACjC,MAAM,uBAAuB;AAS7B,MAAM,yCAAyC;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
4
+ "sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nimport type {\n AlertSubscriptionReportType,\n AlertSubscriptionType,\n} from '../enums/AlertSubscription.enum.js'\n\nexport interface AlertSubscriptionDto {\n uuid: string\n userIdOrWebhook: string\n type: AlertSubscriptionType\n report: AlertSubscriptionReportType\n webhookAuth?: AlertSubscriptionWebhookAuth\n createdBy: string\n createdByName: string\n created: Date\n updatedBy: string\n updatedByName: string\n updated: Date\n lastTimeTriggered?: Date\n numberOfTimesTriggered: number\n /**\n * The address of an `Email` subscription. alert-distributor adds it from the\n * user id, which its list endpoint then replaces with the name of the user.\n * Thus this field is the only reliable way to identify the person.\n */\n recepientEmail?: string\n}\n\nexport interface AlertSubscriptionEntryDto {\n errorMessage?: string\n errorType?: string\n flowNodeId?: string\n jobKey?: string\n creationTime?: string\n processInstanceId?: string\n processName?: string\n processVersion?: number\n bpmnProcessId?: string\n}\n\nexport const HMAC_SUPPORTED_ALGORITHM = 'sha256'\nexport const HMAC_HEADER_POSITION = 'X-Camunda-Signature-256'\n\n/**\n * Webhook targets a customer may not point an alert at, to limit SSRF.\n *\n * Despite the name, each entry is a URL prefix, scheme included: callers match\n * with `url.href.startsWith(entry)`. So add entries as `https://<host>`, with\n * no trailing slash and no path.\n */\nexport const FORBIDDEN_HOSTNAMES_FOR_WEBHOOK_ALERTS = [\n 'https://camunda.com',\n 'https://console.cloud.camunda.io',\n 'https://accounts.cloud.camunda.io',\n 'https://console.camunda.io',\n 'https://accounts.camunda.io',\n 'https://console.ultrawombat.com',\n 'https://console.cloud.ultrawombat.com',\n 'https://accounts.cloud.ultrawombat.com',\n 'https://accounts.ultrawombat.com',\n 'https://console.cloud.dev.ultrawombat.com',\n 'https://console.dev.ultrawombat.com',\n 'https://accounts.cloud.dev.ultrawombat.com',\n 'https://accounts.dev.ultrawombat.com',\n // Hub and consolelegacy, all stages.\n // Note: This list contains URL prefixes (`https://<host>`), not bare hostnames.\n // Callers compare with `url.href.startsWith(...)`.\n 'https://hub.dev.ultrawombat.com',\n 'https://hub.ultrawombat.com',\n 'https://hub.camunda.io',\n 'https://consolelegacy.dev.ultrawombat.com',\n 'https://consolelegacy.ultrawombat.com',\n 'https://consolelegacy.camunda.io',\n]\n\nexport type ALERT_SUBSCRIPTION_TYPES = 'ProcessAlert'\nexport type ALERT_SUBSCRIPTION_REPORT_TYPES = 'Email' | 'Webhook'\n\nexport interface AlertSubscriptionWebhookAuth {\n hmac?: {\n secret: string\n }\n}\n\nexport type CheckFailedReason =\n | 'urlEmpty'\n | 'urlNoHead'\n | 'generatedHmacNotAccepted'\n | 'urlBad'\n | 'urlPrivate'\n"],
5
+ "mappings": "AA6CO,MAAM,2BAA2B;AACjC,MAAM,uBAAuB;AAS7B,MAAM,yCAAyC;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
6
6
  "names": []
7
7
  }
@@ -19,6 +19,44 @@
19
19
  */
20
20
  export declare const APPROVAL_JOB_STATUSES: readonly ["pending", "approved", "executing", "executed", "failed", "rejected", "withdrawn", "expired"];
21
21
  export type ApprovalJobStatus = (typeof APPROVAL_JOB_STATUSES)[number];
22
+ /**
23
+ * The states in which the action of a request is still COMING.
24
+ *
25
+ * `pending` waits for approvals, `approved` waits for a person to press the
26
+ * button, and `executing` is running now. Each of them can still change the
27
+ * world, so two requests for one target must not be in these states together.
28
+ * `dedupeKey` reads this set for that reason.
29
+ *
30
+ * `failed` is NOT here, although a person can run a failed request again. A
31
+ * failed request runs only when a person presses the button, and that person
32
+ * sees the row. Blocking a new request on a failed one would also trap the
33
+ * key: `canWithdraw` accepts `pending` alone, so nobody could close it.
34
+ *
35
+ * `executed`, `rejected`, `withdrawn` and `expired` are the end of a request.
36
+ */
37
+ export declare const APPROVAL_LIVE_STATUSES: readonly ["pending", "approved", "executing"];
38
+ /**
39
+ * How many requests one page of the list holds when the caller asks for no
40
+ * number.
41
+ *
42
+ * The list is a work queue that a Camundi reads, so the page is large: a
43
+ * person wants the whole queue and not ten rows at a time. What the number is
44
+ * for is the ceiling below.
45
+ */
46
+ export declare const APPROVAL_PAGE_SIZE_DEFAULT = 500;
47
+ /**
48
+ * The largest page that the service builds, and the largest that a caller may
49
+ * ask for. The API answers 400 above it.
50
+ *
51
+ * The list had no limit at all. One authenticated caller could then make the
52
+ * service read the whole table into memory, serialize it, and do it again, so
53
+ * the cost of a request grew with the age of the service and with nothing that
54
+ * the caller had to pay for. A ceiling turns that into a fixed cost.
55
+ *
56
+ * It is HERE and not only in the service, because the view must know it: a
57
+ * page that asked for more would get a 400 instead of rows.
58
+ */
59
+ export declare const APPROVAL_PAGE_SIZE_MAX = 2000;
22
60
  /**
23
61
  * What happens when a request reaches its threshold.
24
62
  *
@@ -26,6 +64,22 @@ export type ApprovalJobStatus = (typeof APPROVAL_JOB_STATUSES)[number];
26
64
  * `approved` and waits for a person to run it.
27
65
  */
28
66
  export type ApprovalExecutionMode = 'auto' | 'explicit';
67
+ /**
68
+ * WHERE the action runs. It is a separate question from who starts it.
69
+ *
70
+ * - `inline` runs the action inside the call that started it, and the answer
71
+ * already carries the outcome. Right for one call to another service.
72
+ * - `background` answers as soon as the request is claimed, and the action
73
+ * runs on. It reports into `ApprovalJobDto.progress`, and the row is the
74
+ * only place that holds the progress, so a reload and a second person read
75
+ * the same numbers. A pod that stops loses the run, and the reconciler of
76
+ * control-backend recovers the row.
77
+ *
78
+ * An action over thousands of targets MUST be `background`. `inline` would
79
+ * hold one HTTP request for the whole run, and the ingress, the load balancer
80
+ * and the browser each have their own deadline before it finishes.
81
+ */
82
+ export type ApprovalActionMode = 'inline' | 'background';
29
83
  /**
30
84
  * A person, as an approval request records them.
31
85
  *
@@ -41,24 +95,168 @@ export interface ApprovalAck extends ApprovalActor {
41
95
  /** An ISO timestamp. It is in a json column, so it is a string, not a Date. */
42
96
  date: string;
43
97
  }
98
+ /**
99
+ * What one value of a summary field is.
100
+ *
101
+ * `text` is what a person reads. `id` is what a link points at, and it is
102
+ * absent when the kind names no entity. The two are separate because the text
103
+ * is a COPY that the request keeps: an organization that gets a new name after
104
+ * the request was opened must still read as the organization that the person
105
+ * chose.
106
+ */
107
+ export interface ApprovalSummaryValue {
108
+ text: string;
109
+ id?: string;
110
+ }
111
+ /**
112
+ * How a reader must render one field of a summary.
113
+ *
114
+ * `org`, `cluster` and `user` name an entity, so a reader can link to it and
115
+ * can resolve a fresh name. The others are plain: `date` is an ISO timestamp,
116
+ * and `code` is a value that must keep its characters, such as a json fragment.
117
+ */
118
+ export declare const APPROVAL_SUMMARY_FIELD_KINDS: readonly ["text", "number", "boolean", "date", "org", "cluster", "user", "code"];
119
+ export type ApprovalSummaryFieldKind = (typeof APPROVAL_SUMMARY_FIELD_KINDS)[number];
120
+ /**
121
+ * One labelled field of the payload of a request.
122
+ *
123
+ * This is what makes ONE view able to show EVERY use case. The payload of a
124
+ * request is an object of anything, so a generic view can only print it as
125
+ * json. The use case turns it into labelled fields here, and the view renders
126
+ * a field by its kind: it links an `org`, it formats a `date`, and it lists
127
+ * 300 clusters as 300 links.
128
+ *
129
+ * `values` is always an array. A field that holds one value holds an array of
130
+ * one, so a reader needs no branch for "one or many".
131
+ */
132
+ export interface ApprovalSummaryField {
133
+ label: string;
134
+ kind: ApprovalSummaryFieldKind;
135
+ values: ApprovalSummaryValue[];
136
+ }
137
+ /**
138
+ * How long a run may go quiet before a reader calls it stalled.
139
+ *
140
+ * The pod that runs an action writes its progress and renews its lease on one
141
+ * timer, so a healthy run touches the row well inside this window. A row that
142
+ * is `executing` and older than this held a pod that stopped, and the
143
+ * reconciler of control-backend is on its way to it.
144
+ *
145
+ * It lives here, and not in the config of the service, because a READER needs
146
+ * it: the browser must decide what to show without a second call, and a number
147
+ * that only the backend knew would make the two disagree. `ACTION_LEASE_MS` in
148
+ * `config/index.ts` is held below this on purpose.
149
+ */
150
+ export declare const APPROVAL_RUN_STALE_MS = 120000;
151
+ /**
152
+ * What an action reports while it runs.
153
+ *
154
+ * This is what makes a long action watchable. Every field is in the DATABASE
155
+ * and not in the answer to the call that started the run, so three readers see
156
+ * the same thing: the person who pressed the button, the same person after a
157
+ * reload, and a second person who opens the request.
158
+ *
159
+ * `total` can be 0 while an action counts its targets, and it stays 0 for an
160
+ * action that cannot count them. A reader must then show that it runs, and no
161
+ * percentage.
162
+ */
163
+ export interface ApprovalProgress {
164
+ /** Targets in total. 0 means the action did not say. */
165
+ total: number;
166
+ /** Targets finished, whatever the outcome of each one. */
167
+ done: number;
168
+ /** Targets that finished with an error. Part of `done`. */
169
+ failed: number;
170
+ /** One short line for a person, such as the step that runs now. */
171
+ note?: string;
172
+ /** An ISO timestamp: when the run started. */
173
+ startedAt: string;
174
+ /** An ISO timestamp: when the run last wrote anything. */
175
+ updatedAt: string;
176
+ }
177
+ /** The share of the work that is finished, or `undefined` with no total. */
178
+ export declare function progressFraction(progress: ApprovalProgress | undefined): number | undefined;
179
+ /**
180
+ * Reports whether a running request went quiet.
181
+ *
182
+ * True means that the pod which held the run stopped, or lost the row. It does
183
+ * NOT mean that the action failed: the reconciler either runs it again, or it
184
+ * writes `failed` so that a person can. See `APPROVAL_RUN_STALE_MS`.
185
+ */
186
+ export declare function isRunStalled(job: ApprovalJobDto, now: Date): boolean;
44
187
  /** One approval request, as the API returns it. */
45
188
  export interface ApprovalJobDto {
46
189
  id: string;
47
190
  type: string;
48
191
  dedupeKey?: string;
49
192
  payload: Record<string, unknown>;
193
+ /**
194
+ * The payload as labelled fields. See `ApprovalSummaryField`.
195
+ *
196
+ * The use case builds it when the request is opened AND every time the
197
+ * request is read, so a field that a later version of the use case adds
198
+ * appears on a request that is already open.
199
+ */
200
+ summary: ApprovalSummaryField[];
201
+ /** Why the person opened the request. Some use cases demand one. */
202
+ reason?: string;
50
203
  createdBy: ApprovalActor;
51
204
  acks: ApprovalAck[];
52
- /** Copied from the use case, so a reader can render "1 of 2". */
205
+ /**
206
+ * How many approvals THIS request needs.
207
+ *
208
+ * It is on the request and not only on the use case, because the person who
209
+ * opens one can ask for more than the floor of the use case. A bulk
210
+ * operation over 3000 clusters is the same action as one over 3, and it can
211
+ * still deserve a third pair of eyes. `ApprovalUseCaseDto.minAcks` is the
212
+ * floor that a caller cannot go below.
213
+ */
53
214
  requiredAcks: number;
215
+ /**
216
+ * What a person must hold to approve, reject or run THIS request.
217
+ *
218
+ * It is on the request and not only on the use case, because a payload can
219
+ * raise the guard: a generation update with `yoloForceUpdate` needs the yolo
220
+ * permission on top of the base one, and the same use case without the flag
221
+ * does not. The set is computed ONE time, when the request is opened, so it
222
+ * is also the record of what the request was guarded by.
223
+ *
224
+ * A person needs EVERY permission in the set, not one of them.
225
+ */
226
+ requiredPermissions: string[];
54
227
  executionMode: ApprovalExecutionMode;
228
+ actionMode: ApprovalActionMode;
55
229
  status: ApprovalJobStatus;
56
230
  closedBy?: ApprovalActor;
57
231
  closedReason?: string;
58
232
  /** An ISO timestamp. */
59
233
  expiresAt: string;
234
+ /**
235
+ * An ISO timestamp: when the current run took the request.
236
+ *
237
+ * It is set every time a run starts, so a run that the reconciler starts
238
+ * again moves it. `attempts` says how many runs there were.
239
+ */
240
+ executingSince?: string;
241
+ /** How many times a run started. A recovery raises it. */
242
+ attempts: number;
243
+ /**
244
+ * What the action reports while it runs. See `ApprovalProgress`.
245
+ *
246
+ * It stays on the row after the run, so the record of a finished run says
247
+ * how many targets it touched and how many refused.
248
+ */
249
+ progress?: ApprovalProgress;
60
250
  executedAt?: string;
61
251
  executionError?: string;
252
+ /**
253
+ * What the action reported when it ran.
254
+ *
255
+ * The shape belongs to the use case. An action over many targets puts the
256
+ * result of each target here, which is the difference between "it failed"
257
+ * and "it did 2998 of 3000, and these two refused".
258
+ */
259
+ executionResult?: Record<string, unknown>;
62
260
  createdAt: string;
63
261
  updatedAt: string;
64
262
  }
@@ -73,11 +271,67 @@ export interface ApprovalUseCaseDto {
73
271
  type: string;
74
272
  /** A short name for a person to read. */
75
273
  label: string;
76
- requiredAcks: number;
274
+ /** One sentence: what the action does, and what it cannot undo. */
275
+ description: string;
276
+ /** The approvals a new request asks for when the caller names no number. */
277
+ defaultAcks: number;
278
+ /**
279
+ * The floor. A caller can ask for more approvals, never for fewer.
280
+ *
281
+ * It is a separate number from `defaultAcks` because the two answer
282
+ * different questions: the default is what the form offers, and the floor is
283
+ * what the service refuses to go below.
284
+ */
285
+ minAcks: number;
77
286
  ttlDays: number;
78
287
  executionMode: ApprovalExecutionMode;
79
- /** The permission that acknowledge, reject and run all need. */
288
+ /** See `ApprovalActionMode`. An action over many targets is `background`. */
289
+ actionMode: ApprovalActionMode;
290
+ /**
291
+ * True when a run that was interrupted can start again by itself.
292
+ *
293
+ * It is a promise about the ACTION, and the reconciler reads it. Set it only
294
+ * when the action can run a second time over the same payload and do no harm
295
+ * twice: it must read what the first run already finished, out of
296
+ * `ApprovalJobDto.executionResult`, and skip it.
297
+ *
298
+ * False, the default, is the safe answer. An interrupted run then lands in
299
+ * `failed`, which a person can run again from the detail page, and a person
300
+ * decides whether running it twice is safe.
301
+ */
302
+ resumable: boolean;
303
+ /**
304
+ * The permission that every request of this use case needs to approve,
305
+ * reject or run. A payload can add more. See
306
+ * `ApprovalJobDto.requiredPermissions`, which is the effective set.
307
+ *
308
+ * It is also the permission to SEE a request of this use case, because a
309
+ * person who cannot act on one has no reason to read it. The person who
310
+ * opened a request always sees that request.
311
+ */
80
312
  requiredPermission: string;
81
- /** The permission to open a request. Absent means any signed-in user. */
313
+ /**
314
+ * The permission to open a request. Absent means any signed-in Camundi.
315
+ *
316
+ * Set it when the pool that can ASK must be as small as the pool that can
317
+ * approve. Leave it absent when the approvals alone are the guard, and the
318
+ * proposal is not.
319
+ */
82
320
  createPermission?: string;
321
+ /** True when a request cannot be opened without a reason. */
322
+ reasonRequired: boolean;
323
+ /**
324
+ * True when the ACTION needs the email address of the person who asked.
325
+ *
326
+ * A token can carry no address, and the row then stores
327
+ * `UNKNOWN_ACTOR_EMAIL`. An action that sends that placeholder to another
328
+ * service asks it to find a user that does not exist, so the request can
329
+ * only fail — and it fails at the END, after other people spent their
330
+ * approvals on it. A use case that carries the creator into its call sets
331
+ * this, and `canCreate` then refuses such a caller at the start.
332
+ *
333
+ * `extend-trial` is one: accounts makes the creator the actor of the
334
+ * extension, through `onBehalfOf`.
335
+ */
336
+ creatorEmailRequired?: boolean;
83
337
  }
@@ -8,7 +8,48 @@ const APPROVAL_JOB_STATUSES = [
8
8
  "withdrawn",
9
9
  "expired"
10
10
  ];
11
+ const APPROVAL_LIVE_STATUSES = [
12
+ "pending",
13
+ "approved",
14
+ "executing"
15
+ ];
16
+ const APPROVAL_PAGE_SIZE_DEFAULT = 500;
17
+ const APPROVAL_PAGE_SIZE_MAX = 2e3;
18
+ const APPROVAL_SUMMARY_FIELD_KINDS = [
19
+ "text",
20
+ "number",
21
+ "boolean",
22
+ "date",
23
+ "org",
24
+ "cluster",
25
+ "user",
26
+ "code"
27
+ ];
28
+ const APPROVAL_RUN_STALE_MS = 12e4;
29
+ function progressFraction(progress) {
30
+ if (!progress || progress.total <= 0) {
31
+ return void 0;
32
+ }
33
+ return Math.min(1, progress.done / progress.total);
34
+ }
35
+ function isRunStalled(job, now) {
36
+ if (job.status !== "executing") {
37
+ return false;
38
+ }
39
+ const lastSign = job.progress?.updatedAt ?? job.executingSince;
40
+ if (!lastSign) {
41
+ return false;
42
+ }
43
+ return now.getTime() - new Date(lastSign).getTime() > APPROVAL_RUN_STALE_MS;
44
+ }
11
45
  export {
12
- APPROVAL_JOB_STATUSES
46
+ APPROVAL_JOB_STATUSES,
47
+ APPROVAL_LIVE_STATUSES,
48
+ APPROVAL_PAGE_SIZE_DEFAULT,
49
+ APPROVAL_PAGE_SIZE_MAX,
50
+ APPROVAL_RUN_STALE_MS,
51
+ APPROVAL_SUMMARY_FIELD_KINDS,
52
+ isRunStalled,
53
+ progressFraction
13
54
  };
14
55
  //# sourceMappingURL=ApprovalJob.dto.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/dtos/ApprovalJob.dto.ts"],
4
- "sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\n/**\n * The wire types of the generic approval mechanism.\n *\n * This file holds no logic and no dependency, because three consumers read it:\n * control-backend, control-frontend and cloudbot. The rules that read these\n * types are in `../approvals/policy.js`, and they are pure for the same reason.\n */\n\n/**\n * Every state that an approval request can hold.\n *\n * A `const` tuple, not an enum, so it is both the union type and the list that\n * validates a status filter at run time.\n *\n * The path of a request: `pending` until it reaches the threshold. Then\n * `executing` (automatic mode) or `approved` (explicit mode, which waits for a\n * person to press the button). `executed` or `failed` after the action ran. A\n * request that never reaches the threshold ends `rejected`, `withdrawn` or\n * `expired`.\n */\nexport const APPROVAL_JOB_STATUSES = [\n 'pending',\n 'approved',\n 'executing',\n 'executed',\n 'failed',\n 'rejected',\n 'withdrawn',\n 'expired',\n] as const\n\nexport type ApprovalJobStatus = (typeof APPROVAL_JOB_STATUSES)[number]\n\n/**\n * What happens when a request reaches its threshold.\n *\n * `auto` runs the action immediately. `explicit` holds the request at\n * `approved` and waits for a person to run it.\n */\nexport type ApprovalExecutionMode = 'auto' | 'explicit'\n\n/**\n * A person, as an approval request records them.\n *\n * Both keys are kept. The machine then never has to choose one identity key,\n * which is what split the earlier acknowledgement flows: some held an email and\n * some held a user id, and the two could not be compared.\n */\nexport interface ApprovalActor {\n userId: string\n email: string\n}\n\nexport interface ApprovalAck extends ApprovalActor {\n /** An ISO timestamp. It is in a json column, so it is a string, not a Date. */\n date: string\n}\n\n/** One approval request, as the API returns it. */\nexport interface ApprovalJobDto {\n id: string\n type: string\n dedupeKey?: string\n payload: Record<string, unknown>\n createdBy: ApprovalActor\n acks: ApprovalAck[]\n /** Copied from the use case, so a reader can render \"1 of 2\". */\n requiredAcks: number\n executionMode: ApprovalExecutionMode\n status: ApprovalJobStatus\n closedBy?: ApprovalActor\n closedReason?: string\n /** An ISO timestamp. */\n expiresAt: string\n executedAt?: string\n executionError?: string\n createdAt: string\n updatedAt: string\n}\n\n/**\n * What a use case is, as the API reports it.\n *\n * The frontend needs `requiredPermission` to run the same rule as the backend.\n * PR #9301 kept this registry a second time, by hand, in the frontend. A\n * registry that the server owns means a new use case needs no frontend deploy.\n */\nexport interface ApprovalUseCaseDto {\n type: string\n /** A short name for a person to read. */\n label: string\n requiredAcks: number\n ttlDays: number\n executionMode: ApprovalExecutionMode\n /** The permission that acknowledge, reject and run all need. */\n requiredPermission: string\n /** The permission to open a request. Absent means any signed-in user. */\n createPermission?: string\n}\n"],
5
- "mappings": "AA0BO,MAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
4
+ "sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\n/**\n * The wire types of the generic approval mechanism.\n *\n * This file holds no logic and no dependency, because three consumers read it:\n * control-backend, control-frontend and cloudbot. The rules that read these\n * types are in `../approvals/policy.js`, and they are pure for the same reason.\n */\n\n/**\n * Every state that an approval request can hold.\n *\n * A `const` tuple, not an enum, so it is both the union type and the list that\n * validates a status filter at run time.\n *\n * The path of a request: `pending` until it reaches the threshold. Then\n * `executing` (automatic mode) or `approved` (explicit mode, which waits for a\n * person to press the button). `executed` or `failed` after the action ran. A\n * request that never reaches the threshold ends `rejected`, `withdrawn` or\n * `expired`.\n */\nexport const APPROVAL_JOB_STATUSES = [\n 'pending',\n 'approved',\n 'executing',\n 'executed',\n 'failed',\n 'rejected',\n 'withdrawn',\n 'expired',\n] as const\n\nexport type ApprovalJobStatus = (typeof APPROVAL_JOB_STATUSES)[number]\n\n/**\n * The states in which the action of a request is still COMING.\n *\n * `pending` waits for approvals, `approved` waits for a person to press the\n * button, and `executing` is running now. Each of them can still change the\n * world, so two requests for one target must not be in these states together.\n * `dedupeKey` reads this set for that reason.\n *\n * `failed` is NOT here, although a person can run a failed request again. A\n * failed request runs only when a person presses the button, and that person\n * sees the row. Blocking a new request on a failed one would also trap the\n * key: `canWithdraw` accepts `pending` alone, so nobody could close it.\n *\n * `executed`, `rejected`, `withdrawn` and `expired` are the end of a request.\n */\nexport const APPROVAL_LIVE_STATUSES = [\n 'pending',\n 'approved',\n 'executing',\n] as const satisfies readonly ApprovalJobStatus[]\n\n/**\n * How many requests one page of the list holds when the caller asks for no\n * number.\n *\n * The list is a work queue that a Camundi reads, so the page is large: a\n * person wants the whole queue and not ten rows at a time. What the number is\n * for is the ceiling below.\n */\nexport const APPROVAL_PAGE_SIZE_DEFAULT = 500\n\n/**\n * The largest page that the service builds, and the largest that a caller may\n * ask for. The API answers 400 above it.\n *\n * The list had no limit at all. One authenticated caller could then make the\n * service read the whole table into memory, serialize it, and do it again, so\n * the cost of a request grew with the age of the service and with nothing that\n * the caller had to pay for. A ceiling turns that into a fixed cost.\n *\n * It is HERE and not only in the service, because the view must know it: a\n * page that asked for more would get a 400 instead of rows.\n */\nexport const APPROVAL_PAGE_SIZE_MAX = 2000\n\n/**\n * What happens when a request reaches its threshold.\n *\n * `auto` runs the action immediately. `explicit` holds the request at\n * `approved` and waits for a person to run it.\n */\nexport type ApprovalExecutionMode = 'auto' | 'explicit'\n\n/**\n * WHERE the action runs. It is a separate question from who starts it.\n *\n * - `inline` runs the action inside the call that started it, and the answer\n * already carries the outcome. Right for one call to another service.\n * - `background` answers as soon as the request is claimed, and the action\n * runs on. It reports into `ApprovalJobDto.progress`, and the row is the\n * only place that holds the progress, so a reload and a second person read\n * the same numbers. A pod that stops loses the run, and the reconciler of\n * control-backend recovers the row.\n *\n * An action over thousands of targets MUST be `background`. `inline` would\n * hold one HTTP request for the whole run, and the ingress, the load balancer\n * and the browser each have their own deadline before it finishes.\n */\nexport type ApprovalActionMode = 'inline' | 'background'\n\n/**\n * A person, as an approval request records them.\n *\n * Both keys are kept. The machine then never has to choose one identity key,\n * which is what split the earlier acknowledgement flows: some held an email and\n * some held a user id, and the two could not be compared.\n */\nexport interface ApprovalActor {\n userId: string\n email: string\n}\n\nexport interface ApprovalAck extends ApprovalActor {\n /** An ISO timestamp. It is in a json column, so it is a string, not a Date. */\n date: string\n}\n\n/**\n * What one value of a summary field is.\n *\n * `text` is what a person reads. `id` is what a link points at, and it is\n * absent when the kind names no entity. The two are separate because the text\n * is a COPY that the request keeps: an organization that gets a new name after\n * the request was opened must still read as the organization that the person\n * chose.\n */\nexport interface ApprovalSummaryValue {\n text: string\n id?: string\n}\n\n/**\n * How a reader must render one field of a summary.\n *\n * `org`, `cluster` and `user` name an entity, so a reader can link to it and\n * can resolve a fresh name. The others are plain: `date` is an ISO timestamp,\n * and `code` is a value that must keep its characters, such as a json fragment.\n */\nexport const APPROVAL_SUMMARY_FIELD_KINDS = [\n 'text',\n 'number',\n 'boolean',\n 'date',\n 'org',\n 'cluster',\n 'user',\n 'code',\n] as const\n\nexport type ApprovalSummaryFieldKind =\n (typeof APPROVAL_SUMMARY_FIELD_KINDS)[number]\n\n/**\n * One labelled field of the payload of a request.\n *\n * This is what makes ONE view able to show EVERY use case. The payload of a\n * request is an object of anything, so a generic view can only print it as\n * json. The use case turns it into labelled fields here, and the view renders\n * a field by its kind: it links an `org`, it formats a `date`, and it lists\n * 300 clusters as 300 links.\n *\n * `values` is always an array. A field that holds one value holds an array of\n * one, so a reader needs no branch for \"one or many\".\n */\nexport interface ApprovalSummaryField {\n label: string\n kind: ApprovalSummaryFieldKind\n values: ApprovalSummaryValue[]\n}\n\n/**\n * How long a run may go quiet before a reader calls it stalled.\n *\n * The pod that runs an action writes its progress and renews its lease on one\n * timer, so a healthy run touches the row well inside this window. A row that\n * is `executing` and older than this held a pod that stopped, and the\n * reconciler of control-backend is on its way to it.\n *\n * It lives here, and not in the config of the service, because a READER needs\n * it: the browser must decide what to show without a second call, and a number\n * that only the backend knew would make the two disagree. `ACTION_LEASE_MS` in\n * `config/index.ts` is held below this on purpose.\n */\nexport const APPROVAL_RUN_STALE_MS = 120_000\n\n/**\n * What an action reports while it runs.\n *\n * This is what makes a long action watchable. Every field is in the DATABASE\n * and not in the answer to the call that started the run, so three readers see\n * the same thing: the person who pressed the button, the same person after a\n * reload, and a second person who opens the request.\n *\n * `total` can be 0 while an action counts its targets, and it stays 0 for an\n * action that cannot count them. A reader must then show that it runs, and no\n * percentage.\n */\nexport interface ApprovalProgress {\n /** Targets in total. 0 means the action did not say. */\n total: number\n /** Targets finished, whatever the outcome of each one. */\n done: number\n /** Targets that finished with an error. Part of `done`. */\n failed: number\n /** One short line for a person, such as the step that runs now. */\n note?: string\n /** An ISO timestamp: when the run started. */\n startedAt: string\n /** An ISO timestamp: when the run last wrote anything. */\n updatedAt: string\n}\n\n/** The share of the work that is finished, or `undefined` with no total. */\nexport function progressFraction(\n progress: ApprovalProgress | undefined,\n): number | undefined {\n if (!progress || progress.total <= 0) {\n return undefined\n }\n return Math.min(1, progress.done / progress.total)\n}\n\n/**\n * Reports whether a running request went quiet.\n *\n * True means that the pod which held the run stopped, or lost the row. It does\n * NOT mean that the action failed: the reconciler either runs it again, or it\n * writes `failed` so that a person can. See `APPROVAL_RUN_STALE_MS`.\n */\nexport function isRunStalled(job: ApprovalJobDto, now: Date): boolean {\n if (job.status !== 'executing') {\n return false\n }\n const lastSign = job.progress?.updatedAt ?? job.executingSince\n if (!lastSign) {\n return false\n }\n return now.getTime() - new Date(lastSign).getTime() > APPROVAL_RUN_STALE_MS\n}\n\n/** One approval request, as the API returns it. */\nexport interface ApprovalJobDto {\n id: string\n type: string\n dedupeKey?: string\n payload: Record<string, unknown>\n /**\n * The payload as labelled fields. See `ApprovalSummaryField`.\n *\n * The use case builds it when the request is opened AND every time the\n * request is read, so a field that a later version of the use case adds\n * appears on a request that is already open.\n */\n summary: ApprovalSummaryField[]\n /** Why the person opened the request. Some use cases demand one. */\n reason?: string\n createdBy: ApprovalActor\n acks: ApprovalAck[]\n /**\n * How many approvals THIS request needs.\n *\n * It is on the request and not only on the use case, because the person who\n * opens one can ask for more than the floor of the use case. A bulk\n * operation over 3000 clusters is the same action as one over 3, and it can\n * still deserve a third pair of eyes. `ApprovalUseCaseDto.minAcks` is the\n * floor that a caller cannot go below.\n */\n requiredAcks: number\n /**\n * What a person must hold to approve, reject or run THIS request.\n *\n * It is on the request and not only on the use case, because a payload can\n * raise the guard: a generation update with `yoloForceUpdate` needs the yolo\n * permission on top of the base one, and the same use case without the flag\n * does not. The set is computed ONE time, when the request is opened, so it\n * is also the record of what the request was guarded by.\n *\n * A person needs EVERY permission in the set, not one of them.\n */\n requiredPermissions: string[]\n executionMode: ApprovalExecutionMode\n actionMode: ApprovalActionMode\n status: ApprovalJobStatus\n closedBy?: ApprovalActor\n closedReason?: string\n /** An ISO timestamp. */\n expiresAt: string\n /**\n * An ISO timestamp: when the current run took the request.\n *\n * It is set every time a run starts, so a run that the reconciler starts\n * again moves it. `attempts` says how many runs there were.\n */\n executingSince?: string\n /** How many times a run started. A recovery raises it. */\n attempts: number\n /**\n * What the action reports while it runs. See `ApprovalProgress`.\n *\n * It stays on the row after the run, so the record of a finished run says\n * how many targets it touched and how many refused.\n */\n progress?: ApprovalProgress\n executedAt?: string\n executionError?: string\n /**\n * What the action reported when it ran.\n *\n * The shape belongs to the use case. An action over many targets puts the\n * result of each target here, which is the difference between \"it failed\"\n * and \"it did 2998 of 3000, and these two refused\".\n */\n executionResult?: Record<string, unknown>\n createdAt: string\n updatedAt: string\n}\n\n/**\n * What a use case is, as the API reports it.\n *\n * The frontend needs `requiredPermission` to run the same rule as the backend.\n * PR #9301 kept this registry a second time, by hand, in the frontend. A\n * registry that the server owns means a new use case needs no frontend deploy.\n */\nexport interface ApprovalUseCaseDto {\n type: string\n /** A short name for a person to read. */\n label: string\n /** One sentence: what the action does, and what it cannot undo. */\n description: string\n /** The approvals a new request asks for when the caller names no number. */\n defaultAcks: number\n /**\n * The floor. A caller can ask for more approvals, never for fewer.\n *\n * It is a separate number from `defaultAcks` because the two answer\n * different questions: the default is what the form offers, and the floor is\n * what the service refuses to go below.\n */\n minAcks: number\n ttlDays: number\n executionMode: ApprovalExecutionMode\n /** See `ApprovalActionMode`. An action over many targets is `background`. */\n actionMode: ApprovalActionMode\n /**\n * True when a run that was interrupted can start again by itself.\n *\n * It is a promise about the ACTION, and the reconciler reads it. Set it only\n * when the action can run a second time over the same payload and do no harm\n * twice: it must read what the first run already finished, out of\n * `ApprovalJobDto.executionResult`, and skip it.\n *\n * False, the default, is the safe answer. An interrupted run then lands in\n * `failed`, which a person can run again from the detail page, and a person\n * decides whether running it twice is safe.\n */\n resumable: boolean\n /**\n * The permission that every request of this use case needs to approve,\n * reject or run. A payload can add more. See\n * `ApprovalJobDto.requiredPermissions`, which is the effective set.\n *\n * It is also the permission to SEE a request of this use case, because a\n * person who cannot act on one has no reason to read it. The person who\n * opened a request always sees that request.\n */\n requiredPermission: string\n /**\n * The permission to open a request. Absent means any signed-in Camundi.\n *\n * Set it when the pool that can ASK must be as small as the pool that can\n * approve. Leave it absent when the approvals alone are the guard, and the\n * proposal is not.\n */\n createPermission?: string\n /** True when a request cannot be opened without a reason. */\n reasonRequired: boolean\n /**\n * True when the ACTION needs the email address of the person who asked.\n *\n * A token can carry no address, and the row then stores\n * `UNKNOWN_ACTOR_EMAIL`. An action that sends that placeholder to another\n * service asks it to find a user that does not exist, so the request can\n * only fail \u2014 and it fails at the END, after other people spent their\n * approvals on it. A use case that carries the creator into its call sets\n * this, and `canCreate` then refuses such a caller at the start.\n *\n * `extend-trial` is one: accounts makes the creator the actor of the\n * extension, through `onBehalfOf`.\n */\n creatorEmailRequired?: boolean\n}\n"],
5
+ "mappings": "AA0BO,MAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAmBO,MAAM,yBAAyB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF;AAUO,MAAM,6BAA6B;AAcnC,MAAM,yBAAyB;AAiE/B,MAAM,+BAA+B;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAoCO,MAAM,wBAAwB;AA8B9B,SAAS,iBACd,UACoB;AACpB,MAAI,CAAC,YAAY,SAAS,SAAS,GAAG;AACpC,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,GAAG,SAAS,OAAO,SAAS,KAAK;AACnD;AASO,SAAS,aAAa,KAAqB,KAAoB;AACpE,MAAI,IAAI,WAAW,aAAa;AAC9B,WAAO;AAAA,EACT;AACA,QAAM,WAAW,IAAI,UAAU,aAAa,IAAI;AAChD,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,EACT;AACA,SAAO,IAAI,QAAQ,IAAI,IAAI,KAAK,QAAQ,EAAE,QAAQ,IAAI;AACxD;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Cluster settings for which an organization can hold a default value.
3
+ *
4
+ * A field that is absent keeps the value that the `Cluster` constructor
5
+ * writes. The defaults apply at creation only: they seed the new row, and
6
+ * nothing applies them again. A cluster that exists keeps its own values.
7
+ *
8
+ * A value on the cluster changes afterwards through the surface of that
9
+ * setting, with the permissions of that setting — thus the customer changes
10
+ * most of them alone, and a Camunda-managed setting such as the optional
11
+ * telemetry needs a Camundi (#9732).
12
+ */
13
+ export interface ClusterCreationDefaults {
14
+ autoUpdate?: boolean;
15
+ businessIdUniquenessEnabled?: boolean;
16
+ enforceGroups?: boolean;
17
+ mcpEnabled?: boolean;
18
+ multiTenancyEnabled?: boolean;
19
+ optionalTelemetryEnabled?: boolean;
20
+ rba?: boolean;
21
+ sessionHeartbeatEnabled?: boolean;
22
+ sessionMaxInactiveIntervalMinutes?: number;
23
+ }
24
+ export type ClusterCreationDefaultKey = keyof ClusterCreationDefaults;
25
+ /**
26
+ * Who can write the default of a field today.
27
+ *
28
+ * - `planned`: nobody. The backend accepts the field, and Control shows the
29
+ * row without a control. The mechanism is there, and the field waits for
30
+ * the team that owns the setting.
31
+ * - `control`: a Camundi in Control, after a support request.
32
+ * - `customer`: a Camundi in Control, and the organization itself in the
33
+ * Console settings.
34
+ *
35
+ * The accounts route refuses what this value does not permit, thus a widened
36
+ * value is the whole rollout of a field.
37
+ */
38
+ export type ClusterCreationDefaultAvailability = 'planned' | 'control' | 'customer';
39
+ export interface ClusterCreationDefaultField {
40
+ key: ClusterCreationDefaultKey;
41
+ /** Name of the field on the Control panel. */
42
+ label: string;
43
+ kind: 'boolean' | 'number';
44
+ availability: ClusterCreationDefaultAvailability;
45
+ /**
46
+ * Value that a new cluster gets when the organization holds no default.
47
+ * It must agree with the `Cluster` constructor, because the panel shows it
48
+ * as the value that applies.
49
+ */
50
+ fallback: boolean | number;
51
+ /** Bounds of a number field. They mirror the operator CRD validation. */
52
+ min?: number;
53
+ max?: number;
54
+ /**
55
+ * Console permissions that permit a write of this default. The user needs
56
+ * ALL of them, in addition to the base permission of the route.
57
+ *
58
+ * Each path is a path into `ConsolePermissions.Features`. The frontend adds
59
+ * the `console.` prefix that the session endpoint uses; the backend reads
60
+ * the path from `res.locals.permissions`.
61
+ *
62
+ * A field without a list is a setting that the customer can change without
63
+ * help. The default of such a field gives no more power than the customer
64
+ * has, thus the base permission is sufficient.
65
+ *
66
+ * The per-cluster route of a setting can ask for a different permission for
67
+ * each direction — `autoUpdate` is such a route. A default holds both
68
+ * directions over time, and one entry cannot say which, thus the list asks
69
+ * for all permissions of that route. The default is then never easier to
70
+ * write than the setting itself.
71
+ */
72
+ permissionAllOf?: readonly string[];
73
+ /**
74
+ * Permissions of accounts that a Camundi needs for this field, in addition
75
+ * to the permission of the route. The user needs ALL of them.
76
+ *
77
+ * Each path is a path into `AccountsPermissions.Features`. The frontend
78
+ * reads the path as it is, because the session endpoint carries the
79
+ * accounts keys without a prefix.
80
+ *
81
+ * The permission of the route is `admin.readAll`, which is a read
82
+ * permission that many roles hold. A field that a Camundi may write names
83
+ * the write permission here, thus the read of the surface and the write of
84
+ * a value stay two different questions.
85
+ */
86
+ accountsPermissionAllOf?: readonly string[];
87
+ }
88
+ /**
89
+ * The defaultable fields, in the order that the Control panel shows them.
90
+ *
91
+ * This list is the only place that knows a field is defaultable. The route
92
+ * builds its validation from it, the creation path reads it to merge, and the
93
+ * panel builds its controls from it. A new cluster setting becomes
94
+ * defaultable through one entry here.
95
+ */
96
+ export declare const CLUSTER_CREATION_DEFAULT_FIELDS: readonly ClusterCreationDefaultField[];
@@ -0,0 +1,93 @@
1
+ const CLUSTER_CREATION_DEFAULT_FIELDS = [
2
+ {
3
+ key: "optionalTelemetryEnabled",
4
+ label: "Optional telemetry",
5
+ kind: "boolean",
6
+ // Camunda-managed: the customer asked for a toggle, and the answer is
7
+ // a support request. The org default follows that rule.
8
+ availability: "control",
9
+ fallback: true,
10
+ permissionAllOf: ["clusters.canUpdateOptionalTelemetry"]
11
+ },
12
+ {
13
+ key: "sessionMaxInactiveIntervalMinutes",
14
+ label: "Session idle timeout (minutes)",
15
+ kind: "number",
16
+ // The first field that the organization sets itself. A security
17
+ // posture is the reason for an org-wide idle timeout, and the customer
18
+ // owns that posture.
19
+ availability: "customer",
20
+ // The same permission that a limit of the organization asks for. A
21
+ // default of a cluster setting is org-wide configuration, thus it
22
+ // follows the Limits tab and not the read permission of the route.
23
+ accountsPermissionAllOf: ["admin.mayChangeOrgLimits"],
24
+ fallback: 30,
25
+ min: 1,
26
+ max: 1440
27
+ },
28
+ {
29
+ key: "sessionHeartbeatEnabled",
30
+ availability: "planned",
31
+ label: "Session heartbeat",
32
+ kind: "boolean",
33
+ fallback: false
34
+ },
35
+ {
36
+ key: "multiTenancyEnabled",
37
+ availability: "planned",
38
+ label: "Multi-tenancy",
39
+ kind: "boolean",
40
+ fallback: false
41
+ },
42
+ {
43
+ key: "rba",
44
+ availability: "planned",
45
+ label: "Resource-based authorizations",
46
+ kind: "boolean",
47
+ fallback: false
48
+ },
49
+ {
50
+ key: "enforceGroups",
51
+ availability: "planned",
52
+ label: "Enforce groups",
53
+ kind: "boolean",
54
+ fallback: true
55
+ },
56
+ {
57
+ key: "mcpEnabled",
58
+ availability: "planned",
59
+ label: "MCP",
60
+ kind: "boolean",
61
+ fallback: false
62
+ },
63
+ {
64
+ key: "businessIdUniquenessEnabled",
65
+ availability: "planned",
66
+ label: "Business ID uniqueness",
67
+ kind: "boolean",
68
+ fallback: false
69
+ },
70
+ {
71
+ key: "autoUpdate",
72
+ availability: "planned",
73
+ label: "Auto-update",
74
+ kind: "boolean",
75
+ // The creation path resolves an absent value to false, and the channel
76
+ // condition can still remove it: a cluster on a channel that is not
77
+ // Stable never gets auto-update. The `true` of the `Cluster`
78
+ // constructor never applies here, because every caller of
79
+ // `createNewCluster` gives the field a value.
80
+ fallback: false,
81
+ // The per-cluster route asks for `canSendToSleep` to turn auto-update
82
+ // off and for `canUpgradeGenerationYolo` to turn it on. A default holds
83
+ // both directions, thus it asks for both permissions.
84
+ permissionAllOf: [
85
+ "clusters.canSendToSleep",
86
+ "clusters.canUpgradeGenerationYolo"
87
+ ]
88
+ }
89
+ ];
90
+ export {
91
+ CLUSTER_CREATION_DEFAULT_FIELDS
92
+ };
93
+ //# sourceMappingURL=ClusterCreationDefaults.dto.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/dtos/ClusterCreationDefaults.dto.ts"],
4
+ "sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\n/**\n * Cluster settings for which an organization can hold a default value.\n *\n * A field that is absent keeps the value that the `Cluster` constructor\n * writes. The defaults apply at creation only: they seed the new row, and\n * nothing applies them again. A cluster that exists keeps its own values.\n *\n * A value on the cluster changes afterwards through the surface of that\n * setting, with the permissions of that setting \u2014 thus the customer changes\n * most of them alone, and a Camunda-managed setting such as the optional\n * telemetry needs a Camundi (#9732).\n */\nexport interface ClusterCreationDefaults {\n autoUpdate?: boolean\n businessIdUniquenessEnabled?: boolean\n enforceGroups?: boolean\n mcpEnabled?: boolean\n multiTenancyEnabled?: boolean\n optionalTelemetryEnabled?: boolean\n rba?: boolean\n sessionHeartbeatEnabled?: boolean\n sessionMaxInactiveIntervalMinutes?: number\n}\n\nexport type ClusterCreationDefaultKey = keyof ClusterCreationDefaults\n\n/**\n * Who can write the default of a field today.\n *\n * - `planned`: nobody. The backend accepts the field, and Control shows the\n * row without a control. The mechanism is there, and the field waits for\n * the team that owns the setting.\n * - `control`: a Camundi in Control, after a support request.\n * - `customer`: a Camundi in Control, and the organization itself in the\n * Console settings.\n *\n * The accounts route refuses what this value does not permit, thus a widened\n * value is the whole rollout of a field.\n */\nexport type ClusterCreationDefaultAvailability =\n | 'planned'\n | 'control'\n | 'customer'\n\nexport interface ClusterCreationDefaultField {\n key: ClusterCreationDefaultKey\n /** Name of the field on the Control panel. */\n label: string\n kind: 'boolean' | 'number'\n availability: ClusterCreationDefaultAvailability\n /**\n * Value that a new cluster gets when the organization holds no default.\n * It must agree with the `Cluster` constructor, because the panel shows it\n * as the value that applies.\n */\n fallback: boolean | number\n /** Bounds of a number field. They mirror the operator CRD validation. */\n min?: number\n max?: number\n /**\n * Console permissions that permit a write of this default. The user needs\n * ALL of them, in addition to the base permission of the route.\n *\n * Each path is a path into `ConsolePermissions.Features`. The frontend adds\n * the `console.` prefix that the session endpoint uses; the backend reads\n * the path from `res.locals.permissions`.\n *\n * A field without a list is a setting that the customer can change without\n * help. The default of such a field gives no more power than the customer\n * has, thus the base permission is sufficient.\n *\n * The per-cluster route of a setting can ask for a different permission for\n * each direction \u2014 `autoUpdate` is such a route. A default holds both\n * directions over time, and one entry cannot say which, thus the list asks\n * for all permissions of that route. The default is then never easier to\n * write than the setting itself.\n */\n permissionAllOf?: readonly string[]\n /**\n * Permissions of accounts that a Camundi needs for this field, in addition\n * to the permission of the route. The user needs ALL of them.\n *\n * Each path is a path into `AccountsPermissions.Features`. The frontend\n * reads the path as it is, because the session endpoint carries the\n * accounts keys without a prefix.\n *\n * The permission of the route is `admin.readAll`, which is a read\n * permission that many roles hold. A field that a Camundi may write names\n * the write permission here, thus the read of the surface and the write of\n * a value stay two different questions.\n */\n accountsPermissionAllOf?: readonly string[]\n}\n\n/**\n * The defaultable fields, in the order that the Control panel shows them.\n *\n * This list is the only place that knows a field is defaultable. The route\n * builds its validation from it, the creation path reads it to merge, and the\n * panel builds its controls from it. A new cluster setting becomes\n * defaultable through one entry here.\n */\nexport const CLUSTER_CREATION_DEFAULT_FIELDS: readonly ClusterCreationDefaultField[] =\n [\n {\n key: 'optionalTelemetryEnabled',\n label: 'Optional telemetry',\n kind: 'boolean',\n // Camunda-managed: the customer asked for a toggle, and the answer is\n // a support request. The org default follows that rule.\n availability: 'control',\n fallback: true,\n permissionAllOf: ['clusters.canUpdateOptionalTelemetry'],\n },\n {\n key: 'sessionMaxInactiveIntervalMinutes',\n label: 'Session idle timeout (minutes)',\n kind: 'number',\n // The first field that the organization sets itself. A security\n // posture is the reason for an org-wide idle timeout, and the customer\n // owns that posture.\n availability: 'customer',\n // The same permission that a limit of the organization asks for. A\n // default of a cluster setting is org-wide configuration, thus it\n // follows the Limits tab and not the read permission of the route.\n accountsPermissionAllOf: ['admin.mayChangeOrgLimits'],\n fallback: 30,\n min: 1,\n max: 1440,\n },\n {\n key: 'sessionHeartbeatEnabled',\n availability: 'planned',\n label: 'Session heartbeat',\n kind: 'boolean',\n fallback: false,\n },\n {\n key: 'multiTenancyEnabled',\n availability: 'planned',\n label: 'Multi-tenancy',\n kind: 'boolean',\n fallback: false,\n },\n {\n key: 'rba',\n availability: 'planned',\n label: 'Resource-based authorizations',\n kind: 'boolean',\n fallback: false,\n },\n {\n key: 'enforceGroups',\n availability: 'planned',\n label: 'Enforce groups',\n kind: 'boolean',\n fallback: true,\n },\n {\n key: 'mcpEnabled',\n availability: 'planned',\n label: 'MCP',\n kind: 'boolean',\n fallback: false,\n },\n {\n key: 'businessIdUniquenessEnabled',\n availability: 'planned',\n label: 'Business ID uniqueness',\n kind: 'boolean',\n fallback: false,\n },\n {\n key: 'autoUpdate',\n availability: 'planned',\n label: 'Auto-update',\n kind: 'boolean',\n // The creation path resolves an absent value to false, and the channel\n // condition can still remove it: a cluster on a channel that is not\n // Stable never gets auto-update. The `true` of the `Cluster`\n // constructor never applies here, because every caller of\n // `createNewCluster` gives the field a value.\n fallback: false,\n // The per-cluster route asks for `canSendToSleep` to turn auto-update\n // off and for `canUpgradeGenerationYolo` to turn it on. A default holds\n // both directions, thus it asks for both permissions.\n permissionAllOf: [\n 'clusters.canSendToSleep',\n 'clusters.canUpgradeGenerationYolo',\n ],\n },\n ]\n"],
5
+ "mappings": "AA4GO,MAAM,kCACX;AAAA,EACE;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA;AAAA;AAAA,IAGN,cAAc;AAAA,IACd,UAAU;AAAA,IACV,iBAAiB,CAAC,qCAAqC;AAAA,EACzD;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA;AAAA;AAAA;AAAA,IAIN,cAAc;AAAA;AAAA;AAAA;AAAA,IAId,yBAAyB,CAAC,0BAA0B;AAAA,IACpD,UAAU;AAAA,IACV,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,cAAc;AAAA,IACd,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,cAAc;AAAA,IACd,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,cAAc;AAAA,IACd,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,cAAc;AAAA,IACd,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,cAAc;AAAA,IACd,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,cAAc;AAAA,IACd,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,cAAc;AAAA,IACd,OAAO;AAAA,IACP,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMN,UAAU;AAAA;AAAA;AAAA;AAAA,IAIV,iBAAiB;AAAA,MACf;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }