@camunda/ccma-shared-types 2.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_tests/approvals/cluster-bulk-operation.spec.d.ts +1 -0
- package/dist/_tests/dtos/FeatureFlagOverride.dto.spec.d.ts +1 -0
- package/dist/_tests/permissions/session-permissions.spec.d.ts +1 -0
- package/dist/annotations/policy.d.ts +98 -0
- package/dist/annotations/policy.js +61 -0
- package/dist/annotations/policy.js.map +7 -0
- package/dist/approvals/cluster-bulk-operation.d.ts +196 -0
- package/dist/approvals/cluster-bulk-operation.js +127 -0
- package/dist/approvals/cluster-bulk-operation.js.map +7 -0
- package/dist/approvals/policy.d.ts +138 -9
- package/dist/approvals/policy.js +100 -23
- package/dist/approvals/policy.js.map +2 -2
- package/dist/approvals/use-cases.const.d.ts +114 -0
- package/dist/approvals/use-cases.const.js +31 -0
- package/dist/approvals/use-cases.const.js.map +7 -0
- package/dist/config/camundi-permission-rules.js +24 -3
- package/dist/config/camundi-permission-rules.js.map +2 -2
- package/dist/constants/misc.const.d.ts +2 -0
- package/dist/constants/misc.const.js +4 -0
- package/dist/constants/misc.const.js.map +2 -2
- package/dist/dtos/AlertSubscription.dto.d.ts +6 -0
- package/dist/dtos/AlertSubscription.dto.js.map +2 -2
- package/dist/dtos/Annotation.dto.d.ts +240 -0
- package/dist/dtos/Annotation.dto.js +47 -0
- package/dist/dtos/Annotation.dto.js.map +7 -0
- package/dist/dtos/ApprovalJob.dto.d.ts +408 -4
- package/dist/dtos/ApprovalJob.dto.js +64 -1
- package/dist/dtos/ApprovalJob.dto.js.map +2 -2
- package/dist/dtos/ClusterOwnerMove.dto.d.ts +81 -0
- package/dist/dtos/ClusterOwnerMove.dto.js +1 -0
- package/dist/dtos/ClusterOwnerMove.dto.js.map +7 -0
- package/dist/dtos/CommonCluster.dto.d.ts +6 -0
- package/dist/dtos/ControlV2.dto.d.ts +55 -3
- package/dist/dtos/FeatureFlagOverride.dto.d.ts +226 -0
- package/dist/dtos/FeatureFlagOverride.dto.js +38 -0
- package/dist/dtos/FeatureFlagOverride.dto.js.map +7 -0
- package/dist/dtos/FeatureFlags.dto.d.ts +58 -45
- package/dist/dtos/FeatureFlags.dto.js +10 -0
- package/dist/dtos/FeatureFlags.dto.js.map +3 -3
- package/dist/dtos/Generations.dto.d.ts +17 -0
- package/dist/dtos/Generations.dto.js +7 -0
- package/dist/dtos/Generations.dto.js.map +2 -2
- package/dist/dtos/Generations.dto.spec.d.ts +1 -0
- package/dist/dtos/K8sContext.dto.d.ts +13 -4
- package/dist/dtos/ReleaseTrainSanity.dto.d.ts +6 -0
- package/dist/enums/SystemAuditEntity.enum.d.ts +1 -0
- package/dist/enums/SystemAuditEntity.enum.js +1 -0
- package/dist/enums/SystemAuditEntity.enum.js.map +2 -2
- package/dist/index.d.ts +21 -10
- package/dist/index.js +157 -9
- package/dist/index.js.map +2 -2
- package/dist/permissions/accounts.permissions.d.ts +35 -0
- package/dist/permissions/accounts.permissions.js +33 -1
- package/dist/permissions/accounts.permissions.js.map +2 -2
- package/dist/permissions/combo.d.ts +9 -0
- package/dist/permissions/combo.js +64 -3
- package/dist/permissions/combo.js.map +2 -2
- package/dist/permissions/console.permissions.d.ts +32 -0
- package/dist/permissions/console.permissions.js +24 -2
- package/dist/permissions/console.permissions.js.map +2 -2
- package/dist/types/Roles.d.ts +1 -1
- package/dist/types/Roles.js.map +2 -2
- package/dist/utils/versionCheck.utils.d.ts +9 -0
- package/dist/utils/versionCheck.utils.js +7 -2
- package/dist/utils/versionCheck.utils.js.map +2 -2
- package/package.json +1 -1
- package/dist/constants/failoverRegionPairs.const.d.ts +0 -36
- package/dist/constants/failoverRegionPairs.const.js +0 -34
- package/dist/constants/failoverRegionPairs.const.js.map +0 -7
- /package/dist/_tests/{constants/failoverRegionPairs.const.spec.d.ts → annotations/policy.spec.d.ts} +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a move of a cluster to another organization did not repair. The move
|
|
3
|
+
* keeps going when a warning occurs. A Camundi reads the warnings and decides.
|
|
4
|
+
*/
|
|
5
|
+
export interface MoveClusterWarnings {
|
|
6
|
+
/**
|
|
7
|
+
* The plan type of the cluster, when the sales plan of the target
|
|
8
|
+
* organization does not configure it. The cluster keeps running and stays
|
|
9
|
+
* manageable. That organization cannot create another cluster of this plan
|
|
10
|
+
* type and cannot duplicate this one, because only a POST route mounts
|
|
11
|
+
* `validPlanTypeValidator`. `id` supports a search of a support case.
|
|
12
|
+
* `name` is what the dialog shows.
|
|
13
|
+
*/
|
|
14
|
+
planTypeNotConfigured?: {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
};
|
|
18
|
+
/** True when the cluster holds an external IdP configuration. */
|
|
19
|
+
externalIdp: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The k8s objects of the cluster that carry the organization in a label:
|
|
22
|
+
* Backup, BackupSchedule, ExternalMonitoring, and PrivateConnectivity
|
|
23
|
+
* custom resources. The move patches each one's `orgId` label before it
|
|
24
|
+
* writes the database row for a backup. `failed` lists each object whose
|
|
25
|
+
* label patch failed, with its kind and name. For a failed Backup, a watch
|
|
26
|
+
* reconnect can write the old organization into its database row again. A
|
|
27
|
+
* Camundi must patch a failed label by hand.
|
|
28
|
+
*
|
|
29
|
+
* A cluster does not always have every kind of object. A cluster with no
|
|
30
|
+
* monitoring, or no private connectivity, counts as nothing for that kind,
|
|
31
|
+
* the same way a cluster with no backup counts as nothing.
|
|
32
|
+
*/
|
|
33
|
+
orgLabels: {
|
|
34
|
+
patched: number;
|
|
35
|
+
failed: {
|
|
36
|
+
kind: string;
|
|
37
|
+
name: string;
|
|
38
|
+
error: string;
|
|
39
|
+
}[];
|
|
40
|
+
};
|
|
41
|
+
alertSubscriptions: {
|
|
42
|
+
kept: {
|
|
43
|
+
report: string;
|
|
44
|
+
target: string;
|
|
45
|
+
}[];
|
|
46
|
+
deleted: {
|
|
47
|
+
target: string;
|
|
48
|
+
}[];
|
|
49
|
+
failed: {
|
|
50
|
+
target: string;
|
|
51
|
+
error: string;
|
|
52
|
+
}[];
|
|
53
|
+
/**
|
|
54
|
+
* True when console could not read the subscriptions of the cluster. The
|
|
55
|
+
* three lists are then empty, and empty is not proof that the cluster holds
|
|
56
|
+
* no subscription. A Camundi must look at the subscriptions by hand.
|
|
57
|
+
*/
|
|
58
|
+
readFailed: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* True when console read the subscriptions but could not read the members
|
|
61
|
+
* of the target organization, thus it could not check who still belongs to
|
|
62
|
+
* it. Every subscription is then kept. A person outside the target
|
|
63
|
+
* organization can still get alerts for this cluster. A Camundi must check
|
|
64
|
+
* the membership by hand.
|
|
65
|
+
*/
|
|
66
|
+
membershipCheckFailed: boolean;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export interface MoveClusterOwnerResult {
|
|
70
|
+
clusterId: string;
|
|
71
|
+
fromOrgId: string;
|
|
72
|
+
toOrgId: string;
|
|
73
|
+
warnings: MoveClusterWarnings;
|
|
74
|
+
/**
|
|
75
|
+
* True when the cluster already belonged to `toOrgId` before this request.
|
|
76
|
+
* The request repaired the k8s org labels and the `Backup.orgId` rows, and
|
|
77
|
+
* it moved nothing else. It ran no guard, no owner write, no audit entry,
|
|
78
|
+
* and no alert-subscription prune.
|
|
79
|
+
*/
|
|
80
|
+
ownerUnchanged: boolean;
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=ClusterOwnerMove.dto.js.map
|
|
@@ -124,6 +124,12 @@ export interface CommonClusterDto {
|
|
|
124
124
|
* primary → secondary order. Prefer this over parsing `backupRegion`.
|
|
125
125
|
*/
|
|
126
126
|
backupRegionCodes?: string[];
|
|
127
|
+
/**
|
|
128
|
+
* True when console-admin.yaml marks this cluster's backup pairing for
|
|
129
|
+
* failover. SREs own the flag, per stage. The failover action reads it —
|
|
130
|
+
* do not infer failover support from the region codes.
|
|
131
|
+
*/
|
|
132
|
+
backupFailoverSupported?: boolean;
|
|
127
133
|
backupRegionId?: string | null;
|
|
128
134
|
/**
|
|
129
135
|
* True once the operator's initial primary→secondary bucket sync has
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CamundaClusterStage } from '../types/CamundaClusterLabels.type.js';
|
|
1
2
|
import type { SalesPlanType } from '../types/SalesPlanType.type.js';
|
|
2
3
|
import type { LicensingModelType } from './Organization.dto.js';
|
|
3
4
|
/** Generic cursor-paginated response. `nextCursor === null` on the last page. */
|
|
@@ -112,6 +113,21 @@ export interface ClusterListItemV2Dto {
|
|
|
112
113
|
orgName: string;
|
|
113
114
|
/** Normalized server-side; augmented on the fly from the K8s watcher (§9.4 B1). */
|
|
114
115
|
status: string;
|
|
116
|
+
/**
|
|
117
|
+
* The `metadata.generation` of the ZeebeCluster CR, from the K8s watcher at
|
|
118
|
+
* the time of the read, as in `ClusterLiveStateV2Entry`. A row thus carries
|
|
119
|
+
* the baseline that a poll of `GET /v2/clusters/live-state` compares with.
|
|
120
|
+
* Absent when the watcher does not know the cluster, or on an older
|
|
121
|
+
* console-backend.
|
|
122
|
+
*/
|
|
123
|
+
specGeneration?: number;
|
|
124
|
+
/**
|
|
125
|
+
* Cluster environment stage (dev/test/stage/prod), from the single `camunda`
|
|
126
|
+
* label. Absent when the cluster has no camunda label or more than one, so a
|
|
127
|
+
* surface can only trust it when it is set — the same guard the cluster
|
|
128
|
+
* detail page and accounts use.
|
|
129
|
+
*/
|
|
130
|
+
stageTag?: CamundaClusterStage;
|
|
115
131
|
/** Plan type name (`ClusterPlanType.name`). Backs the "Plan type" column. */
|
|
116
132
|
planTypeName: string;
|
|
117
133
|
/**
|
|
@@ -129,6 +145,11 @@ export interface ClusterListItemV2Dto {
|
|
|
129
145
|
numberOfAllocatedHwPackages?: number;
|
|
130
146
|
/** Concrete plan name (`ClusterPlan.name`). Backs the "Plan" column. */
|
|
131
147
|
planName?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Concrete plan uuid (`ClusterPlan.uuid`). Names are not unique, so this is
|
|
150
|
+
* the key to compare a cluster with a target plan.
|
|
151
|
+
*/
|
|
152
|
+
planId?: string;
|
|
132
153
|
/** Generation name, for display. Group and filter by `generationId`. */
|
|
133
154
|
generation: string;
|
|
134
155
|
/** Generation uuid. Names are not unique, so this is the grouping key. */
|
|
@@ -149,6 +170,14 @@ export interface ClusterListItemV2Dto {
|
|
|
149
170
|
* suspend and not the total time that the cluster was asleep.
|
|
150
171
|
*/
|
|
151
172
|
suspendedAt?: string;
|
|
173
|
+
/** The "Authorizations" toggle of cluster create. */
|
|
174
|
+
identityBackendChecksEnabled?: boolean;
|
|
175
|
+
multiTenancyEnabled?: boolean;
|
|
176
|
+
/** BYOIDP. */
|
|
177
|
+
externalIdpEnabled?: boolean;
|
|
178
|
+
/** Session idle timeout. */
|
|
179
|
+
sessionMaxInactiveIntervalMinutes?: number;
|
|
180
|
+
sessionHeartbeatEnabled?: boolean;
|
|
152
181
|
}
|
|
153
182
|
/** Detail extends the list shape; richer fields are added in Phase 3. */
|
|
154
183
|
export type ClusterDetailV2Dto = ClusterListItemV2Dto;
|
|
@@ -213,10 +242,11 @@ export interface ListClustersV2Params extends CursorParams {
|
|
|
213
242
|
* by the DB-backed columns behind these list columns (the values are the
|
|
214
243
|
* frontend column `key`s, mapped to SQL expressions in the controller):
|
|
215
244
|
* `orgName` (owner id), `autoUpdate`, `planName`, `planTypeName`, `generation`,
|
|
216
|
-
* `channel
|
|
217
|
-
* from live K8s state / context
|
|
245
|
+
* `channel`, `stageTag` (the single `camunda` label). `status`/`region` are
|
|
246
|
+
* intentionally excluded — they're computed from live K8s state / context
|
|
247
|
+
* config, not SQL columns.
|
|
218
248
|
*/
|
|
219
|
-
sort?: 'name' | 'created' | 'orgName' | 'autoUpdate' | 'planName' | 'planTypeName' | 'generation' | 'channel';
|
|
249
|
+
sort?: 'name' | 'created' | 'orgName' | 'autoUpdate' | 'planName' | 'planTypeName' | 'generation' | 'channel' | 'stageTag';
|
|
220
250
|
order?: 'asc' | 'desc';
|
|
221
251
|
filter?: {
|
|
222
252
|
status?: string;
|
|
@@ -332,6 +362,28 @@ export interface ClusterParametersV2Dto {
|
|
|
332
362
|
generations: ClusterParameterOptionV2Dto[];
|
|
333
363
|
datacenters: ClusterDatacenterOptionV2Dto[];
|
|
334
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* One cluster in the watcher memory of console-backend: `[id, status,
|
|
367
|
+
* specGeneration]`. `status` is lowercase, as in `ClusterListItemV2Dto`.
|
|
368
|
+
* `specGeneration` is the `metadata.generation` of the ZeebeCluster CR.
|
|
369
|
+
* Kubernetes increments it on each change of the spec, for example a new
|
|
370
|
+
* generation or a new plan. It is `0` when the CR does not give one.
|
|
371
|
+
*/
|
|
372
|
+
export type ClusterLiveStateV2Entry = [
|
|
373
|
+
id: string,
|
|
374
|
+
status: string,
|
|
375
|
+
specGeneration: number
|
|
376
|
+
];
|
|
377
|
+
/**
|
|
378
|
+
* The whole fleet as the watcher sees it (GET /v2/clusters/live-state). The
|
|
379
|
+
* route reads memory only, never the database, thus a client can poll it every
|
|
380
|
+
* second. The entries are sorted by id, thus two equal states give the same
|
|
381
|
+
* body and the same ETag, also on two pods. A pod that is late gives a
|
|
382
|
+
* different body until it gets the same watch events.
|
|
383
|
+
*/
|
|
384
|
+
export interface ClusterLiveStateV2Dto {
|
|
385
|
+
items: ClusterLiveStateV2Entry[];
|
|
386
|
+
}
|
|
335
387
|
/**
|
|
336
388
|
* Aggregated cluster distribution counts (GET /v2/clusters/facets), backing
|
|
337
389
|
* the list page's distribution charts and quick-filter tiles without a
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { Stage } from '../constants/misc.const.js';
|
|
2
|
+
/**
|
|
3
|
+
* The wire shapes of the feature flag surface of control-backend.
|
|
4
|
+
*
|
|
5
|
+
* They are here and not in `@camunda-cloud/featureflags`, because
|
|
6
|
+
* `@camunda/ccma-api` is published and cannot name a type from that private
|
|
7
|
+
* package. The RULES stay there: `resolveFlag` decides a value, and
|
|
8
|
+
* `FLAG_REGISTRY` holds the metadata. This file carries the answer over HTTP.
|
|
9
|
+
*
|
|
10
|
+
* `flagPath` is a `string` in every shape below. The typed `FlagPath` needs
|
|
11
|
+
* `Features`, and a caller of the API names a flag that the SERVER validates
|
|
12
|
+
* against its own registry. A type that the browser holds cannot do that job,
|
|
13
|
+
* because the server can be older or newer than the bundle.
|
|
14
|
+
*/
|
|
15
|
+
/** Which readers an override reaches. It is `FlagOverrideScope`. */
|
|
16
|
+
export declare const FEATURE_FLAG_OVERRIDE_SCOPES: readonly ["stage", "org"];
|
|
17
|
+
export type FeatureFlagOverrideScope = (typeof FEATURE_FLAG_OVERRIDE_SCOPES)[number];
|
|
18
|
+
/** Why a flag has the value that it has. It is `FlagReason`. */
|
|
19
|
+
export declare const FEATURE_FLAG_REASONS: readonly ["DEFAULT", "OVERRIDE_STAGE", "OVERRIDE_ORG", "OVERRIDE_REFUSED"];
|
|
20
|
+
export type FeatureFlagReason = (typeof FEATURE_FLAG_REASONS)[number];
|
|
21
|
+
/** How long a flag is meant to live. It is `FlagType`. */
|
|
22
|
+
export declare const FEATURE_FLAG_TYPES: readonly ["release", "ops", "perm"];
|
|
23
|
+
export type FeatureFlagType = (typeof FEATURE_FLAG_TYPES)[number];
|
|
24
|
+
/**
|
|
25
|
+
* One condition of the code rule of a flag.
|
|
26
|
+
*
|
|
27
|
+
* The code rule is a LIST of these, and a reader gets the flag when ANY one
|
|
28
|
+
* matches. An empty list is off for every reader. `FLAG_RULES` of
|
|
29
|
+
* `@camunda-cloud/featureflags` holds the rules, and `defaults` evaluates the
|
|
30
|
+
* same objects, thus the description cannot drift from the value.
|
|
31
|
+
*/
|
|
32
|
+
export type FeatureFlagCondition =
|
|
33
|
+
/** On for every reader. */
|
|
34
|
+
{
|
|
35
|
+
kind: 'always';
|
|
36
|
+
}
|
|
37
|
+
/** On when the stage of the reader is one of `stages`. */
|
|
38
|
+
| {
|
|
39
|
+
kind: 'stage';
|
|
40
|
+
stages: Stage[];
|
|
41
|
+
}
|
|
42
|
+
/** On when the email address of the reader is a Camundi address. */
|
|
43
|
+
| {
|
|
44
|
+
kind: 'camundiEmail';
|
|
45
|
+
}
|
|
46
|
+
/** On when the email address of the reader has one of `domains`. */
|
|
47
|
+
| {
|
|
48
|
+
kind: 'emailDomain';
|
|
49
|
+
domains: string[];
|
|
50
|
+
}
|
|
51
|
+
/** On when the organization of the reader is one of `orgIds`. */
|
|
52
|
+
| {
|
|
53
|
+
kind: 'org';
|
|
54
|
+
orgIds: string[];
|
|
55
|
+
};
|
|
56
|
+
/** One stored override, as a reader of the API sees it. */
|
|
57
|
+
export interface FeatureFlagOverrideDto {
|
|
58
|
+
id: string;
|
|
59
|
+
flagPath: string;
|
|
60
|
+
scope: FeatureFlagOverrideScope;
|
|
61
|
+
/** The stage for `stage`, and the organization id for `org`. */
|
|
62
|
+
target: string;
|
|
63
|
+
enabled: boolean;
|
|
64
|
+
/** Why the person asked for it. The approval request demands one. */
|
|
65
|
+
reason: string;
|
|
66
|
+
/** The approval request that wrote this row. Every row names one. */
|
|
67
|
+
approvalId: string;
|
|
68
|
+
createdBy: {
|
|
69
|
+
userId: string;
|
|
70
|
+
email: string;
|
|
71
|
+
};
|
|
72
|
+
createdAt: string;
|
|
73
|
+
/** When the override stops by itself. */
|
|
74
|
+
expiresAt: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* One flag, with everything a person needs to decide about it.
|
|
78
|
+
*
|
|
79
|
+
* The row carries the code value AND the effective value on purpose. A page
|
|
80
|
+
* that showed only the effective value would hide the drift, which is the one
|
|
81
|
+
* thing an override makes easy to lose.
|
|
82
|
+
*/
|
|
83
|
+
export interface FeatureFlagRowDto {
|
|
84
|
+
flagPath: string;
|
|
85
|
+
label: string;
|
|
86
|
+
type: FeatureFlagType;
|
|
87
|
+
owner: string;
|
|
88
|
+
reason: string;
|
|
89
|
+
/** Whether an override can move this flag at all. */
|
|
90
|
+
overridable: boolean;
|
|
91
|
+
/** The date after which a `release` flag is late. */
|
|
92
|
+
expiresAt?: string;
|
|
93
|
+
/**
|
|
94
|
+
* What the code answers for the stage of this service, for NOBODY.
|
|
95
|
+
*
|
|
96
|
+
* The evaluation carries the stage alone: no email address and no
|
|
97
|
+
* organization id. A flag whose condition reads one of those answers
|
|
98
|
+
* something else for a real reader — `console.clusterSuspendButton` is
|
|
99
|
+
* `false` here on prod and `true` for a Camundi, and
|
|
100
|
+
* `console.privateLink87` is `false` here and `true` for an allowlisted
|
|
101
|
+
* organization.
|
|
102
|
+
*
|
|
103
|
+
* It is a BASELINE, and that is the right answer for this surface: one
|
|
104
|
+
* value that every reader of the page sees, which is what makes two
|
|
105
|
+
* Camundis able to talk about it. A per-viewer value would answer a
|
|
106
|
+
* different thing to each of them, and a per-organization value needs an
|
|
107
|
+
* organization that the route does not take.
|
|
108
|
+
*/
|
|
109
|
+
codeValue: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* The code rule of the flag: it is on when ANY condition matches.
|
|
112
|
+
*
|
|
113
|
+
* This is what `codeValue` cannot say. It names the stages, the email
|
|
114
|
+
* rules and the organization allowlist that decide the value for a real
|
|
115
|
+
* reader.
|
|
116
|
+
*/
|
|
117
|
+
rule: FeatureFlagCondition[];
|
|
118
|
+
/**
|
|
119
|
+
* The code value on each known stage, for NOBODY.
|
|
120
|
+
*
|
|
121
|
+
* The same baseline as `codeValue`, for every stage and not only this one.
|
|
122
|
+
* It shows how far the flag stands on its rollout ladder.
|
|
123
|
+
*/
|
|
124
|
+
codeValueByStage: Record<Stage, boolean>;
|
|
125
|
+
/**
|
|
126
|
+
* The baseline of `codeValue` with a live override in front of it.
|
|
127
|
+
*
|
|
128
|
+
* Same evaluation context, thus the same limit: it is what a reader with
|
|
129
|
+
* no email address and no organization gets. Use it to see the DRIFT
|
|
130
|
+
* between the store and the source, which is what this surface exists
|
|
131
|
+
* for, and not as the answer for one person.
|
|
132
|
+
*/
|
|
133
|
+
effectiveValue: boolean;
|
|
134
|
+
/** Why `effectiveValue` is what it is. */
|
|
135
|
+
effectiveReason: FeatureFlagReason;
|
|
136
|
+
/** The live override of the stage, if there is one. */
|
|
137
|
+
stageOverride?: FeatureFlagOverrideDto;
|
|
138
|
+
/**
|
|
139
|
+
* How many live overrides name an organization.
|
|
140
|
+
*
|
|
141
|
+
* A COUNT, and not the rows. The rows carry an organization id, an email
|
|
142
|
+
* address and a reason for every organization that holds an override, and
|
|
143
|
+
* this answer has no page, thus it would grow with the number of customers
|
|
144
|
+
* and hand all of it to every reader. The page shows the number.
|
|
145
|
+
*/
|
|
146
|
+
orgOverrideCount: number;
|
|
147
|
+
}
|
|
148
|
+
/** The answer of `GET /api/feature-flags`. */
|
|
149
|
+
export interface FeatureFlagsDto {
|
|
150
|
+
/** The stage that this service runs on. It decides `codeValue`. */
|
|
151
|
+
stage: string;
|
|
152
|
+
/** The moment when the service built this answer. */
|
|
153
|
+
observedAt: string;
|
|
154
|
+
flags: FeatureFlagRowDto[];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The permissions that one override payload needs, on top of nothing.
|
|
158
|
+
*
|
|
159
|
+
* It is here, and shared, because BOTH sides run it: `permissionsFor` of the
|
|
160
|
+
* use case stores the answer on the row, and the dialog of control-frontend
|
|
161
|
+
* passes the same answer to `canCreate`. Two copies of this rule would let a
|
|
162
|
+
* button offer an override that the service then refuses with a 403.
|
|
163
|
+
*
|
|
164
|
+
* A prod stage override reaches every customer, thus it asks for the narrower
|
|
165
|
+
* permission as well — to approve AND to ask.
|
|
166
|
+
*/
|
|
167
|
+
export declare function featureFlagOverridePermissions(payload: {
|
|
168
|
+
scope: FeatureFlagOverrideScope;
|
|
169
|
+
target: string;
|
|
170
|
+
}): string[];
|
|
171
|
+
/**
|
|
172
|
+
* The one written form of a target.
|
|
173
|
+
*
|
|
174
|
+
* A GUID reads the same in either case, and `ABC…` and `abc…` are the same
|
|
175
|
+
* organization. The store and the resolver compare TEXT, so two forms of one
|
|
176
|
+
* id would be two overrides: the unique index would allow both to be live,
|
|
177
|
+
* and a row written in capitals would never match the id that a reader
|
|
178
|
+
* carries. Everything that writes a target passes it through here first, so
|
|
179
|
+
* only one form ever reaches the table.
|
|
180
|
+
*
|
|
181
|
+
* A stage name is lowercase already, thus this does not change one.
|
|
182
|
+
*/
|
|
183
|
+
export declare function normalizeFeatureFlagOverrideTarget(target: string): string;
|
|
184
|
+
/**
|
|
185
|
+
* What is wrong with the target of an override, if anything.
|
|
186
|
+
*
|
|
187
|
+
* It answers `undefined` when the target is good, and a sentence for a person
|
|
188
|
+
* when it is not.
|
|
189
|
+
*
|
|
190
|
+
* It is here, and shared, for the same reason as
|
|
191
|
+
* `featureFlagOverridePermissions` above: BOTH sides run it. The dialog of
|
|
192
|
+
* control-frontend keeps the button disabled while it answers, and the create
|
|
193
|
+
* route of control-backend refuses the call. The dialog accepted any text
|
|
194
|
+
* that was not empty before this, thus it offered `prod` on dev and
|
|
195
|
+
* `not-an-org` as an organization, and both ended in a 400 after the person
|
|
196
|
+
* had filled the whole form.
|
|
197
|
+
*
|
|
198
|
+
* TWO rules, and the first surprises people:
|
|
199
|
+
*
|
|
200
|
+
* 1. A stage override names THE STAGE OF THE SERVICE, and no other. Each
|
|
201
|
+
* deployed stage of control-backend holds its own database, thus an
|
|
202
|
+
* override written on dev reaches dev readers alone. A request for
|
|
203
|
+
* another stage would collect its approvals and move nothing.
|
|
204
|
+
* 2. An org override names a GUID.
|
|
205
|
+
*/
|
|
206
|
+
export declare function featureFlagOverrideTargetProblem(args: {
|
|
207
|
+
scope: FeatureFlagOverrideScope;
|
|
208
|
+
target: string;
|
|
209
|
+
/** The stage that the service runs on. `FeatureFlagsDto.stage` carries it. */
|
|
210
|
+
stage: string;
|
|
211
|
+
}): string | undefined;
|
|
212
|
+
/** The payload of a `feature-flag-override` approval request. */
|
|
213
|
+
export interface FeatureFlagOverridePayloadDto {
|
|
214
|
+
flagPath: string;
|
|
215
|
+
scope: FeatureFlagOverrideScope;
|
|
216
|
+
target: string;
|
|
217
|
+
enabled: boolean;
|
|
218
|
+
/**
|
|
219
|
+
* How many days the override holds, from the moment the action RUNS.
|
|
220
|
+
*
|
|
221
|
+
* A number of days and not a date, for the same reason as `extend-trial`:
|
|
222
|
+
* days pass between the request and the run, and a stored date would lose
|
|
223
|
+
* part of its life while the request waited for its approvals.
|
|
224
|
+
*/
|
|
225
|
+
days: number;
|
|
226
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { AccountsPermissions } from "../permissions/accounts.permissions.js";
|
|
2
|
+
const ACCOUNTS_CONTROL_FLAG_OVERRIDE = AccountsPermissions.CONTROL_FLAG_OVERRIDE;
|
|
3
|
+
const ACCOUNTS_CONTROL_FLAG_OVERRIDE_PROD = AccountsPermissions.CONTROL_FLAG_OVERRIDE_PROD;
|
|
4
|
+
const FEATURE_FLAG_OVERRIDE_SCOPES = ["stage", "org"];
|
|
5
|
+
const FEATURE_FLAG_REASONS = [
|
|
6
|
+
"DEFAULT",
|
|
7
|
+
"OVERRIDE_STAGE",
|
|
8
|
+
"OVERRIDE_ORG",
|
|
9
|
+
"OVERRIDE_REFUSED"
|
|
10
|
+
];
|
|
11
|
+
const FEATURE_FLAG_TYPES = ["release", "ops", "perm"];
|
|
12
|
+
function featureFlagOverridePermissions(payload) {
|
|
13
|
+
const base = [ACCOUNTS_CONTROL_FLAG_OVERRIDE];
|
|
14
|
+
return payload.scope === "stage" && payload.target === "prod" ? [...base, ACCOUNTS_CONTROL_FLAG_OVERRIDE_PROD] : base;
|
|
15
|
+
}
|
|
16
|
+
const GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
17
|
+
function normalizeFeatureFlagOverrideTarget(target) {
|
|
18
|
+
return target.trim().toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
function featureFlagOverrideTargetProblem(args) {
|
|
21
|
+
const target = normalizeFeatureFlagOverrideTarget(args.target);
|
|
22
|
+
if (target.length === 0) {
|
|
23
|
+
return args.scope === "stage" ? "Name the stage to move." : "Name the organization to move.";
|
|
24
|
+
}
|
|
25
|
+
if (args.scope === "stage") {
|
|
26
|
+
return target === args.stage ? void 0 : `This Camunda Control runs on ${args.stage} and can move only that stage. Open the request on the Camunda Control of ${target}.`;
|
|
27
|
+
}
|
|
28
|
+
return GUID.test(target) ? void 0 : "An organization id is a GUID, like 11111111-1111-1111-1111-111111111111.";
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
FEATURE_FLAG_OVERRIDE_SCOPES,
|
|
32
|
+
FEATURE_FLAG_REASONS,
|
|
33
|
+
FEATURE_FLAG_TYPES,
|
|
34
|
+
featureFlagOverridePermissions,
|
|
35
|
+
featureFlagOverrideTargetProblem,
|
|
36
|
+
normalizeFeatureFlagOverrideTarget
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=FeatureFlagOverride.dto.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/dtos/FeatureFlagOverride.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 { Stage } from '../constants/misc.const.js'\nimport { AccountsPermissions } from '../permissions/accounts.permissions.js'\n\nconst ACCOUNTS_CONTROL_FLAG_OVERRIDE = AccountsPermissions.CONTROL_FLAG_OVERRIDE\nconst ACCOUNTS_CONTROL_FLAG_OVERRIDE_PROD =\n AccountsPermissions.CONTROL_FLAG_OVERRIDE_PROD\n\n/**\n * The wire shapes of the feature flag surface of control-backend.\n *\n * They are here and not in `@camunda-cloud/featureflags`, because\n * `@camunda/ccma-api` is published and cannot name a type from that private\n * package. The RULES stay there: `resolveFlag` decides a value, and\n * `FLAG_REGISTRY` holds the metadata. This file carries the answer over HTTP.\n *\n * `flagPath` is a `string` in every shape below. The typed `FlagPath` needs\n * `Features`, and a caller of the API names a flag that the SERVER validates\n * against its own registry. A type that the browser holds cannot do that job,\n * because the server can be older or newer than the bundle.\n */\n\n/** Which readers an override reaches. It is `FlagOverrideScope`. */\nexport const FEATURE_FLAG_OVERRIDE_SCOPES = ['stage', 'org'] as const\n\nexport type FeatureFlagOverrideScope =\n (typeof FEATURE_FLAG_OVERRIDE_SCOPES)[number]\n\n/** Why a flag has the value that it has. It is `FlagReason`. */\nexport const FEATURE_FLAG_REASONS = [\n 'DEFAULT',\n 'OVERRIDE_STAGE',\n 'OVERRIDE_ORG',\n 'OVERRIDE_REFUSED',\n] as const\n\nexport type FeatureFlagReason = (typeof FEATURE_FLAG_REASONS)[number]\n\n/** How long a flag is meant to live. It is `FlagType`. */\nexport const FEATURE_FLAG_TYPES = ['release', 'ops', 'perm'] as const\n\nexport type FeatureFlagType = (typeof FEATURE_FLAG_TYPES)[number]\n\n/**\n * One condition of the code rule of a flag.\n *\n * The code rule is a LIST of these, and a reader gets the flag when ANY one\n * matches. An empty list is off for every reader. `FLAG_RULES` of\n * `@camunda-cloud/featureflags` holds the rules, and `defaults` evaluates the\n * same objects, thus the description cannot drift from the value.\n */\nexport type FeatureFlagCondition =\n /** On for every reader. */\n | { kind: 'always' }\n /** On when the stage of the reader is one of `stages`. */\n | { kind: 'stage'; stages: Stage[] }\n /** On when the email address of the reader is a Camundi address. */\n | { kind: 'camundiEmail' }\n /** On when the email address of the reader has one of `domains`. */\n | { kind: 'emailDomain'; domains: string[] }\n /** On when the organization of the reader is one of `orgIds`. */\n | { kind: 'org'; orgIds: string[] }\n\n/** One stored override, as a reader of the API sees it. */\nexport interface FeatureFlagOverrideDto {\n id: string\n flagPath: string\n scope: FeatureFlagOverrideScope\n /** The stage for `stage`, and the organization id for `org`. */\n target: string\n enabled: boolean\n /** Why the person asked for it. The approval request demands one. */\n reason: string\n /** The approval request that wrote this row. Every row names one. */\n approvalId: string\n createdBy: { userId: string; email: string }\n createdAt: string\n /** When the override stops by itself. */\n expiresAt: string\n}\n\n/**\n * One flag, with everything a person needs to decide about it.\n *\n * The row carries the code value AND the effective value on purpose. A page\n * that showed only the effective value would hide the drift, which is the one\n * thing an override makes easy to lose.\n */\nexport interface FeatureFlagRowDto {\n flagPath: string\n label: string\n type: FeatureFlagType\n owner: string\n reason: string\n /** Whether an override can move this flag at all. */\n overridable: boolean\n /** The date after which a `release` flag is late. */\n expiresAt?: string\n /**\n * What the code answers for the stage of this service, for NOBODY.\n *\n * The evaluation carries the stage alone: no email address and no\n * organization id. A flag whose condition reads one of those answers\n * something else for a real reader \u2014 `console.clusterSuspendButton` is\n * `false` here on prod and `true` for a Camundi, and\n * `console.privateLink87` is `false` here and `true` for an allowlisted\n * organization.\n *\n * It is a BASELINE, and that is the right answer for this surface: one\n * value that every reader of the page sees, which is what makes two\n * Camundis able to talk about it. A per-viewer value would answer a\n * different thing to each of them, and a per-organization value needs an\n * organization that the route does not take.\n */\n codeValue: boolean\n /**\n * The code rule of the flag: it is on when ANY condition matches.\n *\n * This is what `codeValue` cannot say. It names the stages, the email\n * rules and the organization allowlist that decide the value for a real\n * reader.\n */\n rule: FeatureFlagCondition[]\n /**\n * The code value on each known stage, for NOBODY.\n *\n * The same baseline as `codeValue`, for every stage and not only this one.\n * It shows how far the flag stands on its rollout ladder.\n */\n codeValueByStage: Record<Stage, boolean>\n /**\n * The baseline of `codeValue` with a live override in front of it.\n *\n * Same evaluation context, thus the same limit: it is what a reader with\n * no email address and no organization gets. Use it to see the DRIFT\n * between the store and the source, which is what this surface exists\n * for, and not as the answer for one person.\n */\n effectiveValue: boolean\n /** Why `effectiveValue` is what it is. */\n effectiveReason: FeatureFlagReason\n /** The live override of the stage, if there is one. */\n stageOverride?: FeatureFlagOverrideDto\n /**\n * How many live overrides name an organization.\n *\n * A COUNT, and not the rows. The rows carry an organization id, an email\n * address and a reason for every organization that holds an override, and\n * this answer has no page, thus it would grow with the number of customers\n * and hand all of it to every reader. The page shows the number.\n */\n orgOverrideCount: number\n}\n\n/** The answer of `GET /api/feature-flags`. */\nexport interface FeatureFlagsDto {\n /** The stage that this service runs on. It decides `codeValue`. */\n stage: string\n /** The moment when the service built this answer. */\n observedAt: string\n flags: FeatureFlagRowDto[]\n}\n\n/**\n * The permissions that one override payload needs, on top of nothing.\n *\n * It is here, and shared, because BOTH sides run it: `permissionsFor` of the\n * use case stores the answer on the row, and the dialog of control-frontend\n * passes the same answer to `canCreate`. Two copies of this rule would let a\n * button offer an override that the service then refuses with a 403.\n *\n * A prod stage override reaches every customer, thus it asks for the narrower\n * permission as well \u2014 to approve AND to ask.\n */\nexport function featureFlagOverridePermissions(payload: {\n scope: FeatureFlagOverrideScope\n target: string\n}): string[] {\n const base = [ACCOUNTS_CONTROL_FLAG_OVERRIDE]\n return payload.scope === 'stage' && payload.target === 'prod'\n ? [...base, ACCOUNTS_CONTROL_FLAG_OVERRIDE_PROD]\n : base\n}\n\n/**\n * A GUID, as the create route reads one.\n *\n * The server says `z.guid()`. This is the same shape for the browser, which\n * holds no zod: 8-4-4-4-12 hexadecimal characters.\n */\nconst GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\n/**\n * The one written form of a target.\n *\n * A GUID reads the same in either case, and `ABC\u2026` and `abc\u2026` are the same\n * organization. The store and the resolver compare TEXT, so two forms of one\n * id would be two overrides: the unique index would allow both to be live,\n * and a row written in capitals would never match the id that a reader\n * carries. Everything that writes a target passes it through here first, so\n * only one form ever reaches the table.\n *\n * A stage name is lowercase already, thus this does not change one.\n */\nexport function normalizeFeatureFlagOverrideTarget(target: string): string {\n return target.trim().toLowerCase()\n}\n\n/**\n * What is wrong with the target of an override, if anything.\n *\n * It answers `undefined` when the target is good, and a sentence for a person\n * when it is not.\n *\n * It is here, and shared, for the same reason as\n * `featureFlagOverridePermissions` above: BOTH sides run it. The dialog of\n * control-frontend keeps the button disabled while it answers, and the create\n * route of control-backend refuses the call. The dialog accepted any text\n * that was not empty before this, thus it offered `prod` on dev and\n * `not-an-org` as an organization, and both ended in a 400 after the person\n * had filled the whole form.\n *\n * TWO rules, and the first surprises people:\n *\n * 1. A stage override names THE STAGE OF THE SERVICE, and no other. Each\n * deployed stage of control-backend holds its own database, thus an\n * override written on dev reaches dev readers alone. A request for\n * another stage would collect its approvals and move nothing.\n * 2. An org override names a GUID.\n */\nexport function featureFlagOverrideTargetProblem(args: {\n scope: FeatureFlagOverrideScope\n target: string\n /** The stage that the service runs on. `FeatureFlagsDto.stage` carries it. */\n stage: string\n}): string | undefined {\n const target = normalizeFeatureFlagOverrideTarget(args.target)\n if (target.length === 0) {\n return args.scope === 'stage'\n ? 'Name the stage to move.'\n : 'Name the organization to move.'\n }\n if (args.scope === 'stage') {\n return target === args.stage\n ? undefined\n : `This Camunda Control runs on ${args.stage} and can move only that ` +\n `stage. Open the request on the Camunda Control of ${target}.`\n }\n return GUID.test(target)\n ? undefined\n : 'An organization id is a GUID, like 11111111-1111-1111-1111-111111111111.'\n}\n\n/** The payload of a `feature-flag-override` approval request. */\nexport interface FeatureFlagOverridePayloadDto {\n flagPath: string\n scope: FeatureFlagOverrideScope\n target: string\n enabled: boolean\n /**\n * How many days the override holds, from the moment the action RUNS.\n *\n * A number of days and not a date, for the same reason as `extend-trial`:\n * days pass between the request and the run, and a stored date would lose\n * part of its life while the request waited for its approvals.\n */\n days: number\n}\n"],
|
|
5
|
+
"mappings": "AAOA,SAAS,2BAA2B;AAEpC,MAAM,iCAAiC,oBAAoB;AAC3D,MAAM,sCACJ,oBAAoB;AAiBf,MAAM,+BAA+B,CAAC,SAAS,KAAK;AAMpD,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKO,MAAM,qBAAqB,CAAC,WAAW,OAAO,MAAM;AAuIpD,SAAS,+BAA+B,SAGlC;AACX,QAAM,OAAO,CAAC,8BAA8B;AAC5C,SAAO,QAAQ,UAAU,WAAW,QAAQ,WAAW,SACnD,CAAC,GAAG,MAAM,mCAAmC,IAC7C;AACN;AAQA,MAAM,OAAO;AAcN,SAAS,mCAAmC,QAAwB;AACzE,SAAO,OAAO,KAAK,EAAE,YAAY;AACnC;AAwBO,SAAS,iCAAiC,MAK1B;AACrB,QAAM,SAAS,mCAAmC,KAAK,MAAM;AAC7D,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,KAAK,UAAU,UAClB,4BACA;AAAA,EACN;AACA,MAAI,KAAK,UAAU,SAAS;AAC1B,WAAO,WAAW,KAAK,QACnB,SACA,gCAAgC,KAAK,KAAK,6EACa,MAAM;AAAA,EACnE;AACA,SAAO,KAAK,KAAK,MAAM,IACnB,SACA;AACN;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -2,52 +2,65 @@
|
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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.
|
|
11
39
|
*
|
|
12
40
|
* A c4 host reads one flag with `useC4FeatureFlag('console.appIntegrations')`
|
|
13
41
|
* — a dot path into this object.
|
|
14
42
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
}
|
|
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>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": [],
|
|
4
|
-
"sourcesContent": [],
|
|
5
|
-
"mappings": "",
|
|
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;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|