@camunda/ccma-shared-types 1.0.0 → 1.2.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 (69) hide show
  1. package/dist/_tests/approvals/policy.spec.d.ts +1 -0
  2. package/dist/_tests/dtos/Generations.dto.spec.d.ts +1 -0
  3. package/dist/approvals/policy.d.ts +109 -0
  4. package/dist/approvals/policy.js +123 -0
  5. package/dist/approvals/policy.js.map +7 -0
  6. package/dist/config/camundi-permission-rules.js +10 -4
  7. package/dist/config/camundi-permission-rules.js.map +2 -2
  8. package/dist/constants/misc.const.d.ts +21 -0
  9. package/dist/constants/misc.const.js +5 -1
  10. package/dist/constants/misc.const.js.map +2 -2
  11. package/dist/constants/v2-list.const.d.ts +12 -0
  12. package/dist/constants/v2-list.const.js +9 -0
  13. package/dist/constants/v2-list.const.js.map +7 -0
  14. package/dist/constants/version-gates.const.d.ts +22 -0
  15. package/dist/constants/version-gates.const.js +2 -0
  16. package/dist/constants/version-gates.const.js.map +2 -2
  17. package/dist/dtos/AlertSubscription.dto.d.ts +7 -0
  18. package/dist/dtos/AlertSubscription.dto.js +11 -1
  19. package/dist/dtos/AlertSubscription.dto.js.map +2 -2
  20. package/dist/dtos/ApprovalJob.dto.d.ts +83 -0
  21. package/dist/dtos/ApprovalJob.dto.js +14 -0
  22. package/dist/dtos/ApprovalJob.dto.js.map +7 -0
  23. package/dist/dtos/ClusterDeferredSpecChange.dto.d.ts +24 -0
  24. package/dist/dtos/ClusterDeferredSpecChange.dto.js +1 -0
  25. package/dist/dtos/ClusterDeferredSpecChange.dto.js.map +7 -0
  26. package/dist/dtos/ConsoleCluster.dto.d.ts +8 -0
  27. package/dist/dtos/ControlV2.dto.d.ts +87 -3
  28. package/dist/dtos/FeatureFlags.dto.d.ts +53 -0
  29. package/dist/dtos/FeatureFlags.dto.js +1 -0
  30. package/dist/dtos/FeatureFlags.dto.js.map +7 -0
  31. package/dist/dtos/Generations.dto.d.ts +10 -0
  32. package/dist/dtos/Generations.dto.js +46 -4
  33. package/dist/dtos/Generations.dto.js.map +2 -2
  34. package/dist/dtos/Hdp.dto.d.ts +29 -0
  35. package/dist/dtos/Organization.dto.d.ts +52 -1
  36. package/dist/dtos/Organization.dto.js.map +1 -1
  37. package/dist/enums/AuditService.enum.d.ts +2 -1
  38. package/dist/enums/AuditService.enum.js +1 -0
  39. package/dist/enums/AuditService.enum.js.map +2 -2
  40. package/dist/enums/SystemAuditEntity.enum.d.ts +6 -2
  41. package/dist/enums/SystemAuditEntity.enum.js +4 -0
  42. package/dist/enums/SystemAuditEntity.enum.js.map +2 -2
  43. package/dist/index.d.ts +16 -8
  44. package/dist/index.js +55 -1
  45. package/dist/index.js.map +2 -2
  46. package/dist/search/cluster-search-fields.js +9 -1
  47. package/dist/search/cluster-search-fields.js.map +2 -2
  48. package/dist/search/compiler.d.ts +25 -0
  49. package/dist/search/compiler.js +12 -3
  50. package/dist/search/compiler.js.map +2 -2
  51. package/dist/search/index.d.ts +1 -1
  52. package/dist/search/index.js +4 -0
  53. package/dist/search/index.js.map +2 -2
  54. package/dist/types/ClusterBulkOperation.type.d.ts +30 -0
  55. package/dist/types/OrganizationPermission.type.js +5 -2
  56. package/dist/types/OrganizationPermission.type.js.map +2 -2
  57. package/dist/utils/camundiPermissionAugmenter.utils.d.ts +14 -0
  58. package/dist/utils/camundiPermissionAugmenter.utils.js +4 -0
  59. package/dist/utils/camundiPermissionAugmenter.utils.js.map +2 -2
  60. package/dist/utils/camundiPermissionAugmenter.utils.spec.d.ts +1 -0
  61. package/dist/utils/orgStartDate.utils.d.ts +28 -0
  62. package/dist/utils/orgStartDate.utils.js +25 -0
  63. package/dist/utils/orgStartDate.utils.js.map +7 -0
  64. package/dist/utils/orgStartDate.utils.spec.d.ts +1 -0
  65. package/dist/utils/orgs/OrgHelper.spec.d.ts +1 -0
  66. package/dist/utils/versionCheck.utils.d.ts +43 -0
  67. package/dist/utils/versionCheck.utils.js +35 -12
  68. package/dist/utils/versionCheck.utils.js.map +2 -2
  69. 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\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]\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;AAE7B,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;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\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;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The wire types of the generic approval mechanism.
3
+ *
4
+ * This file holds no logic and no dependency, because three consumers read it:
5
+ * control-backend, control-frontend and cloudbot. The rules that read these
6
+ * types are in `../approvals/policy.js`, and they are pure for the same reason.
7
+ */
8
+ /**
9
+ * Every state that an approval request can hold.
10
+ *
11
+ * A `const` tuple, not an enum, so it is both the union type and the list that
12
+ * validates a status filter at run time.
13
+ *
14
+ * The path of a request: `pending` until it reaches the threshold. Then
15
+ * `executing` (automatic mode) or `approved` (explicit mode, which waits for a
16
+ * person to press the button). `executed` or `failed` after the action ran. A
17
+ * request that never reaches the threshold ends `rejected`, `withdrawn` or
18
+ * `expired`.
19
+ */
20
+ export declare const APPROVAL_JOB_STATUSES: readonly ["pending", "approved", "executing", "executed", "failed", "rejected", "withdrawn", "expired"];
21
+ export type ApprovalJobStatus = (typeof APPROVAL_JOB_STATUSES)[number];
22
+ /**
23
+ * What happens when a request reaches its threshold.
24
+ *
25
+ * `auto` runs the action immediately. `explicit` holds the request at
26
+ * `approved` and waits for a person to run it.
27
+ */
28
+ export type ApprovalExecutionMode = 'auto' | 'explicit';
29
+ /**
30
+ * A person, as an approval request records them.
31
+ *
32
+ * Both keys are kept. The machine then never has to choose one identity key,
33
+ * which is what split the earlier acknowledgement flows: some held an email and
34
+ * some held a user id, and the two could not be compared.
35
+ */
36
+ export interface ApprovalActor {
37
+ userId: string;
38
+ email: string;
39
+ }
40
+ export interface ApprovalAck extends ApprovalActor {
41
+ /** An ISO timestamp. It is in a json column, so it is a string, not a Date. */
42
+ date: string;
43
+ }
44
+ /** One approval request, as the API returns it. */
45
+ export interface ApprovalJobDto {
46
+ id: string;
47
+ type: string;
48
+ dedupeKey?: string;
49
+ payload: Record<string, unknown>;
50
+ createdBy: ApprovalActor;
51
+ acks: ApprovalAck[];
52
+ /** Copied from the use case, so a reader can render "1 of 2". */
53
+ requiredAcks: number;
54
+ executionMode: ApprovalExecutionMode;
55
+ status: ApprovalJobStatus;
56
+ closedBy?: ApprovalActor;
57
+ closedReason?: string;
58
+ /** An ISO timestamp. */
59
+ expiresAt: string;
60
+ executedAt?: string;
61
+ executionError?: string;
62
+ createdAt: string;
63
+ updatedAt: string;
64
+ }
65
+ /**
66
+ * What a use case is, as the API reports it.
67
+ *
68
+ * The frontend needs `requiredPermission` to run the same rule as the backend.
69
+ * PR #9301 kept this registry a second time, by hand, in the frontend. A
70
+ * registry that the server owns means a new use case needs no frontend deploy.
71
+ */
72
+ export interface ApprovalUseCaseDto {
73
+ type: string;
74
+ /** A short name for a person to read. */
75
+ label: string;
76
+ requiredAcks: number;
77
+ ttlDays: number;
78
+ executionMode: ApprovalExecutionMode;
79
+ /** The permission that acknowledge, reject and run all need. */
80
+ requiredPermission: string;
81
+ /** The permission to open a request. Absent means any signed-in user. */
82
+ createPermission?: string;
83
+ }
@@ -0,0 +1,14 @@
1
+ const APPROVAL_JOB_STATUSES = [
2
+ "pending",
3
+ "approved",
4
+ "executing",
5
+ "executed",
6
+ "failed",
7
+ "rejected",
8
+ "withdrawn",
9
+ "expired"
10
+ ];
11
+ export {
12
+ APPROVAL_JOB_STATUSES
13
+ };
14
+ //# sourceMappingURL=ApprovalJob.dto.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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;",
6
+ "names": []
7
+ }
@@ -0,0 +1,24 @@
1
+ /** Which write to the ZeebeCluster CR failed. */
2
+ export type DeferredSpecChangeType = 'full' | 'cloud';
3
+ /**
4
+ * A spec change that the console backend kept in its database, because the
5
+ * k8s controller refused the write while the cluster was asleep. The console
6
+ * backend writes the full spec from the database at the next resume, so the
7
+ * change reaches the CR then. Present on the cluster detail read for callers
8
+ * with read-all-cluster-info.
9
+ *
10
+ * A value here does not block the cluster. It tells support that the CR and
11
+ * the database are different until the cluster resumes.
12
+ */
13
+ export interface ClusterDeferredSpecChange {
14
+ /** When the first write of this group of changes failed. ISO 8601. */
15
+ since: string;
16
+ /** When the last write failed. ISO 8601. */
17
+ lastAttemptAt: string;
18
+ /** How many writes failed since `since`. */
19
+ attempts: number;
20
+ /** The message of the last failure, truncated. */
21
+ lastError: string;
22
+ /** The write that failed: the full spec, or the cloud (business) spec. */
23
+ changeType: DeferredSpecChangeType;
24
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=ClusterDeferredSpecChange.dto.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -1,4 +1,5 @@
1
1
  import type { ClusterLabels } from '../types/CamundaClusterLabels.type.js';
2
+ import type { ClusterDeferredSpecChange } from './ClusterDeferredSpecChange.dto.js';
2
3
  import type { ClusterIpWhitelistDto } from './ClusterIpWhitelist.dto.js';
3
4
  import type { ClusterPlanDto } from './ClusterPlan.dto.js';
4
5
  import type { ClusterSpecHistory } from './ClusterSpecHistory.dto.js';
@@ -20,4 +21,11 @@ export interface ConsoleClusterDto extends CommonClusterDto {
20
21
  * "Spec Changes" tab.
21
22
  */
22
23
  specHistory?: ClusterSpecHistory;
24
+ /**
25
+ * Set when the k8s controller refused a spec write for this cluster while
26
+ * the cluster was asleep. The change is in the database, and the next
27
+ * resume writes it to the CR. Present under the same permission group as
28
+ * `specHistory`.
29
+ */
30
+ deferredSpecChange?: ClusterDeferredSpecChange;
23
31
  }
@@ -1,4 +1,5 @@
1
1
  import type { SalesPlanType } from '../types/SalesPlanType.type.js';
2
+ import type { LicensingModelType } from './Organization.dto.js';
2
3
  /** Generic cursor-paginated response. `nextCursor === null` on the last page. */
3
4
  export interface CursorPage<T> {
4
5
  items: T[];
@@ -42,6 +43,19 @@ export interface OrgListItemV2Dto {
42
43
  /** Concrete plan name (`SalesPlan.name`), distinct from the tier `salesPlanType`. */
43
44
  salesPlanName?: string;
44
45
  salesForceId?: string;
46
+ /** ISO-8601 end of the trial, when the org is or was on a trial plan. */
47
+ trialExpiresAt?: string;
48
+ /**
49
+ * Persisted trial state: `true` when the trial ran out, `false` while it
50
+ * runs. Absent when the org is not on a trial plan or the state is not set.
51
+ */
52
+ trialExpired?: boolean;
53
+ /** Org metadata flag: the org can only create clusters on AWS. */
54
+ awsOnly?: boolean;
55
+ /** Org metadata flag: enterprise plan limited to development use. */
56
+ enterpriseDevOnly?: boolean;
57
+ /** Org metadata: which licensing model prices the org. */
58
+ licensingModel?: LicensingModelType;
45
59
  }
46
60
  export interface ListOrganizationsV2Params extends CursorParams {
47
61
  q?: string;
@@ -115,7 +129,10 @@ export interface ClusterListItemV2Dto {
115
129
  numberOfAllocatedHwPackages?: number;
116
130
  /** Concrete plan name (`ClusterPlan.name`). Backs the "Plan" column. */
117
131
  planName?: string;
132
+ /** Generation name, for display. Group and filter by `generationId`. */
118
133
  generation: string;
134
+ /** Generation uuid. Names are not unique, so this is the grouping key. */
135
+ generationId?: string;
119
136
  region: string;
120
137
  /** Display-only; not a sort/filter key (§9.4 B2). */
121
138
  load?: number;
@@ -129,6 +146,55 @@ export interface ClusterListItemV2Dto {
129
146
  }
130
147
  /** Detail extends the list shape; richer fields are added in Phase 3. */
131
148
  export type ClusterDetailV2Dto = ClusterListItemV2Dto;
149
+ /**
150
+ * How much work a cluster did, inside the 30-day activity window.
151
+ *
152
+ * `idle` and `unknown` are different facts and must stay apart. `idle` is
153
+ * evidence that the cluster did no work. `unknown` is the absence of
154
+ * evidence: on the dev stage most clusters have no row at all.
155
+ *
156
+ * The source is the Holistic Data Platform (HDP), which holds one row per
157
+ * cluster, per day, per value metric. The window is 30 days, thus a bucket
158
+ * for "did work, but more than 30 days ago" is not possible.
159
+ */
160
+ export type ClusterActivityBucket =
161
+ /** Work inside the 30-day window. */
162
+ 'active'
163
+ /** Rows exist inside the window. All values are zero. */
164
+ | 'idle'
165
+ /** No row at all inside the window. */
166
+ | 'unknown';
167
+ /** Quick-filter tiles on the clusters list. */
168
+ export type ClusterActivityTile = 'all' | 'active' | 'idle' | 'unknown'
169
+ /** More than 90 days old, and not active. */
170
+ | 'old-idle'
171
+ /** No more than 30 days old, with no work and no data. */
172
+ | 'new-idle';
173
+ export interface ClusterActivityV2Dto {
174
+ clusterId: string;
175
+ /**
176
+ * ISO-8601 instant of the last UTC day with work. HDP buckets by day, thus
177
+ * the time part is always midnight. Absent when the cluster did no work
178
+ * inside the window.
179
+ */
180
+ lastActiveAt?: string;
181
+ processInstances30d: number;
182
+ bucket: ClusterActivityBucket;
183
+ }
184
+ export interface ClusterActivityV2Response {
185
+ /**
186
+ * One entry per live cluster that has an HDP row inside the window. A
187
+ * cluster in the `unknown` bucket has no entry, because the bucket means
188
+ * that no row exists.
189
+ */
190
+ items: ClusterActivityV2Dto[];
191
+ /** Fleet-wide tile counts. The server joins `cluster.created` for the age tiles. */
192
+ counts: Record<ClusterActivityTile, number>;
193
+ /** ISO-8601 instant when the server assembled this response. */
194
+ fetchedAt: string;
195
+ /** The number of days that the window covers. */
196
+ windowDays: number;
197
+ }
132
198
  export interface ListClustersV2Params extends CursorParams {
133
199
  q?: string;
134
200
  /**
@@ -171,8 +237,11 @@ export interface ListClustersV2Params extends CursorParams {
171
237
  * above, which is the separate "Plan type" filter over `ClusterPlanType`.
172
238
  */
173
239
  planNames?: string[];
174
- /** Restrict to these generation names (`generation.name`). */
175
- generationNames?: string[];
240
+ /**
241
+ * Restrict to these generation uuids (`generation.uuid`). Never by name:
242
+ * a generation name is not unique, so one name can match two generations.
243
+ */
244
+ generationIds?: string[];
176
245
  /** Restrict to these cloud providers (resolved via the k8s-context config). */
177
246
  providers?: string[];
178
247
  /** Exact k8s context uuid. */
@@ -201,8 +270,22 @@ export interface ListClustersV2Params extends CursorParams {
201
270
  minorVersion?: string;
202
271
  /** Restrict to these release channel names. */
203
272
  channels?: string[];
204
- /** ISO-8601 lower bound on `created` (e.g. "created in the last 7 days"). */
273
+ /**
274
+ * ISO-8601 lower bound on `created` (e.g. "created in the last 7 days").
275
+ * Inclusive: a cluster created at this instant matches.
276
+ */
205
277
  createdAfter?: string;
278
+ /**
279
+ * ISO-8601 upper bound on `created`. Pairs with `createdAfter` for the
280
+ * age axis. Exclusive: a cluster created at this instant does not match.
281
+ */
282
+ createdBefore?: string;
283
+ /**
284
+ * Restrict to these activity buckets. The controller resolves the buckets
285
+ * to a cluster id set from the cached HDP activity map, the same way
286
+ * `filter.status` resolves K8s state to a cluster id set.
287
+ */
288
+ activity?: ClusterActivityBucket[];
206
289
  };
207
290
  }
208
291
  export interface ClusterParameterOptionV2Dto {
@@ -234,6 +317,7 @@ export interface ClusterParametersV2Dto {
234
317
  export interface ClusterFacetsV2Dto {
235
318
  total: number;
236
319
  status: Record<string, number>;
320
+ /** Keyed by generation uuid, not name: names are not unique. */
237
321
  generation: Record<string, number>;
238
322
  planType: Record<string, number>;
239
323
  channel: Record<string, number>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * This type gives the shape of the feature flags of the management apps. It is
3
+ * also the body of `GET external/feature-flags` on accounts-backend.
4
+ *
5
+ * The shape is here, but the evaluation is not. `features({stage, orgId,
6
+ * email})` in `@camunda-cloud/featureflags` stays the only place that decides
7
+ * if a flag is on. That package is private, thus a published package
8
+ * (`@camunda/ccma-api`, `@camunda/ccma-saas-frontend`) cannot name the type
9
+ * from there. `@camunda-cloud/featureflags` re-exports `Features` from here.
10
+ * Thus the type has one definition, and every existing import path still works.
11
+ *
12
+ * A c4 host reads one flag with `useC4FeatureFlag('console.optionalTelemetry')`
13
+ * — a dot path into this object.
14
+ */
15
+ export interface Features {
16
+ console: {
17
+ admin: {
18
+ overrideOrgRole: boolean;
19
+ };
20
+ clusterSuspendButton: boolean;
21
+ clusterSleepWarning: boolean;
22
+ backupRestore: boolean;
23
+ failover: boolean;
24
+ privateLink87: boolean;
25
+ byoidp: boolean;
26
+ optimizeUnsetOnDisable: boolean;
27
+ appIntegrations: boolean;
28
+ optionalTelemetry: boolean;
29
+ releaseTrainSanityFix: boolean;
30
+ hubHelpCenterLinks: boolean;
31
+ legacyModelerApiNaming: boolean;
32
+ backupScheduleApi: boolean;
33
+ clusterCreationBlocked: boolean;
34
+ deferSpecChangeWhileAsleep: boolean;
35
+ clusterDuplication: boolean;
36
+ };
37
+ control: {
38
+ frontendTelemetry: boolean;
39
+ sendToExpiredTrialApproval: boolean;
40
+ };
41
+ accounts: {
42
+ moveClusterOrg: boolean;
43
+ orgLimitAuditLog: boolean;
44
+ orgReservationAuditLog: boolean;
45
+ deleteLiteLLMKeyOnTrialExpiry: boolean;
46
+ };
47
+ shared: {
48
+ cyberpunkBrutalism: boolean;
49
+ tenantsValueMetric: boolean;
50
+ hdpValueMetricsFor2025: boolean;
51
+ permissionsRework: boolean;
52
+ };
53
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=FeatureFlags.dto.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -30,3 +30,13 @@ export declare function sortGenerationBySemverOfZeebe(generationA: {
30
30
  zeebe: string;
31
31
  };
32
32
  }): number;
33
+ /**
34
+ * Orders generation names by product, then newest first within a product, by
35
+ * numeric collation: `8.10` comes before `8.9`, and `gen10` before `gen9`.
36
+ * Products stay in alphabetic order, so the Camunda generations come before
37
+ * the older Zeebe ones. A release comes before its prereleases, so
38
+ * `8.10.0` is above `8.10.0-alpha1`. For a list that has no zeebe image to
39
+ * compare, such as a filter facet. When the image is known, use
40
+ * `sortGenerationBySemverOfZeebe`.
41
+ */
42
+ export declare const compareGenerationNamesNewestFirst: (nameA: string, nameB: string) => number;
@@ -1,21 +1,63 @@
1
1
  import semver from "semver";
2
+ import { stripSecfixPostfix } from "../utils/versionCheck.utils.js";
3
+ const secfixRank = (version) => version === stripSecfixPostfix(version) ? 0 : 1;
2
4
  function sortGenerationBySemverOfZeebe(generationA, generationB) {
3
5
  const versionAvalid = generationA.versions?.zeebe !== void 0 && semver.valid(generationA.versions.zeebe.split(":")[1]) !== null;
4
6
  const versionBvalid = generationB.versions?.zeebe !== void 0 && semver.valid(generationB.versions.zeebe.split(":")[1]) !== null;
5
7
  if (versionAvalid && versionBvalid) {
6
- return semver.compare(
7
- generationB.versions.zeebe.split(":")[1],
8
- generationA.versions.zeebe.split(":")[1]
8
+ const zeebeA = generationA.versions.zeebe.split(":")[1];
9
+ const zeebeB = generationB.versions.zeebe.split(":")[1];
10
+ const byVersion = semver.compare(
11
+ stripSecfixPostfix(zeebeB),
12
+ stripSecfixPostfix(zeebeA)
9
13
  );
14
+ if (byVersion !== 0) {
15
+ return byVersion;
16
+ }
17
+ const bySecfix = secfixRank(zeebeB) - secfixRank(zeebeA);
18
+ if (bySecfix !== 0) {
19
+ return bySecfix;
20
+ }
21
+ return compareGenerationNamesNewestFirst(generationA.name, generationB.name);
10
22
  } else if (versionAvalid) {
11
23
  return -1;
12
24
  } else if (versionBvalid) {
13
25
  return 1;
14
26
  } else {
15
- return generationB.name.localeCompare(generationA.name);
27
+ return compareGenerationNamesNewestFirst(generationA.name, generationB.name);
16
28
  }
17
29
  }
30
+ const productPrefix = (name) => {
31
+ const firstDigit = name.search(/\d/);
32
+ return firstDigit === -1 ? name : name.slice(0, firstDigit);
33
+ };
34
+ const splitPrerelease = (name) => {
35
+ const dash = name.indexOf("-", name.search(/\d/));
36
+ return dash === -1 ? [name, ""] : [name.slice(0, dash), name.slice(dash + 1)];
37
+ };
38
+ const compareGenerationNamesNewestFirst = (nameA, nameB) => {
39
+ const collation = { numeric: true, sensitivity: "base" };
40
+ const byProduct = productPrefix(nameA).localeCompare(
41
+ productPrefix(nameB),
42
+ void 0,
43
+ collation
44
+ );
45
+ if (byProduct !== 0) {
46
+ return byProduct;
47
+ }
48
+ const [baseA, suffixA] = splitPrerelease(nameA);
49
+ const [baseB, suffixB] = splitPrerelease(nameB);
50
+ const byBase = baseB.localeCompare(baseA, void 0, collation);
51
+ if (byBase !== 0) {
52
+ return byBase;
53
+ }
54
+ if (suffixA === "" || suffixB === "") {
55
+ return suffixA === suffixB ? 0 : suffixA === "" ? -1 : 1;
56
+ }
57
+ return suffixB.localeCompare(suffixA, void 0, collation);
58
+ };
18
59
  export {
60
+ compareGenerationNamesNewestFirst,
19
61
  sortGenerationBySemverOfZeebe
20
62
  };
21
63
  //# sourceMappingURL=Generations.dto.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/dtos/Generations.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 semver from 'semver'\nimport type { ZeebeClusterVersionsDto } from '../types/C8ClusterComponentVersions.type.js'\n\nexport interface GenerationDto {\n name: string\n uuid: string\n versions: ZeebeClusterVersionsDto\n upgradeableFrom: GenerationDto[]\n createdBy: string\n createdByName?: string\n created: Date\n updatedBy: string\n updated: Date\n isUnsupportedStarting?: Date\n isBespoke?: boolean\n}\n\nexport interface GenerationWithClusterCountDto extends GenerationDto {\n clusterCount: number\n}\n\nexport interface UpgradableGenerationsDto {\n preferred: GenerationDto | null\n options: GenerationDto[]\n}\n\nexport function sortGenerationBySemverOfZeebe(\n generationA: { name: string; versions?: { zeebe: string } },\n generationB: { name: string; versions?: { zeebe: string } },\n) {\n // magic sorting algorithm\n // uses the generation version of zeebe for comparison\n // if version invalid: use localeCompare\n\n const versionAvalid: boolean =\n generationA.versions?.zeebe !== undefined &&\n semver.valid(generationA.versions.zeebe.split(':')[1]) !== null\n const versionBvalid: boolean =\n generationB.versions?.zeebe !== undefined &&\n semver.valid(generationB.versions.zeebe.split(':')[1]) !== null\n\n if (versionAvalid && versionBvalid) {\n return semver.compare(\n generationB.versions!.zeebe.split(':')[1],\n generationA.versions!.zeebe.split(':')[1],\n )\n } else if (versionAvalid) {\n return -1\n } else if (versionBvalid) {\n return 1\n } else {\n return generationB.name.localeCompare(generationA.name)\n }\n}\n"],
5
- "mappings": "AAMA,OAAO,YAAY;AA0BZ,SAAS,8BACd,aACA,aACA;AAKA,QAAM,gBACJ,YAAY,UAAU,UAAU,UAChC,OAAO,MAAM,YAAY,SAAS,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,MAAM;AAC7D,QAAM,gBACJ,YAAY,UAAU,UAAU,UAChC,OAAO,MAAM,YAAY,SAAS,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,MAAM;AAE7D,MAAI,iBAAiB,eAAe;AAClC,WAAO,OAAO;AAAA,MACZ,YAAY,SAAU,MAAM,MAAM,GAAG,EAAE,CAAC;AAAA,MACxC,YAAY,SAAU,MAAM,MAAM,GAAG,EAAE,CAAC;AAAA,IAC1C;AAAA,EACF,WAAW,eAAe;AACxB,WAAO;AAAA,EACT,WAAW,eAAe;AACxB,WAAO;AAAA,EACT,OAAO;AACL,WAAO,YAAY,KAAK,cAAc,YAAY,IAAI;AAAA,EACxD;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 semver from 'semver'\nimport type { ZeebeClusterVersionsDto } from '../types/C8ClusterComponentVersions.type.js'\nimport { stripSecfixPostfix } from '../utils/versionCheck.utils.js'\n\nexport interface GenerationDto {\n name: string\n uuid: string\n versions: ZeebeClusterVersionsDto\n upgradeableFrom: GenerationDto[]\n createdBy: string\n createdByName?: string\n created: Date\n updatedBy: string\n updated: Date\n isUnsupportedStarting?: Date\n isBespoke?: boolean\n}\n\nexport interface GenerationWithClusterCountDto extends GenerationDto {\n clusterCount: number\n}\n\nexport interface UpgradableGenerationsDto {\n preferred: GenerationDto | null\n options: GenerationDto[]\n}\n\nconst secfixRank = (version: string): number =>\n version === stripSecfixPostfix(version) ? 0 : 1\n\nexport function sortGenerationBySemverOfZeebe(\n generationA: { name: string; versions?: { zeebe: string } },\n generationB: { name: string; versions?: { zeebe: string } },\n) {\n // magic sorting algorithm\n // uses the generation version of zeebe for comparison\n // if version invalid: use localeCompare\n\n const versionAvalid: boolean =\n generationA.versions?.zeebe !== undefined &&\n semver.valid(generationA.versions.zeebe.split(':')[1]) !== null\n const versionBvalid: boolean =\n generationB.versions?.zeebe !== undefined &&\n semver.valid(generationB.versions.zeebe.split(':')[1]) !== null\n\n if (versionAvalid && versionBvalid) {\n const zeebeA = generationA.versions!.zeebe.split(':')[1]\n const zeebeB = generationB.versions!.zeebe.split(':')[1]\n const byVersion = semver.compare(\n stripSecfixPostfix(zeebeB),\n stripSecfixPostfix(zeebeA),\n )\n if (byVersion !== 0) {\n return byVersion\n }\n // Both builds carry the same Camunda version here, so only the postfix\n // separates them. A secfix build holds that version plus a security patch,\n // thus it is the newer of the two and takes the higher rank.\n const bySecfix = secfixRank(zeebeB) - secfixRank(zeebeA)\n if (bySecfix !== 0) {\n return bySecfix\n }\n // Same image on both (8.9+gen5 and 8.9+gen6 both run 8.9.5). The name is\n // the only order left, so a picker does not show them in input order.\n return compareGenerationNamesNewestFirst(generationA.name, generationB.name)\n } else if (versionAvalid) {\n return -1\n } else if (versionBvalid) {\n return 1\n } else {\n return compareGenerationNamesNewestFirst(generationA.name, generationB.name)\n }\n}\n\n/** The text before the first digit: \"Camunda \" for \"Camunda 8.9+gen20\". */\nconst productPrefix = (name: string): string => {\n const firstDigit = name.search(/\\d/)\n return firstDigit === -1 ? name : name.slice(0, firstDigit)\n}\n\n/**\n * Splits a name at the first dash after the version: \"Camunda 8.10.0-alpha1\"\n * gives [\"Camunda 8.10.0\", \"alpha1\"]. A name without a dash gives an empty\n * suffix.\n */\nconst splitPrerelease = (name: string): [base: string, suffix: string] => {\n const dash = name.indexOf('-', name.search(/\\d/))\n return dash === -1 ? [name, ''] : [name.slice(0, dash), name.slice(dash + 1)]\n}\n\n/**\n * Orders generation names by product, then newest first within a product, by\n * numeric collation: `8.10` comes before `8.9`, and `gen10` before `gen9`.\n * Products stay in alphabetic order, so the Camunda generations come before\n * the older Zeebe ones. A release comes before its prereleases, so\n * `8.10.0` is above `8.10.0-alpha1`. For a list that has no zeebe image to\n * compare, such as a filter facet. When the image is known, use\n * `sortGenerationBySemverOfZeebe`.\n */\nexport const compareGenerationNamesNewestFirst = (\n nameA: string,\n nameB: string,\n): number => {\n const collation = { numeric: true, sensitivity: 'base' } as const\n const byProduct = productPrefix(nameA).localeCompare(\n productPrefix(nameB),\n undefined,\n collation,\n )\n if (byProduct !== 0) {\n return byProduct\n }\n const [baseA, suffixA] = splitPrerelease(nameA)\n const [baseB, suffixB] = splitPrerelease(nameB)\n const byBase = baseB.localeCompare(baseA, undefined, collation)\n if (byBase !== 0) {\n return byBase\n }\n if (suffixA === '' || suffixB === '') {\n return suffixA === suffixB ? 0 : suffixA === '' ? -1 : 1\n }\n return suffixB.localeCompare(suffixA, undefined, collation)\n}\n"],
5
+ "mappings": "AAMA,OAAO,YAAY;AAEnB,SAAS,0BAA0B;AAyBnC,MAAM,aAAa,CAAC,YAClB,YAAY,mBAAmB,OAAO,IAAI,IAAI;AAEzC,SAAS,8BACd,aACA,aACA;AAKA,QAAM,gBACJ,YAAY,UAAU,UAAU,UAChC,OAAO,MAAM,YAAY,SAAS,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,MAAM;AAC7D,QAAM,gBACJ,YAAY,UAAU,UAAU,UAChC,OAAO,MAAM,YAAY,SAAS,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,MAAM;AAE7D,MAAI,iBAAiB,eAAe;AAClC,UAAM,SAAS,YAAY,SAAU,MAAM,MAAM,GAAG,EAAE,CAAC;AACvD,UAAM,SAAS,YAAY,SAAU,MAAM,MAAM,GAAG,EAAE,CAAC;AACvD,UAAM,YAAY,OAAO;AAAA,MACvB,mBAAmB,MAAM;AAAA,MACzB,mBAAmB,MAAM;AAAA,IAC3B;AACA,QAAI,cAAc,GAAG;AACnB,aAAO;AAAA,IACT;AAIA,UAAM,WAAW,WAAW,MAAM,IAAI,WAAW,MAAM;AACvD,QAAI,aAAa,GAAG;AAClB,aAAO;AAAA,IACT;AAGA,WAAO,kCAAkC,YAAY,MAAM,YAAY,IAAI;AAAA,EAC7E,WAAW,eAAe;AACxB,WAAO;AAAA,EACT,WAAW,eAAe;AACxB,WAAO;AAAA,EACT,OAAO;AACL,WAAO,kCAAkC,YAAY,MAAM,YAAY,IAAI;AAAA,EAC7E;AACF;AAGA,MAAM,gBAAgB,CAAC,SAAyB;AAC9C,QAAM,aAAa,KAAK,OAAO,IAAI;AACnC,SAAO,eAAe,KAAK,OAAO,KAAK,MAAM,GAAG,UAAU;AAC5D;AAOA,MAAM,kBAAkB,CAAC,SAAiD;AACxE,QAAM,OAAO,KAAK,QAAQ,KAAK,KAAK,OAAO,IAAI,CAAC;AAChD,SAAO,SAAS,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,MAAM,GAAG,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC,CAAC;AAC9E;AAWO,MAAM,oCAAoC,CAC/C,OACA,UACW;AACX,QAAM,YAAY,EAAE,SAAS,MAAM,aAAa,OAAO;AACvD,QAAM,YAAY,cAAc,KAAK,EAAE;AAAA,IACrC,cAAc,KAAK;AAAA,IACnB;AAAA,IACA;AAAA,EACF;AACA,MAAI,cAAc,GAAG;AACnB,WAAO;AAAA,EACT;AACA,QAAM,CAAC,OAAO,OAAO,IAAI,gBAAgB,KAAK;AAC9C,QAAM,CAAC,OAAO,OAAO,IAAI,gBAAgB,KAAK;AAC9C,QAAM,SAAS,MAAM,cAAc,OAAO,QAAW,SAAS;AAC9D,MAAI,WAAW,GAAG;AAChB,WAAO;AAAA,EACT;AACA,MAAI,YAAY,MAAM,YAAY,IAAI;AACpC,WAAO,YAAY,UAAU,IAAI,YAAY,KAAK,KAAK;AAAA,EACzD;AACA,SAAO,QAAQ,cAAc,SAAS,QAAW,SAAS;AAC5D;",
6
6
  "names": []
7
7
  }
@@ -22,4 +22,33 @@ export declare namespace Hdp {
22
22
  minDate: string;
23
23
  maxDate: string;
24
24
  }
25
+ /**
26
+ * The work of one cluster inside the activity window. HDP aggregates, thus
27
+ * one row comes back for each cluster and not one for each cluster and day.
28
+ */
29
+ interface ClusterActivityRow {
30
+ clusterId: string;
31
+ /**
32
+ * The last UTC day with work, as `YYYY-MM-DD`. When no day inside the
33
+ * window carries work, this field is absent. accounts-backend makes this
34
+ * string from the BigQuery DATE, thus no caller reads the shape that the
35
+ * BigQuery client gives.
36
+ */
37
+ lastActiveAt?: string;
38
+ processInstances: number;
39
+ }
40
+ /**
41
+ * Every cluster that HDP holds inside the window, for the whole fleet.
42
+ *
43
+ * The reader joins these rows against its own cluster table. HDP keeps the
44
+ * rows of clusters that no longer exist, thus the set is bigger than the
45
+ * live fleet.
46
+ */
47
+ interface ClusterActivity {
48
+ rows: ClusterActivityRow[];
49
+ /** The window that `rows` covers, in days. */
50
+ windowDays: number;
51
+ /** The time of the query, as an ISO instant. */
52
+ fetchedAt: string;
53
+ }
25
54
  }
@@ -4,6 +4,14 @@ import type { OrganizationToSalesPlanDto } from './OrganizationToSalesPlan.dto.j
4
4
  export type LicensingModelType = 'legacy' | '2025';
5
5
  export declare const VALID_LICENSING_MODELS: readonly LicensingModelType[];
6
6
  export declare const LICENSING_MODEL_DISPLAY_NAMES: Record<LicensingModelType, string>;
7
+ /**
8
+ * Upstream that holds a managed LLM key.
9
+ *
10
+ * `@camunda-cloud/featureflags` owns the `LlmProvider` type and its rollout
11
+ * rules. This package must not depend on that one, so the union is repeated
12
+ * here. Keep the two in step.
13
+ */
14
+ export type LlmProviderName = 'litellm' | 'openrouter';
7
15
  export interface OrganizationMetadata {
8
16
  clickedRequestPaidPlan?: boolean;
9
17
  salesForceId?: string;
@@ -13,10 +21,53 @@ export interface OrganizationMetadata {
13
21
  autoJoin?: AutoJoinAssignment;
14
22
  llm?: {
15
23
  tenantKey: string;
16
- teamId: string;
24
+ /**
25
+ * Team that holds the key on LiteLLM.
26
+ *
27
+ * Optional, and every call site already treats it as such. Old LiteLLM
28
+ * orgs got a key without a team. OpenRouter has no teams at all.
29
+ */
30
+ teamId?: string;
17
31
  created: number;
18
32
  /** Epoch ms of the last key rotation — set when the tenant key is regenerated. */
19
33
  updated?: number;
34
+ /**
35
+ * Upstream that holds this key. Absent means `litellm`, because every key
36
+ * from before the OpenRouter rollout is on LiteLLM.
37
+ *
38
+ * This value, and not the OpenRouter feature flag, decides where the
39
+ * read, block, top-up and delete paths look for the key. A flag change
40
+ * must never move a live key to a provider that does not hold it.
41
+ */
42
+ provider?: LlmProviderName;
43
+ /**
44
+ * Handle for the key on OpenRouter, which addresses a key by hash and not
45
+ * by its value. Absent for a LiteLLM key.
46
+ *
47
+ * Without it, an OpenRouter key can be found again only by a list request
48
+ * that matches on the name of the key.
49
+ */
50
+ keyHash?: string;
51
+ /**
52
+ * The key that a provider migration replaced, kept until the delete of
53
+ * that key is confirmed.
54
+ *
55
+ * The migration to OpenRouter writes the new key over `tenantKey` before
56
+ * it deletes the old one. Without this record the old key stays alive on
57
+ * the old upstream and nothing points at it. The migration removes this
58
+ * field when the old upstream confirms the delete, and it uses the field
59
+ * to find the orgs that a re-run must finish.
60
+ *
61
+ * Absent for every org that never moved upstream.
62
+ */
63
+ migratedFrom?: {
64
+ /** Upstream that holds the replaced key. */
65
+ provider: LlmProviderName;
66
+ /** The replaced key itself. It is a credential. Never log it. */
67
+ tenantKey: string;
68
+ /** Team that held the replaced key on LiteLLM. */
69
+ teamId?: string;
70
+ };
20
71
  };
21
72
  }
22
73
  export interface AutoJoinAssignment {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/dtos/Organization.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 { OrganizationPermissions } from '../types/OrganizationPermission.type.js'\nimport type { SalesPlanType } from '../types/SalesPlanType.type.js'\nimport type { OrganizationToSalesPlanDto } from './OrganizationToSalesPlan.dto.js'\n\nexport type LicensingModelType = 'legacy' | '2025'\n\nexport const VALID_LICENSING_MODELS: readonly LicensingModelType[] = [\n 'legacy',\n '2025',\n] as const\n\nexport const LICENSING_MODEL_DISPLAY_NAMES: Record<LicensingModelType, string> =\n {\n legacy: 'Legacy (PI, DI, TU)',\n '2025': '2025 (PI+Tenant)',\n } as const\n\nexport interface OrganizationMetadata {\n clickedRequestPaidPlan?: boolean\n salesForceId?: string\n enterpriseDevOnly?: boolean\n awsOnly?: boolean\n licensingModel?: LicensingModelType\n autoJoin?: AutoJoinAssignment\n llm?: {\n tenantKey: string\n teamId: string\n created: number\n /** Epoch ms of the last key rotation \u2014 set when the tenant key is regenerated. */\n updated?: number\n }\n}\n\nexport interface AutoJoinAssignment {\n departments: Array<{ hibobId: string; roles: string[] }>\n divisions: Array<{ hibobId: string; roles: string[] }>\n subdivisions: Array<{ hibobId: string; roles: string[] }>\n}\nexport interface OrganizationDto {\n uuid: string\n name: string\n organizationToSalesPlan?: OrganizationToSalesPlanDto\n permissions?: OrganizationPermissions\n metadata?: OrganizationMetadata\n created: Date\n internal: boolean\n}\n\nexport interface OrgOwner {\n owner: string\n email: string\n first: string\n last: string\n}\n\nexport interface OrgWithNameAndSalesPlan {\n uuid: string\n name: string | undefined\n salesPlanName: string | undefined\n salesPlanUuid: string | undefined\n salesPlanType: SalesPlanType | undefined\n}\n"],
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 { OrganizationPermissions } from '../types/OrganizationPermission.type.js'\nimport type { SalesPlanType } from '../types/SalesPlanType.type.js'\nimport type { OrganizationToSalesPlanDto } from './OrganizationToSalesPlan.dto.js'\n\nexport type LicensingModelType = 'legacy' | '2025'\n\nexport const VALID_LICENSING_MODELS: readonly LicensingModelType[] = [\n 'legacy',\n '2025',\n] as const\n\nexport const LICENSING_MODEL_DISPLAY_NAMES: Record<LicensingModelType, string> =\n {\n legacy: 'Legacy (PI, DI, TU)',\n '2025': '2025 (PI+Tenant)',\n } as const\n\n/**\n * Upstream that holds a managed LLM key.\n *\n * `@camunda-cloud/featureflags` owns the `LlmProvider` type and its rollout\n * rules. This package must not depend on that one, so the union is repeated\n * here. Keep the two in step.\n */\nexport type LlmProviderName = 'litellm' | 'openrouter'\n\nexport interface OrganizationMetadata {\n clickedRequestPaidPlan?: boolean\n salesForceId?: string\n enterpriseDevOnly?: boolean\n awsOnly?: boolean\n licensingModel?: LicensingModelType\n autoJoin?: AutoJoinAssignment\n llm?: {\n tenantKey: string\n /**\n * Team that holds the key on LiteLLM.\n *\n * Optional, and every call site already treats it as such. Old LiteLLM\n * orgs got a key without a team. OpenRouter has no teams at all.\n */\n teamId?: string\n created: number\n /** Epoch ms of the last key rotation \u2014 set when the tenant key is regenerated. */\n updated?: number\n /**\n * Upstream that holds this key. Absent means `litellm`, because every key\n * from before the OpenRouter rollout is on LiteLLM.\n *\n * This value, and not the OpenRouter feature flag, decides where the\n * read, block, top-up and delete paths look for the key. A flag change\n * must never move a live key to a provider that does not hold it.\n */\n provider?: LlmProviderName\n /**\n * Handle for the key on OpenRouter, which addresses a key by hash and not\n * by its value. Absent for a LiteLLM key.\n *\n * Without it, an OpenRouter key can be found again only by a list request\n * that matches on the name of the key.\n */\n keyHash?: string\n /**\n * The key that a provider migration replaced, kept until the delete of\n * that key is confirmed.\n *\n * The migration to OpenRouter writes the new key over `tenantKey` before\n * it deletes the old one. Without this record the old key stays alive on\n * the old upstream and nothing points at it. The migration removes this\n * field when the old upstream confirms the delete, and it uses the field\n * to find the orgs that a re-run must finish.\n *\n * Absent for every org that never moved upstream.\n */\n migratedFrom?: {\n /** Upstream that holds the replaced key. */\n provider: LlmProviderName\n /** The replaced key itself. It is a credential. Never log it. */\n tenantKey: string\n /** Team that held the replaced key on LiteLLM. */\n teamId?: string\n }\n }\n}\n\nexport interface AutoJoinAssignment {\n departments: Array<{ hibobId: string; roles: string[] }>\n divisions: Array<{ hibobId: string; roles: string[] }>\n subdivisions: Array<{ hibobId: string; roles: string[] }>\n}\nexport interface OrganizationDto {\n uuid: string\n name: string\n organizationToSalesPlan?: OrganizationToSalesPlanDto\n permissions?: OrganizationPermissions\n metadata?: OrganizationMetadata\n created: Date\n internal: boolean\n}\n\nexport interface OrgOwner {\n owner: string\n email: string\n first: string\n last: string\n}\n\nexport interface OrgWithNameAndSalesPlan {\n uuid: string\n name: string | undefined\n salesPlanName: string | undefined\n salesPlanUuid: string | undefined\n salesPlanType: SalesPlanType | undefined\n}\n"],
5
5
  "mappings": "AAYO,MAAM,yBAAwD;AAAA,EACnE;AAAA,EACA;AACF;AAEO,MAAM,gCACX;AAAA,EACE,QAAQ;AAAA,EACR,QAAQ;AACV;",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
1
  export declare enum AuditService {
2
2
  CONSOLE = "console",
3
- ACCOUNTS = "accounts"
3
+ ACCOUNTS = "accounts",
4
+ CONTROL = "control"
4
5
  }
@@ -1,6 +1,7 @@
1
1
  var AuditService = /* @__PURE__ */ ((AuditService2) => {
2
2
  AuditService2["CONSOLE"] = "console";
3
3
  AuditService2["ACCOUNTS"] = "accounts";
4
+ AuditService2["CONTROL"] = "control";
4
5
  return AuditService2;
5
6
  })(AuditService || {});
6
7
  export {