@serve.zone/interfaces 32.61.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.
- package/changelog.md +17 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/coremail.d.ts +37 -1
- package/dist_ts/data/coremail.js +23 -1
- package/dist_ts/data/coremail.runtime.d.ts +10 -1
- package/dist_ts/data/coremail.runtime.js +23 -1
- package/dist_ts/data/index.d.ts +1 -0
- package/dist_ts/data/index.js +2 -1
- package/dist_ts/data/mail.d.ts +36 -0
- package/dist_ts/data/mail.js +45 -2
- package/dist_ts/data/runtimeassignment.d.ts +4 -1
- package/dist_ts/data/runtimeassignment.js +5 -2
- package/dist_ts/data/serviceruntime.d.ts +14 -1
- package/dist_ts/data/serviceruntime.js +2 -2
- package/dist_ts/data/settings.d.ts +21 -0
- package/dist_ts/data/settings.js +38 -1
- package/dist_ts/data/workloadreplicaidentity.d.ts +101 -0
- package/dist_ts/data/workloadreplicaidentity.js +159 -0
- package/dist_ts/requests/coremail.d.ts +6 -1
- package/package.json +1 -1
- package/readme.md +153 -7
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/coremail.runtime.ts +26 -1
- package/ts/data/coremail.ts +42 -1
- package/ts/data/index.ts +1 -0
- package/ts/data/mail.ts +48 -0
- package/ts/data/runtimeassignment.ts +4 -1
- package/ts/data/serviceruntime.ts +14 -1
- package/ts/data/settings.ts +46 -0
- package/ts/data/workloadreplicaidentity.ts +219 -0
- package/ts/requests/coremail.ts +6 -1
|
@@ -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=
|
|
@@ -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
|
-
|
|
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> {
|
package/package.json
CHANGED
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
|
|
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`. `
|
|
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
|
|
4162
|
-
|
|
4163
|
-
current
|
|
4164
|
-
|
|
4165
|
-
|
|
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
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -48,6 +48,10 @@ import {
|
|
|
48
48
|
type TCoreMailWorkloadOperation,
|
|
49
49
|
} from './coremail.js';
|
|
50
50
|
import type { IMailConnectionInfo } from './mail.js';
|
|
51
|
+
import {
|
|
52
|
+
validateWorkloadReplicaIdentity,
|
|
53
|
+
type IWorkloadReplicaIdentity,
|
|
54
|
+
} from './workloadreplicaidentity.js';
|
|
51
55
|
|
|
52
56
|
export const coreMailCanonicalJsonRules = strictCanonicalJsonRules;
|
|
53
57
|
|
|
@@ -1889,11 +1893,32 @@ export const normalizeCoreMailDesiredState = (
|
|
|
1889
1893
|
return deepFreezeValue(normalized);
|
|
1890
1894
|
};
|
|
1891
1895
|
|
|
1896
|
+
/**
|
|
1897
|
+
* The replica a CoreMail reports, read exactly as one of two closed shapes.
|
|
1898
|
+
*
|
|
1899
|
+
* CoreMail 34 and later report `IWorkloadReplicaIdentity`, the identity its runtime injects, and
|
|
1900
|
+
* it is read by that contract's own rules (`validateWorkloadReplicaIdentity`). A CoreMail before
|
|
1901
|
+
* 34 reports the deprecated `ICoreMailReplicaIdentity`, recognised by its `taskId`, and it is read
|
|
1902
|
+
* as before. Neither shape admits a member of the other.
|
|
1903
|
+
*/
|
|
1892
1904
|
export const normalizeCoreMailReplicaIdentity = (
|
|
1893
1905
|
valueArg: unknown,
|
|
1894
1906
|
pathArg = 'replica',
|
|
1895
|
-
): ICoreMailReplicaIdentity => {
|
|
1907
|
+
): IWorkloadReplicaIdentity | ICoreMailReplicaIdentity => {
|
|
1896
1908
|
const record = readRecord(valueArg, pathArg);
|
|
1909
|
+
if (!Object.hasOwn(record, 'taskId')) {
|
|
1910
|
+
assertKeys(record, pathArg, ['serviceId', 'replicaId', 'attemptId', 'imageDigest']);
|
|
1911
|
+
const errors = validateWorkloadReplicaIdentity(record);
|
|
1912
|
+
if (errors.length > 0) {
|
|
1913
|
+
fail(`${pathArg}.${errors[0]}`);
|
|
1914
|
+
}
|
|
1915
|
+
return deepFreezeValue({
|
|
1916
|
+
serviceId: record.serviceId as string,
|
|
1917
|
+
replicaId: record.replicaId as string,
|
|
1918
|
+
attemptId: record.attemptId as string,
|
|
1919
|
+
imageDigest: record.imageDigest as IWorkloadReplicaIdentity['imageDigest'],
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1897
1922
|
assertKeys(
|
|
1898
1923
|
record,
|
|
1899
1924
|
pathArg,
|
package/ts/data/coremail.ts
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* binding authority.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { IWorkloadReplicaIdentity } from './workloadreplicaidentity.js';
|
|
11
|
+
import type { TCorestoreDatabaseCredentialKey, TCorestoreObjectStorageCredentialSecretKey } from '../runtime.corestore.js';
|
|
12
|
+
import type { TObjectStorageEndpointEnvironmentKey } from '../platform/objectstorage.js';
|
|
13
|
+
|
|
10
14
|
export type TCoreMailSha256 = `sha256:${string}`;
|
|
11
15
|
|
|
12
16
|
export type TCoreMailCapability = 'outbound' | 'inbound';
|
|
@@ -293,6 +297,15 @@ export interface ICoreMailDesiredState {
|
|
|
293
297
|
cursorKeys: ICoreMailRuntimeKeyReference[];
|
|
294
298
|
}
|
|
295
299
|
|
|
300
|
+
/**
|
|
301
|
+
* The replica identity CoreMail before 34 reports, read from the `COREMAIL_*` variables its
|
|
302
|
+
* provisioner fabricated.
|
|
303
|
+
*
|
|
304
|
+
* @deprecated CoreMail 34 and later report `IWorkloadReplicaIdentity`, which its runtime injects
|
|
305
|
+
* (`workloadReplicaIdentityEnvironmentNames`); `taskId` is that identity's `attemptId`.
|
|
306
|
+
* `normalizeCoreMailReplicaIdentity` still reads this shape, exactly, from a CoreMail before 34;
|
|
307
|
+
* removed in the next major.
|
|
308
|
+
*/
|
|
296
309
|
export interface ICoreMailReplicaIdentity {
|
|
297
310
|
taskId: string;
|
|
298
311
|
serviceId: string;
|
|
@@ -314,7 +327,11 @@ export interface ICoreMailBindingReconciliationStatus {
|
|
|
314
327
|
}
|
|
315
328
|
|
|
316
329
|
export interface ICoreMailReconciliationStatus {
|
|
317
|
-
|
|
330
|
+
/**
|
|
331
|
+
* The reporting replica: `IWorkloadReplicaIdentity` from CoreMail 34 on, the deprecated
|
|
332
|
+
* `ICoreMailReplicaIdentity` from an earlier CoreMail. Each is read exactly.
|
|
333
|
+
*/
|
|
334
|
+
replica: IWorkloadReplicaIdentity | ICoreMailReplicaIdentity;
|
|
318
335
|
state: TCoreMailReconciliationState;
|
|
319
336
|
appliedConfigEpoch: number;
|
|
320
337
|
appliedDesiredStateDigest: TCoreMailSha256;
|
|
@@ -495,4 +512,28 @@ export const coreMailRuntimeKeys = Object.freeze({
|
|
|
495
512
|
controlBootstrap: 'COREMAIL_CONTROL_BOOTSTRAP',
|
|
496
513
|
controlCredentialSecret: 'COREMAIL_CONTROL_CREDENTIAL_SECRET',
|
|
497
514
|
gatewayCredentialSecret: 'COREMAIL_GATEWAY_CREDENTIAL',
|
|
515
|
+
/** Optional listener port inside the workload. */
|
|
516
|
+
port: 'COREMAIL_PORT',
|
|
517
|
+
/** Optional comma-separated canonical hostname list. */
|
|
518
|
+
hostnames: 'COREMAIL_HOSTNAMES',
|
|
519
|
+
/** Optional MongoDB connection pool bound. */
|
|
520
|
+
mongoDbMaxPoolSize: 'COREMAIL_MONGODB_MAX_POOL_SIZE',
|
|
498
521
|
} as const);
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* The storage CoreMail 34 and later read, under the names its platform bindings deliver: the
|
|
525
|
+
* Corestore database binding's `MONGODB_URL` and `MONGODB_DATABASE`, the object-storage binding's
|
|
526
|
+
* `S3_ENDPOINT`, `S3_REGION` and `S3_BUCKET`, and that binding's `S3_ACCESS_KEY` and
|
|
527
|
+
* `S3_SECRET_KEY` secrets. They replace `COREMAIL_MONGODB_URL`, `COREMAIL_MONGODB_NAME`,
|
|
528
|
+
* `COREMAIL_STORAGE_DESCRIPTOR` and `COREMAIL_BUCKET_NAME`.
|
|
529
|
+
*/
|
|
530
|
+
export const coreMailStorageEnvironmentNames = Object.freeze({
|
|
531
|
+
databaseUrl: 'MONGODB_URL',
|
|
532
|
+
databaseName: 'MONGODB_DATABASE',
|
|
533
|
+
objectStorageEndpoint: 'S3_ENDPOINT',
|
|
534
|
+
objectStorageRegion: 'S3_REGION',
|
|
535
|
+
objectStorageBucket: 'S3_BUCKET',
|
|
536
|
+
objectStorageAccessKey: 'S3_ACCESS_KEY',
|
|
537
|
+
objectStorageSecretKey: 'S3_SECRET_KEY',
|
|
538
|
+
} as const satisfies Readonly<Record<string,
|
|
539
|
+
TCorestoreDatabaseCredentialKey | TObjectStorageEndpointEnvironmentKey | TCorestoreObjectStorageCredentialSecretKey>>);
|
package/ts/data/index.ts
CHANGED
package/ts/data/mail.ts
CHANGED
|
@@ -338,6 +338,54 @@ export interface IServiceMailConfig {
|
|
|
338
338
|
/** Caller-writable mail configuration. CoreMail binding metadata is excluded. */
|
|
339
339
|
export type TServiceMailWritableConfig = Omit<IServiceMailConfig, 'coreMail'>;
|
|
340
340
|
|
|
341
|
+
/**
|
|
342
|
+
* The environment names a service's default sender is delivered under, one spelling for the
|
|
343
|
+
* provisioners (Cloudly, Onebox) and the reader (`@serve.zone/platformclient`).
|
|
344
|
+
*
|
|
345
|
+
* Three groups, each delivered whole: the typed dcrouter-direct binding (`from`, `typedUrl`,
|
|
346
|
+
* `apiCredentialId`, `apiCredentialSecret`; `typedUrl` is `ICloudlySettings.dcrouterMailEndpointUrl`),
|
|
347
|
+
* its SMTP compatibility surface (`smtp*`), and a CoreMail binding (`coreMail*`, with `from`).
|
|
348
|
+
* `apiCredentialSecret`, `smtpPassword` and `coreMailCredentialSecret` are secrets and are
|
|
349
|
+
* delivered as secrets. Every further sender address uses the same names scoped to its address
|
|
350
|
+
* token (`createServiceMailAddressEnvironmentNames`).
|
|
351
|
+
*/
|
|
352
|
+
export const serviceMailEnvironmentNames = Object.freeze({
|
|
353
|
+
from: 'MAIL_FROM',
|
|
354
|
+
typedUrl: 'MAIL_TYPED_URL',
|
|
355
|
+
apiCredentialId: 'MAIL_API_CREDENTIAL_ID',
|
|
356
|
+
apiCredentialSecret: 'MAIL_API_CREDENTIAL_SECRET',
|
|
357
|
+
smtpHost: 'SMTP_HOST',
|
|
358
|
+
smtpPort: 'SMTP_PORT',
|
|
359
|
+
smtpTlsMode: 'SMTP_TLS_MODE',
|
|
360
|
+
smtpUsername: 'SMTP_USERNAME',
|
|
361
|
+
smtpPassword: 'SMTP_PASSWORD',
|
|
362
|
+
coreMailUrl: 'MAIL_COREMAIL_URL',
|
|
363
|
+
coreMailBindingId: 'MAIL_COREMAIL_BINDING_ID',
|
|
364
|
+
coreMailCredentialId: 'MAIL_COREMAIL_CREDENTIAL_ID',
|
|
365
|
+
coreMailCredentialVersionNumber: 'MAIL_COREMAIL_CREDENTIAL_VERSION',
|
|
366
|
+
coreMailCredentialSecret: 'MAIL_COREMAIL_CREDENTIAL_SECRET',
|
|
367
|
+
} as const);
|
|
368
|
+
|
|
369
|
+
export type TServiceMailEnvironmentNames = Readonly<Record<keyof typeof serviceMailEnvironmentNames, string>>;
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* The names one further sender address is delivered under: each of `serviceMailEnvironmentNames`
|
|
373
|
+
* with `MAIL_<token>_` in place of its `MAIL_` prefix, or before it where it has none
|
|
374
|
+
* (`MAIL_<token>_FROM`, `MAIL_<token>_TYPED_URL`, `MAIL_<token>_SMTP_HOST`,
|
|
375
|
+
* `MAIL_<token>_COREMAIL_URL`). The token is the address's upper-case token
|
|
376
|
+
* (`^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$`); any other token is the caller's error and is thrown.
|
|
377
|
+
*/
|
|
378
|
+
export const createServiceMailAddressEnvironmentNames = (addressTokenArg: string): TServiceMailEnvironmentNames => {
|
|
379
|
+
if (typeof addressTokenArg !== 'string' || !/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/.test(addressTokenArg)) {
|
|
380
|
+
throw new Error('service mail address token must be an upper-case address token');
|
|
381
|
+
}
|
|
382
|
+
const scoped: Record<string, string> = {};
|
|
383
|
+
for (const [key, name] of Object.entries(serviceMailEnvironmentNames)) {
|
|
384
|
+
scoped[key] = `MAIL_${addressTokenArg}_${name.startsWith('MAIL_') ? name.slice('MAIL_'.length) : name}`;
|
|
385
|
+
}
|
|
386
|
+
return Object.freeze(scoped) as TServiceMailEnvironmentNames;
|
|
387
|
+
};
|
|
388
|
+
|
|
341
389
|
export interface IServiceMailTargetAddressSummary {
|
|
342
390
|
address: string;
|
|
343
391
|
inbound: {
|
|
@@ -168,7 +168,10 @@ export type TRuntimeImagePullRefusalReason = typeof runtimeImagePullRefusalReaso
|
|
|
168
168
|
* Why a node refused to start a run before any runtime effect of it, as the node names it:
|
|
169
169
|
* `unsupported-readiness` a readiness probe the run's network cannot carry (a TCP or HTTP probe of
|
|
170
170
|
* a run without network authority), `unresolved-authority` a workload authority the node cannot
|
|
171
|
-
* resolve, and `invalid` an assignment or configuration the node cannot read
|
|
171
|
+
* resolve, and `invalid` an assignment or configuration the node cannot read or cannot run as stated,
|
|
172
|
+
* such as a config environment that states a name the runtime injects
|
|
173
|
+
* (`composeWorkloadReplicaEnvironment` answering `workload-identity-environment-conflict`, a
|
|
174
|
+
* service-status reason only Cloudly states).
|
|
172
175
|
*/
|
|
173
176
|
export const runtimeAssignmentRefusalReasons = ['unsupported-readiness', 'unresolved-authority', 'invalid'] as const;
|
|
174
177
|
export type TRuntimeAssignmentRefusalReason = typeof runtimeAssignmentRefusalReasons[number];
|
|
@@ -93,7 +93,7 @@ const serviceRuntimeRefusals = [
|
|
|
93
93
|
'objectstorage-endpoint-missing', 'objectstorage-binding-invalid', 'objectstorage-environment-conflict',
|
|
94
94
|
'objectstorage-endpoint-unselected', 'database-endpoint-missing', 'database-endpoint-unselected',
|
|
95
95
|
'published-port-reserved', 'published-port-symmetric-without-egress', 'readiness-network-unsupported',
|
|
96
|
-
'cluster-relay-missing', 'cluster-ingress-environment-conflict',
|
|
96
|
+
'cluster-relay-missing', 'cluster-ingress-environment-conflict', 'workload-identity-environment-conflict',
|
|
97
97
|
] as const;
|
|
98
98
|
|
|
99
99
|
/**
|
|
@@ -183,6 +183,19 @@ export type TServiceRuntimeWaitingReason = typeof serviceRuntimeWaitingReasons[n
|
|
|
183
183
|
* `cluster-ingress-environment-conflict` is an ingress service whose own environment already sets
|
|
184
184
|
* one of `clusterIngressEnvironmentNames`: Cloudly owns those names, and a service value would
|
|
185
185
|
* otherwise silently replace Cloudly's or be replaced by it.
|
|
186
|
+
*
|
|
187
|
+
* `workload-identity-environment-conflict` is a service whose effective environment, or one of
|
|
188
|
+
* whose launcher-environment secrets, states one of `workloadReplicaIdentityEnvironmentNames`
|
|
189
|
+
* (`findWorkloadReplicaIdentityEnvironmentConflicts`): every runtime injects those names into every
|
|
190
|
+
* workload (`composeWorkloadReplicaEnvironment`), so a service value would silently replace the
|
|
191
|
+
* replica's identity or be replaced by it. Cloudly alone states it, and only in this status, which
|
|
192
|
+
* it stores and answers on `getServiceRuntimeSpec`, `getServiceRuntimeSpecForCluster` and
|
|
193
|
+
* `getServiceRuntimeSpecs`. Those requests carry no protocol offer, so the reason cannot be gated
|
|
194
|
+
* on a peer's version, and no other refusal names this check. A reader built before the release
|
|
195
|
+
* that added it refuses such a status whole, so every reader of statuses moves to that release
|
|
196
|
+
* before Cloudly does, and a Cloudly that has stored one is not rolled back below it. A node never
|
|
197
|
+
* states it: a Pallet whose composition refuses reports the run as the existing
|
|
198
|
+
* `TRuntimeAssignmentFailureCause` `refused` with `invalid`.
|
|
186
199
|
*/
|
|
187
200
|
export type TServiceRuntimeRefusal = typeof serviceRuntimeRefusals[number];
|
|
188
201
|
|