@serve.zone/interfaces 32.40.1 → 32.41.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-10-02 - 32.41.0
4
+
5
+ ### Features
6
+
7
+ - An administrator can export a cluster relay's bearer without an exec into Cloudly's process.
8
+ `exportClusterRelayAuthorization { identity, clusterId, expectedGeneration }` answers
9
+ `IClusterRelayAuthorizationExport`: the sealed relay credential at exactly that generation, the
10
+ bearer it delivered (`authorization`) and the audit `receipt` Cloudly writes before it answers.
11
+ Cloudly mints nothing to answer it; a new bearer is a `rotate` followed by an export.
12
+ `getClusterRelayAuthorizationExports { identity, clusterId }` answers the value-free receipts.
13
+ `data.clusterRelayAuthorizationExportRefusals` names the refusals, and
14
+ `bindClusterRelayAuthorizationExport`, `isClusterRelayAuthorization` and the exact-keyed
15
+ snapshots let a reader check an answer before it writes the bearer, without ever quoting it.
16
+
3
17
  ## 2026-10-02 - 32.40.1
4
18
 
5
19
  ### Fixes
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.40.1',
6
+ version: '32.41.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFNBQVM7SUFDbEIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
@@ -106,6 +106,50 @@ export interface IClusterLegacyRelayCredentialRetirementResult {
106
106
  /** A replay returns the recorded invalidation; nothing is revoked twice. */
107
107
  replayed: boolean;
108
108
  }
109
+ /**
110
+ * What an administrator names to export one cluster's relay bearer: the cluster, at exactly the
111
+ * generation `getClusterRelayCredential` answered. Never a minimum: a rotation that committed
112
+ * after that read is refused rather than answered with a bearer the operator did not ask for.
113
+ */
114
+ export interface IClusterRelayAuthorizationExportRequest {
115
+ clusterId: string;
116
+ expectedGeneration: number;
117
+ }
118
+ /**
119
+ * The audit record of one answered export, which Cloudly writes before it answers the bearer.
120
+ *
121
+ * Append-only and value-free: it names who received which credential's bearer and when, never
122
+ * the bearer or a hash of it. Every answered export writes one, so a retried export is a second
123
+ * receipt rather than a replay: each is a separate handover of the same bearer.
124
+ */
125
+ export interface IClusterRelayAuthorizationExportReceipt {
126
+ id: string;
127
+ clusterId: string;
128
+ /** The exact relay credential whose bearer was answered: identity, generation and digest. */
129
+ credential: IRuntimeAuthorityReference;
130
+ /** The administrator the bearer was answered to, read from the verified credential. */
131
+ actorId: string;
132
+ exportedAt: number;
133
+ }
134
+ /**
135
+ * One cluster's relay bearer, answered to an administrator so it can be handed to the relay
136
+ * without reaching into Cloudly's process.
137
+ *
138
+ * The only member of this contract that carries a bearer. It is the delivered version the
139
+ * credential names, at the requested generation, proved against the hash that generation issued;
140
+ * Cloudly mints nothing to answer it. The reader owns the bytes: it writes them where the relay
141
+ * reads them and never logs, prints or persists them anywhere else.
142
+ */
143
+ export interface IClusterRelayAuthorizationExport {
144
+ /** The relay credential the bearer authenticates as, at exactly the requested generation. */
145
+ credential: IClusterRelayCredential;
146
+ /**
147
+ * The bearer, byte for byte what the relay presents: `clusterRelayToken_`, the credential's
148
+ * 64-hex `id`, `_` and 64 hex characters, with no line terminator.
149
+ */
150
+ authorization: string;
151
+ receipt: IClusterRelayAuthorizationExportReceipt;
152
+ }
109
153
  export declare const clusterRelayCredentialContract: Readonly<{
110
154
  readonly credentialDigestDomain: "serve.zone/cluster-relay-credential";
111
155
  readonly legacyCredentialDigestDomain: "serve.zone/cluster-relay-legacy-credential";
@@ -114,6 +158,12 @@ export declare const clusterRelayCredentialContract: Readonly<{
114
158
  /** Every reason Cloudly answers instead of mutating or retiring a cluster relay authority. */
115
159
  export declare const clusterRelayCredentialRefusals: readonly ["relay-credential-absent", "legacy-credential-absent", "legacy-credential-already-retired", "legacy-retirement-replacement-not-current", "legacy-retirement-registration-not-current", "legacy-retirement-relay-not-live", "conflict"];
116
160
  export type TClusterRelayCredentialRefusal = typeof clusterRelayCredentialRefusals[number];
161
+ /**
162
+ * Every reason Cloudly answers instead of a relay bearer. Its own family, because an export
163
+ * changes no record: it has no `conflict`, and a generation it was not asked for is a mismatch.
164
+ */
165
+ export declare const clusterRelayAuthorizationExportRefusals: readonly ["relay-credential-absent", "relay-credential-generation-mismatch", "relay-authorization-missing", "relay-authorization-ambiguous"];
166
+ export type TClusterRelayAuthorizationExportRefusal = typeof clusterRelayAuthorizationExportRefusals[number];
117
167
  /** Structural copy only; whether the record is the cluster's current one is Cloudly's to decide. */
118
168
  export declare const snapshotClusterRelayCredential: (valueArg: unknown) => IClusterRelayCredential;
119
169
  export declare const computeClusterRelayCredentialDigest: (valueArg: unknown) => Promise<TSha256Digest>;
@@ -138,4 +188,26 @@ export declare const bindClusterLegacyRelayCredentialInvalidation: (invalidation
138
188
  legacyCredential: IClusterLegacyRelayCredential;
139
189
  acceptedRegistration: IClusterRelayAcceptedRegistration;
140
190
  }>;
191
+ /**
192
+ * Whether a value has the exact shape of a relay bearer: `clusterRelayToken_<64 hex>_<64 hex>`.
193
+ *
194
+ * Shape only: whether it authenticates is Cloudly's to decide against the hash it keeps.
195
+ */
196
+ export declare const isClusterRelayAuthorization: (valueArg: unknown) => valueArg is string;
197
+ export declare const snapshotClusterRelayAuthorizationExportRequest: (valueArg: unknown) => IClusterRelayAuthorizationExportRequest;
198
+ export declare const snapshotClusterRelayAuthorizationExportReceipt: (valueArg: unknown) => IClusterRelayAuthorizationExportReceipt;
199
+ /**
200
+ * Structural copy of an export answer. A refusal never quotes the answer, because it carries a
201
+ * bearer: every failure is the contract's one value-free message.
202
+ */
203
+ export declare const snapshotClusterRelayAuthorizationExport: (valueArg: unknown) => IClusterRelayAuthorizationExport;
204
+ /**
205
+ * Joins an export answer to the request it answers, before a reader writes a byte of the bearer.
206
+ *
207
+ * The credential is sealed with its own digest and is the requested cluster's at exactly the
208
+ * requested generation; the bearer names that credential; the receipt names that cluster and that
209
+ * exact credential. Content join only: whether the bearer authenticates is Cloudly's to prove,
210
+ * and Cloudly proves it before it answers.
211
+ */
212
+ export declare const bindClusterRelayAuthorizationExport: (exportArg: unknown, requestArg: unknown) => Promise<IClusterRelayAuthorizationExport>;
141
213
  export {};
@@ -22,6 +22,20 @@ export const clusterRelayCredentialRefusals = Object.freeze([
22
22
  /** An expected generation was not the exact current one, or a mutation ID met a changed body. */
23
23
  'conflict',
24
24
  ]);
25
+ /**
26
+ * Every reason Cloudly answers instead of a relay bearer. Its own family, because an export
27
+ * changes no record: it has no `conflict`, and a generation it was not asked for is a mismatch.
28
+ */
29
+ export const clusterRelayAuthorizationExportRefusals = Object.freeze([
30
+ /** No relay credential is minted for the cluster. */
31
+ 'relay-credential-absent',
32
+ /** The credential is at another generation than the one named, before the read or after it. */
33
+ 'relay-credential-generation-mismatch',
34
+ /** The version the credential delivered is not stored as an active version. */
35
+ 'relay-authorization-missing',
36
+ /** The secret's active version, or its bytes, are not what the credential issued. */
37
+ 'relay-authorization-ambiguous',
38
+ ]);
25
39
  const fail = () => { throw new Error('Cluster relay credential contract rejected.'); };
26
40
  const safe = (operationArg) => {
27
41
  try {
@@ -189,4 +203,71 @@ export const bindClusterLegacyRelayCredentialInvalidation = async (invalidationA
189
203
  return fail();
190
204
  }
191
205
  };
192
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3RlcnJlbGF5Y3JlZGVudGlhbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvY2x1c3RlcnJlbGF5Y3JlZGVudGlhbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQ0wsd0NBQXdDLEVBQ3hDLHdDQUF3QyxHQUN6QyxNQUFNLGNBQWMsQ0FBQztBQUt0QixPQUFPLEVBQ0wsY0FBYyxFQUNkLGNBQWMsRUFDZCxhQUFhLEVBQ2IsWUFBWSxFQUNaLFdBQVcsRUFDWCxTQUFTLEVBQ1QsYUFBYSxFQUNiLGdCQUFnQixFQUNoQixrQkFBa0IsRUFDbEIsY0FBYyxHQUNmLE1BQU0sK0JBQStCLENBQUM7QUE4R3ZDLE1BQU0sQ0FBQyxNQUFNLDhCQUE4QixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDMUQsc0JBQXNCLEVBQUUscUNBQXFDO0lBQzdELDRCQUE0QixFQUFFLDRDQUE0QztJQUMxRSw4QkFBOEIsRUFBRSx5REFBeUQ7Q0FDakYsQ0FBQyxDQUFDO0FBRVosOEZBQThGO0FBQzlGLE1BQU0sQ0FBQyxNQUFNLDhCQUE4QixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDMUQsbUVBQW1FO0lBQ25FLHlCQUF5QjtJQUN6QiwyRUFBMkU7SUFDM0UsMEJBQTBCO0lBQzFCLGlGQUFpRjtJQUNqRixtQ0FBbUM7SUFDbkMsNkZBQTZGO0lBQzdGLDJDQUEyQztJQUMzQyxpR0FBaUc7SUFDakcsNENBQTRDO0lBQzVDLHNGQUFzRjtJQUN0RixrQ0FBa0M7SUFDbEMsaUdBQWlHO0lBQ2pHLFVBQVU7Q0FDRixDQUFDLENBQUM7QUFJWixNQUFNLElBQUksR0FBRyxHQUFVLEVBQUUsR0FBRyxNQUFNLElBQUksS0FBSyxDQUFDLDZDQUE2QyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDOUYsTUFBTSxJQUFJLEdBQUcsQ0FBSSxZQUFxQixFQUFLLEVBQUU7SUFDM0MsSUFBSSxDQUFDO1FBQUMsT0FBTyxZQUFZ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
206
+ const clusterRelayAuthorizationPrefix = 'clusterRelayToken_';
207
+ const clusterRelayAuthorizationPattern = /^clusterRelayToken_[a-f0-9]{64}_[a-f0-9]{64}$/;
208
+ /**
209
+ * Whether a value has the exact shape of a relay bearer: `clusterRelayToken_<64 hex>_<64 hex>`.
210
+ *
211
+ * Shape only: whether it authenticates is Cloudly's to decide against the hash it keeps.
212
+ */
213
+ export const isClusterRelayAuthorization = (valueArg) => typeof valueArg === 'string' && clusterRelayAuthorizationPattern.test(valueArg);
214
+ /** The credential identity a relay bearer names: the 64 hex characters after its prefix. */
215
+ const clusterRelayAuthorizationCredentialId = (authorizationArg) => authorizationArg.slice(clusterRelayAuthorizationPrefix.length, clusterRelayAuthorizationPrefix.length + 64);
216
+ export const snapshotClusterRelayAuthorizationExportRequest = (valueArg) => safe(() => {
217
+ const value = networkRecord(captureNetwork(valueArg), ['clusterId', 'expectedGeneration']);
218
+ return {
219
+ clusterId: networkId(value.clusterId),
220
+ expectedGeneration: networkCounter(value.expectedGeneration),
221
+ };
222
+ });
223
+ export const snapshotClusterRelayAuthorizationExportReceipt = (valueArg) => safe(() => {
224
+ const value = networkRecord(captureNetwork(valueArg), [
225
+ 'id', 'clusterId', 'credential', 'actorId', 'exportedAt',
226
+ ]);
227
+ return {
228
+ id: networkId(value.id),
229
+ clusterId: networkId(value.clusterId),
230
+ credential: networkReference(value.credential),
231
+ actorId: networkId(value.actorId),
232
+ exportedAt: networkCounter(value.exportedAt),
233
+ };
234
+ });
235
+ /**
236
+ * Structural copy of an export answer. A refusal never quotes the answer, because it carries a
237
+ * bearer: every failure is the contract's one value-free message.
238
+ */
239
+ export const snapshotClusterRelayAuthorizationExport = (valueArg) => safe(() => {
240
+ const value = networkRecord(captureNetwork(valueArg), ['credential', 'authorization', 'receipt']);
241
+ requireNetwork(isClusterRelayAuthorization(value.authorization));
242
+ return {
243
+ credential: snapshotClusterRelayCredential(value.credential),
244
+ authorization: value.authorization,
245
+ receipt: snapshotClusterRelayAuthorizationExportReceipt(value.receipt),
246
+ };
247
+ });
248
+ /**
249
+ * Joins an export answer to the request it answers, before a reader writes a byte of the bearer.
250
+ *
251
+ * The credential is sealed with its own digest and is the requested cluster's at exactly the
252
+ * requested generation; the bearer names that credential; the receipt names that cluster and that
253
+ * exact credential. Content join only: whether the bearer authenticates is Cloudly's to prove,
254
+ * and Cloudly proves it before it answers.
255
+ */
256
+ export const bindClusterRelayAuthorizationExport = async (exportArg, requestArg) => {
257
+ try {
258
+ const request = snapshotClusterRelayAuthorizationExportRequest(requestArg);
259
+ const answer = snapshotClusterRelayAuthorizationExport(exportArg);
260
+ if ((await validateClusterRelayCredential(answer.credential)).length)
261
+ fail();
262
+ requireNetwork(answer.credential.clusterId === request.clusterId
263
+ && answer.credential.generation === request.expectedGeneration
264
+ && clusterRelayAuthorizationCredentialId(answer.authorization) === answer.credential.id
265
+ && answer.receipt.clusterId === request.clusterId
266
+ && networkEqual(answer.receipt.credential, networkReferenceOf(answer.credential)));
267
+ return answer;
268
+ }
269
+ catch {
270
+ return fail();
271
+ }
272
+ };
273
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3RlcnJlbGF5Y3JlZGVudGlhbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvY2x1c3RlcnJlbGF5Y3JlZGVudGlhbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQ0wsd0NBQXdDLEVBQ3hDLHdDQUF3QyxHQUN6QyxNQUFNLGNBQWMsQ0FBQztBQUt0QixPQUFPLEVBQ0wsY0FBYyxFQUNkLGNBQWMsRUFDZCxhQUFhLEVBQ2IsWUFBWSxFQUNaLFdBQVcsRUFDWCxTQUFTLEVBQ1QsYUFBYSxFQUNiLGdCQUFnQixFQUNoQixrQkFBa0IsRUFDbEIsY0FBYyxHQUNmLE1BQU0sK0JBQStCLENBQUM7QUE2SnZDLE1BQU0sQ0FBQyxNQUFNLDhCQUE4QixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDMUQsc0JBQXNCLEVBQUUscUNBQXFDO0lBQzdELDRCQUE0QixFQUFFLDRDQUE0QztJQUMxRSw4QkFBOEIsRUFBRSx5REFBeUQ7Q0FDakYsQ0FBQyxDQUFDO0FBRVosOEZBQThGO0FBQzlGLE1BQU0sQ0FBQyxNQUFNLDhCQUE4QixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDMUQsbUVBQW1FO0lBQ25FLHlCQUF5QjtJQUN6QiwyRUFBMkU7SUFDM0UsMEJBQTBCO0lBQzFCLGlGQUFpRjtJQUNqRixtQ0FBbUM7SUFDbkMsNkZBQTZGO0lBQzdGLDJDQUEyQztJQUMzQyxpR0FBaUc7SUFDakcsNENBQTRDO0lBQzVDLHNGQUFzRjtJQUN0RixrQ0FBa0M7SUFDbEMsaUdBQWlHO0lBQ2pHLFVBQVU7Q0FDRixDQUFDLENBQUM7QUFJWjs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSx1Q0FBdUMsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ25FLHFEQUFxRDtJQUNyRCx5QkFBeUI7SUFDekIsK0ZBQStGO0lBQy9GLHNDQUFzQztJQUN0QywrRUFBK0U7SUFDL0UsNkJBQTZCO0lBQzdCLHFGQUFxRjtJQUNy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
@@ -170,6 +170,40 @@ export interface IReq_Admin_Cloudly_RetireLegacyClusterRelayCredential extends p
170
170
  };
171
171
  response: relayCredentialInterfaces.IClusterLegacyRelayCredentialRetirementResult;
172
172
  }
173
+ /**
174
+ * Answers one cluster's relay bearer to an administrator, so it reaches the relay without an exec
175
+ * into Cloudly's process.
176
+ *
177
+ * Cloudly mints nothing here: a bearer is issued by `mutateClusterRelayCredential` and delivered as
178
+ * the encrypted version the credential names, and this reads exactly that version at exactly
179
+ * `expectedGeneration`, proved against the hash that generation issued. A new bearer is a
180
+ * rotation followed by an export at the new generation. Only the verified credential of a freshly
181
+ * authenticated administrator is read, as for a credential mutation, and Cloudly writes the
182
+ * `receipt` before it answers, so no bearer leaves without an audit record. The bearer is in this
183
+ * answer and nowhere else: never in a log, a receipt or another request. Cloudly refuses with one
184
+ * of `data.clusterRelayAuthorizationExportRefusals`.
185
+ */
186
+ export interface IReq_Admin_Cloudly_ExportClusterRelayAuthorization extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_Cloudly_ExportClusterRelayAuthorization> {
187
+ method: 'exportClusterRelayAuthorization';
188
+ request: relayCredentialInterfaces.IClusterRelayAuthorizationExportRequest & {
189
+ identity: userInterfaces.IIdentityCredential;
190
+ };
191
+ response: relayCredentialInterfaces.IClusterRelayAuthorizationExport;
192
+ }
193
+ /**
194
+ * The audit trail of one cluster's relay bearer exports: every receipt, oldest first. Value-free,
195
+ * like the receipts themselves; empty while no bearer was ever exported.
196
+ */
197
+ export interface IReq_Admin_Cloudly_GetClusterRelayAuthorizationExports extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_Cloudly_GetClusterRelayAuthorizationExports> {
198
+ method: 'getClusterRelayAuthorizationExports';
199
+ request: {
200
+ identity: userInterfaces.IIdentityCredential;
201
+ clusterId: string;
202
+ };
203
+ response: {
204
+ receipts: relayCredentialInterfaces.IClusterRelayAuthorizationExportReceipt[];
205
+ };
206
+ }
173
207
  /**
174
208
  * Cloudly hands the cluster relay the DNS lease renewals it has signed ahead of time for one node.
175
209
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "32.40.1",
3
+ "version": "32.41.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
@@ -2723,8 +2723,9 @@ cluster authority every cluster-side component used before it. `IClusterRelayCre
2723
2723
  metadata: the cluster, a `generation` that advances by one per accepted mint or rotation, the
2724
2724
  machine `userId` it authenticates as, and `delivery`, the `ISecretVersionReference` of the
2725
2725
  immutable encrypted version the bearer was delivered as. No record, receipt, request or response
2726
- of this contract carries a bearer, a hash or key material, and every reader is exact-keyed, so a
2727
- peer that adds a secret-looking member is refused rather than copied.
2726
+ of this contract carries a bearer, a hash or key material, except the one answer that exists to
2727
+ hand the bearer over (see the export below), and every reader is exact-keyed, so a peer that adds
2728
+ a secret-looking member is refused rather than copied.
2728
2729
 
2729
2730
  The credential authenticates the cluster's whole control-plane session, exactly what the shared
2730
2731
  authority authenticated, so a relay changes one credential and keeps every call it makes. The gain
@@ -2776,7 +2777,36 @@ it names or that record's revoked successor — and to a registration whose `cre
2776
2777
  invalidation's `replacement`. It is a content check; dereferencing each record as current and
2777
2778
  requiring the live socket remain Cloudly's.
2778
2779
 
2779
- All three requests read only the verified `IIdentityCredential`, never an identity member a caller
2780
+ `exportClusterRelayAuthorization { identity, clusterId, expectedGeneration }` answers the bearer
2781
+ itself, so an operator hands it to the relay without an exec into Cloudly's process. Cloudly mints
2782
+ nothing to answer it: the bearer is the encrypted version the credential's `delivery` names, read
2783
+ at exactly `expectedGeneration` (never a minimum) and proved against the hash that generation
2784
+ issued, so a rotation committing meanwhile is refused rather than answered. A new bearer is a
2785
+ `rotate` followed by an export at the new generation. It requires a freshly authenticated
2786
+ administrator, exactly as a credential mutation does. `IClusterRelayAuthorizationExport` carries
2787
+ the sealed `credential` at that generation, the `authorization` (the bearer, byte for byte what the
2788
+ relay presents: `clusterRelayToken_<credential id>_<64 hex>`, no line terminator) and the
2789
+ `receipt`. It is the only answer of this contract that carries a bearer; Cloudly never logs it, and
2790
+ its reader writes it where the relay reads it and nowhere else.
2791
+
2792
+ Every answered export is audited: Cloudly writes `IClusterRelayAuthorizationExportReceipt`
2793
+ (`id`, `clusterId`, the exact `credential` reference, the administrator's `actorId`,
2794
+ `exportedAt`) before it answers, so no bearer leaves without a record, and a retried export is a
2795
+ second receipt rather than a replay. A receipt is value-free: no bearer, no hash.
2796
+ `getClusterRelayAuthorizationExports { identity, clusterId }` answers a cluster's receipts, oldest
2797
+ first. Refusals are `data.clusterRelayAuthorizationExportRefusals`
2798
+ (`data.TClusterRelayAuthorizationExportRefusal`): `relay-credential-absent`,
2799
+ `relay-credential-generation-mismatch`, `relay-authorization-missing` and
2800
+ `relay-authorization-ambiguous`. A reader checks an answer with
2801
+ `bindClusterRelayAuthorizationExport(answer, { clusterId, expectedGeneration })` before it writes a
2802
+ byte: the credential's digest holds, it is the requested cluster's at the requested generation, the
2803
+ bearer names that credential, and the receipt names that exact credential. Every refusal is the
2804
+ contract's one value-free message and never quotes the answer. `isClusterRelayAuthorization`
2805
+ states the bearer's shape, and `snapshotClusterRelayAuthorizationExportRequest`,
2806
+ `snapshotClusterRelayAuthorizationExportReceipt` and `snapshotClusterRelayAuthorizationExport` are
2807
+ the exact-keyed readers.
2808
+
2809
+ All five requests read only the verified `IIdentityCredential`, never an identity member a caller
2780
2810
  states about itself.
2781
2811
 
2782
2812
  ### Spark Legacy Runtime Retirement
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.40.1',
6
+ version: '32.41.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
@@ -128,6 +128,53 @@ export interface IClusterLegacyRelayCredentialRetirementResult {
128
128
  replayed: boolean;
129
129
  }
130
130
 
131
+ /**
132
+ * What an administrator names to export one cluster's relay bearer: the cluster, at exactly the
133
+ * generation `getClusterRelayCredential` answered. Never a minimum: a rotation that committed
134
+ * after that read is refused rather than answered with a bearer the operator did not ask for.
135
+ */
136
+ export interface IClusterRelayAuthorizationExportRequest {
137
+ clusterId: string;
138
+ expectedGeneration: number;
139
+ }
140
+
141
+ /**
142
+ * The audit record of one answered export, which Cloudly writes before it answers the bearer.
143
+ *
144
+ * Append-only and value-free: it names who received which credential's bearer and when, never
145
+ * the bearer or a hash of it. Every answered export writes one, so a retried export is a second
146
+ * receipt rather than a replay: each is a separate handover of the same bearer.
147
+ */
148
+ export interface IClusterRelayAuthorizationExportReceipt {
149
+ id: string;
150
+ clusterId: string;
151
+ /** The exact relay credential whose bearer was answered: identity, generation and digest. */
152
+ credential: IRuntimeAuthorityReference;
153
+ /** The administrator the bearer was answered to, read from the verified credential. */
154
+ actorId: string;
155
+ exportedAt: number;
156
+ }
157
+
158
+ /**
159
+ * One cluster's relay bearer, answered to an administrator so it can be handed to the relay
160
+ * without reaching into Cloudly's process.
161
+ *
162
+ * The only member of this contract that carries a bearer. It is the delivered version the
163
+ * credential names, at the requested generation, proved against the hash that generation issued;
164
+ * Cloudly mints nothing to answer it. The reader owns the bytes: it writes them where the relay
165
+ * reads them and never logs, prints or persists them anywhere else.
166
+ */
167
+ export interface IClusterRelayAuthorizationExport {
168
+ /** The relay credential the bearer authenticates as, at exactly the requested generation. */
169
+ credential: IClusterRelayCredential;
170
+ /**
171
+ * The bearer, byte for byte what the relay presents: `clusterRelayToken_`, the credential's
172
+ * 64-hex `id`, `_` and 64 hex characters, with no line terminator.
173
+ */
174
+ authorization: string;
175
+ receipt: IClusterRelayAuthorizationExportReceipt;
176
+ }
177
+
131
178
  export const clusterRelayCredentialContract = Object.freeze({
132
179
  credentialDigestDomain: 'serve.zone/cluster-relay-credential',
133
180
  legacyCredentialDigestDomain: 'serve.zone/cluster-relay-legacy-credential',
@@ -154,6 +201,24 @@ export const clusterRelayCredentialRefusals = Object.freeze([
154
201
 
155
202
  export type TClusterRelayCredentialRefusal = typeof clusterRelayCredentialRefusals[number];
156
203
 
204
+ /**
205
+ * Every reason Cloudly answers instead of a relay bearer. Its own family, because an export
206
+ * changes no record: it has no `conflict`, and a generation it was not asked for is a mismatch.
207
+ */
208
+ export const clusterRelayAuthorizationExportRefusals = Object.freeze([
209
+ /** No relay credential is minted for the cluster. */
210
+ 'relay-credential-absent',
211
+ /** The credential is at another generation than the one named, before the read or after it. */
212
+ 'relay-credential-generation-mismatch',
213
+ /** The version the credential delivered is not stored as an active version. */
214
+ 'relay-authorization-missing',
215
+ /** The secret's active version, or its bytes, are not what the credential issued. */
216
+ 'relay-authorization-ambiguous',
217
+ ] as const);
218
+
219
+ export type TClusterRelayAuthorizationExportRefusal =
220
+ typeof clusterRelayAuthorizationExportRefusals[number];
221
+
157
222
  const fail = (): never => { throw new Error('Cluster relay credential contract rejected.'); };
158
223
  const safe = <T>(operationArg: () => T): T => {
159
224
  try { return operationArg(); } catch { return fail(); }
@@ -359,3 +424,87 @@ export const bindClusterLegacyRelayCredentialInvalidation = async (
359
424
  return { invalidation, legacyCredential, acceptedRegistration };
360
425
  } catch { return fail(); }
361
426
  };
427
+
428
+ const clusterRelayAuthorizationPrefix = 'clusterRelayToken_';
429
+ const clusterRelayAuthorizationPattern = /^clusterRelayToken_[a-f0-9]{64}_[a-f0-9]{64}$/;
430
+
431
+ /**
432
+ * Whether a value has the exact shape of a relay bearer: `clusterRelayToken_<64 hex>_<64 hex>`.
433
+ *
434
+ * Shape only: whether it authenticates is Cloudly's to decide against the hash it keeps.
435
+ */
436
+ export const isClusterRelayAuthorization = (valueArg: unknown): valueArg is string =>
437
+ typeof valueArg === 'string' && clusterRelayAuthorizationPattern.test(valueArg);
438
+
439
+ /** The credential identity a relay bearer names: the 64 hex characters after its prefix. */
440
+ const clusterRelayAuthorizationCredentialId = (authorizationArg: string): string =>
441
+ authorizationArg.slice(
442
+ clusterRelayAuthorizationPrefix.length,
443
+ clusterRelayAuthorizationPrefix.length + 64,
444
+ );
445
+
446
+ export const snapshotClusterRelayAuthorizationExportRequest = (
447
+ valueArg: unknown,
448
+ ): IClusterRelayAuthorizationExportRequest => safe(() => {
449
+ const value = networkRecord(captureNetwork(valueArg), ['clusterId', 'expectedGeneration']);
450
+ return {
451
+ clusterId: networkId(value.clusterId),
452
+ expectedGeneration: networkCounter(value.expectedGeneration),
453
+ };
454
+ });
455
+
456
+ export const snapshotClusterRelayAuthorizationExportReceipt = (
457
+ valueArg: unknown,
458
+ ): IClusterRelayAuthorizationExportReceipt => safe(() => {
459
+ const value = networkRecord(captureNetwork(valueArg), [
460
+ 'id', 'clusterId', 'credential', 'actorId', 'exportedAt',
461
+ ]);
462
+ return {
463
+ id: networkId(value.id),
464
+ clusterId: networkId(value.clusterId),
465
+ credential: networkReference(value.credential),
466
+ actorId: networkId(value.actorId),
467
+ exportedAt: networkCounter(value.exportedAt),
468
+ };
469
+ });
470
+
471
+ /**
472
+ * Structural copy of an export answer. A refusal never quotes the answer, because it carries a
473
+ * bearer: every failure is the contract's one value-free message.
474
+ */
475
+ export const snapshotClusterRelayAuthorizationExport = (
476
+ valueArg: unknown,
477
+ ): IClusterRelayAuthorizationExport => safe(() => {
478
+ const value = networkRecord(captureNetwork(valueArg), ['credential', 'authorization', 'receipt']);
479
+ requireNetwork(isClusterRelayAuthorization(value.authorization));
480
+ return {
481
+ credential: snapshotClusterRelayCredential(value.credential),
482
+ authorization: value.authorization,
483
+ receipt: snapshotClusterRelayAuthorizationExportReceipt(value.receipt),
484
+ };
485
+ });
486
+
487
+ /**
488
+ * Joins an export answer to the request it answers, before a reader writes a byte of the bearer.
489
+ *
490
+ * The credential is sealed with its own digest and is the requested cluster's at exactly the
491
+ * requested generation; the bearer names that credential; the receipt names that cluster and that
492
+ * exact credential. Content join only: whether the bearer authenticates is Cloudly's to prove,
493
+ * and Cloudly proves it before it answers.
494
+ */
495
+ export const bindClusterRelayAuthorizationExport = async (
496
+ exportArg: unknown,
497
+ requestArg: unknown,
498
+ ): Promise<IClusterRelayAuthorizationExport> => {
499
+ try {
500
+ const request = snapshotClusterRelayAuthorizationExportRequest(requestArg);
501
+ const answer = snapshotClusterRelayAuthorizationExport(exportArg);
502
+ if ((await validateClusterRelayCredential(answer.credential)).length) fail();
503
+ requireNetwork(answer.credential.clusterId === request.clusterId
504
+ && answer.credential.generation === request.expectedGeneration
505
+ && clusterRelayAuthorizationCredentialId(answer.authorization) === answer.credential.id
506
+ && answer.receipt.clusterId === request.clusterId
507
+ && networkEqual(answer.receipt.credential, networkReferenceOf(answer.credential)));
508
+ return answer;
509
+ } catch { return fail(); }
510
+ };
@@ -219,6 +219,50 @@ extends plugins.typedrequestInterfaces.implementsTR<
219
219
  response: relayCredentialInterfaces.IClusterLegacyRelayCredentialRetirementResult;
220
220
  }
221
221
 
222
+ /**
223
+ * Answers one cluster's relay bearer to an administrator, so it reaches the relay without an exec
224
+ * into Cloudly's process.
225
+ *
226
+ * Cloudly mints nothing here: a bearer is issued by `mutateClusterRelayCredential` and delivered as
227
+ * the encrypted version the credential names, and this reads exactly that version at exactly
228
+ * `expectedGeneration`, proved against the hash that generation issued. A new bearer is a
229
+ * rotation followed by an export at the new generation. Only the verified credential of a freshly
230
+ * authenticated administrator is read, as for a credential mutation, and Cloudly writes the
231
+ * `receipt` before it answers, so no bearer leaves without an audit record. The bearer is in this
232
+ * answer and nowhere else: never in a log, a receipt or another request. Cloudly refuses with one
233
+ * of `data.clusterRelayAuthorizationExportRefusals`.
234
+ */
235
+ export interface IReq_Admin_Cloudly_ExportClusterRelayAuthorization
236
+ extends plugins.typedrequestInterfaces.implementsTR<
237
+ plugins.typedrequestInterfaces.ITypedRequest,
238
+ IReq_Admin_Cloudly_ExportClusterRelayAuthorization
239
+ > {
240
+ method: 'exportClusterRelayAuthorization';
241
+ request: relayCredentialInterfaces.IClusterRelayAuthorizationExportRequest & {
242
+ identity: userInterfaces.IIdentityCredential;
243
+ };
244
+ response: relayCredentialInterfaces.IClusterRelayAuthorizationExport;
245
+ }
246
+
247
+ /**
248
+ * The audit trail of one cluster's relay bearer exports: every receipt, oldest first. Value-free,
249
+ * like the receipts themselves; empty while no bearer was ever exported.
250
+ */
251
+ export interface IReq_Admin_Cloudly_GetClusterRelayAuthorizationExports
252
+ extends plugins.typedrequestInterfaces.implementsTR<
253
+ plugins.typedrequestInterfaces.ITypedRequest,
254
+ IReq_Admin_Cloudly_GetClusterRelayAuthorizationExports
255
+ > {
256
+ method: 'getClusterRelayAuthorizationExports';
257
+ request: {
258
+ identity: userInterfaces.IIdentityCredential;
259
+ clusterId: string;
260
+ };
261
+ response: {
262
+ receipts: relayCredentialInterfaces.IClusterRelayAuthorizationExportReceipt[];
263
+ };
264
+ }
265
+
222
266
  /**
223
267
  * Cloudly hands the cluster relay the DNS lease renewals it has signed ahead of time for one node.
224
268
  *