@bussolabs/closeyourit-cli 0.22.0 → 0.23.1
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/README.md +44 -3
- package/dist/base.d.ts +23 -0
- package/dist/base.js +64 -4
- package/dist/commands/assistant/ask.d.ts +24 -0
- package/dist/commands/assistant/ask.js +112 -0
- package/dist/commands/assistant/conversations/create.d.ts +9 -0
- package/dist/commands/assistant/conversations/create.js +25 -0
- package/dist/commands/assistant/conversations/delete.d.ts +12 -0
- package/dist/commands/assistant/conversations/delete.js +25 -0
- package/dist/commands/assistant/conversations/list.d.ts +10 -0
- package/dist/commands/assistant/conversations/list.js +30 -0
- package/dist/commands/assistant/conversations/show.d.ts +9 -0
- package/dist/commands/assistant/conversations/show.js +25 -0
- package/dist/commands/errors/assign.d.ts +14 -0
- package/dist/commands/errors/assign.js +52 -0
- package/dist/commands/errors/bulk-triage.d.ts +11 -0
- package/dist/commands/errors/bulk-triage.js +41 -0
- package/dist/commands/errors/delete.d.ts +13 -0
- package/dist/commands/errors/delete.js +28 -0
- package/dist/commands/errors/grouping-rules/create.d.ts +15 -0
- package/dist/commands/errors/grouping-rules/create.js +44 -0
- package/dist/commands/errors/grouping-rules/delete.d.ts +13 -0
- package/dist/commands/errors/grouping-rules/delete.js +27 -0
- package/dist/commands/errors/grouping-rules/list.d.ts +10 -0
- package/dist/commands/errors/grouping-rules/list.js +28 -0
- package/dist/commands/errors/grouping-rules/update.d.ts +18 -0
- package/dist/commands/errors/grouping-rules/update.js +43 -0
- package/dist/commands/errors/merge.d.ts +14 -0
- package/dist/commands/errors/merge.js +39 -0
- package/dist/commands/errors/split.d.ts +13 -0
- package/dist/commands/errors/split.js +34 -0
- package/dist/commands/invitations/create.js +9 -2
- package/dist/commands/invitations/resend.js +8 -2
- package/dist/commands/kb/attach.d.ts +12 -0
- package/dist/commands/kb/attach.js +49 -0
- package/dist/commands/kb/attachment-download.d.ts +14 -0
- package/dist/commands/kb/attachment-download.js +38 -0
- package/dist/commands/kb/attachments.d.ts +9 -0
- package/dist/commands/kb/attachments.js +36 -0
- package/dist/commands/kb/create.d.ts +4 -1
- package/dist/commands/kb/create.js +41 -5
- package/dist/commands/kb/detach.d.ts +13 -0
- package/dist/commands/kb/detach.js +26 -0
- package/dist/commands/kb/show.js +3 -0
- package/dist/commands/kb/update.d.ts +4 -0
- package/dist/commands/kb/update.js +69 -21
- package/dist/commands/metrics/bulk-triage.d.ts +11 -0
- package/dist/commands/metrics/bulk-triage.js +44 -0
- package/dist/commands/service-accounts/show.d.ts +9 -0
- package/dist/commands/service-accounts/show.js +26 -0
- package/dist/commands/service-accounts/tokens/list.d.ts +12 -0
- package/dist/commands/service-accounts/tokens/list.js +40 -0
- package/dist/commands/service-accounts/update.d.ts +31 -0
- package/dist/commands/service-accounts/update.js +105 -0
- package/dist/commands/shared/delete.d.ts +12 -0
- package/dist/commands/shared/delete.js +30 -0
- package/dist/commands/shared/list.d.ts +6 -0
- package/dist/commands/shared/list.js +45 -0
- package/dist/commands/shared/set.d.ts +14 -0
- package/dist/commands/shared/set.js +41 -0
- package/dist/commands/tickets/ask.d.ts +13 -0
- package/dist/commands/tickets/ask.js +81 -0
- package/dist/commands/tickets/dependencies/add.d.ts +15 -0
- package/dist/commands/tickets/dependencies/add.js +62 -0
- package/dist/commands/tickets/dependencies/list.d.ts +12 -0
- package/dist/commands/tickets/dependencies/list.js +37 -0
- package/dist/commands/tickets/dependencies/remove.d.ts +14 -0
- package/dist/commands/tickets/dependencies/remove.js +59 -0
- package/dist/commands/tickets/work-context.d.ts +15 -0
- package/dist/commands/tickets/work-context.js +130 -0
- package/dist/commands/vault/requests/approve.d.ts +9 -0
- package/dist/commands/vault/requests/approve.js +40 -0
- package/dist/commands/vault/requests/list.d.ts +6 -0
- package/dist/commands/vault/requests/list.js +29 -0
- package/dist/commands/vault/requests/reject.d.ts +12 -0
- package/dist/commands/vault/requests/reject.js +52 -0
- package/dist/commands/workload/create.d.ts +15 -0
- package/dist/commands/workload/create.js +45 -0
- package/dist/commands/workload/delete.d.ts +12 -0
- package/dist/commands/workload/delete.js +26 -0
- package/dist/commands/workload/list.d.ts +9 -0
- package/dist/commands/workload/list.js +29 -0
- package/dist/commands/workload/participant/add.d.ts +10 -0
- package/dist/commands/workload/participant/add.js +27 -0
- package/dist/commands/workload/participant/remove.d.ts +10 -0
- package/dist/commands/workload/participant/remove.js +23 -0
- package/dist/commands/workload/promote.d.ts +17 -0
- package/dist/commands/workload/promote.js +58 -0
- package/dist/commands/workload/show.d.ts +9 -0
- package/dist/commands/workload/show.js +24 -0
- package/dist/commands/workload/update.d.ts +17 -0
- package/dist/commands/workload/update.js +45 -0
- package/dist/errors/error-codes.d.ts +17 -0
- package/dist/errors/error-codes.js +36 -0
- package/dist/lib/api.d.ts +7 -1
- package/dist/lib/api.js +8 -0
- package/dist/lib/assistant.d.ts +29 -0
- package/dist/lib/assistant.js +76 -0
- package/dist/lib/bulk-triage.d.ts +35 -0
- package/dist/lib/bulk-triage.js +55 -0
- package/dist/lib/grouping-rules.d.ts +28 -0
- package/dist/lib/grouping-rules.js +39 -0
- package/dist/lib/knowledge.d.ts +15 -0
- package/dist/lib/knowledge.js +36 -0
- package/dist/lib/output.js +10 -0
- package/dist/lib/poll.d.ts +51 -0
- package/dist/lib/poll.js +59 -0
- package/dist/lib/service-accounts.d.ts +65 -0
- package/dist/lib/service-accounts.js +132 -0
- package/dist/lib/vault-requests.d.ts +38 -0
- package/dist/lib/vault-requests.js +125 -0
- package/dist/lib/workload.d.ts +37 -0
- package/dist/lib/workload.js +64 -0
- package/oclif.manifest.json +6290 -4001
- package/opencli.json +2164 -549
- package/package.json +27 -3
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.environmentsLine = environmentsLine;
|
|
4
|
+
exports.normalizeEnvironmentCodes = normalizeEnvironmentCodes;
|
|
5
|
+
exports.accountSummary = accountSummary;
|
|
6
|
+
exports.withoutSecret = withoutSecret;
|
|
7
|
+
exports.tokenStatus = tokenStatus;
|
|
8
|
+
exports.unknownEnvironmentCodes = unknownEnvironmentCodes;
|
|
9
|
+
exports.assertBoundaryApplied = assertBoundaryApplied;
|
|
10
|
+
const api_1 = require("./api");
|
|
11
|
+
const error_codes_1 = require("../errors/error-codes");
|
|
12
|
+
/**
|
|
13
|
+
* Reading and correcting a service account without recreating it (CYCL-46).
|
|
14
|
+
*
|
|
15
|
+
* One rule runs through everything here: on the secret boundary of a service account, an EMPTY list
|
|
16
|
+
* of environment codes does not mean "no access", it means "every environment". So the empty list is
|
|
17
|
+
* never a neutral value to fall into by accident — it is a decision, and it has to be printed with
|
|
18
|
+
* words that cannot be misread.
|
|
19
|
+
*/
|
|
20
|
+
/** The boundary in words: the codes, or the fact that there is no boundary at all. */
|
|
21
|
+
function environmentsLine(codes) {
|
|
22
|
+
const list = normalizeEnvironmentCodes(Array.isArray(codes) ? codes : []);
|
|
23
|
+
return `secret environments: ${list.length > 0 ? list.join(', ') : 'all (no restriction)'}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Environment codes the way the server stores them: trimmed, lowercase, each one once, blanks
|
|
27
|
+
* dropped — the same normalisation `Connections::SetSecretAccess#sanitize` applies before saving.
|
|
28
|
+
* Sending them already in that shape is what makes "what I asked for" and "what came back"
|
|
29
|
+
* comparable at all: otherwise ` Staging ` would look dropped every single time.
|
|
30
|
+
*/
|
|
31
|
+
function normalizeEnvironmentCodes(values) {
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
const codes = [];
|
|
34
|
+
for (const value of values) {
|
|
35
|
+
const code = String(value ?? '').trim().toLowerCase();
|
|
36
|
+
if (code === '' || seen.has(code))
|
|
37
|
+
continue;
|
|
38
|
+
seen.add(code);
|
|
39
|
+
codes.push(code);
|
|
40
|
+
}
|
|
41
|
+
return codes;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The fields of a service account worth printing, as an explicit allow-list — the same discipline as
|
|
45
|
+
* `changeRequestSummary`: `renderRecord` prints every scalar it is handed, so handing it the raw
|
|
46
|
+
* payload would print whatever the server adds next, up to and including a field carrying a token.
|
|
47
|
+
* `secret_environment_codes` is left out on purpose: it is an array (renderRecord would swallow it)
|
|
48
|
+
* and it deserves the sentence `environmentsLine` builds, not a bare key.
|
|
49
|
+
*/
|
|
50
|
+
function accountSummary(account) {
|
|
51
|
+
const summary = {
|
|
52
|
+
id: account.id,
|
|
53
|
+
name: account.name,
|
|
54
|
+
handle: account.handle,
|
|
55
|
+
created_at: account.created_at,
|
|
56
|
+
active_tokens_count: account.active_tokens_count,
|
|
57
|
+
};
|
|
58
|
+
return Object.fromEntries(Object.entries(summary).filter(([, value]) => value !== undefined && value !== null));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A copy of a token row without the secret, on both the human and the `--json` path.
|
|
62
|
+
*
|
|
63
|
+
* `UserApiTokenSerializer` never ships it — the secret exists only in the response of `tokens
|
|
64
|
+
* create`, reveal-once, and in the database there is just a prefix. This strips it anyway, for the
|
|
65
|
+
* same reason `withoutValue` does it on the vault queue: the CLI decides what leaves the terminal,
|
|
66
|
+
* so a payload that one day carried the secret (a serializer edit, a proxy, a hand-rolled backend)
|
|
67
|
+
* cannot turn a plain listing into a second reveal.
|
|
68
|
+
*/
|
|
69
|
+
function withoutSecret(row) {
|
|
70
|
+
if (row === null || typeof row !== 'object')
|
|
71
|
+
return row;
|
|
72
|
+
const { secret: _secret, ...rest } = row;
|
|
73
|
+
return rest;
|
|
74
|
+
}
|
|
75
|
+
/** `active` while the token works, `revoked` once it has been withdrawn — what the reader is after. */
|
|
76
|
+
function tokenStatus(token) {
|
|
77
|
+
return token.revoked_at ? 'revoked' : 'active';
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The requested codes an organization does not declare — checked BEFORE writing.
|
|
81
|
+
*
|
|
82
|
+
* `SetSecretAccess` intersects what it receives with the codes the organization really has and
|
|
83
|
+
* saves what is left, answering 200 either way. On a typo that intersection is empty, and an empty
|
|
84
|
+
* allow-list is not "no environments", it is EVERY environment: `--secret-environment stagin` would
|
|
85
|
+
* hand the account the production secrets it was being restricted away from. Catching it here means
|
|
86
|
+
* the mistake costs an error message instead of an open door.
|
|
87
|
+
*
|
|
88
|
+
* `known` empty is treated as "could not read them" by the caller, not as "nothing is valid": a
|
|
89
|
+
* lookup that came back empty (no permission, a serializer without `code`) must not block a command
|
|
90
|
+
* the caller is entitled to run.
|
|
91
|
+
*/
|
|
92
|
+
function unknownEnvironmentCodes(requested, known) {
|
|
93
|
+
const declared = new Set(normalizeEnvironmentCodes(known));
|
|
94
|
+
return requested.filter((code) => !declared.has(code));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Refuse an environment boundary the server did not actually put in place.
|
|
98
|
+
*
|
|
99
|
+
* The pre-flight check above catches the typo before it is written, but it cannot be the only
|
|
100
|
+
* guard: reading the environments needs `environments.view`, a permission managing members does not
|
|
101
|
+
* imply, and an environment can be removed between the two calls. So the request and the response
|
|
102
|
+
* are compared here too, and any difference — a code dropped OR a code appearing that nobody asked
|
|
103
|
+
* for, which would mean a wider boundary than requested — is an error, not a footnote.
|
|
104
|
+
*
|
|
105
|
+
* Whatever the server did save stays saved (the boundary is one call away from being fixed, and
|
|
106
|
+
* rolling it back blindly could lock out an agent mid-run), but the command says so and exits
|
|
107
|
+
* non-zero — the same shape as `assertNothingDropped` on a partial bulk triage.
|
|
108
|
+
*/
|
|
109
|
+
function assertBoundaryApplied(requested, applied) {
|
|
110
|
+
const inForce = applied.length > 0 ? applied.join(', ') : 'all environments (no restriction)';
|
|
111
|
+
// --all-environments: the removal is the whole point, so a boundary still standing means the
|
|
112
|
+
// command did nothing while looking like it worked.
|
|
113
|
+
if (requested.length === 0) {
|
|
114
|
+
if (applied.length === 0)
|
|
115
|
+
return;
|
|
116
|
+
throw new api_1.ApiRequestError(500, error_codes_1.ErrorCodes.ServiceAccount.restrictionNotApplied, `The restriction was not removed: the account can still read secrets only in ${inForce}. ` +
|
|
117
|
+
'Nothing else changed — run the command again, or check the account from the web vault.');
|
|
118
|
+
}
|
|
119
|
+
const kept = new Set(applied.map((code) => code.toLowerCase()));
|
|
120
|
+
const asked = new Set(requested);
|
|
121
|
+
const dropped = requested.filter((code) => !kept.has(code));
|
|
122
|
+
const extra = applied.filter((code) => !asked.has(code.toLowerCase()));
|
|
123
|
+
if (dropped.length === 0 && extra.length === 0)
|
|
124
|
+
return;
|
|
125
|
+
const what = dropped.length > 0
|
|
126
|
+
? `Not applied: ${dropped.join(', ')} — the server kept a different boundary ` +
|
|
127
|
+
'(an environment removed in the meantime, or a code this organization does not have).'
|
|
128
|
+
: `Applied without being asked for: ${extra.join(', ')} — the boundary is wider than requested ` +
|
|
129
|
+
`(${requested.join(', ')}).`;
|
|
130
|
+
throw new api_1.ApiRequestError(404, error_codes_1.ErrorCodes.ServiceAccount.boundaryMismatch, `${what} In force now: ${inForce}. ` +
|
|
131
|
+
'Run "environments list" to see the real codes, then set the boundary again.');
|
|
132
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Org-level collection of the secret changes waiting for a second approval (CYCL-40). */
|
|
2
|
+
export declare const CHANGE_REQUESTS_PATH = "/cli/v1/vault/change_requests";
|
|
3
|
+
/**
|
|
4
|
+
* A copy of a change-request row without the proposed value.
|
|
5
|
+
*
|
|
6
|
+
* `SecretChangeRequestSerializer` never ships `value` — it is encrypted at rest and a change
|
|
7
|
+
* request is only ever allowed to reveal the NAME and the intent. This strips it anyway, on both
|
|
8
|
+
* the human and the `--json` path: the CLI decides what leaves the terminal, so a payload that one
|
|
9
|
+
* day carried the secret (a serializer edit, a proxy, a hand-rolled backend) cannot leak it through
|
|
10
|
+
* a command whose whole point is reviewing secrets without seeing them.
|
|
11
|
+
*/
|
|
12
|
+
export declare function withoutValue<T>(row: T): T;
|
|
13
|
+
/** One row of `vault requests list`: never the value, only what identifies the pending change. */
|
|
14
|
+
export declare function changeRequestRow(row: Record<string, unknown>): string[];
|
|
15
|
+
/** Column headers of `vault requests list`, in the order `changeRequestRow` fills them. */
|
|
16
|
+
export declare const CHANGE_REQUEST_HEADERS: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Short human wording of what a request is about — `STRIPE_KEY (set) on ACME/production`. Empty when
|
|
19
|
+
* the payload names nothing, so the caller can fall back to a bare confirmation instead of printing
|
|
20
|
+
* a line full of question marks.
|
|
21
|
+
*/
|
|
22
|
+
export declare function changeRequestLabel(row: Record<string, unknown>): string;
|
|
23
|
+
/**
|
|
24
|
+
* The fields of a decided request worth printing, as an explicit allow-list — the same discipline as
|
|
25
|
+
* `leaseSummary` in `tickets lease claim`: `renderRecord` prints every scalar it is handed, so
|
|
26
|
+
* handing it the raw payload would print whatever the server adds next. Absent/null fields are
|
|
27
|
+
* dropped rather than shown as `-`: an approval has no rejection reason, and a row of dashes reads
|
|
28
|
+
* like missing data instead of "not applicable".
|
|
29
|
+
*/
|
|
30
|
+
export declare function changeRequestSummary(row: Record<string, unknown>): Record<string, unknown>;
|
|
31
|
+
/**
|
|
32
|
+
* Append the way out to a refused decision, keeping the server's own message and code intact — the
|
|
33
|
+
* pattern `fetchLookupRows` already uses on a 403 in `BaseCommand`. Anything else (a 404 on a
|
|
34
|
+
* request outside the visible projects, a plain "Permesso negato" from the RBAC gate, a network
|
|
35
|
+
* failure) passes through untouched: inventing an explanation for an error we have not identified
|
|
36
|
+
* would be worse than the bare code.
|
|
37
|
+
*/
|
|
38
|
+
export declare function describeDecisionError(error: unknown): unknown;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CHANGE_REQUEST_HEADERS = exports.CHANGE_REQUESTS_PATH = void 0;
|
|
4
|
+
exports.withoutValue = withoutValue;
|
|
5
|
+
exports.changeRequestRow = changeRequestRow;
|
|
6
|
+
exports.changeRequestLabel = changeRequestLabel;
|
|
7
|
+
exports.changeRequestSummary = changeRequestSummary;
|
|
8
|
+
exports.describeDecisionError = describeDecisionError;
|
|
9
|
+
const api_1 = require("./api");
|
|
10
|
+
/** Org-level collection of the secret changes waiting for a second approval (CYCL-40). */
|
|
11
|
+
exports.CHANGE_REQUESTS_PATH = '/cli/v1/vault/change_requests';
|
|
12
|
+
/**
|
|
13
|
+
* A copy of a change-request row without the proposed value.
|
|
14
|
+
*
|
|
15
|
+
* `SecretChangeRequestSerializer` never ships `value` — it is encrypted at rest and a change
|
|
16
|
+
* request is only ever allowed to reveal the NAME and the intent. This strips it anyway, on both
|
|
17
|
+
* the human and the `--json` path: the CLI decides what leaves the terminal, so a payload that one
|
|
18
|
+
* day carried the secret (a serializer edit, a proxy, a hand-rolled backend) cannot leak it through
|
|
19
|
+
* a command whose whole point is reviewing secrets without seeing them.
|
|
20
|
+
*/
|
|
21
|
+
function withoutValue(row) {
|
|
22
|
+
if (row === null || typeof row !== 'object')
|
|
23
|
+
return row;
|
|
24
|
+
const { value: _value, ...rest } = row;
|
|
25
|
+
return rest;
|
|
26
|
+
}
|
|
27
|
+
/** `key` of the project the change belongs to (its name as a fallback), or '' when the payload is silent. */
|
|
28
|
+
function projectLabel(row) {
|
|
29
|
+
const project = (row.project ?? {});
|
|
30
|
+
return String(project.key ?? project.name ?? '');
|
|
31
|
+
}
|
|
32
|
+
/** `code` of the target environment (its label as a fallback), or '' when the payload is silent. */
|
|
33
|
+
function environmentLabel(row) {
|
|
34
|
+
const environment = (row.environment ?? {});
|
|
35
|
+
return String(environment.code ?? environment.label ?? '');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Who asked for the change: name, email as a fallback, '' when nobody is attached. The requester FK
|
|
39
|
+
* is nullified when the account is deleted (the audit outlives the person), so this is a real case,
|
|
40
|
+
* not a defensive flourish.
|
|
41
|
+
*/
|
|
42
|
+
function requesterLabel(row) {
|
|
43
|
+
const requester = (row.requested_by ?? {});
|
|
44
|
+
return String(requester.name ?? requester.email ?? '');
|
|
45
|
+
}
|
|
46
|
+
/** One row of `vault requests list`: never the value, only what identifies the pending change. */
|
|
47
|
+
function changeRequestRow(row) {
|
|
48
|
+
return [
|
|
49
|
+
String(row.id ?? ''),
|
|
50
|
+
projectLabel(row),
|
|
51
|
+
environmentLabel(row),
|
|
52
|
+
String(row.action ?? ''),
|
|
53
|
+
String(row.name ?? ''),
|
|
54
|
+
requesterLabel(row),
|
|
55
|
+
String(row.created_at ?? ''),
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
/** Column headers of `vault requests list`, in the order `changeRequestRow` fills them. */
|
|
59
|
+
exports.CHANGE_REQUEST_HEADERS = ['ID', 'PROJECT', 'ENVIRONMENT', 'ACTION', 'NAME', 'REQUESTED BY', 'ASKED AT'];
|
|
60
|
+
/**
|
|
61
|
+
* Short human wording of what a request is about — `STRIPE_KEY (set) on ACME/production`. Empty when
|
|
62
|
+
* the payload names nothing, so the caller can fall back to a bare confirmation instead of printing
|
|
63
|
+
* a line full of question marks.
|
|
64
|
+
*/
|
|
65
|
+
function changeRequestLabel(row) {
|
|
66
|
+
const name = String(row.name ?? '').trim();
|
|
67
|
+
if (name === '')
|
|
68
|
+
return '';
|
|
69
|
+
const action = String(row.action ?? '').trim();
|
|
70
|
+
const where = [projectLabel(row), environmentLabel(row)].filter(Boolean).join('/');
|
|
71
|
+
return [name, action && `(${action})`, where && `on ${where}`].filter(Boolean).join(' ');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The fields of a decided request worth printing, as an explicit allow-list — the same discipline as
|
|
75
|
+
* `leaseSummary` in `tickets lease claim`: `renderRecord` prints every scalar it is handed, so
|
|
76
|
+
* handing it the raw payload would print whatever the server adds next. Absent/null fields are
|
|
77
|
+
* dropped rather than shown as `-`: an approval has no rejection reason, and a row of dashes reads
|
|
78
|
+
* like missing data instead of "not applicable".
|
|
79
|
+
*/
|
|
80
|
+
function changeRequestSummary(row) {
|
|
81
|
+
const summary = {
|
|
82
|
+
id: row.id,
|
|
83
|
+
project: projectLabel(row) || undefined,
|
|
84
|
+
environment: environmentLabel(row) || undefined,
|
|
85
|
+
name: row.name,
|
|
86
|
+
action: row.action,
|
|
87
|
+
status: row.status,
|
|
88
|
+
requested_by: requesterLabel(row) || undefined,
|
|
89
|
+
requested_at: row.created_at,
|
|
90
|
+
decided_at: row.decided_at,
|
|
91
|
+
reason: row.reason,
|
|
92
|
+
};
|
|
93
|
+
return Object.fromEntries(Object.entries(summary).filter(([, value]) => value !== undefined && value !== null));
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The four ways a decision is refused, each with the way out. The backend codes are stable and its
|
|
97
|
+
* messages are already clear about WHAT happened; what a refusal alone does not say is what to do
|
|
98
|
+
* next — and all four look identical from the outside ("it did not work"), while they call for
|
|
99
|
+
* opposite reactions: hand the request to a colleague, log in as yourself, refresh the queue, write
|
|
100
|
+
* a real reason.
|
|
101
|
+
*/
|
|
102
|
+
const DECISION_HINTS = {
|
|
103
|
+
// Four-eyes separation: whoever decides is never whoever asked (Secrets::ChangeRequests::DecisionGuard).
|
|
104
|
+
'R403-CHANGEREQUEST-001': 'The four-eyes rule holds here: this one has to be decided by someone else with secrets.manage on that project.',
|
|
105
|
+
// CYRA-640: the second approval is a person's, never a machine's — a service-account token
|
|
106
|
+
// authenticates like a personal one, so the refusal has to name the difference.
|
|
107
|
+
'R403-CHANGEREQUEST-003': 'Only a person can decide a secret change request: a service account cannot, so run this with your own login.',
|
|
108
|
+
'R409-CHANGEREQUEST-001': 'It was already decided (applied, rejected or withdrawn) — run "vault requests list" to see what is still waiting.',
|
|
109
|
+
'R422-CHANGEREQUEST-001': 'Pass a --reason that says why: a rejection freezes the request, and whoever asked needs to know what to fix.',
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Append the way out to a refused decision, keeping the server's own message and code intact — the
|
|
113
|
+
* pattern `fetchLookupRows` already uses on a 403 in `BaseCommand`. Anything else (a 404 on a
|
|
114
|
+
* request outside the visible projects, a plain "Permesso negato" from the RBAC gate, a network
|
|
115
|
+
* failure) passes through untouched: inventing an explanation for an error we have not identified
|
|
116
|
+
* would be worse than the bare code.
|
|
117
|
+
*/
|
|
118
|
+
function describeDecisionError(error) {
|
|
119
|
+
if (!(error instanceof api_1.ApiRequestError))
|
|
120
|
+
return error;
|
|
121
|
+
const hint = DECISION_HINTS[error.code];
|
|
122
|
+
if (!hint)
|
|
123
|
+
return error;
|
|
124
|
+
return new api_1.ApiRequestError(error.status, error.code, `${error.message}. ${hint}`, error.details);
|
|
125
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* La board del carico di lavoro non-dev (fiere, materiale cartaceo, incontri, chiamate): una sola
|
|
3
|
+
* raccolta d'organizzazione, con radice di tenancy il TEAM e non il progetto — per questo l'endpoint
|
|
4
|
+
* non è annidato sotto `/projects/:id` come quasi tutto il resto della CLI.
|
|
5
|
+
*/
|
|
6
|
+
export declare const WORKLOAD_ACTIONS_PATH = "/cli/v1/workload/actions";
|
|
7
|
+
/** Gli stati dell'enum del server, nell'ordine in cui un'attività li attraversa. */
|
|
8
|
+
export declare const WORKLOAD_STATUSES: string[];
|
|
9
|
+
/**
|
|
10
|
+
* I campi che `workload create` e `workload update` scrivono allo stesso modo. Il titolo resta
|
|
11
|
+
* fuori: nasce obbligatorio sulla creazione e facoltativo sulla modifica. Il team pure — è
|
|
12
|
+
* immutabile lato server (spostare un'attività cambierebbe chi la vede), quindi si indica solo
|
|
13
|
+
* quando l'attività nasce.
|
|
14
|
+
*/
|
|
15
|
+
export declare const actionFieldFlags: {
|
|
16
|
+
description: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
|
+
status: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
|
+
'scheduled-at': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
|
+
'due-at': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
|
+
ticket: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
|
+
};
|
|
22
|
+
type ActionFlags = Record<string, unknown>;
|
|
23
|
+
/** Popola `body` coi campi scalari presenti nei flag (chiavi snake_case attese dall'API CLI). */
|
|
24
|
+
export declare function applyActionBody(body: ActionFlags, flags: ActionFlags): void;
|
|
25
|
+
/**
|
|
26
|
+
* Vero quando il ticket indicato NON è finito sull'attività: il server sostituisce in silenzio con
|
|
27
|
+
* «nessun ticket» un id che chi scrive non può vedere (anti-BOLA), e senza dirlo l'attività
|
|
28
|
+
* risulterebbe collegata a chi l'ha appena salvata — mentre non lo è. Falso quando non c'era niente
|
|
29
|
+
* da collegare (flag assente, o stringa vuota che scollega apposta) o quando il legame c'è davvero.
|
|
30
|
+
*
|
|
31
|
+
* Sulla modifica la conseguenza è più pesante che sulla creazione — quel silenzio toglie anche il
|
|
32
|
+
* ticket che c'era prima — quindi l'avviso vero e proprio lo scrive ciascun comando.
|
|
33
|
+
*/
|
|
34
|
+
export declare function ticketLinkMissing(reference: string | undefined, resolvedId: string | undefined, data: Record<string, unknown>): boolean;
|
|
35
|
+
/** La riga dei partecipanti: `renderRecord` stampa solo i campi scalari, e i nomi sono un elenco. */
|
|
36
|
+
export declare function participantsLine(data: Record<string, unknown>): string;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.actionFieldFlags = exports.WORKLOAD_STATUSES = exports.WORKLOAD_ACTIONS_PATH = void 0;
|
|
4
|
+
exports.applyActionBody = applyActionBody;
|
|
5
|
+
exports.ticketLinkMissing = ticketLinkMissing;
|
|
6
|
+
exports.participantsLine = participantsLine;
|
|
7
|
+
const core_1 = require("@oclif/core");
|
|
8
|
+
const output_1 = require("./output");
|
|
9
|
+
/**
|
|
10
|
+
* La board del carico di lavoro non-dev (fiere, materiale cartaceo, incontri, chiamate): una sola
|
|
11
|
+
* raccolta d'organizzazione, con radice di tenancy il TEAM e non il progetto — per questo l'endpoint
|
|
12
|
+
* non è annidato sotto `/projects/:id` come quasi tutto il resto della CLI.
|
|
13
|
+
*/
|
|
14
|
+
exports.WORKLOAD_ACTIONS_PATH = '/cli/v1/workload/actions';
|
|
15
|
+
/** Gli stati dell'enum del server, nell'ordine in cui un'attività li attraversa. */
|
|
16
|
+
exports.WORKLOAD_STATUSES = ['planned', 'in_progress', 'done', 'cancelled'];
|
|
17
|
+
/**
|
|
18
|
+
* I campi che `workload create` e `workload update` scrivono allo stesso modo. Il titolo resta
|
|
19
|
+
* fuori: nasce obbligatorio sulla creazione e facoltativo sulla modifica. Il team pure — è
|
|
20
|
+
* immutabile lato server (spostare un'attività cambierebbe chi la vede), quindi si indica solo
|
|
21
|
+
* quando l'attività nasce.
|
|
22
|
+
*/
|
|
23
|
+
exports.actionFieldFlags = {
|
|
24
|
+
description: core_1.Flags.string({ description: 'What the activity is about (free text)' }),
|
|
25
|
+
status: core_1.Flags.string({
|
|
26
|
+
description: `Status (${exports.WORKLOAD_STATUSES.join(', ')})`,
|
|
27
|
+
options: exports.WORKLOAD_STATUSES,
|
|
28
|
+
}),
|
|
29
|
+
'scheduled-at': core_1.Flags.string({ description: 'When it takes place (ISO 8601)' }),
|
|
30
|
+
'due-at': core_1.Flags.string({ description: 'Deadline (ISO 8601)' }),
|
|
31
|
+
ticket: core_1.Flags.string({
|
|
32
|
+
description: 'Link an existing ticket by code (e.g. DRFL-3) or id — pass an empty string to unlink',
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
/** Popola `body` coi campi scalari presenti nei flag (chiavi snake_case attese dall'API CLI). */
|
|
36
|
+
function applyActionBody(body, flags) {
|
|
37
|
+
if (flags.description !== undefined)
|
|
38
|
+
body.description = flags.description;
|
|
39
|
+
if (flags.status !== undefined)
|
|
40
|
+
body.status = flags.status;
|
|
41
|
+
if (flags['scheduled-at'] !== undefined)
|
|
42
|
+
body.scheduled_at = flags['scheduled-at'];
|
|
43
|
+
if (flags['due-at'] !== undefined)
|
|
44
|
+
body.due_at = flags['due-at'];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Vero quando il ticket indicato NON è finito sull'attività: il server sostituisce in silenzio con
|
|
48
|
+
* «nessun ticket» un id che chi scrive non può vedere (anti-BOLA), e senza dirlo l'attività
|
|
49
|
+
* risulterebbe collegata a chi l'ha appena salvata — mentre non lo è. Falso quando non c'era niente
|
|
50
|
+
* da collegare (flag assente, o stringa vuota che scollega apposta) o quando il legame c'è davvero.
|
|
51
|
+
*
|
|
52
|
+
* Sulla modifica la conseguenza è più pesante che sulla creazione — quel silenzio toglie anche il
|
|
53
|
+
* ticket che c'era prima — quindi l'avviso vero e proprio lo scrive ciascun comando.
|
|
54
|
+
*/
|
|
55
|
+
function ticketLinkMissing(reference, resolvedId, data) {
|
|
56
|
+
if (!reference || !resolvedId)
|
|
57
|
+
return false;
|
|
58
|
+
return data.ticket_id !== resolvedId;
|
|
59
|
+
}
|
|
60
|
+
/** La riga dei partecipanti: `renderRecord` stampa solo i campi scalari, e i nomi sono un elenco. */
|
|
61
|
+
function participantsLine(data) {
|
|
62
|
+
const names = (Array.isArray(data.participants) ? data.participants : []).map((name) => (0, output_1.sanitize)(name));
|
|
63
|
+
return `participants: ${names.length > 0 ? names.join(', ') : '-'}`;
|
|
64
|
+
}
|