@serve.zone/interfaces 32.60.0 → 32.61.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.
@@ -0,0 +1,282 @@
1
+ import { readDataElements, readDataProperty, readExactDataRecord } from '../private/dataproperties.js';
2
+ import {
3
+ isReservedSecretKey, platformReservedSecretKeyPrefixes, runtimeInjectionSecretKeyPrefixes,
4
+ runtimeInjectionSecretKeys,
5
+ } from './secret.js';
6
+
7
+ /**
8
+ * A delegated administrator token (a "temp token") hands an agent a narrow, short-lived slice of
9
+ * one human administrator's authority, without the administrator's password or session.
10
+ *
11
+ * - Only a human administrator who signed in within five minutes mints one
12
+ * (`mintDelegatedAdminToken`), for scopes named explicitly from `delegatedAdminTokenScopes`.
13
+ * The plaintext token is answered exactly once; Cloudly stores only its hash.
14
+ * - It is exchanged with `getIdentityByToken` for a session that only the requests of its scopes
15
+ * (`delegatedAdminTokenScopeRequests`) admit. Every such request re-reads the token, so the
16
+ * session carries no validity of its own, and acts as `<administrator> via temp token <id>`.
17
+ * - It ends at its expiry, at most `maximumLifetimeMs` after the mint and never extended; when any
18
+ * administrator revokes it; or when the minting administrator's credential generation changes.
19
+ * - A delegated session never mints, lists or revokes tokens, and no scope reaches users, roles,
20
+ * human credentials, deployment tokens, authority records or organization membership.
21
+ */
22
+ export const delegatedAdminTokenContract = Object.freeze({
23
+ /** The lifetime a mint that names none receives. */
24
+ defaultLifetimeMs: 60 * 60 * 1000,
25
+ /** The hard cap: a token lives at most one hour from its mint and is never extended. */
26
+ maximumLifetimeMs: 60 * 60 * 1000,
27
+ minimumLifetimeMs: 60 * 1000,
28
+ /** Every token is `delegatedAdminToken_<tokenId>_<secret>`, both parts 64 lowercase hex. */
29
+ tokenPrefix: 'delegatedAdminToken_',
30
+ /** Tokens one listing reports at most, newest first; a listing that leaves any out says so. */
31
+ maximumListedTokens: 256,
32
+ /** Secret key prefixes one token is granted at most. */
33
+ maximumSecretKeyPrefixes: 16,
34
+ });
35
+
36
+ /**
37
+ * What a token may be minted for. Each scope admits exactly the requests
38
+ * `delegatedAdminTokenScopeRequests` names for it, and nothing else does.
39
+ *
40
+ * - `secrets`: the operator-managed secrets of services and SecretSets: list and read their
41
+ * metadata and read the ingress recipient a value is sealed to; create, rotate, retire and revoke
42
+ * those whose key starts with a prefix the token was granted (`secretKeyPrefixes`, see
43
+ * `isDelegatedSecretKey`; `changeSecretLifecycle` only with the actions
44
+ * `delegatedAdminTokenSecretLifecycleActions` names, never `delete`). A token creates a secret
45
+ * only as a launcher-environment variable, never as a file. A token minted without prefixes
46
+ * creates, rotates, retires and revokes nothing.
47
+ * - `serviceCredentials`: a service's platform credential (`SERVEZONE_PLATFORM_AUTHORIZATION`):
48
+ * inspect, ensure, rotate and revoke it.
49
+ */
50
+ export const delegatedAdminTokenScopes = ['secrets', 'serviceCredentials'] as const;
51
+
52
+ export type TDelegatedAdminTokenScope = typeof delegatedAdminTokenScopes[number];
53
+
54
+ /** The exact requests each scope admits a delegated session on, by their wire method names. */
55
+ export const delegatedAdminTokenScopeRequests = Object.freeze({
56
+ secrets: Object.freeze([
57
+ 'getSecretIngressRecipient', 'listSecrets', 'getSecretMetadata', 'createSecret', 'rotateSecret',
58
+ 'changeSecretLifecycle',
59
+ ] as const),
60
+ serviceCredentials: Object.freeze(['getServiceMachineCredential', 'mutateServiceMachineCredential'] as const),
61
+ }) satisfies Readonly<Record<TDelegatedAdminTokenScope, readonly string[]>>;
62
+
63
+ /** A request one scope admits, by its wire method name. */
64
+ export type TDelegatedAdminTokenScopeRequest<TScope extends TDelegatedAdminTokenScope = TDelegatedAdminTokenScope> =
65
+ (typeof delegatedAdminTokenScopeRequests)[TScope][number];
66
+
67
+ /** Whether `scopeArg` admits the request a wire method names. */
68
+ export const isDelegatedAdminTokenScopeRequest = <TScope extends TDelegatedAdminTokenScope>(
69
+ scopeArg: TScope,
70
+ methodArg: unknown,
71
+ ): methodArg is TDelegatedAdminTokenScopeRequest<TScope> => (
72
+ (delegatedAdminTokenScopeRequests[scopeArg] as readonly unknown[]).includes(methodArg)
73
+ );
74
+
75
+ /**
76
+ * The `changeSecretLifecycle` actions the `secrets` scope delegates, each only for a secret whose key
77
+ * the token's grant covers (`isDelegatedSecretKey`). Deleting a secret stays with a human
78
+ * administrator: a delegated session asking for it is refused like a request outside its scope.
79
+ */
80
+ export const delegatedAdminTokenSecretLifecycleActions = Object.freeze(['retire', 'revoke'] as const);
81
+
82
+ /** One token, value-free. Neither the token nor its hash is ever part of it. */
83
+ export interface IDelegatedAdminTokenMetadata {
84
+ /** 64 lowercase hex; the part of the token that names it. */
85
+ tokenId: string;
86
+ /** The human administrator who minted the token and whom every use acts for. */
87
+ userId: string;
88
+ /** Distinct, in `delegatedAdminTokenScopes` order. */
89
+ scopes: TDelegatedAdminTokenScope[];
90
+ /**
91
+ * The secret key prefixes the token creates, rotates, retires and revokes secrets under, sorted
92
+ * and distinct; empty when it was granted none. Only a token with the `secrets` scope carries any.
93
+ */
94
+ secretKeyPrefixes: string[];
95
+ createdAt: number;
96
+ expiresAt: number;
97
+ /** When an administrator revoked the token; `null` while it was never revoked. */
98
+ revokedAt: number | null;
99
+ /** The administrator who revoked it; `null` exactly when `revokedAt` is. */
100
+ revokedBy: string | null;
101
+ }
102
+
103
+ /** What a mint states besides the administrator's identity. */
104
+ export interface IDelegatedAdminTokenMint {
105
+ scopes: TDelegatedAdminTokenScope[];
106
+ /** Absent: `defaultLifetimeMs`. Otherwise within `minimumLifetimeMs` and `maximumLifetimeMs`. */
107
+ lifetimeMs?: number;
108
+ /**
109
+ * The secret key prefixes the token may create, rotate, retire and revoke secrets under, each
110
+ * `isDelegatableSecretKeyPrefix`, distinct, at most `maximumSecretKeyPrefixes`; only with the
111
+ * `secrets` scope. Prefixes must be application-specific (`GITSYNC_`): a prefix that overlaps a
112
+ * reserved name is refused. Absent or empty: the token creates, rotates, retires and revokes
113
+ * nothing.
114
+ */
115
+ secretKeyPrefixes?: string[];
116
+ }
117
+
118
+ export interface IDelegatedAdminTokenMintResult {
119
+ /** The plaintext token, answered on this answer only; Cloudly keeps only its hash. */
120
+ token: string;
121
+ metadata: IDelegatedAdminTokenMetadata;
122
+ }
123
+
124
+ /**
125
+ * Every refusal the token requests name, as `IDelegatedAdminTokenErrorData`.
126
+ *
127
+ * - `ADMIN_AUTHORIZATION_REQUIRED`: a mint by anyone but a human administrator who signed in
128
+ * within five minutes, or a listing or revocation by anyone but an administrator. A delegated
129
+ * session is refused here on every request.
130
+ * - `DELEGATED_ADMIN_TOKEN_REQUEST_INVALID`: the request does not satisfy this contract.
131
+ * - `DELEGATED_ADMIN_TOKEN_NOT_FOUND`: no token has that id.
132
+ * - `DELEGATED_ADMIN_TOKEN_INACTIVE`: the token already ended: it expired, was revoked, or its
133
+ * administrator is no longer a human administrator at the credential generation it was minted
134
+ * under.
135
+ */
136
+ export const delegatedAdminTokenRefusals = [
137
+ 'ADMIN_AUTHORIZATION_REQUIRED',
138
+ 'DELEGATED_ADMIN_TOKEN_REQUEST_INVALID',
139
+ 'DELEGATED_ADMIN_TOKEN_NOT_FOUND',
140
+ 'DELEGATED_ADMIN_TOKEN_INACTIVE',
141
+ ] as const;
142
+
143
+ export type TDelegatedAdminTokenRefusal = typeof delegatedAdminTokenRefusals[number];
144
+
145
+ export interface IDelegatedAdminTokenErrorData {
146
+ code: TDelegatedAdminTokenRefusal;
147
+ retryable: false;
148
+ }
149
+
150
+ const fail = (): never => { throw new Error('Delegated admin token contract rejected.'); };
151
+ function requireValue(value: unknown): asserts value { if (!value) fail(); }
152
+ const safe = <T>(operation: () => T): T => { try { return operation(); } catch { return fail(); } };
153
+ /** Exactly this shape's keys, read once as data; an accessor is refused, never invoked. */
154
+ const object = (value: unknown, keys: readonly string[]): Record<string, unknown> => {
155
+ const copy = readExactDataRecord(value, keys);
156
+ requireValue(copy !== null);
157
+ return copy;
158
+ };
159
+ const identifier = (value: unknown): string => {
160
+ requireValue(typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/.test(value));
161
+ return value;
162
+ };
163
+ const timestamp = (value: unknown): number => {
164
+ requireValue(typeof value === 'number' && Number.isSafeInteger(value) && value > 0);
165
+ return value;
166
+ };
167
+ const tokenIdPattern = /^[a-f0-9]{64}$/;
168
+ const tokenPattern = /^delegatedAdminToken_[a-f0-9]{64}_[a-f0-9]{64}$/;
169
+
170
+ /** Whether a value is a delegated admin token in its exact form; it proves nothing else. */
171
+ export const isDelegatedAdminToken = (value: unknown): value is string => (
172
+ typeof value === 'string' && tokenPattern.test(value)
173
+ );
174
+
175
+ /** Whether a value names a token: 64 lowercase hex. */
176
+ export const isDelegatedAdminTokenId = (value: unknown): value is string => (
177
+ typeof value === 'string' && tokenIdPattern.test(value)
178
+ );
179
+
180
+ const secretKeyPrefixPattern = /^[A-Z][A-Z0-9]*_$/;
181
+
182
+ /**
183
+ * Whether a prefix may be granted: `^[A-Z][A-Z0-9]*_$`, and overlapping no reserved name in
184
+ * either direction: it neither starts with nor is the start of an entry of
185
+ * `platformReservedSecretKeyPrefixes` or `runtimeInjectionSecretKeyPrefixes`, and no entry of
186
+ * `runtimeInjectionSecretKeys` starts with it. So a granted prefix is application-specific:
187
+ * `GIT_`, `NODE_`, `NPM_` and `AWS_` (`AWS_CA_BUNDLE`) are refused; `GITSYNC_` is not.
188
+ */
189
+ export const isDelegatableSecretKeyPrefix = (value: unknown): value is string => (
190
+ typeof value === 'string' && value.length <= 64 && secretKeyPrefixPattern.test(value)
191
+ && [...platformReservedSecretKeyPrefixes, ...runtimeInjectionSecretKeyPrefixes]
192
+ .every((reservedArg) => !value.startsWith(reservedArg) && !reservedArg.startsWith(value))
193
+ && runtimeInjectionSecretKeys.every((reservedArg) => !reservedArg.startsWith(value))
194
+ );
195
+
196
+ /**
197
+ * Whether a token granted `prefixesArg` creates, rotates, retires or revokes a secret of key
198
+ * `keyArg`, or delivers a launcher variable of that name: the key starts with a granted prefix and
199
+ * is not reserved (`isReservedSecretKey`).
200
+ */
201
+ export const isDelegatedSecretKey = (prefixesArg: readonly string[], keyArg: string): boolean => (
202
+ prefixesArg.some((prefixArg) => keyArg.startsWith(prefixArg)) && !isReservedSecretKey(keyArg)
203
+ );
204
+
205
+ /** Whether a lifetime is one a mint may state. */
206
+ export const isDelegatedAdminTokenLifetime = (value: unknown): value is number => (
207
+ typeof value === 'number' && Number.isSafeInteger(value)
208
+ && value >= delegatedAdminTokenContract.minimumLifetimeMs
209
+ && value <= delegatedAdminTokenContract.maximumLifetimeMs
210
+ );
211
+
212
+ /** Distinct known scopes, at least one, answered in `delegatedAdminTokenScopes` order. */
213
+ const scopes = (value: unknown, canonical: boolean): TDelegatedAdminTokenScope[] => {
214
+ const entries = readDataElements(value, delegatedAdminTokenScopes.length);
215
+ requireValue(entries !== null && entries.length > 0 && new Set(entries).size === entries.length
216
+ && entries.every((entry) => delegatedAdminTokenScopes.some((scope) => scope === entry)));
217
+ const ordered = delegatedAdminTokenScopes.filter((scope) => entries.includes(scope));
218
+ requireValue(!canonical || ordered.every((scope, index) => entries[index] === scope));
219
+ return ordered;
220
+ };
221
+
222
+ /** Distinct grantable prefixes, at most the cap, answered sorted; `canonical` requires them sorted. */
223
+ const secretKeyPrefixes = (value: unknown, canonical: boolean): string[] => {
224
+ const entries = readDataElements(value, delegatedAdminTokenContract.maximumSecretKeyPrefixes);
225
+ requireValue(entries !== null && new Set(entries).size === entries.length
226
+ && entries.every((entry) => isDelegatableSecretKeyPrefix(entry)));
227
+ const prefixes = (entries as string[]).slice().sort();
228
+ requireValue(!canonical || prefixes.every((prefix, index) => entries[index] === prefix));
229
+ return prefixes;
230
+ };
231
+
232
+ /** Structural copy of a mint; the backend authorizes the administrator and enforces the cap. */
233
+ export const snapshotDelegatedAdminTokenMint = (value: unknown): IDelegatedAdminTokenMint => safe(() => {
234
+ const namesLifetime = readDataProperty(value, 'lifetimeMs') !== null;
235
+ const namesPrefixes = readDataProperty(value, 'secretKeyPrefixes') !== null;
236
+ const mint = object(value, ['scopes', ...(namesLifetime ? ['lifetimeMs'] : []),
237
+ ...(namesPrefixes ? ['secretKeyPrefixes'] : [])]);
238
+ const copy: IDelegatedAdminTokenMint = { scopes: scopes(mint.scopes, false) };
239
+ if (namesLifetime) {
240
+ requireValue(isDelegatedAdminTokenLifetime(mint.lifetimeMs));
241
+ copy.lifetimeMs = mint.lifetimeMs;
242
+ }
243
+ if (namesPrefixes) {
244
+ const prefixes = secretKeyPrefixes(mint.secretKeyPrefixes, false);
245
+ requireValue(prefixes.length === 0 || copy.scopes.includes('secrets'));
246
+ copy.secretKeyPrefixes = prefixes;
247
+ }
248
+ return copy;
249
+ });
250
+
251
+ /** Structural copy of listed or answered metadata. */
252
+ export const snapshotDelegatedAdminTokenMetadata = (value: unknown): IDelegatedAdminTokenMetadata => safe(() => {
253
+ const metadata = object(value, ['tokenId', 'userId', 'scopes', 'secretKeyPrefixes', 'createdAt', 'expiresAt',
254
+ 'revokedAt', 'revokedBy']);
255
+ requireValue(isDelegatedAdminTokenId(metadata.tokenId));
256
+ const createdAt = timestamp(metadata.createdAt);
257
+ const expiresAt = timestamp(metadata.expiresAt);
258
+ requireValue(expiresAt > createdAt
259
+ && expiresAt - createdAt <= delegatedAdminTokenContract.maximumLifetimeMs);
260
+ const revoked = metadata.revokedAt !== null;
261
+ requireValue(revoked === (metadata.revokedBy !== null));
262
+ const revokedAt = revoked ? timestamp(metadata.revokedAt) : null;
263
+ requireValue(revokedAt === null || revokedAt >= createdAt);
264
+ const tokenScopes = scopes(metadata.scopes, true);
265
+ const prefixes = secretKeyPrefixes(metadata.secretKeyPrefixes, true);
266
+ requireValue(prefixes.length === 0 || tokenScopes.includes('secrets'));
267
+ return {
268
+ tokenId: metadata.tokenId, userId: identifier(metadata.userId), scopes: tokenScopes,
269
+ secretKeyPrefixes: prefixes, createdAt, expiresAt, revokedAt,
270
+ revokedBy: revoked ? identifier(metadata.revokedBy) : null,
271
+ };
272
+ });
273
+
274
+ /** Structural copy of a mint's answer: a token in its exact form and the metadata that names it. */
275
+ export const snapshotDelegatedAdminTokenMintResult = (value: unknown): IDelegatedAdminTokenMintResult => safe(() => {
276
+ const result = object(value, ['token', 'metadata']);
277
+ requireValue(isDelegatedAdminToken(result.token));
278
+ const metadata = snapshotDelegatedAdminTokenMetadata(result.metadata);
279
+ requireValue(result.token.slice(delegatedAdminTokenContract.tokenPrefix.length,
280
+ delegatedAdminTokenContract.tokenPrefix.length + 64) === metadata.tokenId && metadata.revokedAt === null);
281
+ return { token: result.token, metadata };
282
+ });
@@ -20,8 +20,16 @@ export type TManagedRouteKind = 'letsencrypt-http01-forward';
20
20
  /**
21
21
  * DNS handling requested alongside a gateway route synchronization.
22
22
  *
23
- * - 'reconcile' claims or replaces exact-host manual A, AAAA, and CNAME
24
- * records, including already-correct records, with gateway-managed state.
23
+ * - 'reconcile' creates, updates and deletes the exact-host A or AAAA record
24
+ * the route's gateway DNS ownership holds. It never adopts, replaces or
25
+ * deletes a record without gateway DNS ownership. The route is satisfied by
26
+ * manual records only when no record at the name is owned and every manual A
27
+ * or AAAA record there matches the desired type, value and proxied flag (TTL
28
+ * ignored); anything else (a differing record, any CNAME, owned and manual
29
+ * records together) and an operator-protected hostname is a non-retryable
30
+ * `record-conflict` with no mutation. When the route stops claiming the
31
+ * hostname while another enabled route on the gateway serves it, the owned
32
+ * record is released to manual ownership instead of deleted.
25
33
  * - 'observe' reports DNS state without mutating it.
26
34
  * - 'skip' bypasses DNS inspection and mutation and is the default when omitted.
27
35
  */
@@ -29,6 +37,14 @@ export type TGatewayRouteDnsMode = 'reconcile' | 'observe' | 'skip';
29
37
 
30
38
  /**
31
39
  * Detailed DNS outcome reported by a gateway route synchronization.
40
+ *
41
+ * - `manual-satisfied`: every manual A or AAAA record at the name matches the desired type,
42
+ * value and proxied flag; the gateway changes and claims nothing and reports them in
43
+ * `records` with `managed: false`.
44
+ * - `managed-released`: the route no longer claims the hostname, but another enabled route on
45
+ * the gateway still serves it, so the gateway released its record to manual ownership instead
46
+ * of deleting it; the released record is reported in `records` with `managed: false`.
47
+ * - `managed-overwrote-manual` and `managed-orphan-deleted`: deprecated; no gateway reports them.
32
48
  */
33
49
  export type TGatewayRouteDnsStatus =
34
50
  | 'not-applicable'
@@ -37,8 +53,10 @@ export type TGatewayRouteDnsStatus =
37
53
  | 'managed-updated'
38
54
  | 'managed-overwrote-manual'
39
55
  | 'managed-unchanged'
56
+ | 'manual-satisfied'
40
57
  | 'observed-satisfied'
41
58
  | 'managed-deleted'
59
+ | 'managed-released'
42
60
  | 'managed-orphan-deleted'
43
61
  | 'record-missing'
44
62
  | 'target-invalid'
@@ -77,7 +95,10 @@ export interface IGatewayRouteDnsResult {
77
95
  zoneName?: string;
78
96
  nameservers?: string[];
79
97
  records?: IGatewayRouteDnsRecord[];
80
- /** Prior manual records replaced by this reconciliation, preserved as evidence. */
98
+ /**
99
+ * Prior manual records replaced by this reconciliation, preserved as evidence.
100
+ * @deprecated No gateway replaces manual records; it accompanied `managed-overwrote-manual`.
101
+ */
81
102
  overwrittenRecords?: IGatewayRouteDnsRecord[];
82
103
  /** Time at which the gateway checked the DNS state, as Unix epoch milliseconds. */
83
104
  checkedAt: number;
package/ts/data/index.ts CHANGED
@@ -9,6 +9,7 @@ export * from './config.js';
9
9
  export * from './corestore.js';
10
10
  export * from './corestorecontroltoken.js';
11
11
  export * from './corestorelegacyadoption.js';
12
+ export * from './delegatedadmintoken.js';
12
13
  export * from './coremail.js';
13
14
  export * from './coremail.runtime.js';
14
15
  export * from './deployment.js';
package/ts/data/secret.ts CHANGED
@@ -276,6 +276,44 @@ export type TSecretDelivery = ISecretLauncherEnvironmentDelivery | ISecretFileDe
276
276
  /** A delivery of a resolved secret manifest: every owner is stated. */
277
277
  export type TSecretResolvedDelivery = ISecretResolvedLauncherEnvironmentDelivery | ISecretFileDelivery;
278
278
 
279
+ /**
280
+ * Prefixes of the names the platform injects into a workload itself (`SERVEZONE_*`): a secret of
281
+ * such a name would replace what the platform delivers.
282
+ */
283
+ export const platformReservedSecretKeyPrefixes = Object.freeze(['SERVEZONE_'] as const);
284
+
285
+ /**
286
+ * Prefixes of the names a loader, language runtime, package manager or library reads to change
287
+ * what a process loads, runs or trusts (`LD_PRELOAD`, `NODE_OPTIONS`, `PYTHONPATH`, `OPENSSL_CONF`,
288
+ * `DOTNET_STARTUP_HOOKS`, `NPM_CONFIG_REGISTRY`, …). Matched as plain prefixes, so `PYTHON`, `PERL`,
289
+ * `RUBY` and `PHP` cover every name that starts with them.
290
+ */
291
+ export const runtimeInjectionSecretKeyPrefixes = Object.freeze([
292
+ 'LD_', 'DYLD_', 'NODE_', 'DENO_', 'BUN_', 'PYTHON', 'PERL', 'RUBY', 'PHP', 'JAVA_', 'JDK_', '_JAVA_', 'DOTNET_',
293
+ 'CORECLR_', 'LUA_', 'GEM_', 'BUNDLE_', 'NPM_CONFIG_', 'PIP_', 'GLIBC_', 'MALLOC_', 'GCONV_', 'OPENSSL_', 'SSL_',
294
+ 'CURL_', 'REQUESTS_', 'GIT_',
295
+ ] as const);
296
+
297
+ /**
298
+ * Exact names a shell, loader, runtime or HTTP client reads to change where a process looks,
299
+ * connects or what it trusts or records (`CLASSPATH`, `AWS_CA_BUNDLE`, `SSLKEYLOGFILE`).
300
+ */
301
+ export const runtimeInjectionSecretKeys = Object.freeze([
302
+ 'PATH', 'HOME', 'SHELL', 'ENV', 'BASH_ENV', 'IFS', 'TMPDIR', 'LOCPATH', 'NLSPATH', 'CLASSPATH', 'HTTP_PROXY',
303
+ 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY', 'FTP_PROXY', 'AWS_CA_BUNDLE', 'SSLKEYLOGFILE',
304
+ ] as const);
305
+
306
+ /**
307
+ * Whether a secret key, or a name a secret is delivered under, is reserved: one of
308
+ * `runtimeInjectionSecretKeys`, or starting with an entry of `platformReservedSecretKeyPrefixes`
309
+ * or `runtimeInjectionSecretKeyPrefixes`. A delegated administrator token never writes such a name.
310
+ */
311
+ export const isReservedSecretKey = (keyArg: string): boolean => (
312
+ (runtimeInjectionSecretKeys as readonly string[]).includes(keyArg)
313
+ || platformReservedSecretKeyPrefixes.some((prefixArg) => keyArg.startsWith(prefixArg))
314
+ || runtimeInjectionSecretKeyPrefixes.some((prefixArg) => keyArg.startsWith(prefixArg))
315
+ );
316
+
279
317
  export const secretValueLimits = Object.freeze({
280
318
  maximumBytes: 500 * 1024,
281
319
  minimumGeneratedBytes: 16,
@@ -8,6 +8,9 @@ import type {
8
8
  IDeploymentMachineUserMetadata, IDeploymentMachineUserMutationResult,
9
9
  TDeploymentMachineUserMutation,
10
10
  } from '../data/deploymentmachineuser.js';
11
+ import type {
12
+ IDelegatedAdminTokenMetadata, IDelegatedAdminTokenMint, IDelegatedAdminTokenMintResult,
13
+ } from '../data/delegatedadmintoken.js';
11
14
 
12
15
  export interface IReq_Admin_GetServiceMachineCredential extends plugins.typedrequestInterfaces.implementsTR<
13
16
  plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_GetServiceMachineCredential
@@ -198,3 +201,51 @@ export interface IReq_Cloudly_Coreflow_ReconcileBaseServices extends plugins.typ
198
201
  routingProof?: ICoreflowRoutingProofResult;
199
202
  };
200
203
  }
204
+
205
+ /**
206
+ * Mints one delegated admin token (`delegatedAdminTokenContract`) for what the request states
207
+ * (`IDelegatedAdminTokenMint`: its scopes, optional `lifetimeMs` and `secretKeyPrefixes`). Requires
208
+ * a human administrator who signed in within five minutes; a delegated session is refused. The
209
+ * plaintext token is answered on this answer only and is never retrievable again, so the request is
210
+ * sent once and its answer goes past every traffic hook. `lifetimeMs` absent is
211
+ * `defaultLifetimeMs`; Cloudly refuses any lifetime above `maximumLifetimeMs`. Refusals travel as
212
+ * `IDelegatedAdminTokenErrorData`.
213
+ */
214
+ export interface IReq_Admin_MintDelegatedAdminToken extends plugins.typedrequestInterfaces.implementsTR<
215
+ plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_MintDelegatedAdminToken
216
+ > {
217
+ method: 'mintDelegatedAdminToken';
218
+ request: { identity: IIdentityCredential } & IDelegatedAdminTokenMint;
219
+ response: IDelegatedAdminTokenMintResult;
220
+ }
221
+
222
+ /**
223
+ * Lists the active delegated admin tokens, value-free, newest first: not expired, not revoked,
224
+ * and their administrator still a human administrator at the credential generation each was
225
+ * minted under. At most `delegatedAdminTokenContract.maximumListedTokens` are answered;
226
+ * `truncated` is true exactly when active tokens beyond them were left out, so no token that
227
+ * needs revoking is hidden without notice. Requires an administrator; a delegated session is
228
+ * refused. Refusals travel as `IDelegatedAdminTokenErrorData`.
229
+ */
230
+ export interface IReq_Admin_GetDelegatedAdminTokens extends plugins.typedrequestInterfaces.implementsTR<
231
+ plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_GetDelegatedAdminTokens
232
+ > {
233
+ method: 'getDelegatedAdminTokens';
234
+ request: { identity: IIdentityCredential };
235
+ response: { tokens: IDelegatedAdminTokenMetadata[]; truncated: boolean };
236
+ }
237
+
238
+ /**
239
+ * Revokes one active delegated admin token; once the revocation commits, no use of the token
240
+ * commits after it, and every session exchanged for it is refused. A success has committed.
241
+ * Requires an administrator, the minting one or any other; a delegated session is refused.
242
+ * Refusals travel as `IDelegatedAdminTokenErrorData`: a token that already does not act (revoked,
243
+ * expired, or its administrator's credential or role changed) is `DELEGATED_ADMIN_TOKEN_INACTIVE`.
244
+ */
245
+ export interface IReq_Admin_RevokeDelegatedAdminToken extends plugins.typedrequestInterfaces.implementsTR<
246
+ plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_RevokeDelegatedAdminToken
247
+ > {
248
+ method: 'revokeDelegatedAdminToken';
249
+ request: { identity: IIdentityCredential; tokenId: string };
250
+ response: { metadata: IDelegatedAdminTokenMetadata };
251
+ }
@@ -7,6 +7,10 @@ import * as userInterfaces from '../data/user.js'
7
7
 
8
8
  /**
9
9
  * get the identity that then will be used to get the config
10
+ *
11
+ * A delegated admin token (`delegatedAdminTokenContract`) is exchanged here too. Its identity is
12
+ * `type: 'machine'`, `role: 'api'`, `userId: 'delegated-admin-token:<tokenId>'`, expiring with the
13
+ * token; only the requests of the token's scopes admit it, and each re-reads the token.
10
14
  */
11
15
  export interface IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken
12
16
  extends plugins.typedrequestInterfaces.implementsTR<