@camunda/ccma-shared-types 4.0.0 → 5.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 (31) hide show
  1. package/dist/approvals/use-cases.const.d.ts +0 -66
  2. package/dist/approvals/use-cases.const.js +2 -11
  3. package/dist/approvals/use-cases.const.js.map +2 -2
  4. package/dist/dtos/ControlV2.dto.d.ts +10 -0
  5. package/dist/dtos/ExternalWebapp.dto.d.ts +12 -0
  6. package/dist/dtos/FeatureFlags.dto.d.ts +51 -58
  7. package/dist/dtos/FeatureFlags.dto.js +0 -10
  8. package/dist/dtos/FeatureFlags.dto.js.map +3 -3
  9. package/dist/dtos/Hdp.dto.d.ts +153 -0
  10. package/dist/dtos/SalesPlan.dto.d.ts +25 -1
  11. package/dist/dtos/SalesPlan.dto.js +19 -6
  12. package/dist/dtos/SalesPlan.dto.js.map +2 -2
  13. package/dist/index.d.ts +7 -9
  14. package/dist/index.js +16 -26
  15. package/dist/index.js.map +2 -2
  16. package/dist/permissions/accounts.permissions.d.ts +0 -33
  17. package/dist/permissions/accounts.permissions.js +0 -31
  18. package/dist/permissions/accounts.permissions.js.map +2 -2
  19. package/dist/permissions/combo.js +0 -6
  20. package/dist/permissions/combo.js.map +2 -2
  21. package/dist/types/PreprodClusterTtl.type.d.ts +33 -0
  22. package/dist/types/PreprodClusterTtl.type.js +37 -0
  23. package/dist/types/PreprodClusterTtl.type.js.map +7 -0
  24. package/dist/types/hdp.types.d.ts +6 -0
  25. package/dist/types/hdp.types.js +3 -1
  26. package/dist/types/hdp.types.js.map +2 -2
  27. package/package.json +1 -1
  28. package/dist/dtos/FeatureFlagOverride.dto.d.ts +0 -226
  29. package/dist/dtos/FeatureFlagOverride.dto.js +0 -38
  30. package/dist/dtos/FeatureFlagOverride.dto.js.map +0 -7
  31. /package/dist/_tests/{dtos/FeatureFlagOverride.dto.spec.d.ts → types/PreprodClusterTtl.type.spec.d.ts} +0 -0
@@ -23,7 +23,6 @@ export declare const APPROVAL_USE_CASE_TYPES: {
23
23
  readonly clusterBulkSleepWakeup: "cluster-bulk-sleep-wakeup";
24
24
  readonly clusterBulkDeletion: "cluster-bulk-deletion";
25
25
  readonly clusterBulkSpecUpdate: "cluster-bulk-spec-update";
26
- readonly featureFlagOverride: "feature-flag-override";
27
26
  };
28
27
  export type ApprovalUseCaseType = (typeof APPROVAL_USE_CASE_TYPES)[keyof typeof APPROVAL_USE_CASE_TYPES];
29
28
  /**
@@ -47,68 +46,3 @@ export declare const EXTEND_TRIAL_MIN_DAYS = 15;
47
46
  * margin as the floor above.
48
47
  */
49
48
  export declare const EXTEND_TRIAL_MAX_DAYS = 61;
50
- /**
51
- * The shortest life of a feature flag override, in days.
52
- *
53
- * One day. An override that ends sooner than the next working day is a change
54
- * that nobody can look at before it goes away again.
55
- */
56
- export declare const FLAG_OVERRIDE_MIN_DAYS = 1;
57
- /**
58
- * The longest life of a feature flag override, in days.
59
- *
60
- * An override is a temporary thing. Without a ceiling it becomes a second,
61
- * invisible layer of configuration, and then nobody can answer "what runs on
62
- * prod?" from the source alone. A change that has to hold for longer belongs
63
- * in `defaults.ts`, where a reviewer reads it.
64
- */
65
- export declare const FLAG_OVERRIDE_MAX_DAYS = 90;
66
- /**
67
- * The life of an override when the person names no other number.
68
- *
69
- * Two weeks is long enough for a rollout to prove itself, and short enough
70
- * that a forgotten override goes away by itself.
71
- */
72
- export declare const FLAG_OVERRIDE_DEFAULT_DAYS = 14;
73
- /**
74
- * Whether a stored override reaches any reader yet.
75
- *
76
- * It is `false` today, and the write path is closed at both ends: the page
77
- * turns the Override button off, and the create route of control-backend
78
- * answers 404. It lives HERE, and not in the page, because both sides read
79
- * it. One switch cannot drift; a disabled button beside an open route is not
80
- * a closed door, and that is what the review found.
81
- *
82
- * ## Why it is still `false`, and what is NOT true any more
83
- *
84
- * This said that control-backend reads exactly ONE flag, `control.approvals`,
85
- * which takes no override, so the set of flags that this service reads and
86
- * the set that take an override do not meet. **That stopped being true while
87
- * this branch was open.** `main` added the annotations component, and
88
- * `requireAnnotationsEnabled` reads `request.flags.control.annotations`:
89
- *
90
- * ```
91
- * approvals.routes.ts:368 if (!request.flags.control.approvals) {
92
- * annotations.routes.ts:313 if (!request.flags.control.annotations) {
93
- * ```
94
- *
95
- * `control.annotations` is `overridable: true`, and `feature-flags.plugin.ts`
96
- * applies the overrides in an application `preHandler`, which runs BEFORE a
97
- * route-level one. So a STAGE override of that one flag would reach a real
98
- * reader today.
99
- *
100
- * The intersection is therefore no longer empty: it holds one flag of 28, in
101
- * one of the two scopes. That is the reason to keep this `false` rather than
102
- * to turn it on. One boolean cannot say "this flag in this scope reaches a
103
- * reader, and the other 27 do not": `true` would open a form that writes a
104
- * row and moves nothing for every flag but one, which is the failure this
105
- * constant exists to prevent, in the other direction.
106
- *
107
- * ## Phase 2
108
- *
109
- * Availability belongs per flag AND per scope, derived from which service
110
- * reads the flag and whether that reader passes an `orgId` — not from one
111
- * global switch. See section 8 of the design. Until then this stays `false`,
112
- * which is safe: it is closed, and never wrongly open.
113
- */
114
- export declare const OVERRIDES_REACH_A_READER = false;
@@ -10,22 +10,13 @@ const APPROVAL_USE_CASE_TYPES = {
10
10
  clusterBulkPlanTypeUpdate: "cluster-bulk-plan-type-update",
11
11
  clusterBulkSleepWakeup: "cluster-bulk-sleep-wakeup",
12
12
  clusterBulkDeletion: "cluster-bulk-deletion",
13
- clusterBulkSpecUpdate: "cluster-bulk-spec-update",
14
- featureFlagOverride: "feature-flag-override"
13
+ clusterBulkSpecUpdate: "cluster-bulk-spec-update"
15
14
  };
16
15
  const EXTEND_TRIAL_MIN_DAYS = 15;
17
16
  const EXTEND_TRIAL_MAX_DAYS = 61;
18
- const FLAG_OVERRIDE_MIN_DAYS = 1;
19
- const FLAG_OVERRIDE_MAX_DAYS = 90;
20
- const FLAG_OVERRIDE_DEFAULT_DAYS = 14;
21
- const OVERRIDES_REACH_A_READER = false;
22
17
  export {
23
18
  APPROVAL_USE_CASE_TYPES,
24
19
  EXTEND_TRIAL_MAX_DAYS,
25
- EXTEND_TRIAL_MIN_DAYS,
26
- FLAG_OVERRIDE_DEFAULT_DAYS,
27
- FLAG_OVERRIDE_MAX_DAYS,
28
- FLAG_OVERRIDE_MIN_DAYS,
29
- OVERRIDES_REACH_A_READER
20
+ EXTEND_TRIAL_MIN_DAYS
30
21
  };
31
22
  //# sourceMappingURL=use-cases.const.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/approvals/use-cases.const.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 names and the value ranges of the registered use cases.\n *\n * control-backend owns the registry, and a frontend reads it at run time\n * (`GET /api/approvals/use-cases`). Two things cannot wait for that answer:\n *\n * - **The identifier**, because a caller must NAME a use case to find it in the\n * registry, and to open a request of it.\n * - **The range of a payload field**, because a form holds the value before any\n * call goes out.\n *\n * Both live here, so the schema of the service and the form of the browser can\n * never hold different numbers.\n */\n\n/** The identifier of each use case, on the wire and in the table. */\nexport const APPROVAL_USE_CASE_TYPES = {\n sendToExpiredTrial: 'send-to-expired-trial',\n extendTrial: 'extend-trial',\n moveClusterOrg: 'move-cluster-org',\n // One use case for each type of a cluster bulk operation. The types need\n // different permissions and different approval counts, and a use case holds\n // one of each. See `cluster-bulk-operation.ts` beside this file.\n clusterBulkGenerationUpdate: 'cluster-bulk-generation-update',\n clusterBulkPlanUpdate: 'cluster-bulk-plan-update',\n clusterBulkPlanTypeUpdate: 'cluster-bulk-plan-type-update',\n clusterBulkSleepWakeup: 'cluster-bulk-sleep-wakeup',\n clusterBulkDeletion: 'cluster-bulk-deletion',\n clusterBulkSpecUpdate: 'cluster-bulk-spec-update',\n featureFlagOverride: 'feature-flag-override',\n} as const\n\nexport type ApprovalUseCaseType =\n (typeof APPROVAL_USE_CASE_TYPES)[keyof typeof APPROVAL_USE_CASE_TYPES]\n\n/**\n * The shortest extension that accounts-backend accepts.\n *\n * `POST /:orgId/extendtrial` refuses a date that is less than 14 days away\n * when the body carries `onBehalfOf`, and it measures those 14 days when the\n * CALL arrives. The floor here is 15 and not 14, for two reasons:\n *\n * - The two clocks are not the same instant. Accounts builds its own \"14 days\n * from now\" a few milliseconds after control-backend builds the date, so a\n * date of exactly 14 days is already in the past for the comparison.\n * - Accounts counts with `setDate`, which is calendar arithmetic. A change of\n * the daylight saving time moves its limit by an hour.\n */\nexport const EXTEND_TRIAL_MIN_DAYS = 15\n\n/**\n * The longest extension that accounts-backend accepts.\n *\n * The same route refuses a date more than 62 days away. 61 keeps the same\n * margin as the floor above.\n */\nexport const EXTEND_TRIAL_MAX_DAYS = 61\n\n/**\n * The shortest life of a feature flag override, in days.\n *\n * One day. An override that ends sooner than the next working day is a change\n * that nobody can look at before it goes away again.\n */\nexport const FLAG_OVERRIDE_MIN_DAYS = 1\n\n/**\n * The longest life of a feature flag override, in days.\n *\n * An override is a temporary thing. Without a ceiling it becomes a second,\n * invisible layer of configuration, and then nobody can answer \"what runs on\n * prod?\" from the source alone. A change that has to hold for longer belongs\n * in `defaults.ts`, where a reviewer reads it.\n */\nexport const FLAG_OVERRIDE_MAX_DAYS = 90\n\n/**\n * The life of an override when the person names no other number.\n *\n * Two weeks is long enough for a rollout to prove itself, and short enough\n * that a forgotten override goes away by itself.\n */\nexport const FLAG_OVERRIDE_DEFAULT_DAYS = 14\n\n/**\n * Whether a stored override reaches any reader yet.\n *\n * It is `false` today, and the write path is closed at both ends: the page\n * turns the Override button off, and the create route of control-backend\n * answers 404. It lives HERE, and not in the page, because both sides read\n * it. One switch cannot drift; a disabled button beside an open route is not\n * a closed door, and that is what the review found.\n *\n * ## Why it is still `false`, and what is NOT true any more\n *\n * This said that control-backend reads exactly ONE flag, `control.approvals`,\n * which takes no override, so the set of flags that this service reads and\n * the set that take an override do not meet. **That stopped being true while\n * this branch was open.** `main` added the annotations component, and\n * `requireAnnotationsEnabled` reads `request.flags.control.annotations`:\n *\n * ```\n * approvals.routes.ts:368 if (!request.flags.control.approvals) {\n * annotations.routes.ts:313 if (!request.flags.control.annotations) {\n * ```\n *\n * `control.annotations` is `overridable: true`, and `feature-flags.plugin.ts`\n * applies the overrides in an application `preHandler`, which runs BEFORE a\n * route-level one. So a STAGE override of that one flag would reach a real\n * reader today.\n *\n * The intersection is therefore no longer empty: it holds one flag of 28, in\n * one of the two scopes. That is the reason to keep this `false` rather than\n * to turn it on. One boolean cannot say \"this flag in this scope reaches a\n * reader, and the other 27 do not\": `true` would open a form that writes a\n * row and moves nothing for every flag but one, which is the failure this\n * constant exists to prevent, in the other direction.\n *\n * ## Phase 2\n *\n * Availability belongs per flag AND per scope, derived from which service\n * reads the flag and whether that reader passes an `orgId` \u2014 not from one\n * global switch. See section 8 of the design. Until then this stays `false`,\n * which is safe: it is closed, and never wrongly open.\n */\nexport const OVERRIDES_REACH_A_READER = false\n"],
5
- "mappings": "AAsBO,MAAM,0BAA0B;AAAA,EACrC,oBAAoB;AAAA,EACpB,aAAa;AAAA,EACb,gBAAgB;AAAA;AAAA;AAAA;AAAA,EAIhB,6BAA6B;AAAA,EAC7B,uBAAuB;AAAA,EACvB,2BAA2B;AAAA,EAC3B,wBAAwB;AAAA,EACxB,qBAAqB;AAAA,EACrB,uBAAuB;AAAA,EACvB,qBAAqB;AACvB;AAkBO,MAAM,wBAAwB;AAQ9B,MAAM,wBAAwB;AAQ9B,MAAM,yBAAyB;AAU/B,MAAM,yBAAyB;AAQ/B,MAAM,6BAA6B;AA2CnC,MAAM,2BAA2B;",
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 names and the value ranges of the registered use cases.\n *\n * control-backend owns the registry, and a frontend reads it at run time\n * (`GET /api/approvals/use-cases`). Two things cannot wait for that answer:\n *\n * - **The identifier**, because a caller must NAME a use case to find it in the\n * registry, and to open a request of it.\n * - **The range of a payload field**, because a form holds the value before any\n * call goes out.\n *\n * Both live here, so the schema of the service and the form of the browser can\n * never hold different numbers.\n */\n\n/** The identifier of each use case, on the wire and in the table. */\nexport const APPROVAL_USE_CASE_TYPES = {\n sendToExpiredTrial: 'send-to-expired-trial',\n extendTrial: 'extend-trial',\n moveClusterOrg: 'move-cluster-org',\n // One use case for each type of a cluster bulk operation. The types need\n // different permissions and different approval counts, and a use case holds\n // one of each. See `cluster-bulk-operation.ts` beside this file.\n clusterBulkGenerationUpdate: 'cluster-bulk-generation-update',\n clusterBulkPlanUpdate: 'cluster-bulk-plan-update',\n clusterBulkPlanTypeUpdate: 'cluster-bulk-plan-type-update',\n clusterBulkSleepWakeup: 'cluster-bulk-sleep-wakeup',\n clusterBulkDeletion: 'cluster-bulk-deletion',\n clusterBulkSpecUpdate: 'cluster-bulk-spec-update',\n} as const\n\nexport type ApprovalUseCaseType =\n (typeof APPROVAL_USE_CASE_TYPES)[keyof typeof APPROVAL_USE_CASE_TYPES]\n\n/**\n * The shortest extension that accounts-backend accepts.\n *\n * `POST /:orgId/extendtrial` refuses a date that is less than 14 days away\n * when the body carries `onBehalfOf`, and it measures those 14 days when the\n * CALL arrives. The floor here is 15 and not 14, for two reasons:\n *\n * - The two clocks are not the same instant. Accounts builds its own \"14 days\n * from now\" a few milliseconds after control-backend builds the date, so a\n * date of exactly 14 days is already in the past for the comparison.\n * - Accounts counts with `setDate`, which is calendar arithmetic. A change of\n * the daylight saving time moves its limit by an hour.\n */\nexport const EXTEND_TRIAL_MIN_DAYS = 15\n\n/**\n * The longest extension that accounts-backend accepts.\n *\n * The same route refuses a date more than 62 days away. 61 keeps the same\n * margin as the floor above.\n */\nexport const EXTEND_TRIAL_MAX_DAYS = 61\n"],
5
+ "mappings": "AAsBO,MAAM,0BAA0B;AAAA,EACrC,oBAAoB;AAAA,EACpB,aAAa;AAAA,EACb,gBAAgB;AAAA;AAAA;AAAA;AAAA,EAIhB,6BAA6B;AAAA,EAC7B,uBAAuB;AAAA,EACvB,2BAA2B;AAAA,EAC3B,wBAAwB;AAAA,EACxB,qBAAqB;AAAA,EACrB,uBAAuB;AACzB;AAkBO,MAAM,wBAAwB;AAQ9B,MAAM,wBAAwB;",
6
6
  "names": []
7
7
  }
@@ -178,6 +178,12 @@ export interface ClusterListItemV2Dto {
178
178
  /** Session idle timeout. */
179
179
  sessionMaxInactiveIntervalMinutes?: number;
180
180
  sessionHeartbeatEnabled?: boolean;
181
+ /** BYOK: `encryption` is `External`. The key can still be missing. */
182
+ byokEnabled?: boolean;
183
+ /** The cluster has at least one external monitoring (BYOM) client. */
184
+ externalMonitoringEnabled?: boolean;
185
+ /** The cluster has a `secure_connectivity` row (AWS PrivateLink). */
186
+ privateConnectivityEnabled?: boolean;
181
187
  }
182
188
  /** Detail extends the list shape; richer fields are added in Phase 3. */
183
189
  export type ClusterDetailV2Dto = ClusterListItemV2Dto;
@@ -299,6 +305,10 @@ export interface ListClustersV2Params extends CursorParams {
299
305
  internal?: boolean;
300
306
  /** Only clusters with (true) / without (false) an active modification. */
301
307
  hasActiveModification?: boolean;
308
+ /** Only clusters with (true) / without (false) the flag of the same name. */
309
+ byokEnabled?: boolean;
310
+ externalMonitoringEnabled?: boolean;
311
+ privateConnectivityEnabled?: boolean;
302
312
  /**
303
313
  * Zeebe minor-version prefix match on the generation's versions (e.g.
304
314
  * "8.7" matches zeebe 8.7.x).
@@ -58,6 +58,18 @@ export interface ExternalOrganizationDto {
58
58
  };
59
59
  };
60
60
  }
61
+ /**
62
+ * Clickwrap config as returned by `GET external/clickwrap-config`.
63
+ *
64
+ * Names the PactSafe group that records a customer's opt-in to alpha features.
65
+ * The deployed accounts-backend owns the identifiers, thus a host does not hold
66
+ * a copy. Each field is optional: the backend omits it when its environment
67
+ * variable is unset, and a host with no `accessId` shows no opt-in.
68
+ */
69
+ export interface ExternalClickwrapConfigDto {
70
+ accessId?: string;
71
+ betaGroupKey?: string;
72
+ }
61
73
  /**
62
74
  * Cluster as returned by `GET external/organizations/:orgId/clusters`.
63
75
  *
@@ -2,65 +2,58 @@
2
2
  * This type gives the shape of the feature flags of the management apps. It is
3
3
  * also the body of `GET external/feature-flags` on accounts-backend.
4
4
  *
5
- * The GROUP is typed and the FLAG is not. A flag can be made at run time, from
6
- * the Feature flags page of Control, thus no interface can name every flag. A
7
- * new GROUP is still a code change, in `FLAG_GROUPS` below.
8
- *
9
- * CAUTION: A wrong flag name is no longer a compile error.
10
- * `FEATURES.console.byoidpp` reads `undefined`, which is falsy, so the
11
- * feature turns off in silence. One thing answers that TODAY:
12
- *
13
- * - `readFlag(features, 'console.byoidp')` of `@camunda-cloud/featureflags`
14
- * answers a strict `boolean` for any path. Prefer it at a new call site.
15
- *
16
- * A second answer is PLANNED and does not exist yet:
17
- * `packages/checks/src/feature-flag-check` finds a flag that no source
18
- * declares but some source reads, and fails the build. It must land before
19
- * wave R2 moves a reader onto the table. Until then the compiler catches a
20
- * wrong GROUP and nothing catches a wrong flag name. See section 5.3 of
21
- * `docs/superpowers/specs/2026-09-23-runtime-feature-flags-design.md`.
22
- *
23
- * The value is not here. `FLAG_RULES` in `@camunda-cloud/featureflags` holds
24
- * the rule of each flag as DATA, `defaults(context)` evaluates it, and
25
- * `resolveFeatures` puts a stored override in front of the answer for one
26
- * organization, one mail domain or one person. `defaults` is the floor, and
27
- * not the runtime answer.
28
- *
29
- * What a flag gates, who owns it and why it exists live in `FLAG_REGISTRY` of
30
- * the same package while the code still holds the flags, and in the
31
- * `feature_flag` table of control-backend after that. They used to be here,
32
- * one comment for each field, and that list cannot survive a flag that no
33
- * field declares.
34
- *
35
- * This type is here and not in `@camunda-cloud/featureflags` because that
36
- * package is private, thus a published package (`@camunda/ccma-api`,
37
- * `@camunda/ccma-saas-frontend`) cannot name the type from there.
38
- * `@camunda-cloud/featureflags` re-exports `Features` from here.
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.
39
11
  *
40
12
  * A c4 host reads one flag with `useC4FeatureFlag('console.appIntegrations')`
41
13
  * — a dot path into this object.
42
14
  */
43
- /**
44
- * Every group of flags.
45
- *
46
- * A group is a code change, because it is the one part of the shape that a
47
- * reader names in a type.
48
- *
49
- * `hub` carries the flags of Camunda Hub, which embeds c4 widgets and reads
50
- * them with `useC4FeatureFlag`. Nothing in this repository reads a `hub`
51
- * flag, thus the group holds no code rule and only ever holds rows. The
52
- * stale-flag check therefore skips it: a check that looks for a reader finds
53
- * none, and that is correct and not a fault.
54
- */
55
- export declare const FLAG_GROUPS: readonly ["console", "control", "accounts", "shared", "hub"];
56
- export type FlagGroup = (typeof FLAG_GROUPS)[number];
57
- /**
58
- * The flags of one group.
59
- *
60
- * The key is a plain `string` and not a union, because a person can make a
61
- * flag at run time. The value is a `boolean` and never `any`: `any` would
62
- * also accept `features.console.foo.bar`, and the tree is one level deep
63
- * under its group.
64
- */
65
- export type FlagValues = Record<string, boolean>;
66
- export type Features = Record<FlagGroup, FlagValues>;
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
+ orgClusterDefaults: boolean;
29
+ releaseTrainSanityFix: boolean;
30
+ hubHelpCenterLinks: boolean;
31
+ legacyModelerApiNaming: boolean;
32
+ backupScheduleApi: boolean;
33
+ clusterCreationBlocked: boolean;
34
+ deferSpecChangeWhileAsleep: boolean;
35
+ clusterDuplication: boolean;
36
+ hubUpgradeBanner: boolean;
37
+ };
38
+ control: {
39
+ frontendTelemetry: boolean;
40
+ approvals: boolean;
41
+ annotations: boolean;
42
+ clusterHostingCostBreakdown: boolean;
43
+ /** Per-cluster disk usage chart (Elasticsearch and Zeebe) on the cluster page. */
44
+ elasticsearchUsageChart: boolean;
45
+ fleetHostingCost: boolean;
46
+ };
47
+ accounts: {
48
+ moveClusterOrg: boolean;
49
+ orgLimitAuditLog: boolean;
50
+ orgReservationAuditLog: boolean;
51
+ deleteLiteLLMKeyOnTrialExpiry: boolean;
52
+ };
53
+ shared: {
54
+ cyberpunkBrutalism: boolean;
55
+ tenantsValueMetric: boolean;
56
+ hdpValueMetricsFor2025: boolean;
57
+ permissionsRework: boolean;
58
+ };
59
+ }
@@ -1,11 +1 @@
1
- const FLAG_GROUPS = [
2
- "console",
3
- "control",
4
- "accounts",
5
- "shared",
6
- "hub"
7
- ];
8
- export {
9
- FLAG_GROUPS
10
- };
11
1
  //# sourceMappingURL=FeatureFlags.dto.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/dtos/FeatureFlags.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 * This type gives the shape of the feature flags of the management apps. It is\n * also the body of `GET external/feature-flags` on accounts-backend.\n *\n * The GROUP is typed and the FLAG is not. A flag can be made at run time, from\n * the Feature flags page of Control, thus no interface can name every flag. A\n * new GROUP is still a code change, in `FLAG_GROUPS` below.\n *\n * CAUTION: A wrong flag name is no longer a compile error.\n * `FEATURES.console.byoidpp` reads `undefined`, which is falsy, so the\n * feature turns off in silence. One thing answers that TODAY:\n *\n * - `readFlag(features, 'console.byoidp')` of `@camunda-cloud/featureflags`\n * answers a strict `boolean` for any path. Prefer it at a new call site.\n *\n * A second answer is PLANNED and does not exist yet:\n * `packages/checks/src/feature-flag-check` finds a flag that no source\n * declares but some source reads, and fails the build. It must land before\n * wave R2 moves a reader onto the table. Until then the compiler catches a\n * wrong GROUP and nothing catches a wrong flag name. See section 5.3 of\n * `docs/superpowers/specs/2026-09-23-runtime-feature-flags-design.md`.\n *\n * The value is not here. `FLAG_RULES` in `@camunda-cloud/featureflags` holds\n * the rule of each flag as DATA, `defaults(context)` evaluates it, and\n * `resolveFeatures` puts a stored override in front of the answer for one\n * organization, one mail domain or one person. `defaults` is the floor, and\n * not the runtime answer.\n *\n * What a flag gates, who owns it and why it exists live in `FLAG_REGISTRY` of\n * the same package while the code still holds the flags, and in the\n * `feature_flag` table of control-backend after that. They used to be here,\n * one comment for each field, and that list cannot survive a flag that no\n * field declares.\n *\n * This type is here and not in `@camunda-cloud/featureflags` because that\n * package is private, thus a published package (`@camunda/ccma-api`,\n * `@camunda/ccma-saas-frontend`) cannot name the type from there.\n * `@camunda-cloud/featureflags` re-exports `Features` from here.\n *\n * A c4 host reads one flag with `useC4FeatureFlag('console.appIntegrations')`\n * \u2014 a dot path into this object.\n */\n\n/**\n * Every group of flags.\n *\n * A group is a code change, because it is the one part of the shape that a\n * reader names in a type.\n *\n * `hub` carries the flags of Camunda Hub, which embeds c4 widgets and reads\n * them with `useC4FeatureFlag`. Nothing in this repository reads a `hub`\n * flag, thus the group holds no code rule and only ever holds rows. The\n * stale-flag check therefore skips it: a check that looks for a reader finds\n * none, and that is correct and not a fault.\n */\nexport const FLAG_GROUPS = [\n 'console',\n 'control',\n 'accounts',\n 'shared',\n 'hub',\n] as const\n\nexport type FlagGroup = (typeof FLAG_GROUPS)[number]\n\n/**\n * The flags of one group.\n *\n * The key is a plain `string` and not a union, because a person can make a\n * flag at run time. The value is a `boolean` and never `any`: `any` would\n * also accept `features.console.foo.bar`, and the tree is one level deep\n * under its group.\n */\nexport type FlagValues = Record<string, boolean>\n\nexport type Features = Record<FlagGroup, FlagValues>\n"],
5
- "mappings": "AA6DO,MAAM,cAAc;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
6
  "names": []
7
7
  }
@@ -51,4 +51,157 @@ export declare namespace Hdp {
51
51
  /** The time of the query, as an ISO instant. */
52
52
  fetchedAt: string;
53
53
  }
54
+ /**
55
+ * A cluster's hosting cost over a trailing window.
56
+ *
57
+ * Both windows end at the anchor day: the latest day that HDP has loaded
58
+ * in the whole cost table, searched over the last `windowDays` days. The
59
+ * cost table loads with a delay, so the anchor is often yesterday, not
60
+ * today. It is today when the table is current. The anchor is table-wide
61
+ * on purpose: it measures the load lag only, and is the same for every
62
+ * cluster. A cluster that stopped costing money does not move its own
63
+ * windows, so its cost shows as a drop. Counting the anchor as day 0,
64
+ * the current window is days `0` through `windowDays - 1` back, and the
65
+ * prior window is days `windowDays` through `2 * windowDays - 1` back.
66
+ * Each window holds exactly `windowDays` days, with no gap and no overlap.
67
+ *
68
+ * `hostingCostUsd` is the loaded cost: the cluster's direct spend plus its
69
+ * share of shared infrastructure. It is what the cluster actually cost, not
70
+ * `raw_cost`, the pre-allocation figure that only reconciles to the cloud
71
+ * bill.
72
+ */
73
+ interface ClusterHostingCost {
74
+ clusterId: string;
75
+ /** The window `hostingCostUsd` covers, in days, ending at the anchor day. */
76
+ windowDays: number;
77
+ hostingCostUsd: number;
78
+ /**
79
+ * The loaded cost of the `windowDays`-day window immediately before this
80
+ * one — days `windowDays` through `2 * windowDays - 1` back from the
81
+ * anchor day (day 0), with no gap and no overlap against the current
82
+ * window. Absent when
83
+ * HDP has no rows there, which is normal for a cluster younger than
84
+ * `windowDays` days: it was not billed yet during the whole prior
85
+ * window. A cluster older than that already has at least a partial
86
+ * prior window, so this field is present (even if small) for any
87
+ * cluster older than `windowDays` days.
88
+ */
89
+ priorHostingCostUsd?: number;
90
+ /**
91
+ * How the current window compares to the prior one: `up`/`down` when the
92
+ * change exceeds a ±5% threshold, `flat` when it is within it. Absent
93
+ * whenever `priorHostingCostUsd` is — a percent change needs a real,
94
+ * non-zero base to compare against, so a cluster with no prior-window
95
+ * data gets no trend rather than a misleading "up" or "down".
96
+ */
97
+ trend?: 'up' | 'down' | 'flat';
98
+ /**
99
+ * The cost-type split (COGS vs R&D) of `hostingCostUsd` over the same
100
+ * window, from the `cost_type` column of the same HDP table. Present
101
+ * only when `control.clusterHostingCostBreakdown` is on for the caller
102
+ * — see `HdpDataHostingCostController.getHostingCost`. Absent rather
103
+ * than computed-then-hidden when the flag is off.
104
+ */
105
+ costTypeBreakdown?: ClusterHostingCostByType;
106
+ /**
107
+ * The allocation-pool split of `hostingCostUsd` over the same window,
108
+ * from the nine `allocated_*` columns of the same HDP table — see
109
+ * {@link ClusterHostingCostAllocation}. Gated by the same flag as
110
+ * `costTypeBreakdown`, and absent for the same reason when it is off.
111
+ */
112
+ allocationBreakdown?: ClusterHostingCostAllocation;
113
+ }
114
+ /**
115
+ * The cost-type split of a `ClusterHostingCost` window. `cost_type`
116
+ * classification is point-in-time against the FinOps mapping sheet, so
117
+ * re-running this query later can classify the same historic day's spend
118
+ * differently.
119
+ *
120
+ * `cogsUsd + rdUsd + otherUsd` reconciles to the window's
121
+ * `hostingCostUsd` to within half a cent. `otherUsd` is not read from
122
+ * the table with its own `cost_type` filter: it is `hostingCostUsd` minus
123
+ * the other two, rounded to the cent. Only `otherUsd` is rounded;
124
+ * `hostingCostUsd`, `cogsUsd` and `rdUsd` keep the full precision of the
125
+ * table, so a sum can differ from the total by that rounding. A `cost_type` value
126
+ * this split does not know about (a third category, or none at all)
127
+ * still lands somewhere visible instead of vanishing from the total. A
128
+ * negative `otherUsd` is possible in principle (`cogsUsd + rdUsd`
129
+ * exceeding `hostingCostUsd`) and is left visible rather than clamped to
130
+ * 0, because it would mean the two named sums are wrong, not that the
131
+ * remainder is — clamping would hide exactly the bug a Camundi most
132
+ * needs to see.
133
+ */
134
+ interface ClusterHostingCostByType {
135
+ /** Cost of Goods Sold: the loaded cost classified as `cost_type = 'COGS'`. */
136
+ cogsUsd: number;
137
+ /** Research & Development: the loaded cost classified as `cost_type = 'R&D'`. */
138
+ rdUsd: number;
139
+ /** The window's cost that `cogsUsd` and `rdUsd` do not account for. */
140
+ otherUsd: number;
141
+ }
142
+ /**
143
+ * The allocation-pool split of a `ClusterHostingCost` window: up to nine
144
+ * `allocated_*` columns of the same HDP table, each summed over the
145
+ * window the same way `hostingCostUsd` is. On a `direct`/`nonprod_direct`
146
+ * row, each pool is the dollar share of `loaded_cost` that one allocation
147
+ * reason contributes.
148
+ *
149
+ * Every field is optional and present only when nonzero for the window:
150
+ * a cluster rarely has all nine populated (most report a nonzero value in
151
+ * only one or two), and a $0 line for the rest would be noise rather than
152
+ * signal. `HdpDataHostingCostController.getHostingCost` filters the zero
153
+ * ones out before returning, and leaves `allocationBreakdown` itself
154
+ * absent when none of the nine are nonzero.
155
+ *
156
+ * These fields do NOT have to sum to `hostingCostUsd`, or to
157
+ * `hostingCostUsd` minus `costTypeBreakdown`'s fields: `toolingLicenseUsd`
158
+ * adds a cost (SaaS vendor tooling) rather than reallocating an existing
159
+ * one, so the pools present can sum past the window's total. Read each
160
+ * field as its own dollar amount, not as a share of a reconciling whole.
161
+ */
162
+ interface ClusterHostingCostAllocation {
163
+ /** Share of shared infra namespaces inside the same client project. */
164
+ infraNamespaceUsd?: number;
165
+ /** Share of idle purchased GKE capacity. */
166
+ gkeUnallocatedUsd?: number;
167
+ /** Share of GKE system overhead (kubelet, kube-proxy, containerd). */
168
+ gkeSystemOverheadUsd?: number;
169
+ /** Share of spend-based CUDs that GKE cannot attribute to a namespace. */
170
+ gkeUnsupportedSkuUsd?: number;
171
+ /** Share of SKUs that GKE has not yet processed. */
172
+ gkeUnknownUsd?: number;
173
+ /** Share of `saas_prod` support projects. Paid clusters only. */
174
+ saasProdSupportUsd?: number;
175
+ /** Share of the account that serves both cloud estates. Paid clusters only. */
176
+ awsGcpSharedUsd?: number;
177
+ /** Share of SaaS vendor tooling (Tsuga, Cloudflare, DoiT, Terraform). Adds cost; does not reallocate it. */
178
+ toolingLicenseUsd?: number;
179
+ /** Share of shared cost inside the same `saas_non_prod` project. */
180
+ nonprodSharedUsd?: number;
181
+ }
182
+ /** The loaded cost of one cluster over the fleet cost window. */
183
+ interface FleetHostingCostRow {
184
+ clusterId: string;
185
+ hostingCostUsd: number;
186
+ }
187
+ /**
188
+ * The loaded hosting cost of every cluster that HDP holds inside the
189
+ * window, for the whole fleet. Each row is the same number as
190
+ * `ClusterHostingCost.hostingCostUsd` gives for that cluster: same table,
191
+ * same `loaded_cost` column, same window that ends at the table-wide anchor
192
+ * day.
193
+ *
194
+ * HDP keeps the rows of clusters that no longer exist, thus the reader
195
+ * matches these rows against its own cluster list. A cluster with no row
196
+ * has no cost data in the window. That is not a cost of 0.
197
+ */
198
+ interface FleetHostingCost {
199
+ rows: FleetHostingCostRow[];
200
+ /** The window that `rows` covers, in days, ending at `anchorDay`. */
201
+ windowDays: number;
202
+ /** The last day of the window, as `YYYY-MM-DD`. Absent when the table has no recent row. */
203
+ anchorDay?: string;
204
+ /** The time of the read, as an ISO instant. */
205
+ fetchedAt: string;
206
+ }
54
207
  }
@@ -64,6 +64,16 @@ export interface Limits extends ClusterLimits {
64
64
  * Users who also hold Member, Admin, Owner, or Operations Engineer are unaffected.
65
65
  */
66
66
  developerNoClusterCreate?: boolean;
67
+ /**
68
+ * On dev and int only: the nightly delete cron deletes every cluster of the
69
+ * org that is older than this number of hours. It asks nobody. Because the
70
+ * cron runs once a day, a cluster can live up to 24 hours longer. We set it
71
+ * on our own test orgs, because a paused cluster keeps its disks and costs
72
+ * money. A sales plan can hold it too: an org copies the limits of its plan
73
+ * when it gets the plan, thus every later org of that plan gets it. An
74
+ * editor on prod does not offer it.
75
+ */
76
+ preprodClusterTtlHours?: number;
67
77
  }
68
78
  export declare const RETENTION_LIMIT_MINIMUM: {
69
79
  operate: number;
@@ -105,6 +115,8 @@ export declare function allSalesPlanLimits(): {
105
115
  description: string;
106
116
  isClusterBasedLimit: boolean;
107
117
  validate?: (input: number) => boolean;
118
+ /** An editor on prod does not offer the limit, because only dev and int act on it. */
119
+ preprodOnly?: boolean;
108
120
  }[];
109
121
  export declare function unusedClusterBasedLimits(limits: ClusterLimits): {
110
122
  key: string;
@@ -112,7 +124,19 @@ export declare function unusedClusterBasedLimits(limits: ClusterLimits): {
112
124
  description: string;
113
125
  isClusterBasedLimit: boolean;
114
126
  }[];
115
- export declare function unusedSalesPlanLimits(limits: Limits): {
127
+ /**
128
+ * Returns whether an editor on the stage offers the limit. A `preprodOnly`
129
+ * limit is offered only on dev and int. Every other limit is offered on
130
+ * every stage.
131
+ */
132
+ export declare function isLimitOfferedOnStage(limit: {
133
+ preprodOnly?: boolean;
134
+ }, stage: string): boolean;
135
+ /**
136
+ * Returns the limits that `limits` does not set yet. With a `stage`, it also
137
+ * leaves out the limits that the stage does not offer.
138
+ */
139
+ export declare function unusedSalesPlanLimits(limits: Limits, stage?: string): {
116
140
  key: string;
117
141
  type: 'number' | 'boolean' | 'string';
118
142
  description: string;
@@ -1,3 +1,4 @@
1
+ import { isPreprodClusterTtlStage } from "../types/PreprodClusterTtl.type.js";
1
2
  const RETENTION_LIMIT_MINIMUM = {
2
3
  operate: 5,
3
4
  tasklist: 5,
@@ -31,28 +32,28 @@ function allClusterBasedLimits() {
31
32
  type: "number",
32
33
  isClusterBasedLimit: true,
33
34
  description: `Data retention in days for Operate. default = ${RETENTION_LIMIT_DEFAULT.operate}days. Min = ${RETENTION_LIMIT_MINIMUM.operate}days, Max = ${RETENTION_LIMIT_MAXIMUM.operate}days.`,
34
- validate: (input) => input >= RETENTION_LIMIT_MINIMUM.operate && input <= RETENTION_LIMIT_MAXIMUM.operate
35
+ validate: (input) => Number.isInteger(input) && input >= RETENTION_LIMIT_MINIMUM.operate && input <= RETENTION_LIMIT_MAXIMUM.operate
35
36
  },
36
37
  {
37
38
  key: "dataRetentionTasklist",
38
39
  type: "number",
39
40
  isClusterBasedLimit: true,
40
41
  description: `Data retention in days for Tasklist. default = ${RETENTION_LIMIT_DEFAULT.tasklist}days. Min = ${RETENTION_LIMIT_MINIMUM.tasklist}days, Max = ${RETENTION_LIMIT_MAXIMUM.tasklist}days.`,
41
- validate: (input) => input >= RETENTION_LIMIT_MINIMUM.tasklist && input <= RETENTION_LIMIT_MAXIMUM.tasklist
42
+ validate: (input) => Number.isInteger(input) && input >= RETENTION_LIMIT_MINIMUM.tasklist && input <= RETENTION_LIMIT_MAXIMUM.tasklist
42
43
  },
43
44
  {
44
45
  key: "dataRetentionOptimize",
45
46
  type: "number",
46
47
  isClusterBasedLimit: true,
47
48
  description: `Data retention in days for Optimize. default = ${RETENTION_LIMIT_DEFAULT.optimize}days. Min = ${RETENTION_LIMIT_MINIMUM.optimize}days, Max = ${RETENTION_LIMIT_MAXIMUM.optimize}days.`,
48
- validate: (input) => input >= RETENTION_LIMIT_MINIMUM.optimize && input <= RETENTION_LIMIT_MAXIMUM.optimize
49
+ validate: (input) => Number.isInteger(input) && input >= RETENTION_LIMIT_MINIMUM.optimize && input <= RETENTION_LIMIT_MAXIMUM.optimize
49
50
  },
50
51
  {
51
52
  key: "unifiedDataRetention",
52
53
  type: "number",
53
54
  isClusterBasedLimit: true,
54
55
  description: `Unified data retention in days for Zeebe broker. default = ${RETENTION_LIMIT_DEFAULT.unified}days. Min = ${RETENTION_LIMIT_MINIMUM.unified}days, Max = ${RETENTION_LIMIT_MAXIMUM.unified}days.`,
55
- validate: (input) => input >= RETENTION_LIMIT_MINIMUM.unified && input <= RETENTION_LIMIT_MAXIMUM.unified
56
+ validate: (input) => Number.isInteger(input) && input >= RETENTION_LIMIT_MINIMUM.unified && input <= RETENTION_LIMIT_MAXIMUM.unified
56
57
  }
57
58
  ];
58
59
  }
@@ -152,6 +153,14 @@ function allSalesPlanLimits() {
152
153
  description: "when enabled, users with only the Developer role cannot create clusters in this org. Admins, Owners, Members, and Operations Engineers are unaffected.",
153
154
  isClusterBasedLimit: false
154
155
  },
156
+ {
157
+ key: "preprodClusterTtlHours",
158
+ type: "number",
159
+ description: "dev and int only: the nightly delete cron deletes every cluster of this org that is older than N hours, with no confirmation. On a sales plan, every org that gets the plan later gets the limit too. For our own test orgs. default = not active",
160
+ isClusterBasedLimit: false,
161
+ validate: (input) => Number.isInteger(input) && input >= 1,
162
+ preprodOnly: true
163
+ },
155
164
  ...allClusterBasedLimits()
156
165
  ];
157
166
  }
@@ -160,9 +169,12 @@ function unusedClusterBasedLimits(limits) {
160
169
  (current) => !(Object.keys(limits).includes(current.key) && limits[current.key] !== null)
161
170
  );
162
171
  }
163
- function unusedSalesPlanLimits(limits) {
172
+ function isLimitOfferedOnStage(limit, stage) {
173
+ return !limit.preprodOnly || isPreprodClusterTtlStage(stage);
174
+ }
175
+ function unusedSalesPlanLimits(limits, stage) {
164
176
  return allSalesPlanLimits().filter(
165
- (current) => !(Object.keys(limits).includes(current.key) && limits[current.key] !== null)
177
+ (current) => !(Object.keys(limits).includes(current.key) && limits[current.key] !== null) && (stage === void 0 || isLimitOfferedOnStage(current, stage))
166
178
  );
167
179
  }
168
180
  export {
@@ -174,6 +186,7 @@ export {
174
186
  allClusterBasedLimits,
175
187
  allSalesPlanLimits,
176
188
  deprecatedLimit,
189
+ isLimitOfferedOnStage,
177
190
  unusedClusterBasedLimits,
178
191
  unusedSalesPlanLimits
179
192
  };