@serve.zone/interfaces 32.60.0 → 32.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.smartconfig.json +4 -2
  2. package/changelog.md +32 -0
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/data/coremail.d.ts +37 -1
  5. package/dist_ts/data/coremail.js +23 -1
  6. package/dist_ts/data/coremail.runtime.d.ts +10 -1
  7. package/dist_ts/data/coremail.runtime.js +23 -1
  8. package/dist_ts/data/delegatedadmintoken.d.ts +142 -0
  9. package/dist_ts/data/delegatedadmintoken.js +197 -0
  10. package/dist_ts/data/gateway.d.ts +23 -4
  11. package/dist_ts/data/gateway.js +1 -1
  12. package/dist_ts/data/index.d.ts +2 -0
  13. package/dist_ts/data/index.js +3 -1
  14. package/dist_ts/data/mail.d.ts +36 -0
  15. package/dist_ts/data/mail.js +45 -2
  16. package/dist_ts/data/runtimeassignment.d.ts +4 -1
  17. package/dist_ts/data/runtimeassignment.js +5 -2
  18. package/dist_ts/data/secret.d.ts +23 -0
  19. package/dist_ts/data/secret.js +33 -1
  20. package/dist_ts/data/serviceruntime.d.ts +14 -1
  21. package/dist_ts/data/serviceruntime.js +2 -2
  22. package/dist_ts/data/settings.d.ts +21 -0
  23. package/dist_ts/data/settings.js +38 -1
  24. package/dist_ts/data/workloadreplicaidentity.d.ts +101 -0
  25. package/dist_ts/data/workloadreplicaidentity.js +159 -0
  26. package/dist_ts/requests/admin.d.ts +52 -0
  27. package/dist_ts/requests/coremail.d.ts +6 -1
  28. package/dist_ts/requests/identity.d.ts +4 -0
  29. package/package.json +5 -8
  30. package/readme.md +154 -8
  31. package/ts/00_commitinfo_data.ts +1 -1
  32. package/ts/data/coremail.runtime.ts +26 -1
  33. package/ts/data/coremail.ts +42 -1
  34. package/ts/data/delegatedadmintoken.ts +282 -0
  35. package/ts/data/gateway.ts +24 -3
  36. package/ts/data/index.ts +2 -0
  37. package/ts/data/mail.ts +48 -0
  38. package/ts/data/runtimeassignment.ts +4 -1
  39. package/ts/data/secret.ts +38 -0
  40. package/ts/data/serviceruntime.ts +14 -1
  41. package/ts/data/settings.ts +46 -0
  42. package/ts/data/workloadreplicaidentity.ts +219 -0
  43. package/ts/requests/admin.ts +51 -0
  44. package/ts/requests/coremail.ts +6 -1
  45. package/ts/requests/identity.ts +4 -0
@@ -0,0 +1,101 @@
1
+ import type { TSha256Digest } from './immutableimage.js';
2
+ import type { IRuntimeAssignment } from './runtimeassignment.js';
3
+ import type { TServiceRuntimeRefusal } from './serviceruntime.js';
4
+ /**
5
+ * Which replica of which workload a process is, as its runtime states it.
6
+ *
7
+ * Every member is the runtime assignment's own (`buildWorkloadReplicaIdentity`), so a workload, its
8
+ * runtime and its controller name a replica with the same four values. `attemptId` is the one
9
+ * execution attempt: a rollout, a replacement or a restart onto another attempt is a new attempt,
10
+ * so the attempt is what a workload reports to correlate itself with the controller's roster.
11
+ */
12
+ export interface IWorkloadReplicaIdentity {
13
+ /** The service the replica runs (`IRuntimeAssignment.serviceId`). */
14
+ serviceId: string;
15
+ /** The stable logical replica (`IRuntimeAssignment.replicaId`), a `isRuntimeReplicaId` name. */
16
+ replicaId: string;
17
+ /** The one execution attempt (`IRuntimeAssignment.attemptId`). */
18
+ attemptId: string;
19
+ /** The root descriptor digest the replica runs (`IRuntimeAssignment.workload.imageDigest`). */
20
+ imageDigest: TSha256Digest;
21
+ }
22
+ /**
23
+ * The environment variable names every runtime injects into every workload it runs, one per
24
+ * identity member. The one spelling Pallet, Onebox, Cloudly's composition refusal and every
25
+ * workload that reads its identity share.
26
+ */
27
+ export declare const workloadReplicaIdentityEnvironmentNames: Readonly<{
28
+ readonly serviceId: 'SERVEZONE_WORKLOAD_SERVICE_ID';
29
+ readonly replicaId: 'SERVEZONE_WORKLOAD_REPLICA_ID';
30
+ readonly attemptId: 'SERVEZONE_WORKLOAD_ATTEMPT_ID';
31
+ readonly imageDigest: 'SERVEZONE_WORKLOAD_IMAGE_DIGEST';
32
+ }>;
33
+ export type TWorkloadReplicaIdentityEnvironmentName = typeof workloadReplicaIdentityEnvironmentNames[keyof IWorkloadReplicaIdentity];
34
+ /**
35
+ * The bounds the injection adds to a runtime config's own.
36
+ *
37
+ * `IRuntimeConfig.environment` keeps its own bound (`runtimeConfigContract.maximumEnvironmentEntries`),
38
+ * so a service states at most that many entries. A runtime's executor admits the config's entries
39
+ * plus the injected identity, `maximumExecutorEnvironmentEntries`, computed from both, never a
40
+ * literal of its own.
41
+ */
42
+ export declare const workloadReplicaIdentityContract: Readonly<{
43
+ injectedEnvironmentEntries: number;
44
+ maximumExecutorEnvironmentEntries: number;
45
+ }>;
46
+ /** Every reason a value is not exactly a workload replica identity; empty when it is one. */
47
+ export declare const validateWorkloadReplicaIdentity: (valueArg: unknown) => string[];
48
+ /** A detached, frozen copy of exactly a workload replica identity; throws naming every violation. */
49
+ export declare const snapshotWorkloadReplicaIdentity: (valueArg: unknown) => IWorkloadReplicaIdentity;
50
+ /**
51
+ * The identity of the replica one runtime assignment runs.
52
+ *
53
+ * Read from the assignment the runtime admitted, through its exact schema, so the answer always
54
+ * satisfies `validateWorkloadReplicaIdentity`. It proves nothing about the assignment's digest or
55
+ * admission; the runtime has judged those before it runs anything.
56
+ */
57
+ export declare const buildWorkloadReplicaIdentity: (assignmentArg: IRuntimeAssignment) => IWorkloadReplicaIdentity;
58
+ /**
59
+ * The injected names among `namesArg`, sorted and unique: empty when none collides.
60
+ *
61
+ * Cloudly's composition refusal passes the keys of a replica's effective environment together with
62
+ * the variable names of its launcher-environment secrets, because WorkloadInit sets a secret over a
63
+ * plain variable of the same name and so would replace the injected identity just as silently.
64
+ */
65
+ export declare const findWorkloadReplicaIdentityEnvironmentConflicts: (namesArg: readonly string[]) => TWorkloadReplicaIdentityEnvironmentName[];
66
+ /** The one reason a workload environment cannot carry its replica identity. */
67
+ export type TWorkloadReplicaEnvironmentRefusal = Extract<TServiceRuntimeRefusal, 'workload-identity-environment-conflict'>;
68
+ /** What a replica runs with, or the one reason its identity cannot be injected. */
69
+ export type TWorkloadReplicaEnvironmentComposition = {
70
+ status: 'composed';
71
+ environment: Record<string, string>;
72
+ } | {
73
+ status: 'refused';
74
+ reason: TWorkloadReplicaEnvironmentRefusal;
75
+ message: string;
76
+ };
77
+ /**
78
+ * The environment one replica runs with: its runtime config's environment with the replica's
79
+ * identity added under `workloadReplicaIdentityEnvironmentNames`.
80
+ *
81
+ * Every runtime injects the identity into every workload, whether the workload reads it or not. A
82
+ * config environment that already states one of the names is refused
83
+ * `workload-identity-environment-conflict`, naming every such key and none of their values, rather
84
+ * than having one value silently replace the other. The reason stays inside the runtime: only
85
+ * Cloudly states it on the wire, in a service's status. A Pallet reports the refused run as the
86
+ * existing `TRuntimeAssignmentFailureCause` `{ kind: 'refused', reason: 'invalid' }`, which every
87
+ * reader since 32.45.0 reads and a controller never re-places with an identical configuration. An
88
+ * identity that is not exactly one is the caller's error and is thrown. The inputs are not
89
+ * changed. A config within
90
+ * `runtimeConfigContract.maximumEnvironmentEntries` composes to at most
91
+ * `workloadReplicaIdentityContract.maximumExecutorEnvironmentEntries` entries.
92
+ */
93
+ export declare const composeWorkloadReplicaEnvironment: (environmentArg: Readonly<Record<string, string>>, identityArg: IWorkloadReplicaIdentity) => TWorkloadReplicaEnvironmentComposition;
94
+ /**
95
+ * The replica identity a workload reads from its own environment (`process.env`).
96
+ *
97
+ * Reads the four injected names only, as own data properties, and nothing else of the environment.
98
+ * Throws naming every name that is missing and every name whose value is not a valid member, never
99
+ * a value, so a workload refuses to start with the exact variable its runtime did not inject.
100
+ */
101
+ export declare const readWorkloadReplicaIdentityEnvironment: (environmentArg: Readonly<Record<string, string | undefined>>) => IWorkloadReplicaIdentity;
@@ -0,0 +1,159 @@
1
+ import { isSha256Digest } from './immutableimage.js';
2
+ import { isRuntimeReplicaId, snapshotRuntimeAssignment } from './runtimeassignment.js';
3
+ import { runtimeConfigContract } from './runtimeconfig.js';
4
+ import { hasExactKeys, readDataProperties } from '../private/dataproperties.js';
5
+ /**
6
+ * The environment variable names every runtime injects into every workload it runs, one per
7
+ * identity member. The one spelling Pallet, Onebox, Cloudly's composition refusal and every
8
+ * workload that reads its identity share.
9
+ */
10
+ export const workloadReplicaIdentityEnvironmentNames = Object.freeze({
11
+ serviceId: 'SERVEZONE_WORKLOAD_SERVICE_ID',
12
+ replicaId: 'SERVEZONE_WORKLOAD_REPLICA_ID',
13
+ attemptId: 'SERVEZONE_WORKLOAD_ATTEMPT_ID',
14
+ imageDigest: 'SERVEZONE_WORKLOAD_IMAGE_DIGEST',
15
+ });
16
+ const identityKeys = Object.freeze(Object.keys(workloadReplicaIdentityEnvironmentNames));
17
+ const identityEnvironmentNames = Object.freeze(Object.values(workloadReplicaIdentityEnvironmentNames));
18
+ /**
19
+ * The bounds the injection adds to a runtime config's own.
20
+ *
21
+ * `IRuntimeConfig.environment` keeps its own bound (`runtimeConfigContract.maximumEnvironmentEntries`),
22
+ * so a service states at most that many entries. A runtime's executor admits the config's entries
23
+ * plus the injected identity, `maximumExecutorEnvironmentEntries`, computed from both, never a
24
+ * literal of its own.
25
+ */
26
+ export const workloadReplicaIdentityContract = Object.freeze({
27
+ injectedEnvironmentEntries: identityKeys.length,
28
+ maximumExecutorEnvironmentEntries: runtimeConfigContract.maximumEnvironmentEntries + identityKeys.length,
29
+ });
30
+ const isIdentifier = (valueArg) => typeof valueArg === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/.test(valueArg);
31
+ /** What each member must hold, in the runtime assignment's own vocabulary. */
32
+ const memberRules = Object.freeze({
33
+ serviceId: { accepts: isIdentifier, requirement: 'a canonical identifier' },
34
+ replicaId: { accepts: isRuntimeReplicaId, requirement: 'a replica id' },
35
+ attemptId: { accepts: isIdentifier, requirement: 'a canonical identifier' },
36
+ imageDigest: {
37
+ accepts: (valueArg) => typeof valueArg === 'string' && isSha256Digest(valueArg),
38
+ requirement: 'a sha256 digest',
39
+ },
40
+ });
41
+ const judgeIdentity = (valueArg) => {
42
+ const record = readDataProperties(valueArg);
43
+ if (record === null) {
44
+ return { record, errors: ['workload replica identity must be a plain record of data properties'] };
45
+ }
46
+ const errors = [];
47
+ if (!hasExactKeys(record, identityKeys)) {
48
+ errors.push(`workload replica identity must state exactly ${identityKeys.join(', ')}`);
49
+ }
50
+ for (const key of identityKeys) {
51
+ if (Object.hasOwn(record, key) && !memberRules[key].accepts(record[key])) {
52
+ errors.push(`${key} must be ${memberRules[key].requirement}`);
53
+ }
54
+ }
55
+ return { record, errors };
56
+ };
57
+ /** Every reason a value is not exactly a workload replica identity; empty when it is one. */
58
+ export const validateWorkloadReplicaIdentity = (valueArg) => judgeIdentity(valueArg).errors;
59
+ /** A detached, frozen copy of exactly a workload replica identity; throws naming every violation. */
60
+ export const snapshotWorkloadReplicaIdentity = (valueArg) => {
61
+ const { record, errors } = judgeIdentity(valueArg);
62
+ if (record === null || errors.length > 0) {
63
+ throw new Error(`workload replica identity refused: ${errors.join('; ')}`);
64
+ }
65
+ return Object.freeze({
66
+ serviceId: record.serviceId,
67
+ replicaId: record.replicaId,
68
+ attemptId: record.attemptId,
69
+ imageDigest: record.imageDigest,
70
+ });
71
+ };
72
+ /**
73
+ * The identity of the replica one runtime assignment runs.
74
+ *
75
+ * Read from the assignment the runtime admitted, through its exact schema, so the answer always
76
+ * satisfies `validateWorkloadReplicaIdentity`. It proves nothing about the assignment's digest or
77
+ * admission; the runtime has judged those before it runs anything.
78
+ */
79
+ export const buildWorkloadReplicaIdentity = (assignmentArg) => {
80
+ const assignment = snapshotRuntimeAssignment(assignmentArg);
81
+ return Object.freeze({
82
+ serviceId: assignment.serviceId,
83
+ replicaId: assignment.replicaId,
84
+ attemptId: assignment.attemptId,
85
+ imageDigest: assignment.workload.imageDigest,
86
+ });
87
+ };
88
+ /**
89
+ * The injected names among `namesArg`, sorted and unique: empty when none collides.
90
+ *
91
+ * Cloudly's composition refusal passes the keys of a replica's effective environment together with
92
+ * the variable names of its launcher-environment secrets, because WorkloadInit sets a secret over a
93
+ * plain variable of the same name and so would replace the injected identity just as silently.
94
+ */
95
+ export const findWorkloadReplicaIdentityEnvironmentConflicts = (namesArg) => identityEnvironmentNames
96
+ .filter((nameArg) => namesArg.includes(nameArg))
97
+ .sort();
98
+ /**
99
+ * The environment one replica runs with: its runtime config's environment with the replica's
100
+ * identity added under `workloadReplicaIdentityEnvironmentNames`.
101
+ *
102
+ * Every runtime injects the identity into every workload, whether the workload reads it or not. A
103
+ * config environment that already states one of the names is refused
104
+ * `workload-identity-environment-conflict`, naming every such key and none of their values, rather
105
+ * than having one value silently replace the other. The reason stays inside the runtime: only
106
+ * Cloudly states it on the wire, in a service's status. A Pallet reports the refused run as the
107
+ * existing `TRuntimeAssignmentFailureCause` `{ kind: 'refused', reason: 'invalid' }`, which every
108
+ * reader since 32.45.0 reads and a controller never re-places with an identical configuration. An
109
+ * identity that is not exactly one is the caller's error and is thrown. The inputs are not
110
+ * changed. A config within
111
+ * `runtimeConfigContract.maximumEnvironmentEntries` composes to at most
112
+ * `workloadReplicaIdentityContract.maximumExecutorEnvironmentEntries` entries.
113
+ */
114
+ export const composeWorkloadReplicaEnvironment = (environmentArg, identityArg) => {
115
+ const identity = snapshotWorkloadReplicaIdentity(identityArg);
116
+ const conflicts = findWorkloadReplicaIdentityEnvironmentConflicts(Object.keys(environmentArg));
117
+ if (conflicts.length > 0) {
118
+ return { status: 'refused', reason: 'workload-identity-environment-conflict',
119
+ message: `workload environment already states ${conflicts.join(', ')}, which the runtime injects` };
120
+ }
121
+ const injected = {};
122
+ for (const key of identityKeys) {
123
+ injected[workloadReplicaIdentityEnvironmentNames[key]] = identity[key];
124
+ }
125
+ return { status: 'composed', environment: { ...environmentArg, ...injected } };
126
+ };
127
+ /**
128
+ * The replica identity a workload reads from its own environment (`process.env`).
129
+ *
130
+ * Reads the four injected names only, as own data properties, and nothing else of the environment.
131
+ * Throws naming every name that is missing and every name whose value is not a valid member, never
132
+ * a value, so a workload refuses to start with the exact variable its runtime did not inject.
133
+ */
134
+ export const readWorkloadReplicaIdentityEnvironment = (environmentArg) => {
135
+ const missing = [];
136
+ const invalid = [];
137
+ const identity = {};
138
+ for (const key of identityKeys) {
139
+ const name = workloadReplicaIdentityEnvironmentNames[key];
140
+ const descriptor = Object.getOwnPropertyDescriptor(environmentArg, name);
141
+ if (descriptor === undefined || !Object.hasOwn(descriptor, 'value') || typeof descriptor.value !== 'string') {
142
+ missing.push(name);
143
+ }
144
+ else if (!memberRules[key].accepts(descriptor.value)) {
145
+ invalid.push(name);
146
+ }
147
+ else {
148
+ identity[key] = descriptor.value;
149
+ }
150
+ }
151
+ if (missing.length > 0 || invalid.length > 0) {
152
+ throw new Error(`workload replica identity environment ${[
153
+ ...missing.length > 0 ? [`lacks ${missing.join(', ')}`] : [],
154
+ ...invalid.length > 0 ? [`states an invalid ${invalid.join(', ')}`] : [],
155
+ ].join('; ')}`);
156
+ }
157
+ return snapshotWorkloadReplicaIdentity(identity);
158
+ };
159
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ya2xvYWRyZXBsaWNhaWRlbnRpdHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL3dvcmtsb2FkcmVwbGljYWlkZW50aXR5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUVyRCxPQUFPLEVBQUUsa0JBQWtCLEVBQUUseUJBQXlCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUN2RixPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUUzRCxPQUFPLEVBQUUsWUFBWSxFQUFFLGtCQUFrQixFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFxQmhGOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSx1Q0FBdUMsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ25FLFNBQVMsRUFBRSwrQkFBK0I7SUFDMUMsU0FBUyxFQUFFLCtCQUErQjtJQUMxQyxTQUFTLEVBQUUsK0JBQStCO0lBQzFDLFdBQVcsRUFBRSxpQ0FBaUM7Q0FDNkIsQ0FBQyxDQUFDO0FBSy9FLE1BQU0sWUFBWSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQ2hDLE1BQU0sQ0FBQyxJQUFJLENBQUMsdUNBQXVDLENBQXVDLENBQzNGLENBQUM7QUFDRixNQUFNLHdCQUF3QixHQUFzQixNQUFNLENBQUMsTUFBTSxDQUMvRCxNQUFNLENBQUMsTUFBTSxDQUFDLHVDQUF1QyxDQUFDLENBQ3ZELENBQUM7QUFFRjs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sK0JBQStCLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUMzRCwwQkFBMEIsRUFBRSxZQUFZLENBQUMsTUFBTTtJQUMvQyxpQ0FBaUMsRUFBRSxxQkFBcUIsQ0FBQyx5QkFBeUIsR0FBRyxZQUFZLENBQUMsTUFBTTtDQUN6RyxDQUFDLENBQUM7QUFFSCxNQUFNLFlBQVksR0FBRyxDQUFDLFFBQWlCLEVBQXNCLEVBQUUsQ0FDN0QsT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLHFDQUFxQyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztBQUV2Riw4RUFBOEU7QUFDOUUsTUFBTSxXQUFXLEdBR1gsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUNsQixTQUFTLEVBQUUsRUFBRSxPQUFPLEVBQUUsWUFBWSxFQUFFLFdBQVcsRUFBRSx3QkFBd0IsRUFBRTtJQUMzRSxTQUFTLEVBQUUsRUFBRSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsV0FBVyxFQUFFLGNBQWMsRUFBRTtJQUN2RSxTQUFTLEVBQUUsRUFBRSxPQUFPLEVBQUUsWUFBWSxFQUFFLFdBQVcsRUFBRSx3QkFBd0IsRUFBRTtJQUMzRSxXQUFXLEVBQUU7UUFDWCxPQUFPLEVBQUUsQ0FBQyxRQUFpQixFQUFFLEVBQUUsQ0FBQyxPQUFPLFFBQVEsS0FBSyxRQUFRLElBQUksY0FBYyxDQUFDLFFBQVEsQ0FBQztRQUN4RixXQUFXLEVBQUUsaUJBQWlCO0tBQy9CO0NBQ0YsQ0FBQyxDQUFDO0FBRUgsTUFBTSxhQUFhLEdBQUcsQ0FBQyxRQUFpQixFQUFnRSxFQUFFO0lBQ3hHLE1BQU0sTUFBTSxHQUFHLGtCQUFrQixDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQzVDLElBQUksTUFBTSxLQUFLLElBQUksRUFBRSxDQUFDO1FBQ3BCLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLENBQUMscUVBQXFFLENBQUMsRUFBRSxDQUFDO0lBQ3JHLENBQUM7SUFDRCxNQUFNLE1BQU0sR0FBYSxFQUFFLENBQUM7SUFDNUIsSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLEVBQUUsWUFBWSxDQUFDLEVBQUUsQ0FBQztRQUN4QyxNQUFNLENBQUMsSUFBSSxDQUFDLGdEQUFnRCxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUN6RixDQUFDO0lBQ0QsS0FBSyxNQUFNLEdBQUcsSUFBSSxZQUFZLEVBQUUsQ0FBQztRQUMvQixJQUFJLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLEdBQUcsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQ3pFLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxHQUFHLFlBQVksV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUM7UUFDaEUsQ0FBQztJQUNILENBQUM7SUFDRCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxDQUFDO0FBQzVCLENBQUMsQ0FBQztBQUVGLDZGQUE2RjtBQUM3RixNQUFNLENBQUMsTUFBTSwrQkFBK0IsR0FBRyxDQUFDLFFBQWlCLEVBQVksRUFBRSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxNQUFNLENBQUM7QUFFL0cscUdBQXFHO0FBQ3JHLE1BQU0sQ0FBQyxNQUFNLCtCQUErQixHQUFHLENBQUMsUUFBaUIsRUFBNEIsRUFBRTtJQUM3RixNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxHQUFHLGFBQWEsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNuRCxJQUFJLE1BQU0sS0FBSyxJQUFJLElBQUksTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN6QyxNQUFNLElBQUksS0FBSyxDQUFDLHNDQUFzQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUM3RSxDQUFDO0lBQ0QsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDO1FBQ25CLFNBQVMsRUFBRSxNQUFNLENBQUMsU0FBbUI7UUFDckMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxTQUFtQjtRQUNyQyxTQUFTLEVBQUUsTUFBTSxDQUFDLFNBQW1CO1FBQ3JDLFdBQVcsRUFBRSxNQUFNLENBQUMsV0FBNEI7S0FDakQsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDO0FBRUY7Ozs7OztHQU1HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sNEJBQTRCLEdBQUcsQ0FBQyxhQUFpQyxFQUE0QixFQUFFO0lBQzFHLE1BQU0sVUFBVSxHQUFHLHlCQUF5QixDQUFDLGFBQWEsQ0FBQyxDQUFDO0lBQzVELE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQztRQUNuQixTQUFTLEVBQUUsVUFBVSxDQUFDLFNBQVM7UUFDL0IsU0FBUyxFQUFFLFVBQVUsQ0FBQyxTQUFTO1FBQy9CLFNBQVMsRUFBRSxVQUFVLENBQUMsU0FBUztRQUMvQixXQUFXLEVBQUUsVUFBVSxDQUFDLFFBQVEsQ0FBQyxXQUFXO0tBQzdDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQztBQUVGOzs7Ozs7R0FNRztBQUNILE1BQU0sQ0FBQyxNQUFNLCtDQUErQyxHQUFHLENBQzdELFFBQTJCLEVBQ2dCLEVBQUUsQ0FBQyx3QkFBd0I7S0FDckUsTUFBTSxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0tBQy9DLElBQUksRUFBK0MsQ0FBQztBQWF2RDs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCxNQUFNLENBQUMsTUFBTSxpQ0FBaUMsR0FBRyxDQUMvQyxjQUFnRCxFQUNoRCxXQUFxQyxFQUNHLEVBQUU7SUFDMUMsTUFBTSxRQUFRLEdBQUcsK0JBQStCLENBQUMsV0FBVyxDQUFDLENBQUM7SUFDOUQsTUFBTSxTQUFTLEdBQUcsK0NBQStDLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDO0lBQy9GLElBQUksU0FBUyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN6QixPQUFPLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsd0NBQXdDO1lBQzFFLE9BQU8sRUFBRSx1Q0FBdUMsU0FBUyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsNkJBQTZCLEVBQUUsQ0FBQztJQUN4RyxDQUFDO0lBQ0QsTUFBTSxRQUFRLEdBQTJCLEVBQUUsQ0FBQztJQUM1QyxLQUFLLE1BQU0sR0FBRyxJQUFJLFlBQVksRUFBRSxDQUFDO1FBQy9CLFFBQVEsQ0FBQyx1Q0FBdUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUN6RSxDQUFDO0lBQ0QsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsV0FBVyxFQUFFLEVBQUUsR0FBRyxjQUFjLEVBQUUsR0FBRyxRQUFRLEVBQUUsRUFBRSxDQUFDO0FBQ2pGLENBQUMsQ0FBQztBQUVGOzs7Ozs7R0FNRztBQUNILE1BQU0sQ0FBQyxNQUFNLHNDQUFzQyxHQUFHLENBQ3BELGNBQTRELEVBQ2xDLEVBQUU7SUFDNUIsTUFBTSxPQUFPLEdBQWEsRUFBRSxDQUFDO0lBQzdCLE1BQU0sT0FBTyxHQUFhLEVBQUUsQ0FBQztJQUM3QixNQUFNLFFBQVEsR0FBMkIsRUFBRSxDQUFDO0lBQzVDLEtBQUssTUFBTSxHQUFHLElBQUksWUFBWSxFQUFFLENBQUM7UUFDL0IsTUFBTSxJQUFJLEdBQUcsdUNBQXVDLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDMUQsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDLHdCQUF3QixDQUFDLGNBQWMsRUFBRSxJQUFJLENBQUMsQ0FBQztRQUN6RSxJQUFJLFVBQVUsS0FBSyxTQUFTLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsSUFBSSxPQUFPLFVBQVUsQ0FBQyxLQUFLLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDNUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNyQixDQUFDO2FBQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDdkQsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNyQixDQUFDO2FBQU0sQ0FBQztZQUNOLFFBQVEsQ0FBQyxHQUFHLENBQUMsR0FBRyxVQUFVLENBQUMsS0FBSyxDQUFDO1FBQ25DLENBQUM7SUFDSCxDQUFDO0lBQ0QsSUFBSSxPQUFPLENBQUMsTUFBTSxHQUFHLENBQUMsSUFBSSxPQUFPLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQzdDLE1BQU0sSUFBSSxLQUFLLENBQUMseUNBQXlDO1lBQ3ZELEdBQUcsT0FBTyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRTtZQUM1RCxHQUFHLE9BQU8sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLHFCQUFxQixPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRTtTQUN6RSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDbEIsQ0FBQztJQUNELE9BQU8sK0JBQStCLENBQUMsUUFBUSxDQUFDLENBQUM7QUFDbkQsQ0FBQyxDQUFDIn0=
@@ -2,6 +2,7 @@ import * as plugins from '../plugins.js';
2
2
  import type { IIdentity, IIdentityCredential, IServiceDeploymentGrant } from '../data/user.js';
3
3
  import type { IServiceMachineCredentialMetadata, IServiceMachineCredentialMutationResult, TServiceMachineCredentialMutation } from '../data/servicecredential.js';
4
4
  import type { IDeploymentMachineUserMetadata, IDeploymentMachineUserMutationResult, TDeploymentMachineUserMutation } from '../data/deploymentmachineuser.js';
5
+ import type { IDelegatedAdminTokenMetadata, IDelegatedAdminTokenMint, IDelegatedAdminTokenMintResult } from '../data/delegatedadmintoken.js';
5
6
  export interface IReq_Admin_GetServiceMachineCredential extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_GetServiceMachineCredential> {
6
7
  method: 'getServiceMachineCredential';
7
8
  request: {
@@ -175,3 +176,54 @@ export interface IReq_Cloudly_Coreflow_ReconcileBaseServices extends plugins.typ
175
176
  routingProof?: ICoreflowRoutingProofResult;
176
177
  };
177
178
  }
179
+ /**
180
+ * Mints one delegated admin token (`delegatedAdminTokenContract`) for what the request states
181
+ * (`IDelegatedAdminTokenMint`: its scopes, optional `lifetimeMs` and `secretKeyPrefixes`). Requires
182
+ * a human administrator who signed in within five minutes; a delegated session is refused. The
183
+ * plaintext token is answered on this answer only and is never retrievable again, so the request is
184
+ * sent once and its answer goes past every traffic hook. `lifetimeMs` absent is
185
+ * `defaultLifetimeMs`; Cloudly refuses any lifetime above `maximumLifetimeMs`. Refusals travel as
186
+ * `IDelegatedAdminTokenErrorData`.
187
+ */
188
+ export interface IReq_Admin_MintDelegatedAdminToken extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_MintDelegatedAdminToken> {
189
+ method: 'mintDelegatedAdminToken';
190
+ request: {
191
+ identity: IIdentityCredential;
192
+ } & IDelegatedAdminTokenMint;
193
+ response: IDelegatedAdminTokenMintResult;
194
+ }
195
+ /**
196
+ * Lists the active delegated admin tokens, value-free, newest first: not expired, not revoked,
197
+ * and their administrator still a human administrator at the credential generation each was
198
+ * minted under. At most `delegatedAdminTokenContract.maximumListedTokens` are answered;
199
+ * `truncated` is true exactly when active tokens beyond them were left out, so no token that
200
+ * needs revoking is hidden without notice. Requires an administrator; a delegated session is
201
+ * refused. Refusals travel as `IDelegatedAdminTokenErrorData`.
202
+ */
203
+ export interface IReq_Admin_GetDelegatedAdminTokens extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_GetDelegatedAdminTokens> {
204
+ method: 'getDelegatedAdminTokens';
205
+ request: {
206
+ identity: IIdentityCredential;
207
+ };
208
+ response: {
209
+ tokens: IDelegatedAdminTokenMetadata[];
210
+ truncated: boolean;
211
+ };
212
+ }
213
+ /**
214
+ * Revokes one active delegated admin token; once the revocation commits, no use of the token
215
+ * commits after it, and every session exchanged for it is refused. A success has committed.
216
+ * Requires an administrator, the minting one or any other; a delegated session is refused.
217
+ * Refusals travel as `IDelegatedAdminTokenErrorData`: a token that already does not act (revoked,
218
+ * expired, or its administrator's credential or role changed) is `DELEGATED_ADMIN_TOKEN_INACTIVE`.
219
+ */
220
+ export interface IReq_Admin_RevokeDelegatedAdminToken extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_RevokeDelegatedAdminToken> {
221
+ method: 'revokeDelegatedAdminToken';
222
+ request: {
223
+ identity: IIdentityCredential;
224
+ tokenId: string;
225
+ };
226
+ response: {
227
+ metadata: IDelegatedAdminTokenMetadata;
228
+ };
229
+ }
@@ -1,6 +1,7 @@
1
1
  import type * as plugins from '../plugins.js';
2
2
  import type { ICoreMailEnvelope, ICoreMailInboundDeliveryPage, ICoreMailDownloadGrant, ICoreMailGatewayMessageDescriptor, ICoreMailGatewayOutboundStatus, ICoreMailInboundDelivery, ICoreMailOutboundMessageDescriptor, ICoreMailRecipientResolution, ICoreMailReconciliationStatus, ICoreMailReplicaIdentity, ICoreMailServiceMailStatistics, ICoreMailSubmission, ICoreMailUploadGrant, TCoreMailInboundAckOutcome, TCoreMailSha256, TCoreMailWorkloadOperation } from '../data/coremail.js';
3
3
  import type { IMailConnectionInfo } from '../data/mail.js';
4
+ import type { IWorkloadReplicaIdentity } from '../data/workloadreplicaidentity.js';
4
5
  export interface IReq_CoreMailAuthenticateWorkload extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_CoreMailAuthenticateWorkload> {
5
6
  method: 'coreMailAuthenticateWorkload';
6
7
  request: {
@@ -125,7 +126,11 @@ export interface IReq_CoreMailAuthenticateControl extends plugins.typedrequestIn
125
126
  authenticated: true;
126
127
  credentialId: string;
127
128
  credentialVersion: number;
128
- replica: ICoreMailReplicaIdentity;
129
+ /**
130
+ * The authenticated replica: `IWorkloadReplicaIdentity` from CoreMail 34 on, the deprecated
131
+ * `ICoreMailReplicaIdentity` from an earlier CoreMail (`normalizeCoreMailReplicaIdentity`).
132
+ */
133
+ replica: IWorkloadReplicaIdentity | ICoreMailReplicaIdentity;
129
134
  };
130
135
  }
131
136
  export interface IReq_CoreMailPrepareDesiredStateUpload extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_CoreMailPrepareDesiredStateUpload> {
@@ -2,6 +2,10 @@ import * as plugins from '../plugins.js';
2
2
  import * as userInterfaces from '../data/user.js';
3
3
  /**
4
4
  * get the identity that then will be used to get the config
5
+ *
6
+ * A delegated admin token (`delegatedAdminTokenContract`) is exchanged here too. Its identity is
7
+ * `type: 'machine'`, `role: 'api'`, `userId: 'delegated-admin-token:<tokenId>'`, expiring with the
8
+ * token; only the requests of the token's scopes admit it, and each re-reads the token.
5
9
  */
6
10
  export interface IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken> {
7
11
  method: 'getIdentityByToken';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "32.60.0",
3
+ "version": "32.62.0",
4
4
  "private": false,
5
5
  "description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
6
6
  "exports": {
@@ -27,10 +27,7 @@
27
27
  "@tsclass/tsclass": "^9.5.1"
28
28
  },
29
29
  "devDependencies": {
30
- "@git.zone/tsbuild": "^6.0.2",
31
- "@git.zone/tsdoc": "^3.0.2",
32
- "@git.zone/tsrun": "^3.0.3",
33
- "@git.zone/tstest": "^8.0.0",
30
+ "@git.zone/tsrepo": "^1.1.1",
34
31
  "@types/node": "26.6.5",
35
32
  "typescript": "^6.0.3"
36
33
  },
@@ -64,8 +61,8 @@
64
61
  "last 1 chrome versions"
65
62
  ],
66
63
  "scripts": {
67
- "test": "tstest test/ --verbose --logfile --timeout 60",
68
- "build": "tsbuild tsfolders",
69
- "buildDocs": "tsdoc"
64
+ "test": "tsrepo test test/ --verbose --logfile --timeout 60",
65
+ "build": "tsrepo build tsfolders",
66
+ "buildDocs": "tsrepo docs"
70
67
  }
71
68
  }
package/readme.md CHANGED
@@ -1370,6 +1370,60 @@ Version 29 widens these numeric TypeScript fields to include those explicit null
1370
1370
  policies; consumers must handle them before accepting configurations from v29.
1371
1371
  Existing numeric configurations retain their exact meaning and digest.
1372
1372
 
1373
+ ### Workload Replica Identity
1374
+
1375
+ Every runtime (Pallet, Onebox) injects the identity of the replica it runs into every workload,
1376
+ whether the workload reads it or not. `data.IWorkloadReplicaIdentity` is exactly
1377
+ `{ serviceId, replicaId, attemptId, imageDigest }`, the runtime assignment's own values:
1378
+ `data.buildWorkloadReplicaIdentity(assignment)` reads them from the admitted assignment through its
1379
+ exact schema, so the answer always validates. `attemptId` is the one execution attempt; a rollout or
1380
+ a replacement is a new attempt, and the controller's `attemptId` already binds the admitting
1381
+ rollout, so no rollout name or generation travels. `data.validateWorkloadReplicaIdentity` names
1382
+ every violation and `data.snapshotWorkloadReplicaIdentity` answers a detached frozen copy or throws.
1383
+
1384
+ `data.workloadReplicaIdentityEnvironmentNames` is the one spelling of the injected names:
1385
+ `SERVEZONE_WORKLOAD_SERVICE_ID`, `SERVEZONE_WORKLOAD_REPLICA_ID`, `SERVEZONE_WORKLOAD_ATTEMPT_ID` and
1386
+ `SERVEZONE_WORKLOAD_IMAGE_DIGEST`. They sit under the platform-reserved `SERVEZONE_` prefix
1387
+ (`platformReservedSecretKeyPrefixes`). A runtime composes what a replica runs with through
1388
+ `data.composeWorkloadReplicaEnvironment(configEnvironment, identity)`: the config's environment
1389
+ with the four names added, or `refused` with `workload-identity-environment-conflict` and a message
1390
+ naming every colliding key and no value (`TWorkloadReplicaEnvironmentComposition`). An identity that
1391
+ is not exactly one is thrown. A workload reads its identity with
1392
+ `data.readWorkloadReplicaIdentityEnvironment(process.env)`, which reads the four names only and
1393
+ throws naming every missing or invalid variable, never a value, so a workload refuses to start
1394
+ naming the variable its runtime did not inject.
1395
+
1396
+ Cloudly refuses a service whose effective environment (image and service values) or one of whose
1397
+ launcher-environment secrets states an injected name, before any runtime sees it:
1398
+ `data.findWorkloadReplicaIdentityEnvironmentConflicts(names)` answers the colliding names among
1399
+ the environment's keys and the secrets' variable names, and the service's status reads
1400
+ `workload-identity-environment-conflict`. Secrets count because WorkloadInit sets a secret over a
1401
+ plain variable of the same name.
1402
+
1403
+ Only Cloudly states `workload-identity-environment-conflict` on the wire, in the service status it
1404
+ stores and answers on `getServiceRuntimeSpec`, `getServiceRuntimeSpecForCluster` and
1405
+ `getServiceRuntimeSpecs`. Those requests carry no protocol offer, so the reason cannot be gated on
1406
+ a reader's version, and no other refusal names this check. A reader built before the interfaces
1407
+ release that introduced this contract refuses such a status whole, so every client that reads
1408
+ statuses moves to that release before Cloudly does, and a Cloudly that has stored one is not rolled
1409
+ back below it. A node never states the reason: a Pallet whose composition refuses (only a Cloudly
1410
+ before this contract places such a run) reports the run as the existing
1411
+ `TRuntimeAssignmentFailureCause` `{ kind: 'refused', reason: 'invalid' }`, which every reader since
1412
+ 32.45.0 reads and a controller never re-places with an identical configuration.
1413
+
1414
+ `IRuntimeConfig.environment` keeps its bound, `runtimeConfigContract.maximumEnvironmentEntries`
1415
+ (128). A runtime's executor admits the config's entries plus the injected ones:
1416
+ `data.workloadReplicaIdentityContract.maximumExecutorEnvironmentEntries`, computed as that bound
1417
+ plus `injectedEnvironmentEntries` (4), never a literal of the runtime's own. Pallet's native
1418
+ invocation check (`rust/src/execution.rs`, today a literal 128) admits exactly this value, and its
1419
+ TypeScript execution owner hands the composed environment to it in place of `config.environment`.
1420
+
1421
+ Injection is gated by the release, not per peer: a Pallet moves to the interfaces release that
1422
+ introduced this contract together with the injection, so a Pallet speaking it injects. Cloudly
1423
+ raises the `minimumPeerVersion` of its Pallet runtime sessions to that release (peers first, see
1424
+ `IProtocolOffer`) in the commit that first deploys a workload requiring its identity, such as
1425
+ CoreMail 34. No controller or runtime keeps a per-version send branch.
1426
+
1373
1427
  ### Authenticated Runtime Sessions
1374
1428
 
1375
1429
  `requests.runtimesession` defines `registerPalletRuntimeSession`,
@@ -2106,7 +2160,8 @@ evidence has not arrived). `refused` carries a `TServiceRuntimeRefusal`:
2106
2160
  `volume-initialization-unknown`, `volume-ownership-unknown`, `launcher-secret-ownership-unknown`,
2107
2161
  `objectstorage-endpoint-missing`, `objectstorage-binding-invalid`, `objectstorage-environment-conflict`,
2108
2162
  `objectstorage-endpoint-unselected`, `database-endpoint-missing`, `database-endpoint-unselected`,
2109
- `cluster-relay-missing` or `cluster-ingress-environment-conflict`.
2163
+ `cluster-relay-missing`, `cluster-ingress-environment-conflict` or
2164
+ `workload-identity-environment-conflict`.
2110
2165
  The first four volume
2111
2166
  refusals name what a local storage claim cannot express: a volume driver other than a node-local
2112
2167
  directory, driver or mount options, one volume mounted by more than one replica or service, and a
@@ -2123,7 +2178,10 @@ designated ingress service, whose environment `composeClusterIngressEnvironment`
2123
2178
  [Cluster Ingress](#cluster-ingress): `cluster-relay-missing` is a cluster that states no valid
2124
2179
  relay, which an administrator enables with `enableClusterRelay`, and
2125
2180
  `cluster-ingress-environment-conflict` an ingress service whose own environment already sets one of
2126
- `clusterIngressEnvironmentNames`. `ports-too-many` is a service declaring more target ports than the
2181
+ `clusterIngressEnvironmentNames`. `workload-identity-environment-conflict` is a service whose
2182
+ effective environment, or one of whose launcher-environment secrets, states one of the names every
2183
+ runtime injects, see [Workload Replica Identity](#workload-replica-identity), which also states why
2184
+ every reader of statuses moves to its release before Cloudly does. `ports-too-many` is a service declaring more target ports than the
2127
2185
  `runtimeConfigContract.maximumTargetPorts` (64) a config carries, where `port-protocol-unsupported`
2128
2186
  is about one port's protocol; `node-authority-unsupported` is a node whose session resolved fewer
2129
2187
  workload authorities than the spec needs, refused at the admit step rather than handed to a node
@@ -4158,11 +4216,70 @@ per-task authentication and readiness, then mark the previous verifier
4158
4216
  `retiring` with `acceptUntil`. Remove the previous verifier and secret only
4159
4217
  after its acceptance window has elapsed and no session uses that composite
4160
4218
  credential identity.
4161
- Reconciliation status carries the exact CoreMail task, rollout generation, and
4162
- image digest so Coreflow can correlate every response with its authoritative
4163
- current task roster. Apply `data.normalizeCoreMailReconciliationStatus` before
4164
- using pending-inbound or composite active-session counts for drain and rotation
4165
- decisions.
4219
+ Reconciliation status and the `coreMailAuthenticateControl` response carry the
4220
+ reporting replica so Coreflow can correlate every response with its
4221
+ authoritative current roster. CoreMail 34 and later report
4222
+ `data.IWorkloadReplicaIdentity` (`serviceId`, `replicaId`, `attemptId`,
4223
+ `imageDigest`), the identity its runtime injects; the attempt is what the roster
4224
+ correlates by. A CoreMail before 34 reports the deprecated
4225
+ `data.ICoreMailReplicaIdentity` (`taskId`, `serviceId`, `rolloutId`,
4226
+ `rolloutGeneration`, `imageDigest`), whose `taskId` is that attempt. Both types
4227
+ accept either shape, and `data.normalizeCoreMailReplicaIdentity` reads each
4228
+ exactly: a record stating `taskId` is the deprecated shape, any other the
4229
+ workload identity, and neither admits a member of the other. The deprecated
4230
+ shape is removed in the next major. Apply
4231
+ `data.normalizeCoreMailReconciliationStatus` before using pending-inbound or
4232
+ composite active-session counts for drain and rotation decisions.
4233
+
4234
+ #### CoreMail runtime environment (CoreMail 34 and later)
4235
+
4236
+ CoreMail 34 reads:
4237
+
4238
+ - its replica identity from `data.workloadReplicaIdentityEnvironmentNames`
4239
+ (`SERVEZONE_WORKLOAD_SERVICE_ID`, `SERVEZONE_WORKLOAD_REPLICA_ID`,
4240
+ `SERVEZONE_WORKLOAD_ATTEMPT_ID`, `SERVEZONE_WORKLOAD_IMAGE_DIGEST`) through
4241
+ `data.readWorkloadReplicaIdentityEnvironment`, and refuses to start naming a
4242
+ missing variable;
4243
+ - its database and object storage under the names its platform bindings deliver,
4244
+ `data.coreMailStorageEnvironmentNames`: `MONGODB_URL` and `MONGODB_DATABASE`
4245
+ (the Corestore database binding), `S3_ENDPOINT`, `S3_REGION` and `S3_BUCKET`
4246
+ (the object-storage binding) and `S3_ACCESS_KEY` and `S3_SECRET_KEY` (that
4247
+ binding's credential secrets);
4248
+ - `COREMAIL_CONTROL_BOOTSTRAP` (`data.coreMailRuntimeKeys.controlBootstrap`), and
4249
+ optionally `COREMAIL_PORT`, `COREMAIL_HOSTNAMES` and
4250
+ `COREMAIL_MONGODB_MAX_POOL_SIZE` (`port`, `hostnames`, `mongoDbMaxPoolSize`);
4251
+ - the secrets its desired state references by name, as before.
4252
+
4253
+ It no longer reads `COREMAIL_SERVICE_ID`, `COREMAIL_TASK_ID`,
4254
+ `COREMAIL_ROLLOUT_ID`, `COREMAIL_ROLLOUT_GENERATION`, `COREMAIL_IMAGE_DIGEST`,
4255
+ `COREMAIL_MONGODB_URL`, `COREMAIL_MONGODB_NAME`, `COREMAIL_STORAGE_DESCRIPTOR`
4256
+ or `COREMAIL_BUCKET_NAME`, and a provisioner stops writing them. CoreMail 34
4257
+ runs only on a runtime that injects its identity, see
4258
+ [Workload Replica Identity](#workload-replica-identity).
4259
+
4260
+ Readers first. A reader built before the interfaces release that introduced
4261
+ `data.IWorkloadReplicaIdentity` refuses CoreMail 34's replica whole, in the
4262
+ `coreMailAuthenticateControl` response and in reconciliation status alike: its
4263
+ `normalizeCoreMailReplicaIdentity` requires `taskId`, `rolloutId` and
4264
+ `rolloutGeneration`. The `minimumPeerVersion` of Pallet runtime sessions does not
4265
+ reach these readers, so the order is fixed: `@serve.zone/api`'s
4266
+ `CoreMailControlClient`, and with it Cloudly's CoreMail manager and Onebox's
4267
+ CoreMail provider, move to that interfaces release before any CoreMail 34 runs.
4268
+ Cloudly deploys CoreMail 34 only once its own reader is on that release, and
4269
+ Onebox moves its CoreMail pin to 34 only in the release that carries the
4270
+ upgraded reader. A CoreMail before 34 keeps writing the deprecated shape, which
4271
+ every reader reads as before.
4272
+
4273
+ In source, both carrying types and the normalizer's return value are
4274
+ `data.IWorkloadReplicaIdentity | data.ICoreMailReplicaIdentity`, a type-level
4275
+ change shipped in a 32.x minor. The consumers that read the replica,
4276
+ `@serve.zone/api` (`CoreMailControlClient`), Cloudly (CoreMail manager), Onebox
4277
+ (CoreMail provider) and CoreMail, narrow by `taskId`:
4278
+
4279
+ ```typescript
4280
+ const replica = data.normalizeCoreMailReplicaIdentity(response.replica);
4281
+ const attemptId = 'taskId' in replica ? replica.taskId : replica.attemptId;
4282
+ ```
4166
4283
 
4167
4284
  #### CoreMail control-plane settings and operator RPCs
4168
4285
 
@@ -4276,6 +4393,35 @@ request fence, and the trusted local organization/cluster expectation. It never
4276
4393
  decrypts. Organization and cluster are derived from the verified cluster JWT
4277
4394
  and cannot be selected by request fields.
4278
4395
 
4396
+ ### Service Mail Environment
4397
+
4398
+ `data.serviceMailEnvironmentNames` spells the environment a service's default
4399
+ sender is delivered under, once for the provisioners (Cloudly, Onebox) and the
4400
+ reader (`@serve.zone/platformclient`). It has three groups, each delivered
4401
+ whole: the typed dcrouter-direct binding (`MAIL_FROM`, `MAIL_TYPED_URL`,
4402
+ `MAIL_API_CREDENTIAL_ID`, `MAIL_API_CREDENTIAL_SECRET`), its SMTP compatibility
4403
+ surface (`SMTP_HOST`, `SMTP_PORT`, `SMTP_TLS_MODE`, `SMTP_USERNAME`,
4404
+ `SMTP_PASSWORD`) and a CoreMail binding (`MAIL_COREMAIL_URL`,
4405
+ `MAIL_COREMAIL_BINDING_ID`, `MAIL_COREMAIL_CREDENTIAL_ID`,
4406
+ `MAIL_COREMAIL_CREDENTIAL_VERSION`, `MAIL_COREMAIL_CREDENTIAL_SECRET`, with
4407
+ `MAIL_FROM`). The credential secrets and passwords are delivered as secrets.
4408
+ `data.createServiceMailAddressEnvironmentNames(token)` answers the same names
4409
+ for a further sender address, scoped to its upper-case address token
4410
+ (`MAIL_<TOKEN>_FROM`, `MAIL_<TOKEN>_TYPED_URL`, `MAIL_<TOKEN>_SMTP_HOST`,
4411
+ `MAIL_<TOKEN>_COREMAIL_URL`, …); any other token is thrown.
4412
+
4413
+ `MAIL_TYPED_URL` and every `MAIL_<TOKEN>_TYPED_URL` carry
4414
+ `ICloudlySettings.dcrouterMailEndpointUrl`, the canonical HTTPS URL of
4415
+ dcrouter's tenant mail listener, and nothing else: never `dcrouterGatewayUrl`,
4416
+ which is Cloudly's own gateway-client endpoint, and never a CoreMail endpoint,
4417
+ which travels as `MAIL_COREMAIL_URL`. `data.validateDcrouterMailEndpointUrl`
4418
+ accepts an `https:` URL without credentials, query or fragment, at most
4419
+ `data.dcrouterMailEndpointUrlMaximumLength` (2,048) characters, written exactly
4420
+ as `new URL(value).href` spells it, and names every violation without quoting
4421
+ the value. When the setting is absent, Cloudly refuses to provision a typed
4422
+ dcrouter-direct mail binding and names `dcrouterMailEndpointUrl` in the refusal;
4423
+ it falls back to no other endpoint.
4424
+
4279
4425
  ### Corestore Runtime Credentials
4280
4426
 
4281
4427
  The Node-only `/runtime` export defines
@@ -4845,7 +4991,7 @@ pnpm test
4845
4991
  pnpm run buildDocs
4846
4992
  ```
4847
4993
 
4848
- The package is authored as ESM TypeScript and built with `tsbuild tsfolders`.
4994
+ The package is authored as ESM TypeScript and built with `tsrepo build tsfolders`.
4849
4995
 
4850
4996
  ## License and Legal Information
4851
4997
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.60.0',
6
+ version: '32.62.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }