@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.
Files changed (37) hide show
  1. package/changelog.md +25 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/data/clusteringress.d.ts +7 -1
  4. package/dist_ts/data/clusteringress.js +15 -1
  5. package/dist_ts/data/coremail.d.ts +37 -1
  6. package/dist_ts/data/coremail.js +23 -1
  7. package/dist_ts/data/coremail.runtime.d.ts +10 -1
  8. package/dist_ts/data/coremail.runtime.js +23 -1
  9. package/dist_ts/data/index.d.ts +2 -0
  10. package/dist_ts/data/index.js +3 -1
  11. package/dist_ts/data/mail.d.ts +36 -0
  12. package/dist_ts/data/mail.js +45 -2
  13. package/dist_ts/data/runtimeassignment.d.ts +28 -1
  14. package/dist_ts/data/runtimeassignment.js +22 -3
  15. package/dist_ts/data/runtimesessionpush.d.ts +24 -0
  16. package/dist_ts/data/runtimesessionpush.js +25 -0
  17. package/dist_ts/data/serviceruntime.d.ts +14 -1
  18. package/dist_ts/data/serviceruntime.js +2 -2
  19. package/dist_ts/data/settings.d.ts +21 -0
  20. package/dist_ts/data/settings.js +38 -1
  21. package/dist_ts/data/workloadreplicaidentity.d.ts +101 -0
  22. package/dist_ts/data/workloadreplicaidentity.js +159 -0
  23. package/dist_ts/requests/coremail.d.ts +6 -1
  24. package/package.json +1 -1
  25. package/readme.md +182 -10
  26. package/ts/00_commitinfo_data.ts +1 -1
  27. package/ts/data/clusteringress.ts +14 -0
  28. package/ts/data/coremail.runtime.ts +26 -1
  29. package/ts/data/coremail.ts +42 -1
  30. package/ts/data/index.ts +2 -0
  31. package/ts/data/mail.ts +48 -0
  32. package/ts/data/runtimeassignment.ts +37 -2
  33. package/ts/data/runtimesessionpush.ts +24 -0
  34. package/ts/data/serviceruntime.ts +14 -1
  35. package/ts/data/settings.ts +46 -0
  36. package/ts/data/workloadreplicaidentity.ts +219 -0
  37. 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
- replica: ICoreMailReplicaIdentity;
129
+ /**
130
+ * The authenticated replica: `IWorkloadReplicaIdentity` from CoreMail 34 on, the deprecated
131
+ * `ICoreMailReplicaIdentity` from an earlier CoreMail (`normalizeCoreMailReplicaIdentity`).
132
+ */
133
+ replica: IWorkloadReplicaIdentity | ICoreMailReplicaIdentity;
129
134
  };
130
135
  }
131
136
  export interface IReq_CoreMailPrepareDesiredStateUpload extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_CoreMailPrepareDesiredStateUpload> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "32.61.0",
3
+ "version": "32.63.0",
4
4
  "private": false,
5
5
  "description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
6
6
  "exports": {
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` or `cluster-ingress-environment-conflict`.
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`. `ports-too-many` is a service declaring more target ports than the
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` and `conflict` (the designation, assignment
3511
- or session changed while the registration was judged). `retryAfterMs` is how long the ingress waits
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 carries the exact CoreMail task, rollout generation, and
4162
- image digest so Coreflow can correlate every response with its authoritative
4163
- current task roster. Apply `data.normalizeCoreMailReconciliationStatus` before
4164
- using pending-inbound or composite active-session counts for drain and rotation
4165
- decisions.
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
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.61.0',
6
+ version: '32.63.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
@@ -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,
@@ -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
- replica: ICoreMailReplicaIdentity;
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';