@serve.zone/interfaces 32.21.0 → 32.23.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 (56) hide show
  1. package/changelog.md +91 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/data/clusteringress.d.ts +58 -0
  4. package/dist_ts/data/clusteringress.js +96 -2
  5. package/dist_ts/data/clusteringressdesignation.d.ts +64 -0
  6. package/dist_ts/data/clusteringressdesignation.js +84 -0
  7. package/dist_ts/data/corestorelegacyadoption.d.ts +178 -0
  8. package/dist_ts/data/corestorelegacyadoption.js +289 -0
  9. package/dist_ts/data/index.d.ts +6 -0
  10. package/dist_ts/data/index.js +6 -1
  11. package/dist_ts/data/networkprojection.d.ts +18 -0
  12. package/dist_ts/data/networkprojection.js +31 -2
  13. package/dist_ts/data/networkprotection.d.ts +13 -0
  14. package/dist_ts/data/networkprotection.js +8 -2
  15. package/dist_ts/data/networkworkloadingress.d.ts +61 -0
  16. package/dist_ts/data/networkworkloadingress.js +54 -0
  17. package/dist_ts/data/runtimelocalstorageclaim.d.ts +124 -0
  18. package/dist_ts/data/runtimelocalstorageclaim.js +167 -0
  19. package/dist_ts/data/service.d.ts +15 -1
  20. package/dist_ts/data/service.js +1 -1
  21. package/dist_ts/data/serviceruntime.d.ts +15 -3
  22. package/dist_ts/data/serviceruntime.js +7 -2
  23. package/dist_ts/data/servicevolumeinitialization.d.ts +91 -0
  24. package/dist_ts/data/servicevolumeinitialization.js +93 -0
  25. package/dist_ts/platform/types.d.ts +45 -0
  26. package/dist_ts/platform/types.js +27 -2
  27. package/dist_ts/private/storagecontract.d.ts +1 -0
  28. package/dist_ts/private/storagecontract.js +2 -1
  29. package/dist_ts/requests/clusteringress.d.ts +52 -0
  30. package/dist_ts/requests/clusteringress.js +1 -1
  31. package/dist_ts/requests/corestore.d.ts +60 -0
  32. package/dist_ts/requests/corestore.js +1 -1
  33. package/dist_ts/requests/platform.d.ts +6 -1
  34. package/dist_ts/requests/runtimesession.d.ts +12 -0
  35. package/dist_ts/requests/service.d.ts +23 -0
  36. package/package.json +1 -1
  37. package/readme.md +262 -2
  38. package/ts/00_commitinfo_data.ts +1 -1
  39. package/ts/data/clusteringress.ts +136 -1
  40. package/ts/data/clusteringressdesignation.ts +141 -0
  41. package/ts/data/corestorelegacyadoption.ts +471 -0
  42. package/ts/data/index.ts +7 -0
  43. package/ts/data/networkprojection.ts +49 -1
  44. package/ts/data/networkprotection.ts +20 -1
  45. package/ts/data/networkworkloadingress.ts +107 -0
  46. package/ts/data/runtimelocalstorageclaim.ts +275 -0
  47. package/ts/data/service.ts +16 -0
  48. package/ts/data/serviceruntime.ts +19 -2
  49. package/ts/data/servicevolumeinitialization.ts +159 -0
  50. package/ts/platform/types.ts +75 -0
  51. package/ts/private/storagecontract.ts +1 -0
  52. package/ts/requests/clusteringress.ts +65 -0
  53. package/ts/requests/corestore.ts +79 -0
  54. package/ts/requests/platform.ts +6 -0
  55. package/ts/requests/runtimesession.ts +16 -0
  56. package/ts/requests/service.ts +28 -0
@@ -0,0 +1,159 @@
1
+ import type { IRuntimeLocalStorageOwnership } from './runtimelocalstorageclaim.js';
2
+ import { runtimeLocalStorageClaimContract } from './runtimelocalstorageclaim.js';
3
+ import type { IServiceRuntimeProfile } from './serviceruntime.js';
4
+ import { hasExactKeys, isPlainRecord } from '../private/dataproperties.js';
5
+ import { isRuntimeIdentityNumber } from '../private/runtimepolicy.js';
6
+
7
+ /**
8
+ * Where an `import` volume's bytes come from.
9
+ *
10
+ * `docker-volume` is a Docker volume on the host the claim's node runs on, named exactly as Docker
11
+ * lists it; a Corestore-driver volume is named the same way. The operator copies its tree into the
12
+ * node's import directory; the node never reads this reference, and the local storage claim does
13
+ * not carry it.
14
+ */
15
+ export type TServiceVolumeImportSource = { kind: 'docker-volume'; volumeName: string };
16
+
17
+ /**
18
+ * The owner and permission bits an `empty` volume's root directory gets.
19
+ *
20
+ * `explicit` states the numeric owner. `run-as` takes the owner from the runtime spec's
21
+ * `profile.runAs` (user and group) when the claim is first issued; the mode is always stated,
22
+ * because no mode is right for every image.
23
+ */
24
+ export type TServiceVolumeOwnership =
25
+ | { kind: 'explicit'; uid: number; gid: number; mode: number }
26
+ | { kind: 'run-as'; mode: number };
27
+
28
+ /**
29
+ * How a service volume's node-local claim starts: `empty` creates a fresh root with the stated
30
+ * ownership, `import` waits for the bytes of an existing volume.
31
+ */
32
+ export type TServiceVolumeInitialization =
33
+ | { kind: 'empty'; ownership: TServiceVolumeOwnership }
34
+ | { kind: 'import'; source: TServiceVolumeImportSource };
35
+
36
+ /**
37
+ * What a volume's initialization derives for a local storage claim, or why it derives nothing.
38
+ *
39
+ * `volume-initialization-unknown` is a volume with no initialization: Cloudly never assumes
40
+ * `empty`, because an empty volume in front of a service whose data sits in a legacy volume would
41
+ * start that service without its data. `volume-ownership-unknown` is a `run-as` ownership while
42
+ * the runtime spec runs the image's own user (`runAs: null`), whose numeric identity Cloudly does
43
+ * not know.
44
+ */
45
+ export type TServiceVolumeClaimInitialization =
46
+ | { status: 'derived'; initialization: 'empty'; initialOwnership: IRuntimeLocalStorageOwnership }
47
+ | { status: 'derived'; initialization: 'import'; initialOwnership: null }
48
+ | { status: 'refused'; reason: 'volume-initialization-unknown' | 'volume-ownership-unknown' };
49
+
50
+ /**
51
+ * Why `setServiceVolumeInitialization` wrote nothing. `volume-not-found` names no declared volume
52
+ * at that mount path, `conflict` a stored initialization other than `expected`,
53
+ * `volume-initialization-invalid` a value this contract refuses, and
54
+ * `volume-initialization-bound` a change while a bound local storage claim of the volume exists:
55
+ * a claim keeps the initialization and ownership it was issued with for its whole life.
56
+ */
57
+ export type TServiceVolumeInitializationRefusal =
58
+ | 'volume-not-found'
59
+ | 'conflict'
60
+ | 'volume-initialization-invalid'
61
+ | 'volume-initialization-bound';
62
+
63
+ /** The named refusal of `setServiceVolumeInitialization`, sent as `TypedResponseError.errorData`. */
64
+ export interface IServiceVolumeInitializationErrorData {
65
+ code: TServiceVolumeInitializationRefusal;
66
+ retryable: false;
67
+ }
68
+
69
+ export const serviceVolumeInitializationContract = Object.freeze({
70
+ /** Every permission bit an ownership may state, the local storage claim's own bound. */
71
+ directoryModeBits: runtimeLocalStorageClaimContract.directoryModeBits,
72
+ maximumDockerVolumeNameLength: 255,
73
+ } as const);
74
+
75
+ /** Docker's own volume name grammar, `[a-zA-Z0-9][a-zA-Z0-9_.-]`, which needs two characters. */
76
+ const dockerVolumeNamePattern = /^[A-Za-z0-9][A-Za-z0-9_.-]+$/;
77
+
78
+ const isDirectoryMode = (valueArg: unknown): valueArg is number => Number.isSafeInteger(valueArg)
79
+ && (valueArg as number) >= 0
80
+ && ((valueArg as number) & ~serviceVolumeInitializationContract.directoryModeBits) === 0;
81
+
82
+ const validateOwnership = (valueArg: unknown, pathArg: string): string[] => {
83
+ if (!isPlainRecord(valueArg)) return [`${pathArg} must use its exact schema`];
84
+ if (valueArg.kind === 'explicit') {
85
+ if (!hasExactKeys(valueArg, ['kind', 'uid', 'gid', 'mode'])) return [`${pathArg} must use its exact schema`];
86
+ const errors: string[] = [];
87
+ if (!isRuntimeIdentityNumber(valueArg.uid) || !isRuntimeIdentityNumber(valueArg.gid)) {
88
+ errors.push(`${pathArg} uid and gid must be runtime identity numbers`);
89
+ }
90
+ if (!isDirectoryMode(valueArg.mode)) errors.push(`${pathArg} mode must hold directory permission bits only`);
91
+ return errors;
92
+ }
93
+ if (valueArg.kind === 'run-as') {
94
+ if (!hasExactKeys(valueArg, ['kind', 'mode'])) return [`${pathArg} must use its exact schema`];
95
+ return isDirectoryMode(valueArg.mode) ? [] : [`${pathArg} mode must hold directory permission bits only`];
96
+ }
97
+ return [`${pathArg} kind must be explicit or run-as`];
98
+ };
99
+
100
+ const validateImportSource = (valueArg: unknown, pathArg: string): string[] => {
101
+ if (!isPlainRecord(valueArg) || valueArg.kind !== 'docker-volume'
102
+ || !hasExactKeys(valueArg, ['kind', 'volumeName'])) {
103
+ return [`${pathArg} must be an exact docker-volume source`];
104
+ }
105
+ return typeof valueArg.volumeName === 'string'
106
+ && valueArg.volumeName.length <= serviceVolumeInitializationContract.maximumDockerVolumeNameLength
107
+ && dockerVolumeNamePattern.test(valueArg.volumeName)
108
+ ? []
109
+ : [`${pathArg} volumeName must be a Docker volume name`];
110
+ };
111
+
112
+ /** Validates one volume initialization and names every reason it is refused. */
113
+ export const validateServiceVolumeInitialization = (
114
+ valueArg: unknown,
115
+ pathArg = 'service volume initialization',
116
+ ): string[] => {
117
+ if (!isPlainRecord(valueArg)) return [`${pathArg} must use its exact schema`];
118
+ if (valueArg.kind === 'empty') {
119
+ if (!hasExactKeys(valueArg, ['kind', 'ownership'])) return [`${pathArg} must use its exact schema`];
120
+ return validateOwnership(valueArg.ownership, `${pathArg} ownership`);
121
+ }
122
+ if (valueArg.kind === 'import') {
123
+ if (!hasExactKeys(valueArg, ['kind', 'source'])) return [`${pathArg} must use its exact schema`];
124
+ return validateImportSource(valueArg.source, `${pathArg} source`);
125
+ }
126
+ return [`${pathArg} kind must be empty or import`];
127
+ };
128
+
129
+ /**
130
+ * The claim fields a volume's initialization derives under a runtime spec's `runAs`.
131
+ *
132
+ * Absent initialization refuses as `volume-initialization-unknown`; a `run-as` ownership under
133
+ * `runAs: null` refuses as `volume-ownership-unknown`. Nothing is defaulted. The answer is for the
134
+ * first generation of a claim only: later generations carry that claim's own initialization and
135
+ * ownership, whatever `runAs` says by then. A malformed initialization or `runAs` throws.
136
+ */
137
+ export const deriveServiceVolumeClaimInitialization = (
138
+ initializationArg: TServiceVolumeInitialization | undefined,
139
+ runAsArg: IServiceRuntimeProfile['runAs'],
140
+ ): TServiceVolumeClaimInitialization => {
141
+ if (initializationArg === undefined) return { status: 'refused', reason: 'volume-initialization-unknown' };
142
+ const errors = validateServiceVolumeInitialization(initializationArg);
143
+ if (errors.length > 0) throw new Error(errors[0]);
144
+ if (runAsArg !== null && !(isPlainRecord(runAsArg) && hasExactKeys(runAsArg, ['user', 'group'])
145
+ && isRuntimeIdentityNumber(runAsArg.user) && isRuntimeIdentityNumber(runAsArg.group))) {
146
+ throw new Error('runAs must be null or an exact user and group');
147
+ }
148
+ if (initializationArg.kind === 'import') {
149
+ return { status: 'derived', initialization: 'import', initialOwnership: null };
150
+ }
151
+ const ownership = initializationArg.ownership;
152
+ if (ownership.kind === 'explicit') {
153
+ return { status: 'derived', initialization: 'empty',
154
+ initialOwnership: { uid: ownership.uid, gid: ownership.gid, mode: ownership.mode } };
155
+ }
156
+ if (runAsArg === null) return { status: 'refused', reason: 'volume-ownership-unknown' };
157
+ return { status: 'derived', initialization: 'empty',
158
+ initialOwnership: { uid: runAsArg.user, gid: runAsArg.group, mode: ownership.mode } };
159
+ };
@@ -16,6 +16,60 @@ export type TPlatformCapability =
16
16
  export type TPlatformAccessMode = 'rpc' | 'binding' | 'sidecar' | 'internal';
17
17
  export type TPlatformBindingStatus = 'requested' | 'provisioning' | 'ready' | 'degraded' | 'failed' | 'disabled';
18
18
  export type TPlatformDesiredState = 'enabled' | 'disabled';
19
+
20
+ export const platformBindingStatusReasons = [
21
+ 'legacy-adoption-pending',
22
+ 'legacy-adoption-conflict',
23
+ 'legacy-adoption-mismatch',
24
+ ] as const;
25
+
26
+ /**
27
+ * Why a binding holds the status it reports, where the status alone does not say.
28
+ *
29
+ * All three come from Corestore's refusal of a credential binding beside a legacy
30
+ * `/resources/provision` resource (`code` in its 409 body, read with
31
+ * `readCorestoreLegacyAdoptionRefusal`):
32
+ *
33
+ * - `legacy-adoption-pending` (status `provisioning`): the service owns a legacy resource of this
34
+ * capability that no offline adoption authorizes yet. It clears when the operator runs
35
+ * Corestore's adoption with the plan Cloudly produced.
36
+ * - `legacy-adoption-conflict` (status `failed`): the adoption authorizes another binding id, or
37
+ * it was already consumed.
38
+ * - `legacy-adoption-mismatch` (status `failed`): the legacy resource is no longer the one the
39
+ * adoption authorized, its database tenant is absent, or the binding's bucket or retention
40
+ * intent differs from the legacy bucket's.
41
+ */
42
+ export type TPlatformBindingStatusReason = typeof platformBindingStatusReasons[number];
43
+
44
+ const platformBindingStatusOfReason: Readonly<Record<TPlatformBindingStatusReason, TPlatformBindingStatus>> =
45
+ Object.freeze({
46
+ 'legacy-adoption-pending': 'provisioning',
47
+ 'legacy-adoption-conflict': 'failed',
48
+ 'legacy-adoption-mismatch': 'failed',
49
+ });
50
+
51
+ /** The one status each reason is reported with. */
52
+ export const getPlatformBindingStatusOfReason = (
53
+ reasonArg: TPlatformBindingStatusReason,
54
+ ): TPlatformBindingStatus => platformBindingStatusOfReason[reasonArg];
55
+
56
+ /**
57
+ * Validates a reported status and its optional reason: a reason must be a member of the
58
+ * vocabulary and come with its one status. An absent reason is valid with every status.
59
+ */
60
+ export const validatePlatformBindingStatusReason = (
61
+ statusArg: unknown,
62
+ reasonArg: unknown,
63
+ pathArg = 'platform binding statusReason',
64
+ ): string[] => {
65
+ if (reasonArg === undefined) return [];
66
+ if (!(platformBindingStatusReasons as readonly unknown[]).includes(reasonArg)) {
67
+ return [`${pathArg} must be a platform binding status reason`];
68
+ }
69
+ return platformBindingStatusOfReason[reasonArg as TPlatformBindingStatusReason] === statusArg
70
+ ? []
71
+ : [`${pathArg} ${reasonArg as string} requires status ${platformBindingStatusOfReason[reasonArg as TPlatformBindingStatusReason]}`];
72
+ };
19
73
  export type TPlatformEndpointProtocol =
20
74
  | 'typedrequest'
21
75
  | 'http'
@@ -55,6 +109,23 @@ export interface IPlatformServiceEndpoint {
55
109
  port?: number;
56
110
  }
57
111
 
112
+ /**
113
+ * The legacy Corestore resource an object-storage or database binding adopts.
114
+ *
115
+ * Server-managed: only `adoptCorestoreLegacyResources` writes it, in the same write that creates
116
+ * the binding, and Cloudly refuses it (and `objectstorageBucketName`) on `upsertPlatformBinding`.
117
+ * `nodeId` is the node whose Corestore holds the resource, so the binding is placed there and
118
+ * nowhere else; `resourceName` is the legacy database or bucket name; `inventorySha256` is the
119
+ * legacy inventory the adoption was checked against. An adopted bucket's
120
+ * `objectstorageBucketName` is the legacy bucket and its `objectstorageRetention.intent` the legacy
121
+ * intent exactly, or absent when the legacy bucket has none.
122
+ */
123
+ export interface IPlatformBindingCorestoreLegacyAdoption {
124
+ nodeId: string;
125
+ resourceName: string;
126
+ inventorySha256: string;
127
+ }
128
+
58
129
  export interface IPlatformBinding {
59
130
  id: string;
60
131
  serviceId: string;
@@ -69,6 +140,10 @@ export interface IPlatformBinding {
69
140
  objectstorageBucketName?: string;
70
141
  /** Capability-specific value-free immutable-retention intent and evidence. */
71
142
  objectstorageRetention?: IObjectStorageRetentionBinding;
143
+ /** Server-managed: the legacy Corestore resource this binding adopts. */
144
+ corestoreLegacyAdoption?: IPlatformBindingCorestoreLegacyAdoption;
145
+ /** Why the binding holds its status, as the responsible cluster reported it. */
146
+ statusReason?: TPlatformBindingStatusReason;
72
147
  createdAt?: number;
73
148
  updatedAt?: number;
74
149
  }
@@ -614,6 +614,7 @@ export const createStorageContractReaders = (failArg: TCanonicalJsonFailure) =>
614
614
  readCapacityQuantity,
615
615
  capacityQuantityBytes,
616
616
  readEnvironmentKey,
617
+ readMountPath,
617
618
  normalizeCapacityRequest,
618
619
  normalizeDelivery,
619
620
  normalizeObjectStorageRequest,
@@ -1,4 +1,10 @@
1
1
  import * as clusterIngressInterfaces from '../data/clusteringress.js';
2
+ import type {
3
+ IClusterIngressDesignation,
4
+ IClusterIngressDesignationRequest,
5
+ } from '../data/clusteringressdesignation.js';
6
+ import type { IServiceRuntimeSpec } from '../data/serviceruntime.js';
7
+ import type { IIdentityCredential } from '../data/user.js';
2
8
  import type { IProtocolOffer } from '../protocol.js';
3
9
  import * as plugins from '../plugins.js';
4
10
 
@@ -70,6 +76,12 @@ export interface IReq_Relay_Cloudly_GetClusterRouteTable extends plugins.typedre
70
76
  * Both hops judge the body in the one server order `protocol.readProtocolOffer`
71
77
  * states, negotiating before the bearer is verified, as the shape states at
72
78
  * `validateClusterIngressRegistration`.
79
+ *
80
+ * A protocol mismatch is refused with `IProtocolRefusal`; every other refusal
81
+ * travels as `IClusterIngressRegistrationRefusal` in `TypedResponseError.errorData`,
82
+ * naming the reason and the `retryAfterMs` the ingress waits before it registers
83
+ * again. The relay forwards Cloudly's refusal untouched and refuses with the same
84
+ * shape when it cannot reach Cloudly at all (`not-ready`).
73
85
  */
74
86
  export interface IReq_Ingress_Relay_Cloudly_RegisterClusterIngress extends plugins.typedrequestInterfaces.implementsTR<
75
87
  plugins.typedrequestInterfaces.ITypedRequest,
@@ -100,6 +112,25 @@ export interface IReq_Ingress_Relay_Cloudly_RegisterClusterIngress extends plugi
100
112
  };
101
113
  }
102
114
 
115
+ /**
116
+ * The relay tells its registered ingress that it holds a newer table.
117
+ *
118
+ * A hint, not a delivery: it carries the revision alone and nothing secret, and
119
+ * the ingress answers it by pulling `getClusterIngressRouteTable` with the
120
+ * revision it already applied. A lost or repeated hint costs nothing, because the
121
+ * table's own revision is the idempotency; a hint whose revision is not greater
122
+ * than the applied one is ignored.
123
+ */
124
+ export interface IReq_Relay_Ingress_PushClusterIngressRouteTableChanged
125
+ extends plugins.typedrequestInterfaces.implementsTR<
126
+ plugins.typedrequestInterfaces.ITypedRequest,
127
+ IReq_Relay_Ingress_PushClusterIngressRouteTableChanged
128
+ > {
129
+ method: 'pushClusterIngressRouteTableChanged';
130
+ request: clusterIngressInterfaces.IClusterIngressRouteTableChange;
131
+ response: Record<string, never>;
132
+ }
133
+
103
134
  /**
104
135
  * The ingress fetches the table the relay holds.
105
136
  *
@@ -147,3 +178,37 @@ export interface IReq_Any_Ingress_GetClusterTrafficStatistics extends plugins.ty
147
178
  nextCursor?: string;
148
179
  };
149
180
  }
181
+
182
+ /**
183
+ * Cloudly designates one existing service as the ingress of a cluster born on Pallet, creating the
184
+ * managed ingress bearer and the designation atomically. A cluster holds one kind of designation:
185
+ * Cloudly refuses a cluster that holds a fleet-cutover ingress designation.
186
+ */
187
+ export interface IReq_Admin_Cloudly_DesignateClusterIngress
188
+ extends plugins.typedrequestInterfaces.implementsTR<
189
+ plugins.typedrequestInterfaces.ITypedRequest,
190
+ IReq_Admin_Cloudly_DesignateClusterIngress
191
+ > {
192
+ method: 'designateClusterIngress';
193
+ request: {
194
+ identity: IIdentityCredential;
195
+ designation: IClusterIngressDesignationRequest;
196
+ };
197
+ response: {
198
+ designation: IClusterIngressDesignation;
199
+ /** The ingress service's exact-cluster runtime spec the designation was accepted against. */
200
+ spec: IServiceRuntimeSpec;
201
+ replayed: boolean;
202
+ };
203
+ }
204
+
205
+ /** The cluster's current ingress designation; `null` when the cluster holds none of this kind. */
206
+ export interface IReq_Admin_Cloudly_GetClusterIngressDesignation
207
+ extends plugins.typedrequestInterfaces.implementsTR<
208
+ plugins.typedrequestInterfaces.ITypedRequest,
209
+ IReq_Admin_Cloudly_GetClusterIngressDesignation
210
+ > {
211
+ method: 'getClusterIngressDesignation';
212
+ request: { identity: IIdentityCredential; clusterId: string };
213
+ response: { designation: IClusterIngressDesignation | null };
214
+ }
@@ -1,5 +1,11 @@
1
1
  import * as plugins from '../plugins.js';
2
2
  import type { IIdentity } from '../data/user.js';
3
+ import type {
4
+ ICorestoreLegacyResourceAdoptionPlanEntry,
5
+ ICorestoreLegacyResourceSelection,
6
+ TClusterCorestoreLegacyResourceInventory,
7
+ } from '../data/corestorelegacyadoption.js';
8
+ import type { IPlatformBinding } from '../platform/types.js';
3
9
  import { hasExactKeys } from '../private/dataproperties.js';
4
10
  import { isClusterNodeId } from '../private/nodeidentifier.js';
5
11
 
@@ -277,3 +283,76 @@ extends plugins.typedrequestInterfaces.implementsTR<
277
283
  inventory: ICorestoreNodeInventory;
278
284
  };
279
285
  }
286
+
287
+ /**
288
+ * Cloudly asks the relay for one exact node's legacy Corestore resources.
289
+ *
290
+ * The relay reads Corestore's `corestoreLegacyAdoptionContract.inventoryPath` with the control
291
+ * token and answers `reachable: true` with its services, or `reachable: false` with the reason, never
292
+ * an empty list for a node it could not read.
293
+ */
294
+ export interface IReq_Cloudly_Relay_GetClusterCorestoreLegacyResources
295
+ extends plugins.typedrequestInterfaces.implementsTR<
296
+ plugins.typedrequestInterfaces.ITypedRequest,
297
+ IReq_Cloudly_Relay_GetClusterCorestoreLegacyResources
298
+ > {
299
+ method: 'getClusterCorestoreLegacyResources';
300
+ request: {
301
+ nodeId: string;
302
+ };
303
+ response: {
304
+ inventory: TClusterCorestoreLegacyResourceInventory;
305
+ };
306
+ }
307
+
308
+ /**
309
+ * An administrator reads one node's legacy Corestore resources through its cluster's relay, with
310
+ * the digest an adoption is checked against (`null` for an unreachable node).
311
+ */
312
+ export interface IReq_Any_Cloudly_GetCorestoreLegacyResources
313
+ extends plugins.typedrequestInterfaces.implementsTR<
314
+ plugins.typedrequestInterfaces.ITypedRequest,
315
+ IReq_Any_Cloudly_GetCorestoreLegacyResources
316
+ > {
317
+ method: 'getCorestoreLegacyResources';
318
+ request: {
319
+ identity: IIdentity;
320
+ clusterId: string;
321
+ nodeId: string;
322
+ };
323
+ response: {
324
+ inventory: TClusterCorestoreLegacyResourceInventory;
325
+ inventorySha256: string | null;
326
+ };
327
+ }
328
+
329
+ /**
330
+ * The only path that creates a binding carrying a legacy bucket name.
331
+ *
332
+ * Cloudly reads the node's legacy inventory again, requires `expectedInventorySha256`, and for
333
+ * each selected resource creates the adopting binding in one transaction: enabled, placed on
334
+ * `nodeId`, with `corestoreLegacyAdoption`, and for a bucket the legacy bucket name and retention
335
+ * intent (`validatePlatformBindingCorestoreLegacyAdoption`). A resource whose adopting binding
336
+ * already exists is answered unchanged. The response lists the adopting bindings and the plan
337
+ * (`createCorestoreLegacyResourceAdoptionPlan`) the operator hands to Corestore's offline
338
+ * `legacy-resource-adoption` migration. Refusals travel as `ICorestoreLegacyAdoptionErrorData`.
339
+ */
340
+ export interface IReq_Any_Cloudly_AdoptCorestoreLegacyResources
341
+ extends plugins.typedrequestInterfaces.implementsTR<
342
+ plugins.typedrequestInterfaces.ITypedRequest,
343
+ IReq_Any_Cloudly_AdoptCorestoreLegacyResources
344
+ > {
345
+ method: 'adoptCorestoreLegacyResources';
346
+ request: {
347
+ identity: IIdentity;
348
+ clusterId: string;
349
+ nodeId: string;
350
+ expectedInventorySha256: string;
351
+ /** Sorted by service then capability (`validateCorestoreLegacyResourceSelections`). */
352
+ resources: ICorestoreLegacyResourceSelection[];
353
+ };
354
+ response: {
355
+ bindings: IPlatformBinding[];
356
+ plan: ICorestoreLegacyResourceAdoptionPlanEntry[];
357
+ };
358
+ }
@@ -5,6 +5,7 @@ import type {
5
5
  IPlatformCapability,
6
6
  IPlatformProviderConfig,
7
7
  TPlatformBindingStatus,
8
+ TPlatformBindingStatusReason,
8
9
  } from '../platform/types.js';
9
10
 
10
11
  export interface IReq_Any_Cloudly_GetPlatformDesiredState
@@ -162,6 +163,11 @@ extends plugins.typedrequestInterfaces.implementsTR<
162
163
  endpoints?: IPlatformBinding['endpoints'];
163
164
  /** Reports the exact provider-returned bucket for an objectstorage binding. */
164
165
  objectstorageBucketName?: IPlatformBinding['objectstorageBucketName'];
166
+ /**
167
+ * Why the binding holds `status`; `validatePlatformBindingStatusReason` pairs each reason with
168
+ * its one status. Absent clears a stored reason.
169
+ */
170
+ statusReason?: TPlatformBindingStatusReason;
165
171
  errorText?: string;
166
172
  };
167
173
  response: {
@@ -8,6 +8,7 @@ import type * as credential from '../data/networkcredential.js';
8
8
  import type * as assignmentSecret from '../runtime.assignmentsecret.js';
9
9
  import type * as fleet from '../data/fleetcutover.js';
10
10
  import type { IRuntimeStorageClaim } from '../data/runtimestorageclaim.js';
11
+ import type * as localStorage from '../data/runtimelocalstorageclaim.js';
11
12
 
12
13
  /** Current-peer, exact-projection authorization; response contains secret keys. */
13
14
  export interface IReq_Pallet_Controller_GetRuntimeManagedVpnCredential extends plugins.typedrequestInterfaces.implementsTR<
@@ -163,6 +164,21 @@ export interface IReq_Controller_Pallet_ApplyRuntimeAssignment extends plugins.t
163
164
  response: data.IApplyRuntimeAssignmentResponse;
164
165
  }
165
166
 
167
+ /**
168
+ * Controller push of one local storage claim over the already authenticated Pallet-initiated
169
+ * connection; `bindApplyRuntimeLocalStorageClaimRequest` binds it to that session. The answer is a
170
+ * durable admission acknowledgement plus the volume's state; it grants no mount by itself, which
171
+ * only an assignment that seals the claim's reference does.
172
+ */
173
+ export interface IReq_Controller_Pallet_ApplyRuntimeLocalStorageClaim extends plugins.typedrequestInterfaces.implementsTR<
174
+ plugins.typedrequestInterfaces.ITypedRequest,
175
+ IReq_Controller_Pallet_ApplyRuntimeLocalStorageClaim
176
+ > {
177
+ method: 'applyRuntimeLocalStorageClaim';
178
+ request: localStorage.IApplyRuntimeLocalStorageClaimRequest;
179
+ response: localStorage.IApplyRuntimeLocalStorageClaimResponse;
180
+ }
181
+
166
182
  /** Current-peer report, including exact historical outbox recovery after reconnect. */
167
183
  export interface IReq_Pallet_Controller_ReportRuntimeAssignmentObservation extends plugins.typedrequestInterfaces.implementsTR<
168
184
  plugins.typedrequestInterfaces.ITypedRequest,
@@ -2,6 +2,7 @@ import * as plugins from '../plugins.js';
2
2
  import type { IService } from '../data/service.js';
3
3
  import type { TServiceUpdateWritableData, TServiceWritableData } from '../data/service.js';
4
4
  import type { IServicePlacement } from '../data/service.js';
5
+ import type { TServiceVolumeInitialization } from '../data/servicevolumeinitialization.js';
5
6
  import type { IRegistryTarget } from '../data/registry.js';
6
7
  import type { IIdentity, IIdentityCredential } from '../data/user.js';
7
8
  import type { IServiceRessources } from '../data/docker.js';
@@ -390,6 +391,33 @@ extends plugins.typedrequestInterfaces.implementsTR<
390
391
  };
391
392
  }
392
393
 
394
+ /**
395
+ * Sets how one declared volume's node-local storage claim starts, the only write of
396
+ * `IServiceVolume.initialization`.
397
+ *
398
+ * The volume is named by its exact declared `mountPath`. `expected` is the initialization the
399
+ * caller read, or `null` when the volume has none; any other stored value refuses as `conflict`.
400
+ * While a bound local storage claim of the volume exists, a different initialization refuses as
401
+ * `volume-initialization-bound`. Refusals travel as `IServiceVolumeInitializationErrorData`.
402
+ */
403
+ export interface IRequest_Any_Cloudly_SetServiceVolumeInitialization
404
+ extends plugins.typedrequestInterfaces.implementsTR<
405
+ plugins.typedrequestInterfaces.ITypedRequest,
406
+ IRequest_Any_Cloudly_SetServiceVolumeInitialization
407
+ > {
408
+ method: 'setServiceVolumeInitialization';
409
+ request: {
410
+ identity: IIdentity;
411
+ serviceId: string;
412
+ mountPath: string;
413
+ expected: TServiceVolumeInitialization | null;
414
+ initialization: TServiceVolumeInitialization;
415
+ };
416
+ response: {
417
+ service: IService;
418
+ };
419
+ }
420
+
393
421
  /**
394
422
  * Writes the complete runtime spec of one service.
395
423
  *