@serve.zone/interfaces 32.61.0 → 32.63.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 +25 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/clusteringress.d.ts +7 -1
- package/dist_ts/data/clusteringress.js +15 -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 +2 -0
- package/dist_ts/data/index.js +3 -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 +28 -1
- package/dist_ts/data/runtimeassignment.js +22 -3
- package/dist_ts/data/runtimesessionpush.d.ts +24 -0
- package/dist_ts/data/runtimesessionpush.js +25 -0
- 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 +182 -10
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/clusteringress.ts +14 -0
- package/ts/data/coremail.runtime.ts +26 -1
- package/ts/data/coremail.ts +42 -1
- package/ts/data/index.ts +2 -0
- package/ts/data/mail.ts +48 -0
- package/ts/data/runtimeassignment.ts +37 -2
- package/ts/data/runtimesessionpush.ts +24 -0
- 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ya2xvYWRyZXBsaWNhaWRlbnRpdHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL3dvcmtsb2FkcmVwbGljYWlkZW50aXR5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUVyRCxPQUFPLEVBQUUsa0JBQWtCLEVBQUUseUJBQXlCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUN2RixPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUUzRCxPQUFPLEVBQUUsWUFBWSxFQUFFLGtCQUFrQixFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFxQmhGOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSx1Q0FBdUMsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ25FLFNBQVMsRUFBRSwrQkFBK0I7SUFDMUMsU0FBUyxFQUFFLCtCQUErQjtJQUMxQyxTQUFTLEVBQUUsK0JBQStCO0lBQzFDLFdBQVcsRUFBRSxpQ0FBaUM7Q0FDNkIsQ0FBQyxDQUFDO0FBSy9FLE1BQU0sWUFBWSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQ2hDLE1BQU0sQ0FBQyxJQUFJLENBQUMsdUNBQXVDLENBQXVDLENBQzNGLENBQUM7QUFDRixNQUFNLHdCQUF3QixHQUFzQixNQUFNLENBQUMsTUFBTSxDQUMvRCxNQUFNLENBQUMsTUFBTSxDQUFDLHVDQUF1QyxDQUFDLENBQ3ZELENBQUM7QUFFRjs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sK0JBQStCLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUMzRCwwQkFBMEIsRUFBRSxZQUFZLENBQUMsTUFBTTtJQUMvQyxpQ0FBaUMsRUFBRSxxQkFBcUIsQ0FBQyx5QkFBeUIsR0FBRyxZQUFZLENBQUMsTUFBTTtDQUN6RyxDQUFDLENBQUM7QUFFSCxNQUFNLFlBQVksR0FBRyxDQUFDLFFBQWlCLEVBQXNC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
|
|
@@ -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
|
@@ -1305,6 +1305,14 @@ and never content, and a controller and a node cannot disagree about the referen
|
|
|
1305
1305
|
absence: an assignment that states no endpoint is different content, and so a different digest, from
|
|
1306
1306
|
one that does.
|
|
1307
1307
|
|
|
1308
|
+
`designation` is the optional role its cluster gives the attempt's service, from
|
|
1309
|
+
`data.runtimeAssignmentDesignations`: `cluster-ingress` for the service Cloudly lists as the cluster's
|
|
1310
|
+
designated ingress (and delivers first). A node begins such a run before every other run it holds,
|
|
1311
|
+
by this statement rather than by delivery order. It is part of the attempt, so every later revision
|
|
1312
|
+
states the same value, and it grants nothing. A reader built before the member refuses an assignment
|
|
1313
|
+
that states it, so a controller states it only on a session whose offer speaks the release that added
|
|
1314
|
+
it or a later one.
|
|
1315
|
+
|
|
1308
1316
|
All fields except the digest itself participate in a domain-separated
|
|
1309
1317
|
SHA-256 digest. Digests provide integrity and compare-and-swap identity;
|
|
1310
1318
|
authentication must separately establish the trusted controller/node scope.
|
|
@@ -1370,6 +1378,60 @@ Version 29 widens these numeric TypeScript fields to include those explicit null
|
|
|
1370
1378
|
policies; consumers must handle them before accepting configurations from v29.
|
|
1371
1379
|
Existing numeric configurations retain their exact meaning and digest.
|
|
1372
1380
|
|
|
1381
|
+
### Workload Replica Identity
|
|
1382
|
+
|
|
1383
|
+
Every runtime (Pallet, Onebox) injects the identity of the replica it runs into every workload,
|
|
1384
|
+
whether the workload reads it or not. `data.IWorkloadReplicaIdentity` is exactly
|
|
1385
|
+
`{ serviceId, replicaId, attemptId, imageDigest }`, the runtime assignment's own values:
|
|
1386
|
+
`data.buildWorkloadReplicaIdentity(assignment)` reads them from the admitted assignment through its
|
|
1387
|
+
exact schema, so the answer always validates. `attemptId` is the one execution attempt; a rollout or
|
|
1388
|
+
a replacement is a new attempt, and the controller's `attemptId` already binds the admitting
|
|
1389
|
+
rollout, so no rollout name or generation travels. `data.validateWorkloadReplicaIdentity` names
|
|
1390
|
+
every violation and `data.snapshotWorkloadReplicaIdentity` answers a detached frozen copy or throws.
|
|
1391
|
+
|
|
1392
|
+
`data.workloadReplicaIdentityEnvironmentNames` is the one spelling of the injected names:
|
|
1393
|
+
`SERVEZONE_WORKLOAD_SERVICE_ID`, `SERVEZONE_WORKLOAD_REPLICA_ID`, `SERVEZONE_WORKLOAD_ATTEMPT_ID` and
|
|
1394
|
+
`SERVEZONE_WORKLOAD_IMAGE_DIGEST`. They sit under the platform-reserved `SERVEZONE_` prefix
|
|
1395
|
+
(`platformReservedSecretKeyPrefixes`). A runtime composes what a replica runs with through
|
|
1396
|
+
`data.composeWorkloadReplicaEnvironment(configEnvironment, identity)`: the config's environment
|
|
1397
|
+
with the four names added, or `refused` with `workload-identity-environment-conflict` and a message
|
|
1398
|
+
naming every colliding key and no value (`TWorkloadReplicaEnvironmentComposition`). An identity that
|
|
1399
|
+
is not exactly one is thrown. A workload reads its identity with
|
|
1400
|
+
`data.readWorkloadReplicaIdentityEnvironment(process.env)`, which reads the four names only and
|
|
1401
|
+
throws naming every missing or invalid variable, never a value, so a workload refuses to start
|
|
1402
|
+
naming the variable its runtime did not inject.
|
|
1403
|
+
|
|
1404
|
+
Cloudly refuses a service whose effective environment (image and service values) or one of whose
|
|
1405
|
+
launcher-environment secrets states an injected name, before any runtime sees it:
|
|
1406
|
+
`data.findWorkloadReplicaIdentityEnvironmentConflicts(names)` answers the colliding names among
|
|
1407
|
+
the environment's keys and the secrets' variable names, and the service's status reads
|
|
1408
|
+
`workload-identity-environment-conflict`. Secrets count because WorkloadInit sets a secret over a
|
|
1409
|
+
plain variable of the same name.
|
|
1410
|
+
|
|
1411
|
+
Only Cloudly states `workload-identity-environment-conflict` on the wire, in the service status it
|
|
1412
|
+
stores and answers on `getServiceRuntimeSpec`, `getServiceRuntimeSpecForCluster` and
|
|
1413
|
+
`getServiceRuntimeSpecs`. Those requests carry no protocol offer, so the reason cannot be gated on
|
|
1414
|
+
a reader's version, and no other refusal names this check. A reader built before the interfaces
|
|
1415
|
+
release that introduced this contract refuses such a status whole, so every client that reads
|
|
1416
|
+
statuses moves to that release before Cloudly does, and a Cloudly that has stored one is not rolled
|
|
1417
|
+
back below it. A node never states the reason: a Pallet whose composition refuses (only a Cloudly
|
|
1418
|
+
before this contract places such a run) reports the run as the existing
|
|
1419
|
+
`TRuntimeAssignmentFailureCause` `{ kind: 'refused', reason: 'invalid' }`, which every reader since
|
|
1420
|
+
32.45.0 reads and a controller never re-places with an identical configuration.
|
|
1421
|
+
|
|
1422
|
+
`IRuntimeConfig.environment` keeps its bound, `runtimeConfigContract.maximumEnvironmentEntries`
|
|
1423
|
+
(128). A runtime's executor admits the config's entries plus the injected ones:
|
|
1424
|
+
`data.workloadReplicaIdentityContract.maximumExecutorEnvironmentEntries`, computed as that bound
|
|
1425
|
+
plus `injectedEnvironmentEntries` (4), never a literal of the runtime's own. Pallet's native
|
|
1426
|
+
invocation check (`rust/src/execution.rs`, today a literal 128) admits exactly this value, and its
|
|
1427
|
+
TypeScript execution owner hands the composed environment to it in place of `config.environment`.
|
|
1428
|
+
|
|
1429
|
+
Injection is gated by the release, not per peer: a Pallet moves to the interfaces release that
|
|
1430
|
+
introduced this contract together with the injection, so a Pallet speaking it injects. Cloudly
|
|
1431
|
+
raises the `minimumPeerVersion` of its Pallet runtime sessions to that release (peers first, see
|
|
1432
|
+
`IProtocolOffer`) in the commit that first deploys a workload requiring its identity, such as
|
|
1433
|
+
CoreMail 34. No controller or runtime keeps a per-version send branch.
|
|
1434
|
+
|
|
1373
1435
|
### Authenticated Runtime Sessions
|
|
1374
1436
|
|
|
1375
1437
|
`requests.runtimesession` defines `registerPalletRuntimeSession`,
|
|
@@ -1510,6 +1572,15 @@ answer, more than a second beyond Cloudly's budget and below TypedSocket's 10 s
|
|
|
1510
1572
|
connection-restore deadline); and a relay's `relaySessionForwardMs` (8 s) for a
|
|
1511
1573
|
session read or report forwarded over its established Cloudly link.
|
|
1512
1574
|
|
|
1575
|
+
`data.runtimeSessionPushBounds.maximumInFlightPerNode` (8) is the other direction: how many requests a
|
|
1576
|
+
controller holds outstanding on one node's runtime session at once, every method and lane together
|
|
1577
|
+
(assignments, local storage claims, the network signing authority, projection and DNS lease renewals,
|
|
1578
|
+
stats reads), counted per node rather than per relay peer. A node holds that many beside the answers to
|
|
1579
|
+
its own requests and their cancellations without closing its session, and answers each within the
|
|
1580
|
+
relay's forward timeout for a push to a node (coreflow's `nodeForwardMs`, 8 s; not
|
|
1581
|
+
`runtimeSessionRequestBounds.relaySessionForwardMs`, which bounds the node's own requests to the
|
|
1582
|
+
controller); a controller that sends more may lose the session.
|
|
1583
|
+
|
|
1513
1584
|
### Assignment-Bound Runtime Secrets
|
|
1514
1585
|
|
|
1515
1586
|
`@serve.zone/interfaces/runtime` exports the Pallet assignment-secret contract.
|
|
@@ -2106,7 +2177,8 @@ evidence has not arrived). `refused` carries a `TServiceRuntimeRefusal`:
|
|
|
2106
2177
|
`volume-initialization-unknown`, `volume-ownership-unknown`, `launcher-secret-ownership-unknown`,
|
|
2107
2178
|
`objectstorage-endpoint-missing`, `objectstorage-binding-invalid`, `objectstorage-environment-conflict`,
|
|
2108
2179
|
`objectstorage-endpoint-unselected`, `database-endpoint-missing`, `database-endpoint-unselected`,
|
|
2109
|
-
`cluster-relay-missing
|
|
2180
|
+
`cluster-relay-missing`, `cluster-ingress-environment-conflict` or
|
|
2181
|
+
`workload-identity-environment-conflict`.
|
|
2110
2182
|
The first four volume
|
|
2111
2183
|
refusals name what a local storage claim cannot express: a volume driver other than a node-local
|
|
2112
2184
|
directory, driver or mount options, one volume mounted by more than one replica or service, and a
|
|
@@ -2123,7 +2195,10 @@ designated ingress service, whose environment `composeClusterIngressEnvironment`
|
|
|
2123
2195
|
[Cluster Ingress](#cluster-ingress): `cluster-relay-missing` is a cluster that states no valid
|
|
2124
2196
|
relay, which an administrator enables with `enableClusterRelay`, and
|
|
2125
2197
|
`cluster-ingress-environment-conflict` an ingress service whose own environment already sets one of
|
|
2126
|
-
`clusterIngressEnvironmentNames`. `
|
|
2198
|
+
`clusterIngressEnvironmentNames`. `workload-identity-environment-conflict` is a service whose
|
|
2199
|
+
effective environment, or one of whose launcher-environment secrets, states one of the names every
|
|
2200
|
+
runtime injects, see [Workload Replica Identity](#workload-replica-identity), which also states why
|
|
2201
|
+
every reader of statuses moves to its release before Cloudly does. `ports-too-many` is a service declaring more target ports than the
|
|
2127
2202
|
`runtimeConfigContract.maximumTargetPorts` (64) a config carries, where `port-protocol-unsupported`
|
|
2128
2203
|
is about one port's protocol; `node-authority-unsupported` is a node whose session resolved fewer
|
|
2129
2204
|
workload authorities than the spec needs, refused at the admit step rather than handed to a node
|
|
@@ -3453,7 +3528,9 @@ checks the hostname's source profile stays the only way in whether or not the no
|
|
|
3453
3528
|
reachable from elsewhere. `validateClusterRouteSourceAllowList(value, path?)` judges one list. A
|
|
3454
3529
|
reader built before the member refuses the whole table that carries it, so an ingress that applies
|
|
3455
3530
|
it registers with `clusterIngressCapabilities.routeSourceAllowList` (`route-source-allowlist`), and
|
|
3456
|
-
Cloudly sends the member only to such an ingress through a relay that reads it.
|
|
3531
|
+
Cloudly sends the member only to such an ingress through a relay that reads it. An ingress that
|
|
3532
|
+
reads the registration refusal `controller-deadline` registers with
|
|
3533
|
+
`clusterIngressCapabilities.controllerDeadlineRefusal` (`controller-deadline-refusal`).
|
|
3457
3534
|
`IClusterPortRoute` forwards a TCP or UDP listen port without terminating anything. `validateClusterRouteTable(value, path?)` returns one
|
|
3458
3535
|
named reason per violation, refuses two routes for one hostname and two port routes for one
|
|
3459
3536
|
transport and port, and — because a validation reason travels into logs — **never quotes
|
|
@@ -3507,8 +3584,15 @@ the order Cloudly judges them: `not-ready` (Cloudly cannot judge registrations y
|
|
|
3507
3584
|
not the connection Cloudly elected for its cluster), `ingress-not-designated`, `node-mismatch` (the
|
|
3508
3585
|
node is not one the designation lets the ingress run on), `assignment-not-running` (no ready
|
|
3509
3586
|
assignment of the ingress service runs on that node under its current session, or it does not carry
|
|
3510
|
-
the designation's current credential), `bearer-refused
|
|
3511
|
-
or session changed while the registration was judged)
|
|
3587
|
+
the designation's current credential), `bearer-refused`, `conflict` (the designation, assignment
|
|
3588
|
+
or session changed while the registration was judged) and `controller-deadline` (Cloudly did not
|
|
3589
|
+
settle the registration within its request budget, `runtimeSessionRequestBounds.controllerBudgetMs`;
|
|
3590
|
+
a momentary state of Cloudly's, so the ingress registers again soon, and a registration whose commit
|
|
3591
|
+
the deadline interrupted is answered as its replay). A reader built before `controller-deadline`
|
|
3592
|
+
reads such a refusal as no named refusal, so Cloudly sends it only to an ingress whose registration
|
|
3593
|
+
states `clusterIngressCapabilities.controllerDeadlineRefusal` (`controller-deadline-refusal`) and
|
|
3594
|
+
answers `not-ready` to every other; the relay forwards a refusal untouched and never reads its
|
|
3595
|
+
reason. `retryAfterMs` is how long the ingress waits
|
|
3512
3596
|
before it registers again, 1 s to 300 s (`clusterIngressRegistrationContract`).
|
|
3513
3597
|
`validateClusterIngressRegistrationRefusal` names every violation and
|
|
3514
3598
|
`readClusterIngressRegistrationRefusal(errorData)` answers the refusal or `null`, reading plain data
|
|
@@ -4158,11 +4242,70 @@ per-task authentication and readiness, then mark the previous verifier
|
|
|
4158
4242
|
`retiring` with `acceptUntil`. Remove the previous verifier and secret only
|
|
4159
4243
|
after its acceptance window has elapsed and no session uses that composite
|
|
4160
4244
|
credential identity.
|
|
4161
|
-
Reconciliation status
|
|
4162
|
-
|
|
4163
|
-
current
|
|
4164
|
-
|
|
4165
|
-
|
|
4245
|
+
Reconciliation status and the `coreMailAuthenticateControl` response carry the
|
|
4246
|
+
reporting replica so Coreflow can correlate every response with its
|
|
4247
|
+
authoritative current roster. CoreMail 34 and later report
|
|
4248
|
+
`data.IWorkloadReplicaIdentity` (`serviceId`, `replicaId`, `attemptId`,
|
|
4249
|
+
`imageDigest`), the identity its runtime injects; the attempt is what the roster
|
|
4250
|
+
correlates by. A CoreMail before 34 reports the deprecated
|
|
4251
|
+
`data.ICoreMailReplicaIdentity` (`taskId`, `serviceId`, `rolloutId`,
|
|
4252
|
+
`rolloutGeneration`, `imageDigest`), whose `taskId` is that attempt. Both types
|
|
4253
|
+
accept either shape, and `data.normalizeCoreMailReplicaIdentity` reads each
|
|
4254
|
+
exactly: a record stating `taskId` is the deprecated shape, any other the
|
|
4255
|
+
workload identity, and neither admits a member of the other. The deprecated
|
|
4256
|
+
shape is removed in the next major. Apply
|
|
4257
|
+
`data.normalizeCoreMailReconciliationStatus` before using pending-inbound or
|
|
4258
|
+
composite active-session counts for drain and rotation decisions.
|
|
4259
|
+
|
|
4260
|
+
#### CoreMail runtime environment (CoreMail 34 and later)
|
|
4261
|
+
|
|
4262
|
+
CoreMail 34 reads:
|
|
4263
|
+
|
|
4264
|
+
- its replica identity from `data.workloadReplicaIdentityEnvironmentNames`
|
|
4265
|
+
(`SERVEZONE_WORKLOAD_SERVICE_ID`, `SERVEZONE_WORKLOAD_REPLICA_ID`,
|
|
4266
|
+
`SERVEZONE_WORKLOAD_ATTEMPT_ID`, `SERVEZONE_WORKLOAD_IMAGE_DIGEST`) through
|
|
4267
|
+
`data.readWorkloadReplicaIdentityEnvironment`, and refuses to start naming a
|
|
4268
|
+
missing variable;
|
|
4269
|
+
- its database and object storage under the names its platform bindings deliver,
|
|
4270
|
+
`data.coreMailStorageEnvironmentNames`: `MONGODB_URL` and `MONGODB_DATABASE`
|
|
4271
|
+
(the Corestore database binding), `S3_ENDPOINT`, `S3_REGION` and `S3_BUCKET`
|
|
4272
|
+
(the object-storage binding) and `S3_ACCESS_KEY` and `S3_SECRET_KEY` (that
|
|
4273
|
+
binding's credential secrets);
|
|
4274
|
+
- `COREMAIL_CONTROL_BOOTSTRAP` (`data.coreMailRuntimeKeys.controlBootstrap`), and
|
|
4275
|
+
optionally `COREMAIL_PORT`, `COREMAIL_HOSTNAMES` and
|
|
4276
|
+
`COREMAIL_MONGODB_MAX_POOL_SIZE` (`port`, `hostnames`, `mongoDbMaxPoolSize`);
|
|
4277
|
+
- the secrets its desired state references by name, as before.
|
|
4278
|
+
|
|
4279
|
+
It no longer reads `COREMAIL_SERVICE_ID`, `COREMAIL_TASK_ID`,
|
|
4280
|
+
`COREMAIL_ROLLOUT_ID`, `COREMAIL_ROLLOUT_GENERATION`, `COREMAIL_IMAGE_DIGEST`,
|
|
4281
|
+
`COREMAIL_MONGODB_URL`, `COREMAIL_MONGODB_NAME`, `COREMAIL_STORAGE_DESCRIPTOR`
|
|
4282
|
+
or `COREMAIL_BUCKET_NAME`, and a provisioner stops writing them. CoreMail 34
|
|
4283
|
+
runs only on a runtime that injects its identity, see
|
|
4284
|
+
[Workload Replica Identity](#workload-replica-identity).
|
|
4285
|
+
|
|
4286
|
+
Readers first. A reader built before the interfaces release that introduced
|
|
4287
|
+
`data.IWorkloadReplicaIdentity` refuses CoreMail 34's replica whole, in the
|
|
4288
|
+
`coreMailAuthenticateControl` response and in reconciliation status alike: its
|
|
4289
|
+
`normalizeCoreMailReplicaIdentity` requires `taskId`, `rolloutId` and
|
|
4290
|
+
`rolloutGeneration`. The `minimumPeerVersion` of Pallet runtime sessions does not
|
|
4291
|
+
reach these readers, so the order is fixed: `@serve.zone/api`'s
|
|
4292
|
+
`CoreMailControlClient`, and with it Cloudly's CoreMail manager and Onebox's
|
|
4293
|
+
CoreMail provider, move to that interfaces release before any CoreMail 34 runs.
|
|
4294
|
+
Cloudly deploys CoreMail 34 only once its own reader is on that release, and
|
|
4295
|
+
Onebox moves its CoreMail pin to 34 only in the release that carries the
|
|
4296
|
+
upgraded reader. A CoreMail before 34 keeps writing the deprecated shape, which
|
|
4297
|
+
every reader reads as before.
|
|
4298
|
+
|
|
4299
|
+
In source, both carrying types and the normalizer's return value are
|
|
4300
|
+
`data.IWorkloadReplicaIdentity | data.ICoreMailReplicaIdentity`, a type-level
|
|
4301
|
+
change shipped in a 32.x minor. The consumers that read the replica,
|
|
4302
|
+
`@serve.zone/api` (`CoreMailControlClient`), Cloudly (CoreMail manager), Onebox
|
|
4303
|
+
(CoreMail provider) and CoreMail, narrow by `taskId`:
|
|
4304
|
+
|
|
4305
|
+
```typescript
|
|
4306
|
+
const replica = data.normalizeCoreMailReplicaIdentity(response.replica);
|
|
4307
|
+
const attemptId = 'taskId' in replica ? replica.taskId : replica.attemptId;
|
|
4308
|
+
```
|
|
4166
4309
|
|
|
4167
4310
|
#### CoreMail control-plane settings and operator RPCs
|
|
4168
4311
|
|
|
@@ -4276,6 +4419,35 @@ request fence, and the trusted local organization/cluster expectation. It never
|
|
|
4276
4419
|
decrypts. Organization and cluster are derived from the verified cluster JWT
|
|
4277
4420
|
and cannot be selected by request fields.
|
|
4278
4421
|
|
|
4422
|
+
### Service Mail Environment
|
|
4423
|
+
|
|
4424
|
+
`data.serviceMailEnvironmentNames` spells the environment a service's default
|
|
4425
|
+
sender is delivered under, once for the provisioners (Cloudly, Onebox) and the
|
|
4426
|
+
reader (`@serve.zone/platformclient`). It has three groups, each delivered
|
|
4427
|
+
whole: the typed dcrouter-direct binding (`MAIL_FROM`, `MAIL_TYPED_URL`,
|
|
4428
|
+
`MAIL_API_CREDENTIAL_ID`, `MAIL_API_CREDENTIAL_SECRET`), its SMTP compatibility
|
|
4429
|
+
surface (`SMTP_HOST`, `SMTP_PORT`, `SMTP_TLS_MODE`, `SMTP_USERNAME`,
|
|
4430
|
+
`SMTP_PASSWORD`) and a CoreMail binding (`MAIL_COREMAIL_URL`,
|
|
4431
|
+
`MAIL_COREMAIL_BINDING_ID`, `MAIL_COREMAIL_CREDENTIAL_ID`,
|
|
4432
|
+
`MAIL_COREMAIL_CREDENTIAL_VERSION`, `MAIL_COREMAIL_CREDENTIAL_SECRET`, with
|
|
4433
|
+
`MAIL_FROM`). The credential secrets and passwords are delivered as secrets.
|
|
4434
|
+
`data.createServiceMailAddressEnvironmentNames(token)` answers the same names
|
|
4435
|
+
for a further sender address, scoped to its upper-case address token
|
|
4436
|
+
(`MAIL_<TOKEN>_FROM`, `MAIL_<TOKEN>_TYPED_URL`, `MAIL_<TOKEN>_SMTP_HOST`,
|
|
4437
|
+
`MAIL_<TOKEN>_COREMAIL_URL`, …); any other token is thrown.
|
|
4438
|
+
|
|
4439
|
+
`MAIL_TYPED_URL` and every `MAIL_<TOKEN>_TYPED_URL` carry
|
|
4440
|
+
`ICloudlySettings.dcrouterMailEndpointUrl`, the canonical HTTPS URL of
|
|
4441
|
+
dcrouter's tenant mail listener, and nothing else: never `dcrouterGatewayUrl`,
|
|
4442
|
+
which is Cloudly's own gateway-client endpoint, and never a CoreMail endpoint,
|
|
4443
|
+
which travels as `MAIL_COREMAIL_URL`. `data.validateDcrouterMailEndpointUrl`
|
|
4444
|
+
accepts an `https:` URL without credentials, query or fragment, at most
|
|
4445
|
+
`data.dcrouterMailEndpointUrlMaximumLength` (2,048) characters, written exactly
|
|
4446
|
+
as `new URL(value).href` spells it, and names every violation without quoting
|
|
4447
|
+
the value. When the setting is absent, Cloudly refuses to provision a typed
|
|
4448
|
+
dcrouter-direct mail binding and names `dcrouterMailEndpointUrl` in the refusal;
|
|
4449
|
+
it falls back to no other endpoint.
|
|
4450
|
+
|
|
4279
4451
|
### Corestore Runtime Credentials
|
|
4280
4452
|
|
|
4281
4453
|
The Node-only `/runtime` export defines
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -44,9 +44,15 @@ export const clusterIngressLimits = Object.freeze({
|
|
|
44
44
|
* `routeSourceAllowList`: the ingress applies `IClusterHttpRoute.sourceAllowList`. A route table
|
|
45
45
|
* that carries the member is refused whole by a reader built before it, so Cloudly sends a route
|
|
46
46
|
* with a source allowlist only to an ingress that registered with this capability.
|
|
47
|
+
*
|
|
48
|
+
* `controllerDeadlineRefusal`: the ingress reads the registration refusal `controller-deadline`
|
|
49
|
+
* (`clusterIngressRegistrationRefusalReasons`) as a named refusal and waits its `retryAfterMs`. A
|
|
50
|
+
* reader built before the reason reads such a refusal as no named refusal, so Cloudly answers it
|
|
51
|
+
* only to a registration that states this capability and answers `not-ready` to every other.
|
|
47
52
|
*/
|
|
48
53
|
export const clusterIngressCapabilities = Object.freeze({
|
|
49
54
|
routeSourceAllowList: 'route-source-allowlist',
|
|
55
|
+
controllerDeadlineRefusal: 'controller-deadline-refusal',
|
|
50
56
|
} as const);
|
|
51
57
|
|
|
52
58
|
export type TClusterIngressCapability =
|
|
@@ -618,6 +624,14 @@ export const clusterIngressRegistrationRefusalReasons = Object.freeze([
|
|
|
618
624
|
'bearer-refused',
|
|
619
625
|
/** The designation, the assignment or the session changed while the registration was judged. */
|
|
620
626
|
'conflict',
|
|
627
|
+
/**
|
|
628
|
+
* Cloudly did not settle the registration within its request budget
|
|
629
|
+
* (`runtimeSessionRequestBounds.controllerBudgetMs`): a momentary state of Cloudly's, never a
|
|
630
|
+
* verdict on the ingress, which registers again soon. A registration whose commit the deadline
|
|
631
|
+
* interrupted may have been accepted; registering again is answered as its replay. Sent only to
|
|
632
|
+
* an ingress that registered with `clusterIngressCapabilities.controllerDeadlineRefusal`.
|
|
633
|
+
*/
|
|
634
|
+
'controller-deadline',
|
|
621
635
|
] as const);
|
|
622
636
|
|
|
623
637
|
export type TClusterIngressRegistrationRefusalReason = typeof clusterIngressRegistrationRefusalReasons[number];
|
|
@@ -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
|
@@ -67,6 +67,7 @@ export * from './runtimestorageclaim.js';
|
|
|
67
67
|
export * from './runtimelocalstorageclaim.js';
|
|
68
68
|
export * from './runtimelocalvolumearchive.js';
|
|
69
69
|
export * from './runtimesession.js';
|
|
70
|
+
export * from './runtimesessionpush.js';
|
|
70
71
|
export * from './runtimesessionrefusal.js';
|
|
71
72
|
export * from './runtimeterminalreceipt.js';
|
|
72
73
|
export * from './secret.js';
|
|
@@ -91,3 +92,4 @@ export * from './traffic.js';
|
|
|
91
92
|
export * from './user.js';
|
|
92
93
|
export * from './version.js';
|
|
93
94
|
export * from './webpush.js';
|
|
95
|
+
export * from './workloadreplicaidentity.js';
|