@serve.zone/interfaces 32.22.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.
@@ -1,6 +1,6 @@
1
1
  import * as plugins from '../plugins.js';
2
2
  import type { IIdentity } from '../data/user.js';
3
- import type { IPlatformBinding, IPlatformCapability, IPlatformProviderConfig, TPlatformBindingStatus } from '../platform/types.js';
3
+ import type { IPlatformBinding, IPlatformCapability, IPlatformProviderConfig, TPlatformBindingStatus, TPlatformBindingStatusReason } from '../platform/types.js';
4
4
  export interface IReq_Any_Cloudly_GetPlatformDesiredState extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_GetPlatformDesiredState> {
5
5
  method: 'getPlatformDesiredState';
6
6
  request: {
@@ -111,6 +111,11 @@ export interface IReq_Any_Cloudly_UpdatePlatformBindingStatus extends plugins.ty
111
111
  endpoints?: IPlatformBinding['endpoints'];
112
112
  /** Reports the exact provider-returned bucket for an objectstorage binding. */
113
113
  objectstorageBucketName?: IPlatformBinding['objectstorageBucketName'];
114
+ /**
115
+ * Why the binding holds `status`; `validatePlatformBindingStatusReason` pairs each reason with
116
+ * its one status. Absent clears a stored reason.
117
+ */
118
+ statusReason?: TPlatformBindingStatusReason;
114
119
  errorText?: string;
115
120
  };
116
121
  response: {
@@ -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 { IImageRelease } from '../data/immutableimage.js';
@@ -267,6 +268,28 @@ export interface IRequest_Any_Cloudly_DeleteServiceById extends plugins.typedreq
267
268
  success: boolean;
268
269
  };
269
270
  }
271
+ /**
272
+ * Sets how one declared volume's node-local storage claim starts, the only write of
273
+ * `IServiceVolume.initialization`.
274
+ *
275
+ * The volume is named by its exact declared `mountPath`. `expected` is the initialization the
276
+ * caller read, or `null` when the volume has none; any other stored value refuses as `conflict`.
277
+ * While a bound local storage claim of the volume exists, a different initialization refuses as
278
+ * `volume-initialization-bound`. Refusals travel as `IServiceVolumeInitializationErrorData`.
279
+ */
280
+ export interface IRequest_Any_Cloudly_SetServiceVolumeInitialization extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_SetServiceVolumeInitialization> {
281
+ method: 'setServiceVolumeInitialization';
282
+ request: {
283
+ identity: IIdentity;
284
+ serviceId: string;
285
+ mountPath: string;
286
+ expected: TServiceVolumeInitialization | null;
287
+ initialization: TServiceVolumeInitialization;
288
+ };
289
+ response: {
290
+ service: IService;
291
+ };
292
+ }
270
293
  /**
271
294
  * Writes the complete runtime spec of one service.
272
295
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "32.22.0",
3
+ "version": "32.23.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
@@ -1418,17 +1418,22 @@ phase, settlement, release or authority booleans or evidence.
1418
1418
  on one spec revision, and each reason belongs to exactly one state. `converged` and `converging`
1419
1419
  carry `reason: null`. `waiting` carries a `TServiceRuntimeWaitingReason`: `node-network-pending`,
1420
1420
  `conflict`, `failure-backoff`, `storage-import-pending` (an `import` local storage claim whose bytes
1421
- have not arrived on its node) or `bindings-pending` (the secret manifest does not cover every enabled
1422
- binding of the service yet). `refused` carries a `TServiceRuntimeRefusal`:
1421
+ have not arrived on its node), `bindings-pending` (the secret manifest does not cover every enabled
1422
+ binding of the service yet) or `legacy-adoption-pending` (a Corestore binding of the service waits for
1423
+ the offline adoption of its legacy resource, see
1424
+ [Corestore Legacy Resource Adoption](#corestore-legacy-resource-adoption)). `refused` carries a
1425
+ `TServiceRuntimeRefusal`:
1423
1426
  `cluster-runtime-missing`, `organization-missing`, `image-plan-missing`, `platform-unsupported`,
1424
1427
  `secrets-unsupported`, `volumes-unsupported`, `port-protocol-unsupported`, `ports-too-many`,
1425
1428
  `readiness-target-undeclared`, `environment-too-large`, `no-eligible-node`, `replica-limit`,
1426
1429
  `node-authority-unsupported`, `network-policy-missing`, `network-policy-mismatch`,
1427
1430
  `network-workload-pool-missing`, `network-workload-pool-exhausted`, `volume-driver-unsupported`,
1428
- `volume-options-unsupported`, `volume-shared-unsupported` or `volume-placement-unsupported`. The four
1429
- volume refusals name what a local storage claim cannot express: a volume driver other than a
1430
- node-local directory, driver or mount options, one volume mounted by more than one replica or
1431
- service, and a placement that does not pin each volume to one node. `ports-too-many` is a service declaring more target ports than the
1431
+ `volume-options-unsupported`, `volume-shared-unsupported`, `volume-placement-unsupported`,
1432
+ `volume-initialization-unknown` or `volume-ownership-unknown`. The first four volume refusals name
1433
+ what a local storage claim cannot express: a volume driver other than a node-local directory, driver
1434
+ or mount options, one volume mounted by more than one replica or service, and a placement that does
1435
+ not pin each volume to one node. The last two name what Cloudly will not guess, see
1436
+ [Service Volume Initialization](#service-volume-initialization). `ports-too-many` is a service declaring more target ports than the
1432
1437
  `runtimeConfigContract.maximumTargetPorts` (64) a config carries, where `port-protocol-unsupported`
1433
1438
  is about one port's protocol; `node-authority-unsupported` is a node whose session resolved fewer
1434
1439
  workload authorities than the spec needs, refused at the admit step rather than handed to a node
@@ -1440,6 +1445,52 @@ is `network-policy-missing`, and a disagreement with the requested egress or pla
1440
1445
  retires running slots.
1441
1446
  `snapshotServiceRuntimeStatus` enforces the pairing.
1442
1447
 
1448
+ ### Service Volume Initialization
1449
+
1450
+ A local storage claim has to say how its volume starts: `empty` with an owner and mode, or `import`
1451
+ of bytes that already exist. `IServiceVolume.initialization` (`TServiceVolumeInitialization`) is
1452
+ where the service states it, per volume, beside the mount path it belongs to:
1453
+
1454
+ - `{ kind: 'empty', ownership }` creates an empty root. `ownership` is
1455
+ `{ kind: 'explicit', uid, gid, mode }`, or `{ kind: 'run-as', mode }`, which takes the owner from
1456
+ the runtime spec's `profile.runAs`. The mode is always stated; it holds directory permission bits
1457
+ only (`serviceVolumeInitializationContract.directoryModeBits`, `0o3777`, setuid refused).
1458
+ - `{ kind: 'import', source: { kind: 'docker-volume', volumeName } }` waits for the bytes of an
1459
+ existing Docker volume on the claim node's host, named exactly as Docker lists it (a
1460
+ Corestore-driver volume included). The operator copies that tree into the node's import directory;
1461
+ the claim does not carry the source, and the node never reads it.
1462
+
1463
+ It lives on the volume and not on the runtime spec because it is a fact about the volume's data,
1464
+ which the service declares, while the runtime spec carries only execution policy and is stored
1465
+ exactly, so a new member there would refuse every stored spec. It is server-managed:
1466
+ `setServiceVolumeInitialization { identity, serviceId, mountPath, expected, initialization }` is its
1467
+ only write. The volume is named by its exact mount path; `expected` is the value the caller read, or
1468
+ `null` for none, and any other stored value refuses. `createService` and `updateService` take volumes
1469
+ as `TServiceWritableVolume` (no `initialization`) and Cloudly refuses one that states it; a stored
1470
+ initialization stays with the volume of the same mount path and goes with the volume. Refusals travel
1471
+ as `IServiceVolumeInitializationErrorData { code, retryable: false }`: `volume-not-found`,
1472
+ `conflict`, `volume-initialization-invalid`, and `volume-initialization-bound`, a change while a
1473
+ bound local storage claim of the volume exists.
1474
+
1475
+ `validateServiceVolumeInitialization(value, path?)` names every reason a value is refused.
1476
+ `deriveServiceVolumeClaimInitialization(initialization, runAs)` is the one rule for the claim fields,
1477
+ and nothing in it is a default:
1478
+
1479
+ | Volume initialization | `runAs` | Claim `initialization` / `initialOwnership` |
1480
+ | --- | --- | --- |
1481
+ | absent | any | refused `volume-initialization-unknown` |
1482
+ | `empty`, `explicit` | any | `empty` / the stated `{ uid, gid, mode }` |
1483
+ | `empty`, `run-as` | `{ user, group }` | `empty` / `{ uid: user, gid: group, mode }` |
1484
+ | `empty`, `run-as` | `null` | refused `volume-ownership-unknown` |
1485
+ | `import` | any | `import` / `null` |
1486
+
1487
+ An absent initialization is refused rather than created empty, because an empty volume in front of a
1488
+ service whose data sits in a legacy Docker volume would start that service without its data. A
1489
+ `run-as` ownership under `runAs: null` is refused because the image's own user is not a number
1490
+ Cloudly knows. The rule decides the first generation of a claim only: every later generation carries
1491
+ that claim's own initialization and ownership, whatever `runAs` says by then, since the node refuses
1492
+ a claim whose initial ownership changed.
1493
+
1443
1494
  ### Runtime Assignment Views
1444
1495
 
1445
1496
  `data.IServiceRuntimeAssignmentView` is one replica slot's current assignment as an administrator
@@ -2632,6 +2683,15 @@ from one that states a value, and nothing downstream defaults or infers one. Clo
2632
2683
  the node stated and resolves it into the route table; the relay never substitutes an address of
2633
2684
  its own.
2634
2685
 
2686
+ The same report carries the optional `hostAddresses`: every IPv4 address the node's host carries on
2687
+ interfaces the node does not manage itself, sorted as strings, unique, never empty when present, at
2688
+ most `runtimeNetworkProtectionContract.maximumHostAddresses` (32), each one admitted by
2689
+ `runtimeNodeUplinkAddress`. Workload bridges and the managed VPN interface are the node's own and
2690
+ are left out. Cloudly names a plan platform endpoint in that node's `hostPlatformEndpointIds` when
2691
+ the endpoint's address is one of them. A relay registration already tells Cloudly which host carries
2692
+ the relay listener and hub; `hostAddresses` is how a host without a relay, such as a node with a
2693
+ Corestore beside it on one machine, states the same. Absent states nothing and is never defaulted.
2694
+
2635
2695
  Shared service port helpers are exported from `data` so Cloudly, App Store resolution, Coreflow, Coretraffic, Onebox, and dcrouter agree on the same normalization rules:
2636
2696
 
2637
2697
  ```typescript
@@ -3316,6 +3376,73 @@ recipient the relay enrolled with `beginSecretRecipientEnrollment` and
3316
3376
  `completeSecretRecipientEnrollment` — see [Corestore Runtime Credentials](#corestore-runtime-credentials).
3317
3377
  No credential travels in the cluster document, and none is read from the relay's environment.
3318
3378
 
3379
+ ### Corestore Legacy Resource Adoption
3380
+
3381
+ A Corestore root converted from a release before 32 still holds the databases and buckets
3382
+ `/resources/provision` created, one per service and capability, in its manifest. Corestore refuses a
3383
+ credential binding beside such a resource until an operator authorizes that exact binding to adopt
3384
+ it, offline, with a plan file; a fresh binding would otherwise start from an empty database or bucket
3385
+ while the data stays behind. These contracts carry that flow across Corestore, the relay, Cloudly and
3386
+ Coreflow, all exported from `data` and `platform`:
3387
+
3388
+ 1. **Inventory.** Corestore answers `GET /control/legacy-resources`
3389
+ (`corestoreLegacyAdoptionContract.inventoryPath`, bearer-authenticated like every control path)
3390
+ with `ICorestoreLegacyResourceInventory { services }`: every service, sorted by id, with one or
3391
+ two `TCorestoreLegacyResource` entries, `database` before `objectstorage`. A database is named by
3392
+ `databaseName`, a bucket by `bucketName` and `retention` (its `IObjectStorageRetentionIntent`, or
3393
+ `null`). Each carries its `adoption`: `{ state: 'unadopted', blocker }` or
3394
+ `{ state: 'authorized', bindingId }`. The blocker names what would stop the offline adoption,
3395
+ `null` when nothing would: `not-adoptable` (an allocation-managed database, another provider, a
3396
+ resource name that is not its database or bucket name, or a bucket name that is not a canonical S3
3397
+ name), `ordinary-credentials-rotated` or `owned-by-binding`. An adoptable bucket must have a
3398
+ canonical S3 name, because a binding request carries only such a name. A consumed adoption removes
3399
+ the legacy entry, so it never appears.
3400
+ 2. **Relay.** `getClusterCorestoreLegacyResources { nodeId }` asks the relay for one node's
3401
+ inventory, answered as `TClusterCorestoreLegacyResourceInventory`: `reachable: true` with the
3402
+ services, or `reachable: false` with `errorCode` `CORESTORE_ENDPOINT_UNKNOWN` or
3403
+ `CORESTORE_UNREACHABLE` and no list. `getCorestoreLegacyResources { identity, clusterId, nodeId }`
3404
+ is the administrator's read through Cloudly, with `inventorySha256`
3405
+ (`computeClusterCorestoreLegacyResourceInventorySha256`, over the node id and the services, not
3406
+ the read time; `null` for an unreachable node).
3407
+ 3. **Adoption.** `adoptCorestoreLegacyResources { identity, clusterId, nodeId,
3408
+ expectedInventorySha256, resources }` is the only path that creates a binding carrying a legacy
3409
+ bucket name. `resources` is a sorted, unique `{ serviceId, capability }` selection
3410
+ (`validateCorestoreLegacyResourceSelections`). Cloudly reads the inventory again, requires the
3411
+ expected digest, and creates each adopting binding in one transaction: enabled, placed on `nodeId`,
3412
+ with `corestoreLegacyAdoption { nodeId, resourceName, inventorySha256 }`, and for a bucket
3413
+ `objectstorageBucketName` set to the legacy bucket and `objectstorageRetention.intent` set to the
3414
+ legacy intent exactly, or absent when the legacy bucket has none.
3415
+ `validatePlatformBindingCorestoreLegacyAdoption(binding, inventory, sha)` is that rule. A resource
3416
+ whose adopting binding already exists is answered unchanged. `upsertPlatformBinding` keeps refusing
3417
+ `objectstorageBucketName`, and refuses `corestoreLegacyAdoption` and `statusReason` as well. The
3418
+ answer lists the bindings and the `plan`. Refusals travel as
3419
+ `ICorestoreLegacyAdoptionErrorData { code, retryable }`: `legacy-inventory-unreachable` (the only
3420
+ retryable one), `legacy-inventory-changed`, `legacy-resource-absent`, `legacy-resource-blocked`,
3421
+ `legacy-resource-authorized-elsewhere`, `service-absent` and `binding-exists`.
3422
+ 4. **Plan.** `ICorestoreLegacyResourceAdoptionPlanEntry` is exactly the
3423
+ `{ serviceId, capability, bindingId }` object Corestore's `legacy-resource-adoption` migration
3424
+ reads. `validateCorestoreLegacyResourceAdoptionPlan` applies that migration's rules: a non-empty
3425
+ array of at most 4096 exact entries, canonical identifiers, no service capability or binding id
3426
+ twice, any order. `createCorestoreLegacyResourceAdoptionPlan(bindings)` builds it from the adopting
3427
+ bindings, sorted by service then capability. The plan restates no legacy name or retention intent:
3428
+ Corestore holds those itself, and the binding request that later reaches it carries the bucket and
3429
+ intent it compares.
3430
+ 5. **Status.** Until the operator has run the migration, Corestore refuses the binding with a 409
3431
+ whose body is `{ ok: false, error, code }`. `readCorestoreLegacyAdoptionRefusal(statusCode, body)`
3432
+ reads `code` as a `TPlatformBindingStatusReason`, and Coreflow reports it through
3433
+ `updatePlatformBindingStatus { statusReason }`; `validatePlatformBindingStatusReason` pairs each
3434
+ reason with its one status (`getPlatformBindingStatusOfReason`):
3435
+
3436
+ | `statusReason` | Status | Corestore refusal |
3437
+ | --- | --- | --- |
3438
+ | `legacy-adoption-pending` | `provisioning` | the service owns a legacy resource of this capability that no adoption authorizes |
3439
+ | `legacy-adoption-conflict` | `failed` | the adoption authorizes another binding id, or it was already consumed |
3440
+ | `legacy-adoption-mismatch` | `failed` | the legacy identity changed since authorization, its database tenant is absent, or the bucket or retention intent differs |
3441
+
3442
+ `IPlatformBinding.statusReason` holds the reported reason; a status report without one clears it.
3443
+ While a binding of a service reports `legacy-adoption-pending`, the service's runtime status waits as
3444
+ `legacy-adoption-pending` instead of `bindings-pending`.
3445
+
3319
3446
  ### Launcher Environment Delivery
3320
3447
 
3321
3448
  Launcher environment delivery uses stable `szsv-<base32-sha256>` Docker
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.22.0',
6
+ version: '32.23.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }