@camunda/ccma-shared-types 1.1.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.
- package/dist/_tests/approvals/policy.spec.d.ts +1 -0
- package/dist/approvals/policy.d.ts +109 -0
- package/dist/approvals/policy.js +123 -0
- package/dist/approvals/policy.js.map +7 -0
- package/dist/config/camundi-permission-rules.js +10 -4
- package/dist/config/camundi-permission-rules.js.map +2 -2
- package/dist/constants/misc.const.d.ts +21 -0
- package/dist/constants/misc.const.js +5 -1
- package/dist/constants/misc.const.js.map +2 -2
- package/dist/constants/v2-list.const.d.ts +12 -0
- package/dist/constants/v2-list.const.js +9 -0
- package/dist/constants/v2-list.const.js.map +7 -0
- package/dist/dtos/ApprovalJob.dto.d.ts +83 -0
- package/dist/dtos/ApprovalJob.dto.js +14 -0
- package/dist/dtos/ApprovalJob.dto.js.map +7 -0
- package/dist/dtos/ControlV2.dto.d.ts +78 -1
- package/dist/dtos/FeatureFlags.dto.d.ts +2 -0
- package/dist/dtos/Generations.dto.d.ts +10 -0
- package/dist/dtos/Generations.dto.js +36 -2
- package/dist/dtos/Generations.dto.js.map +2 -2
- package/dist/dtos/Hdp.dto.d.ts +29 -0
- package/dist/dtos/Organization.dto.d.ts +20 -0
- package/dist/dtos/Organization.dto.js.map +1 -1
- package/dist/enums/AuditService.enum.d.ts +2 -1
- package/dist/enums/AuditService.enum.js +1 -0
- package/dist/enums/AuditService.enum.js.map +2 -2
- package/dist/enums/SystemAuditEntity.enum.d.ts +6 -2
- package/dist/enums/SystemAuditEntity.enum.js +4 -0
- package/dist/enums/SystemAuditEntity.enum.js.map +2 -2
- package/dist/index.d.ts +10 -5
- package/dist/index.js +45 -1
- package/dist/index.js.map +2 -2
- package/dist/search/cluster-search-fields.js +9 -1
- package/dist/search/cluster-search-fields.js.map +2 -2
- package/dist/search/compiler.d.ts +25 -0
- package/dist/search/compiler.js +12 -3
- package/dist/search/compiler.js.map +2 -2
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +4 -0
- package/dist/search/index.js.map +2 -2
- package/dist/types/ClusterBulkOperation.type.d.ts +20 -0
- package/dist/utils/camundiPermissionAugmenter.utils.d.ts +14 -0
- package/dist/utils/camundiPermissionAugmenter.utils.js +4 -0
- package/dist/utils/camundiPermissionAugmenter.utils.js.map +2 -2
- package/dist/utils/camundiPermissionAugmenter.utils.spec.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { ApprovalJobDto, ApprovalUseCaseDto } from '../dtos/ApprovalJob.dto.js';
|
|
2
|
+
/**
|
|
3
|
+
* The rules of the approval mechanism, as pure functions.
|
|
4
|
+
*
|
|
5
|
+
* Three consumers run this file: control-backend decides whether to reject a
|
|
6
|
+
* request, control-frontend decides whether to disable a button, and cloudbot
|
|
7
|
+
* decides whether to answer. One copy means the button and the endpoint can
|
|
8
|
+
* never disagree.
|
|
9
|
+
*
|
|
10
|
+
* Two choices in the signatures carry the design:
|
|
11
|
+
*
|
|
12
|
+
* - The actor is an interface with a `has` method. The backend holds an array
|
|
13
|
+
* of Auth0 claims, and `useSession()` in the frontend gives a flat record of
|
|
14
|
+
* booleans. Neither side has to convert to the shape of the other.
|
|
15
|
+
* - `now` is a parameter. The expiry is lazy, so a stored `pending` request can
|
|
16
|
+
* be expired already. A parameter makes a difference between the two clocks
|
|
17
|
+
* an input that a test can set, and not a silent disagreement.
|
|
18
|
+
*
|
|
19
|
+
* No function here does I/O or returns a promise. The browser call site is what
|
|
20
|
+
* enforces that, and it also stops a later use case from turning a list of 50
|
|
21
|
+
* rows into 200 network calls.
|
|
22
|
+
*/
|
|
23
|
+
/** Who is asking. See the note above on why this is an interface. */
|
|
24
|
+
export interface ApprovalActorView {
|
|
25
|
+
userId: string;
|
|
26
|
+
/**
|
|
27
|
+
* The email of the actor. It is the SECOND identity of the four-eyes rule.
|
|
28
|
+
*
|
|
29
|
+
* `userId` alone is not enough. One person can hold more than one identity in
|
|
30
|
+
* Auth0 — a connection through SSO and a connection through a database, or an
|
|
31
|
+
* account that was made again — and each one carries a different `sub`. The
|
|
32
|
+
* same human then opens a request with one identity and approves it with the
|
|
33
|
+
* other, which is the rule defeated.
|
|
34
|
+
*
|
|
35
|
+
* It is optional because a token can carry no email. See
|
|
36
|
+
* `isSameActorEmail` for what that means for the comparison.
|
|
37
|
+
*/
|
|
38
|
+
email?: string;
|
|
39
|
+
has(permission: string): boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Reports whether two email addresses name one person.
|
|
43
|
+
*
|
|
44
|
+
* Two values that are absent, empty, or `UNKNOWN_ACTOR_EMAIL` are NOT one
|
|
45
|
+
* person. A token with no email would otherwise make every actor equal to
|
|
46
|
+
* every other actor, and the rule would refuse everybody.
|
|
47
|
+
*
|
|
48
|
+
* The comparison removes the spaces and the case. Auth0 gives the address as
|
|
49
|
+
* the identity provider wrote it, so `A.B@camunda.com` and `a.b@camunda.com`
|
|
50
|
+
* are one mailbox and must not pass as two people.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isSameActorEmail(one?: string, other?: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Why an action is not allowed.
|
|
55
|
+
*
|
|
56
|
+
* The code is for a program: control-backend maps it to a status, and the
|
|
57
|
+
* frontend can branch on it. The message is for a person, and it is shown as
|
|
58
|
+
* written.
|
|
59
|
+
*/
|
|
60
|
+
export type ApprovalDenialCode = 'forbidden' | 'not-pending' | 'self-approval' | 'already-acknowledged' | 'expired' | 'not-creator' | 'not-executable' | 'no-reason';
|
|
61
|
+
export type ApprovalDecision = {
|
|
62
|
+
allowed: true;
|
|
63
|
+
} | {
|
|
64
|
+
allowed: false;
|
|
65
|
+
code: ApprovalDenialCode;
|
|
66
|
+
message: string;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Reports whether the time to answer a request has passed.
|
|
70
|
+
*
|
|
71
|
+
* Only a `pending` request expires. A request that already reached an end state
|
|
72
|
+
* keeps that state, because "expired" would lose the answer that was given.
|
|
73
|
+
*/
|
|
74
|
+
export declare function isExpired(job: ApprovalJobDto, now: Date): boolean;
|
|
75
|
+
/** Reports the status to show, which is `expired` once the time has passed. */
|
|
76
|
+
export declare function effectiveStatus(job: ApprovalJobDto, now: Date): ApprovalJobDto['status'];
|
|
77
|
+
/** Reports how many more acknowledgements the request needs. */
|
|
78
|
+
export declare function remainingAcks(job: ApprovalJobDto): number;
|
|
79
|
+
export declare function canCreate(actor: ApprovalActorView, useCase: ApprovalUseCaseDto): ApprovalDecision;
|
|
80
|
+
/**
|
|
81
|
+
* The four-eyes rule.
|
|
82
|
+
*
|
|
83
|
+
* The bans are in the order a person meets them, and each one has its own
|
|
84
|
+
* message: "you already approved this" and "you cannot approve your own
|
|
85
|
+
* request" are different mistakes, and one message for both helps nobody.
|
|
86
|
+
*
|
|
87
|
+
* Each of the two bans on a person reads BOTH identities: the `sub` of the
|
|
88
|
+
* token, and the email. See `ApprovalActorView.email` for why one of them is
|
|
89
|
+
* not enough.
|
|
90
|
+
*/
|
|
91
|
+
export declare function canAcknowledge(job: ApprovalJobDto, actor: ApprovalActorView, useCase: ApprovalUseCaseDto, now: Date): ApprovalDecision;
|
|
92
|
+
/**
|
|
93
|
+
* A rejection needs the same permission as an acknowledgement, and a reason.
|
|
94
|
+
*
|
|
95
|
+
* The creator MAY reject their own request. That is a withdrawal with a reason,
|
|
96
|
+
* and stopping it would only push the person to the withdraw button, where the
|
|
97
|
+
* reason is lost.
|
|
98
|
+
*/
|
|
99
|
+
export declare function canReject(job: ApprovalJobDto, actor: ApprovalActorView, useCase: ApprovalUseCaseDto, now: Date, reason: string): ApprovalDecision;
|
|
100
|
+
/** Only the person who opened a request can take it back. */
|
|
101
|
+
export declare function canWithdraw(job: ApprovalJobDto, actor: ApprovalActorView, now: Date): ApprovalDecision;
|
|
102
|
+
/**
|
|
103
|
+
* Runs a request that has its approvals.
|
|
104
|
+
*
|
|
105
|
+
* This is the button of the explicit mode. `failed` is executable again,
|
|
106
|
+
* because a failure is often a problem of the system that is now repaired, and
|
|
107
|
+
* asking for the approvals a second time helps nobody.
|
|
108
|
+
*/
|
|
109
|
+
export declare function canExecute(job: ApprovalJobDto, actor: ApprovalActorView, useCase: ApprovalUseCaseDto): ApprovalDecision;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { UNKNOWN_ACTOR_EMAIL } from "../constants/misc.const.js";
|
|
2
|
+
function isSameActorEmail(one, other) {
|
|
3
|
+
const normalize = (value) => {
|
|
4
|
+
const trimmed = value?.trim().toLowerCase() ?? "";
|
|
5
|
+
return trimmed === UNKNOWN_ACTOR_EMAIL ? "" : trimmed;
|
|
6
|
+
};
|
|
7
|
+
const left = normalize(one);
|
|
8
|
+
return left.length > 0 && left === normalize(other);
|
|
9
|
+
}
|
|
10
|
+
const ALLOWED = { allowed: true };
|
|
11
|
+
function deny(code, message) {
|
|
12
|
+
return { allowed: false, code, message };
|
|
13
|
+
}
|
|
14
|
+
function isExpired(job, now) {
|
|
15
|
+
return job.status === "pending" && new Date(job.expiresAt).getTime() <= now.getTime();
|
|
16
|
+
}
|
|
17
|
+
function effectiveStatus(job, now) {
|
|
18
|
+
return isExpired(job, now) ? "expired" : job.status;
|
|
19
|
+
}
|
|
20
|
+
function remainingAcks(job) {
|
|
21
|
+
return Math.max(0, job.requiredAcks - job.acks.length);
|
|
22
|
+
}
|
|
23
|
+
function canCreate(actor, useCase) {
|
|
24
|
+
if (useCase.createPermission && !actor.has(useCase.createPermission)) {
|
|
25
|
+
return deny("forbidden", "You cannot open this request.");
|
|
26
|
+
}
|
|
27
|
+
return ALLOWED;
|
|
28
|
+
}
|
|
29
|
+
function canAcknowledge(job, actor, useCase, now) {
|
|
30
|
+
if (!actor.has(useCase.requiredPermission)) {
|
|
31
|
+
return deny("forbidden", "You cannot approve this request.");
|
|
32
|
+
}
|
|
33
|
+
if (isExpired(job, now)) {
|
|
34
|
+
return deny(
|
|
35
|
+
"expired",
|
|
36
|
+
"This request expired and can no longer be approved."
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
if (job.status !== "pending") {
|
|
40
|
+
return deny(
|
|
41
|
+
"not-pending",
|
|
42
|
+
`This request is ${job.status} and can no longer be approved.`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (job.createdBy.userId === actor.userId || isSameActorEmail(job.createdBy.email, actor.email)) {
|
|
46
|
+
return deny(
|
|
47
|
+
"self-approval",
|
|
48
|
+
"You opened this request, so you cannot approve it."
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (job.acks.some(
|
|
52
|
+
(ack) => ack.userId === actor.userId || isSameActorEmail(ack.email, actor.email)
|
|
53
|
+
)) {
|
|
54
|
+
return deny("already-acknowledged", "You already approved this request.");
|
|
55
|
+
}
|
|
56
|
+
return ALLOWED;
|
|
57
|
+
}
|
|
58
|
+
function canReject(job, actor, useCase, now, reason) {
|
|
59
|
+
if (!actor.has(useCase.requiredPermission)) {
|
|
60
|
+
return deny("forbidden", "You cannot reject this request.");
|
|
61
|
+
}
|
|
62
|
+
if (reason.trim().length === 0) {
|
|
63
|
+
return deny("no-reason", "Give a reason to reject this request.");
|
|
64
|
+
}
|
|
65
|
+
if (isExpired(job, now)) {
|
|
66
|
+
return deny(
|
|
67
|
+
"expired",
|
|
68
|
+
"This request expired and can no longer be rejected."
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (job.status !== "pending") {
|
|
72
|
+
return deny(
|
|
73
|
+
"not-pending",
|
|
74
|
+
`This request is ${job.status} and can no longer be rejected.`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return ALLOWED;
|
|
78
|
+
}
|
|
79
|
+
function canWithdraw(job, actor, now) {
|
|
80
|
+
if (job.createdBy.userId !== actor.userId) {
|
|
81
|
+
return deny(
|
|
82
|
+
"not-creator",
|
|
83
|
+
"Only the person who opened a request can withdraw it."
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (isExpired(job, now)) {
|
|
87
|
+
return deny(
|
|
88
|
+
"expired",
|
|
89
|
+
"This request expired and can no longer be withdrawn."
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (job.status !== "pending") {
|
|
93
|
+
return deny(
|
|
94
|
+
"not-pending",
|
|
95
|
+
`This request is ${job.status} and can no longer be withdrawn.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return ALLOWED;
|
|
99
|
+
}
|
|
100
|
+
function canExecute(job, actor, useCase) {
|
|
101
|
+
if (!actor.has(useCase.requiredPermission)) {
|
|
102
|
+
return deny("forbidden", "You cannot run this request.");
|
|
103
|
+
}
|
|
104
|
+
if (job.status !== "approved" && job.status !== "failed") {
|
|
105
|
+
return deny(
|
|
106
|
+
"not-executable",
|
|
107
|
+
`This request is ${job.status} and cannot be run.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return ALLOWED;
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
canAcknowledge,
|
|
114
|
+
canCreate,
|
|
115
|
+
canExecute,
|
|
116
|
+
canReject,
|
|
117
|
+
canWithdraw,
|
|
118
|
+
effectiveStatus,
|
|
119
|
+
isExpired,
|
|
120
|
+
isSameActorEmail,
|
|
121
|
+
remainingAcks
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/approvals/policy.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 { UNKNOWN_ACTOR_EMAIL } from '../constants/misc.const.js'\nimport type {\n ApprovalJobDto,\n ApprovalUseCaseDto,\n} from '../dtos/ApprovalJob.dto.js'\n\n/**\n * The rules of the approval mechanism, as pure functions.\n *\n * Three consumers run this file: control-backend decides whether to reject a\n * request, control-frontend decides whether to disable a button, and cloudbot\n * decides whether to answer. One copy means the button and the endpoint can\n * never disagree.\n *\n * Two choices in the signatures carry the design:\n *\n * - The actor is an interface with a `has` method. The backend holds an array\n * of Auth0 claims, and `useSession()` in the frontend gives a flat record of\n * booleans. Neither side has to convert to the shape of the other.\n * - `now` is a parameter. The expiry is lazy, so a stored `pending` request can\n * be expired already. A parameter makes a difference between the two clocks\n * an input that a test can set, and not a silent disagreement.\n *\n * No function here does I/O or returns a promise. The browser call site is what\n * enforces that, and it also stops a later use case from turning a list of 50\n * rows into 200 network calls.\n */\n\n/** Who is asking. See the note above on why this is an interface. */\nexport interface ApprovalActorView {\n userId: string\n /**\n * The email of the actor. It is the SECOND identity of the four-eyes rule.\n *\n * `userId` alone is not enough. One person can hold more than one identity in\n * Auth0 \u2014 a connection through SSO and a connection through a database, or an\n * account that was made again \u2014 and each one carries a different `sub`. The\n * same human then opens a request with one identity and approves it with the\n * other, which is the rule defeated.\n *\n * It is optional because a token can carry no email. See\n * `isSameActorEmail` for what that means for the comparison.\n */\n email?: string\n has(permission: string): boolean\n}\n\n/**\n * Reports whether two email addresses name one person.\n *\n * Two values that are absent, empty, or `UNKNOWN_ACTOR_EMAIL` are NOT one\n * person. A token with no email would otherwise make every actor equal to\n * every other actor, and the rule would refuse everybody.\n *\n * The comparison removes the spaces and the case. Auth0 gives the address as\n * the identity provider wrote it, so `A.B@camunda.com` and `a.b@camunda.com`\n * are one mailbox and must not pass as two people.\n */\nexport function isSameActorEmail(one?: string, other?: string): boolean {\n const normalize = (value?: string): string => {\n const trimmed = value?.trim().toLowerCase() ?? ''\n return trimmed === UNKNOWN_ACTOR_EMAIL ? '' : trimmed\n }\n const left = normalize(one)\n return left.length > 0 && left === normalize(other)\n}\n\n/**\n * Why an action is not allowed.\n *\n * The code is for a program: control-backend maps it to a status, and the\n * frontend can branch on it. The message is for a person, and it is shown as\n * written.\n */\nexport type ApprovalDenialCode =\n | 'forbidden'\n | 'not-pending'\n | 'self-approval'\n | 'already-acknowledged'\n | 'expired'\n | 'not-creator'\n | 'not-executable'\n | 'no-reason'\n\nexport type ApprovalDecision =\n | { allowed: true }\n | { allowed: false; code: ApprovalDenialCode; message: string }\n\nconst ALLOWED: ApprovalDecision = { allowed: true }\n\nfunction deny(code: ApprovalDenialCode, message: string): ApprovalDecision {\n return { allowed: false, code, message }\n}\n\n/**\n * Reports whether the time to answer a request has passed.\n *\n * Only a `pending` request expires. A request that already reached an end state\n * keeps that state, because \"expired\" would lose the answer that was given.\n */\nexport function isExpired(job: ApprovalJobDto, now: Date): boolean {\n return (\n job.status === 'pending' &&\n new Date(job.expiresAt).getTime() <= now.getTime()\n )\n}\n\n/** Reports the status to show, which is `expired` once the time has passed. */\nexport function effectiveStatus(\n job: ApprovalJobDto,\n now: Date,\n): ApprovalJobDto['status'] {\n return isExpired(job, now) ? 'expired' : job.status\n}\n\n/** Reports how many more acknowledgements the request needs. */\nexport function remainingAcks(job: ApprovalJobDto): number {\n return Math.max(0, job.requiredAcks - job.acks.length)\n}\n\nexport function canCreate(\n actor: ApprovalActorView,\n useCase: ApprovalUseCaseDto,\n): ApprovalDecision {\n if (useCase.createPermission && !actor.has(useCase.createPermission)) {\n return deny('forbidden', 'You cannot open this request.')\n }\n return ALLOWED\n}\n\n/**\n * The four-eyes rule.\n *\n * The bans are in the order a person meets them, and each one has its own\n * message: \"you already approved this\" and \"you cannot approve your own\n * request\" are different mistakes, and one message for both helps nobody.\n *\n * Each of the two bans on a person reads BOTH identities: the `sub` of the\n * token, and the email. See `ApprovalActorView.email` for why one of them is\n * not enough.\n */\nexport function canAcknowledge(\n job: ApprovalJobDto,\n actor: ApprovalActorView,\n useCase: ApprovalUseCaseDto,\n now: Date,\n): ApprovalDecision {\n if (!actor.has(useCase.requiredPermission)) {\n return deny('forbidden', 'You cannot approve this request.')\n }\n if (isExpired(job, now)) {\n return deny(\n 'expired',\n 'This request expired and can no longer be approved.',\n )\n }\n if (job.status !== 'pending') {\n return deny(\n 'not-pending',\n `This request is ${job.status} and can no longer be approved.`,\n )\n }\n if (\n job.createdBy.userId === actor.userId ||\n isSameActorEmail(job.createdBy.email, actor.email)\n ) {\n return deny(\n 'self-approval',\n 'You opened this request, so you cannot approve it.',\n )\n }\n if (\n job.acks.some(\n (ack) =>\n ack.userId === actor.userId || isSameActorEmail(ack.email, actor.email),\n )\n ) {\n return deny('already-acknowledged', 'You already approved this request.')\n }\n return ALLOWED\n}\n\n/**\n * A rejection needs the same permission as an acknowledgement, and a reason.\n *\n * The creator MAY reject their own request. That is a withdrawal with a reason,\n * and stopping it would only push the person to the withdraw button, where the\n * reason is lost.\n */\nexport function canReject(\n job: ApprovalJobDto,\n actor: ApprovalActorView,\n useCase: ApprovalUseCaseDto,\n now: Date,\n reason: string,\n): ApprovalDecision {\n if (!actor.has(useCase.requiredPermission)) {\n return deny('forbidden', 'You cannot reject this request.')\n }\n if (reason.trim().length === 0) {\n return deny('no-reason', 'Give a reason to reject this request.')\n }\n if (isExpired(job, now)) {\n return deny(\n 'expired',\n 'This request expired and can no longer be rejected.',\n )\n }\n if (job.status !== 'pending') {\n return deny(\n 'not-pending',\n `This request is ${job.status} and can no longer be rejected.`,\n )\n }\n return ALLOWED\n}\n\n/** Only the person who opened a request can take it back. */\nexport function canWithdraw(\n job: ApprovalJobDto,\n actor: ApprovalActorView,\n now: Date,\n): ApprovalDecision {\n if (job.createdBy.userId !== actor.userId) {\n return deny(\n 'not-creator',\n 'Only the person who opened a request can withdraw it.',\n )\n }\n if (isExpired(job, now)) {\n return deny(\n 'expired',\n 'This request expired and can no longer be withdrawn.',\n )\n }\n if (job.status !== 'pending') {\n return deny(\n 'not-pending',\n `This request is ${job.status} and can no longer be withdrawn.`,\n )\n }\n return ALLOWED\n}\n\n/**\n * Runs a request that has its approvals.\n *\n * This is the button of the explicit mode. `failed` is executable again,\n * because a failure is often a problem of the system that is now repaired, and\n * asking for the approvals a second time helps nobody.\n */\nexport function canExecute(\n job: ApprovalJobDto,\n actor: ApprovalActorView,\n useCase: ApprovalUseCaseDto,\n): ApprovalDecision {\n if (!actor.has(useCase.requiredPermission)) {\n return deny('forbidden', 'You cannot run this request.')\n }\n if (job.status !== 'approved' && job.status !== 'failed') {\n return deny(\n 'not-executable',\n `This request is ${job.status} and cannot be run.`,\n )\n }\n return ALLOWED\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -26,16 +26,22 @@ const CONFIG = {
|
|
|
26
26
|
// Pre-Sales as per https://confluence.camunda.com/spaces/HAN/pages/168660217/Adjust+C8+SaaS+permissions+and+sales+plan#AdjustC8SaaSpermissionsandsalesplan-DetailsperRole
|
|
27
27
|
},
|
|
28
28
|
subdivisionRules: {
|
|
29
|
-
|
|
30
|
-
//
|
|
29
|
+
// The console team is the "Control plane" subdivision. The id was
|
|
30
|
+
// `256909487` until a HiBob restructure, and the augmenter matches a
|
|
31
|
+
// subdivision exactly and stays quiet on a miss, thus the rule granted
|
|
32
|
+
// nothing to anybody and nobody saw it.
|
|
33
|
+
"267516827": ["accounts:admin", "console:admin", "consoleTeam"],
|
|
34
|
+
// Control plane
|
|
31
35
|
"Engineering Management": [
|
|
32
36
|
"accounts:addSafeRoles",
|
|
33
37
|
"accounts:orgCreator",
|
|
34
38
|
"console:C8ReleaseManager"
|
|
35
39
|
],
|
|
36
40
|
// Engineering Management
|
|
37
|
-
|
|
38
|
-
//
|
|
41
|
+
// Same story: the id `258049291` is gone. The subdivision carries the name
|
|
42
|
+
// `Controller` now, and a name is a valid key — `Engineering Management`
|
|
43
|
+
// above is one too.
|
|
44
|
+
Controller: ["console:clusterPlanManager", "console:operationsEngineer"]
|
|
39
45
|
}
|
|
40
46
|
};
|
|
41
47
|
var camundi_permission_rules_default = CONFIG;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/config/camundi-permission-rules.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 { CamundiPermissionRules } from './camundi-permission-rules.types.js'\n\nconst CONFIG: CamundiPermissionRules = {\n departmentRules: {\n '205882305': ['accounts:read', 'accounts:trialExtender'], // CS\n Marketing: ['accounts:read', 'accounts:trialExtender'],\n '205882307': ['accounts:read', 'accounts:trialExtender'], // revops\n Products: ['console:admin:readonly', 'accounts:read'],\n Executive: ['accounts:read'],\n '259633605': ['accounts:read', 'accounts:trialExtender'], // Sales\n },\n divisionRules: {\n Engineering: ['console:admin:readonly'],\n '259633608': [\n 'accounts:connections:create',\n 'accounts:connections:update',\n 'support:agent',\n ], // Support\n '259633611': [\n 'accounts:trialExtender',\n 'accounts:limitExtender',\n 'support:agent',\n ], // Pre-Sales as per https://confluence.camunda.com/spaces/HAN/pages/168660217/Adjust+C8+SaaS+permissions+and+sales+plan#AdjustC8SaaSpermissionsandsalesplan-DetailsperRole\n },\n subdivisionRules: {\n
|
|
5
|
-
"mappings": "AAQA,MAAM,SAAiC;AAAA,EACrC,iBAAiB;AAAA,IACf,aAAa,CAAC,iBAAiB,wBAAwB;AAAA;AAAA,IACvD,WAAW,CAAC,iBAAiB,wBAAwB;AAAA,IACrD,aAAa,CAAC,iBAAiB,wBAAwB;AAAA;AAAA,IACvD,UAAU,CAAC,0BAA0B,eAAe;AAAA,IACpD,WAAW,CAAC,eAAe;AAAA,IAC3B,aAAa,CAAC,iBAAiB,wBAAwB;AAAA;AAAA,EACzD;AAAA,EACA,eAAe;AAAA,IACb,aAAa,CAAC,wBAAwB;AAAA,IACtC,aAAa;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA,IACA,aAAa;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA,EACF;AAAA,EACA,kBAAkB;AAAA,
|
|
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 { CamundiPermissionRules } from './camundi-permission-rules.types.js'\n\nconst CONFIG: CamundiPermissionRules = {\n departmentRules: {\n '205882305': ['accounts:read', 'accounts:trialExtender'], // CS\n Marketing: ['accounts:read', 'accounts:trialExtender'],\n '205882307': ['accounts:read', 'accounts:trialExtender'], // revops\n Products: ['console:admin:readonly', 'accounts:read'],\n Executive: ['accounts:read'],\n '259633605': ['accounts:read', 'accounts:trialExtender'], // Sales\n },\n divisionRules: {\n Engineering: ['console:admin:readonly'],\n '259633608': [\n 'accounts:connections:create',\n 'accounts:connections:update',\n 'support:agent',\n ], // Support\n '259633611': [\n 'accounts:trialExtender',\n 'accounts:limitExtender',\n 'support:agent',\n ], // Pre-Sales as per https://confluence.camunda.com/spaces/HAN/pages/168660217/Adjust+C8+SaaS+permissions+and+sales+plan#AdjustC8SaaSpermissionsandsalesplan-DetailsperRole\n },\n subdivisionRules: {\n // The console team is the \"Control plane\" subdivision. The id was\n // `256909487` until a HiBob restructure, and the augmenter matches a\n // subdivision exactly and stays quiet on a miss, thus the rule granted\n // nothing to anybody and nobody saw it.\n '267516827': ['accounts:admin', 'console:admin', 'consoleTeam'], // Control plane\n 'Engineering Management': [\n 'accounts:addSafeRoles',\n 'accounts:orgCreator',\n 'console:C8ReleaseManager',\n ], // Engineering Management\n // Same story: the id `258049291` is gone. The subdivision carries the name\n // `Controller` now, and a name is a valid key \u2014 `Engineering Management`\n // above is one too.\n Controller: ['console:clusterPlanManager', 'console:operationsEngineer'],\n },\n}\nexport default CONFIG\n"],
|
|
5
|
+
"mappings": "AAQA,MAAM,SAAiC;AAAA,EACrC,iBAAiB;AAAA,IACf,aAAa,CAAC,iBAAiB,wBAAwB;AAAA;AAAA,IACvD,WAAW,CAAC,iBAAiB,wBAAwB;AAAA,IACrD,aAAa,CAAC,iBAAiB,wBAAwB;AAAA;AAAA,IACvD,UAAU,CAAC,0BAA0B,eAAe;AAAA,IACpD,WAAW,CAAC,eAAe;AAAA,IAC3B,aAAa,CAAC,iBAAiB,wBAAwB;AAAA;AAAA,EACzD;AAAA,EACA,eAAe;AAAA,IACb,aAAa,CAAC,wBAAwB;AAAA,IACtC,aAAa;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA,IACA,aAAa;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA,EACF;AAAA,EACA,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKhB,aAAa,CAAC,kBAAkB,iBAAiB,aAAa;AAAA;AAAA,IAC9D,0BAA0B;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA,IAIA,YAAY,CAAC,8BAA8B,4BAA4B;AAAA,EACzE;AACF;AACA,IAAO,mCAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
export declare const CONSOLE_API_USER_ID = "console-api";
|
|
2
|
+
/**
|
|
3
|
+
* The namespace of the custom claims that Auth0 adds to a token of this fleet.
|
|
4
|
+
*
|
|
5
|
+
* An Auth0 API access token carries no `email` claim of its own. The rules of
|
|
6
|
+
* the tenant put the email of the person under this namespace instead, and
|
|
7
|
+
* every reader of the fleet must look there. A service that reads a top-level
|
|
8
|
+
* `email` claim gets nothing, and the failure is silent: the identity is still
|
|
9
|
+
* valid, so the request continues with an empty email.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CAMUNDA_CLAIMS_NAMESPACE = "https://camunda.com/meta";
|
|
12
|
+
/**
|
|
13
|
+
* The email of an actor whose token carries no email.
|
|
14
|
+
*
|
|
15
|
+
* It is a value and not an empty string, because an empty string reads like a
|
|
16
|
+
* field that nobody wrote. A record that says `unknown` says that the token had
|
|
17
|
+
* no email, which is a different fact.
|
|
18
|
+
*
|
|
19
|
+
* A rule that compares two people MUST NOT treat two of these as one person.
|
|
20
|
+
* `isSameActorEmail` in `approvals/policy.ts` holds that.
|
|
21
|
+
*/
|
|
22
|
+
export declare const UNKNOWN_ACTOR_EMAIL = "unknown";
|
|
2
23
|
export declare const TEMPORARY_CLUSTER_NAME = "Get Started";
|
|
3
24
|
export type Stage = 'local' | 'dev' | 'int' | 'prod';
|
|
4
25
|
export declare const HOTBACKUP_MIN_CREATE_PERIOD: number;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const CONSOLE_API_USER_ID = "console-api";
|
|
2
|
+
const CAMUNDA_CLAIMS_NAMESPACE = "https://camunda.com/meta";
|
|
3
|
+
const UNKNOWN_ACTOR_EMAIL = "unknown";
|
|
2
4
|
const TEMPORARY_CLUSTER_NAME = "Get Started";
|
|
3
5
|
const HOTBACKUP_MIN_CREATE_PERIOD = 1e3 * 60 * 60 * 0.25;
|
|
4
6
|
const SECRETS_MAX_LENGTH = 4096;
|
|
@@ -19,6 +21,7 @@ const MINIMUM_EYEPAIRS = {
|
|
|
19
21
|
};
|
|
20
22
|
const MAX_HARDWARE_PACKAGE_COUNT = 4;
|
|
21
23
|
export {
|
|
24
|
+
CAMUNDA_CLAIMS_NAMESPACE,
|
|
22
25
|
CONSOLE_API_USER_ID,
|
|
23
26
|
FIRSTNAME_LASTNAME_MAX_LENGTH,
|
|
24
27
|
HOTBACKUP_MIN_CREATE_PERIOD,
|
|
@@ -28,6 +31,7 @@ export {
|
|
|
28
31
|
NAMES_OF_ENTERPRISE_SALESPLANS_WITH_SLEEPING_CLUSTERS,
|
|
29
32
|
SECRETS_MAX_LENGTH,
|
|
30
33
|
STARTING_PRICE_PER_MONTH_IN_EURO,
|
|
31
|
-
TEMPORARY_CLUSTER_NAME
|
|
34
|
+
TEMPORARY_CLUSTER_NAME,
|
|
35
|
+
UNKNOWN_ACTOR_EMAIL
|
|
32
36
|
};
|
|
33
37
|
//# sourceMappingURL=misc.const.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/constants/misc.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\nexport const CONSOLE_API_USER_ID = 'console-api'\nexport const TEMPORARY_CLUSTER_NAME = 'Get Started'\nexport type Stage = 'local' | 'dev' | 'int' | 'prod'\nexport const HOTBACKUP_MIN_CREATE_PERIOD = 1000 * 60 * 60 * 0.25 // 15 minutes\n\nexport const SECRETS_MAX_LENGTH = 4096\nexport const FIRSTNAME_LASTNAME_MAX_LENGTH = 255\n\nexport const STARTING_PRICE_PER_MONTH_IN_EURO = 99\n\nexport const MAX_LENGTH_CLUSTER_NAME = 50\n\nexport const NAMES_OF_ENTERPRISE_SALESPLANS_WITH_SLEEPING_CLUSTERS = [\n 'CELP with sleeping clusters',\n 'Camunda Partner',\n]\n\nexport const MINIMUM_EYEPAIRS = {\n CLUSTER_DELETION: 3,\n CLUSTER_GENERATION_UPDATE: 2,\n CLUSTER_PLAN_UPDATE: 2,\n CLUSTER_PLAN_TYPE_UPDATE: 2,\n CLUSTER_PLAN_MODIFICATION: 1,\n CLUSTER_MODIFICATION: 1,\n}\n\nexport const MAX_HARDWARE_PACKAGE_COUNT = 4\n"],
|
|
5
|
-
"mappings": "AAMO,MAAM,sBAAsB;AAC5B,MAAM,yBAAyB;AAE/B,MAAM,8BAA8B,MAAO,KAAK,KAAK;AAErD,MAAM,qBAAqB;AAC3B,MAAM,gCAAgC;AAEtC,MAAM,mCAAmC;AAEzC,MAAM,0BAA0B;AAEhC,MAAM,wDAAwD;AAAA,EACnE;AAAA,EACA;AACF;AAEO,MAAM,mBAAmB;AAAA,EAC9B,kBAAkB;AAAA,EAClB,2BAA2B;AAAA,EAC3B,qBAAqB;AAAA,EACrB,0BAA0B;AAAA,EAC1B,2BAA2B;AAAA,EAC3B,sBAAsB;AACxB;AAEO,MAAM,6BAA6B;",
|
|
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\nexport const CONSOLE_API_USER_ID = 'console-api'\n\n/**\n * The namespace of the custom claims that Auth0 adds to a token of this fleet.\n *\n * An Auth0 API access token carries no `email` claim of its own. The rules of\n * the tenant put the email of the person under this namespace instead, and\n * every reader of the fleet must look there. A service that reads a top-level\n * `email` claim gets nothing, and the failure is silent: the identity is still\n * valid, so the request continues with an empty email.\n */\nexport const CAMUNDA_CLAIMS_NAMESPACE = 'https://camunda.com/meta'\n\n/**\n * The email of an actor whose token carries no email.\n *\n * It is a value and not an empty string, because an empty string reads like a\n * field that nobody wrote. A record that says `unknown` says that the token had\n * no email, which is a different fact.\n *\n * A rule that compares two people MUST NOT treat two of these as one person.\n * `isSameActorEmail` in `approvals/policy.ts` holds that.\n */\nexport const UNKNOWN_ACTOR_EMAIL = 'unknown'\nexport const TEMPORARY_CLUSTER_NAME = 'Get Started'\nexport type Stage = 'local' | 'dev' | 'int' | 'prod'\nexport const HOTBACKUP_MIN_CREATE_PERIOD = 1000 * 60 * 60 * 0.25 // 15 minutes\n\nexport const SECRETS_MAX_LENGTH = 4096\nexport const FIRSTNAME_LASTNAME_MAX_LENGTH = 255\n\nexport const STARTING_PRICE_PER_MONTH_IN_EURO = 99\n\nexport const MAX_LENGTH_CLUSTER_NAME = 50\n\nexport const NAMES_OF_ENTERPRISE_SALESPLANS_WITH_SLEEPING_CLUSTERS = [\n 'CELP with sleeping clusters',\n 'Camunda Partner',\n]\n\nexport const MINIMUM_EYEPAIRS = {\n CLUSTER_DELETION: 3,\n CLUSTER_GENERATION_UPDATE: 2,\n CLUSTER_PLAN_UPDATE: 2,\n CLUSTER_PLAN_TYPE_UPDATE: 2,\n CLUSTER_PLAN_MODIFICATION: 1,\n CLUSTER_MODIFICATION: 1,\n}\n\nexport const MAX_HARDWARE_PACKAGE_COUNT = 4\n"],
|
|
5
|
+
"mappings": "AAMO,MAAM,sBAAsB;AAW5B,MAAM,2BAA2B;AAYjC,MAAM,sBAAsB;AAC5B,MAAM,yBAAyB;AAE/B,MAAM,8BAA8B,MAAO,KAAK,KAAK;AAErD,MAAM,qBAAqB;AAC3B,MAAM,gCAAgC;AAEtC,MAAM,mCAAmC;AAEzC,MAAM,0BAA0B;AAEhC,MAAM,wDAAwD;AAAA,EACnE;AAAA,EACA;AACF;AAEO,MAAM,mBAAmB;AAAA,EAC9B,kBAAkB;AAAA,EAClB,2BAA2B;AAAA,EAC3B,qBAAqB;AAAA,EACrB,0BAA0B;AAAA,EAC1B,2BAA2B;AAAA,EAC3B,sBAAsB;AACxB;AAEO,MAAM,6BAA6B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Largest page a `/v2` list endpoint returns. The backends clamp `limit` to this. */
|
|
2
|
+
export declare const V2_MAX_PAGE_LIMIT = 200;
|
|
3
|
+
/**
|
|
4
|
+
* Most uuids a `/v2` list endpoint accepts in a comma-list query filter such as
|
|
5
|
+
* `filter.ids`. The backends drop entries past this count without an error.
|
|
6
|
+
*/
|
|
7
|
+
export declare const V2_FILTER_IDS_MAX = 500;
|
|
8
|
+
/**
|
|
9
|
+
* Most uuids a `/v2` list endpoint accepts in a POST body list. Over this the
|
|
10
|
+
* endpoint rejects with 400 instead of a silent cut.
|
|
11
|
+
*/
|
|
12
|
+
export declare const V2_BODY_IDS_MAX = 10000;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/constants/v2-list.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/** Largest page a `/v2` list endpoint returns. The backends clamp `limit` to this. */\nexport const V2_MAX_PAGE_LIMIT = 200\n\n/**\n * Most uuids a `/v2` list endpoint accepts in a comma-list query filter such as\n * `filter.ids`. The backends drop entries past this count without an error.\n */\nexport const V2_FILTER_IDS_MAX = 500\n\n/**\n * Most uuids a `/v2` list endpoint accepts in a POST body list. Over this the\n * endpoint rejects with 400 instead of a silent cut.\n */\nexport const V2_BODY_IDS_MAX = 10_000\n"],
|
|
5
|
+
"mappings": "AAOO,MAAM,oBAAoB;AAM1B,MAAM,oBAAoB;AAM1B,MAAM,kBAAkB;",
|
|
6
|
+
"names": []
|
|
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,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
|
+
}
|
|
@@ -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;
|
|
@@ -132,6 +146,55 @@ export interface ClusterListItemV2Dto {
|
|
|
132
146
|
}
|
|
133
147
|
/** Detail extends the list shape; richer fields are added in Phase 3. */
|
|
134
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
|
+
}
|
|
135
198
|
export interface ListClustersV2Params extends CursorParams {
|
|
136
199
|
q?: string;
|
|
137
200
|
/**
|
|
@@ -207,8 +270,22 @@ export interface ListClustersV2Params extends CursorParams {
|
|
|
207
270
|
minorVersion?: string;
|
|
208
271
|
/** Restrict to these release channel names. */
|
|
209
272
|
channels?: string[];
|
|
210
|
-
/**
|
|
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
|
+
*/
|
|
211
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[];
|
|
212
289
|
};
|
|
213
290
|
}
|
|
214
291
|
export interface ClusterParameterOptionV2Dto {
|
|
@@ -32,9 +32,11 @@ export interface Features {
|
|
|
32
32
|
backupScheduleApi: boolean;
|
|
33
33
|
clusterCreationBlocked: boolean;
|
|
34
34
|
deferSpecChangeWhileAsleep: boolean;
|
|
35
|
+
clusterDuplication: boolean;
|
|
35
36
|
};
|
|
36
37
|
control: {
|
|
37
38
|
frontendTelemetry: boolean;
|
|
39
|
+
sendToExpiredTrialApproval: boolean;
|
|
38
40
|
};
|
|
39
41
|
accounts: {
|
|
40
42
|
moveClusterOrg: boolean;
|