@serve.zone/interfaces 19.8.0 → 20.1.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 (44) hide show
  1. package/changelog.md +28 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/data/immutableimage.d.ts +2 -0
  4. package/dist_ts/data/immutableimage.js +1 -1
  5. package/dist_ts/data/index.d.ts +1 -0
  6. package/dist_ts/data/index.js +2 -1
  7. package/dist_ts/data/isolatedrestore.js +7 -82
  8. package/dist_ts/data/secret.d.ts +205 -0
  9. package/dist_ts/data/secret.js +469 -0
  10. package/dist_ts/data/secretbundle.d.ts +4 -0
  11. package/dist_ts/data/secretgroup.d.ts +4 -0
  12. package/dist_ts/data/service.d.ts +18 -6
  13. package/dist_ts/data/service.js +1 -1
  14. package/dist_ts/platform/index.d.ts +3 -1
  15. package/dist_ts/platform/index.js +4 -2
  16. package/dist_ts/platform/storagemigration.d.ts +346 -0
  17. package/dist_ts/platform/storagemigration.golden.d.ts +49 -0
  18. package/dist_ts/platform/storagemigration.golden.js +207 -0
  19. package/dist_ts/platform/storagemigration.js +1554 -0
  20. package/dist_ts/private/canonicaljson.d.ts +14 -0
  21. package/dist_ts/private/canonicaljson.js +154 -0
  22. package/dist_ts/requests/index.d.ts +2 -1
  23. package/dist_ts/requests/index.js +3 -2
  24. package/dist_ts/requests/secret.d.ts +198 -0
  25. package/dist_ts/requests/secret.js +2 -0
  26. package/dist_ts/runtime.d.ts +41 -0
  27. package/dist_ts/runtime.js +33 -0
  28. package/package.json +9 -2
  29. package/readme.md +162 -2
  30. package/ts/00_commitinfo_data.ts +1 -1
  31. package/ts/data/immutableimage.ts +2 -0
  32. package/ts/data/index.ts +1 -0
  33. package/ts/data/isolatedrestore.ts +12 -93
  34. package/ts/data/secret.ts +835 -0
  35. package/ts/data/secretbundle.ts +4 -0
  36. package/ts/data/secretgroup.ts +4 -0
  37. package/ts/data/service.ts +26 -6
  38. package/ts/platform/index.ts +3 -0
  39. package/ts/platform/storagemigration.golden.ts +241 -0
  40. package/ts/platform/storagemigration.ts +3082 -0
  41. package/ts/private/canonicaljson.ts +221 -0
  42. package/ts/requests/index.ts +2 -0
  43. package/ts/requests/secret.ts +256 -0
  44. package/ts/runtime.ts +80 -0
package/readme.md CHANGED
@@ -120,6 +120,130 @@ must reject unknown manifest fields and unsupported required feature IDs before
120
120
  provisioning. Legacy `volumes` and `platformRequirements.s3` remain deprecated
121
121
  inputs for strict resolver normalization only.
122
122
 
123
+ `platform.storagemigration` defines the provider-neutral cutover contract for a
124
+ named object-storage binding. Corestore atomically owns and fences the source
125
+ binding after validating a distinct, unfenced active-object snapshot. Onebox
126
+ only receives a held candidate, stages that exact candidate, stops the matching
127
+ workload generation, and attests the quiesced state. The candidate cannot start
128
+ until `destinationBindingStartAuthorized` is true. Corestore continues returning
129
+ `consumerAction: 'startDestination'` until Onebox submits the mutation-fenced
130
+ consumer activation request and the acknowledgement becomes durable evidence.
131
+ Candidate-issued abort tombstones authorize only `startSource` and never retain
132
+ the staged candidate binding.
133
+
134
+ Every migration DTO and status has an exact, versioned runtime normalizer.
135
+ Unknown fields, provider or pool identifiers, unbounded strings, unsafe
136
+ integers, stale mutation revisions, identity drift, and lifecycle-inconsistent
137
+ fields are rejected. Migration-created digests use strict canonical JSON and a
138
+ bare lowercase 64-hex SHA-256 value; portable golden vectors cover the source
139
+ snapshot, target request, prepare intent, and candidate binding. Pre-cutover
140
+ failures may retry or abort; after the durable commit point, recovery can only
141
+ retry or roll forward. Physical pool IDs, mount details, provider receipts, and
142
+ publication capabilities remain private.
143
+
144
+ Primary exports include:
145
+
146
+ - `IObjectStorageMigrationPrepareRequest` and
147
+ `TObjectStorageMigrationStatus` for the immutable intent and status journal.
148
+ - `IObjectStorageMigrationConsumerQuiesceRequest` with
149
+ `IStorageMigrationConsumerQuiesceEvidence` for exact candidate staging and
150
+ source-workload shutdown.
151
+ - `IObjectStorageMigrationConsumerActivationRequest` with
152
+ `IStorageMigrationConsumerActivationEvidence` for durable destination-start
153
+ acknowledgement.
154
+ - `normalizeObjectStorageMigrationStatus`,
155
+ `bindObjectStorageMigrationConsumerQuiesceRequest`, and
156
+ `bindObjectStorageMigrationConsumerActivationRequest` for strict ingress and
157
+ current-revision mutation fencing.
158
+ - The `create*Sha256` helpers for source snapshots, prepare intent, target
159
+ requests, candidate/active bindings, and persisted staging or activation
160
+ evidence.
161
+
162
+ The phase and consumer-action progression is exact:
163
+
164
+ | Phase | `consumerAction` | Destination start authorized |
165
+ | --- | --- | --- |
166
+ | `preparing`, `transferring` | `wait` | No |
167
+ | `awaitingConsumerQuiesce` | `stageCandidateAndStop` | No |
168
+ | `finalizing`, `committing` | `wait` | No |
169
+ | `readyToStart` | `startDestination` | Yes |
170
+ | `cleanupPending`, `complete` | `none` | Yes; durable activation evidence is required |
171
+ | `aborting` | `wait` | No |
172
+ | `aborted` | `startSource` | No; only the active source binding may restart |
173
+
174
+ Normalize every status before acting, and bind consumer mutations to that exact
175
+ status revision:
176
+
177
+ ```typescript
178
+ const status =
179
+ await platform.storagemigration.normalizeObjectStorageMigrationStatus(
180
+ untrustedStatusPayload,
181
+ );
182
+
183
+ if (status.phase === 'awaitingConsumerQuiesce') {
184
+ const request =
185
+ await platform.storagemigration.bindObjectStorageMigrationConsumerQuiesceRequest(
186
+ untrustedQuiescePayload,
187
+ status,
188
+ );
189
+ await submitQuiesceAcknowledgement(request);
190
+ }
191
+
192
+ if (status.phase === 'readyToStart') {
193
+ if (
194
+ !status.destinationBindingStartAuthorized ||
195
+ status.consumerAction !== 'startDestination'
196
+ ) {
197
+ throw new Error('destination binding is not authorized to start');
198
+ }
199
+ await startWorkload(status.activeBinding);
200
+ const request =
201
+ await platform.storagemigration.bindObjectStorageMigrationConsumerActivationRequest(
202
+ untrustedActivationPayload,
203
+ status,
204
+ );
205
+ await submitActivationAcknowledgement(request);
206
+ }
207
+
208
+ if (status.phase === 'cleanupPending') {
209
+ // Cleanup is reachable only after this durable acknowledgement was accepted.
210
+ const durableActivation = status.consumerActivationEvidence;
211
+ }
212
+
213
+ if (status.phase === 'aborted') {
214
+ if (
215
+ status.destinationBindingStartAuthorized ||
216
+ status.consumerAction !== 'startSource'
217
+ ) {
218
+ throw new Error('invalid aborted migration status');
219
+ }
220
+ await startWorkload(status.activeBinding);
221
+ }
222
+ ```
223
+
224
+ Here `startWorkload`, `submitQuiesceAcknowledgement`, and
225
+ `submitActivationAcknowledgement` are consumer-owned operations, not package
226
+ exports. Canonical digests are produced from normalized payloads:
227
+
228
+ ```typescript
229
+ const snapshotSha256 =
230
+ await platform.storagemigration.createUnfencedObjectStorageBindingControlSnapshotSha256(
231
+ snapshotDigestPayload,
232
+ );
233
+ const migrationSha256 =
234
+ await platform.storagemigration.createObjectStorageMigrationSha256(
235
+ prepareRequest,
236
+ );
237
+ const candidateSha256 =
238
+ await platform.storagemigration.createObjectStorageMigrationBindingSha256(
239
+ candidateBinding,
240
+ );
241
+ const activationRecordSha256 =
242
+ await platform.storagemigration.createObjectStorageMigrationPersistedActivationSha256(
243
+ activationDigestPayload,
244
+ );
245
+ ```
246
+
123
247
  ## Data Contracts
124
248
 
125
249
  Use `data` when you need object shapes that are persisted, exchanged between services, or exposed through the Cloudly API.
@@ -137,7 +261,6 @@ const service: data.IService = {
137
261
  environment: {
138
262
  NODE_ENV: 'production',
139
263
  },
140
- secretBundleId: 'secretbundle-api',
141
264
  serviceCategory: 'workload',
142
265
  deploymentStrategy: 'limited-replicas',
143
266
  scaleFactor: 2,
@@ -186,7 +309,17 @@ Common data contracts include:
186
309
  - Service port contracts including `IServiceTargetPort`, `IServiceDomainRoute`, and `IServicePublicPortMapping` for canonical backend targets, domain target references, and edge/Coretraffic TCP/UDP public exposure.
187
310
  - `IDomain`, `IDnsEntry`, and traffic contracts for routing and DNS management.
188
311
  - Traffic and gateway route contracts including `ICoretrafficPortRouteConfig`, routing `portRoutes`, and `IGatewayClientRoute` client-owned route views. Gateway route intent supports optional match `domains`, `transport`, and `remoteIngress`, plus explicit route `priority` and `managedRouteKind`. Ownership can combine `hostname` with `routeRef` so a normal route and a path-specific managed route for the same hostname reconcile independently.
189
- - `ISecretBundle` and `ISecretGroup` for secret ownership and shared secret groups.
312
+ - Value-free `ISecretMetadata`, `ISecretVersionMetadata`, and `ISecretSetMetadata`
313
+ contracts for operator views, plus exact-version `IResolvedSecretManifest`
314
+ contracts for cluster delivery. Manifest helpers enforce canonical shared
315
+ digests, globally unique environment/file delivery targets, and per-cluster
316
+ desired/applied/previous-accepted rollout state. `ISecretBundle` and
317
+ `ISecretGroup` remain legacy compatibility contracts during the versioned
318
+ migration.
319
+ `listSecrets` exposes the dedicated `targetSecretsRevision` CAS fence;
320
+ `createSecret` consumes and returns that fence, while
321
+ `setServiceSecretSetAttachments` returns the independent
322
+ `secretConfigurationRevision`. Generic service writes own neither revision.
190
323
  - Mail gateway contracts for domain authorities, address bindings, WorkApp bindings, managed SMTP/API credentials, spool items, delivery journals, and inbound/outbound message payloads.
191
324
  - Service-level mail configuration through `IService.data.mail`, including per-address inbound `smtpForward` settings and outbound credential metadata. Cloudly settings include dcrouter gateway, SMTP submission, and inbound forward-target keys for reconciling those bindings.
192
325
  - Web Push contracts for environment-specific service bindings, public credential state, public VAPID key rotation metadata, privacy-minimal notification signals, and redacted delivery state. Subscription endpoints, browser key material, provider ciphertext, VAPID private keys, and credential secrets are intentionally absent from public binding and status DTOs.
@@ -374,6 +507,7 @@ Request groups are exported by product area:
374
507
  - `requests.node`
375
508
  - `requests.platform`
376
509
  - `requests.routing`
510
+ - `requests.secret`
377
511
  - `requests.secretbundle`
378
512
  - `requests.secretgroup`
379
513
  - `requests.server`
@@ -384,6 +518,30 @@ Request groups are exported by product area:
384
518
  - `requests.version`
385
519
  - `requests.webpush`
386
520
 
521
+ Secret material response contracts are not exported from the universal
522
+ browser-facing entrypoint. Node runtimes import the isolated subpath:
523
+
524
+ ```typescript
525
+ import {
526
+ verifyResolvedSecretMaterial,
527
+ } from '@serve.zone/interfaces/runtime';
528
+ import type {
529
+ IReq_GetResolvedSecretMaterial,
530
+ IResolvedSecretMaterial,
531
+ } from '@serve.zone/interfaces/runtime';
532
+
533
+ async function acceptMaterial(material: IResolvedSecretMaterial) {
534
+ if (!await verifyResolvedSecretMaterial(material)) {
535
+ throw new Error('secret material does not match its immutable manifest');
536
+ }
537
+ }
538
+ ```
539
+
540
+ Runtime material is shaped as `{ manifest, values }`. The helper verifies the
541
+ canonical manifest digest and exact one-to-one version-ID coverage before
542
+ plaintext use. The runtime must separately compare the verified manifest
543
+ reference and cluster to its request and authenticated deployment scope.
544
+
387
545
  ## Platform Contracts
388
546
 
389
547
  Use `platform` for current platform-service capabilities and application-facing platform RPCs.
@@ -404,6 +562,8 @@ Available platform modules:
404
562
  - `platform.pushnotification` is the deprecated legacy device-token push contract. New browser Web Push integrations use `requests.webpush`.
405
563
  - `platform.letter` for physical letter workflows.
406
564
  - `platform.ai`, `platform.database`, `platform.objectstorage`, `platform.logging`, `platform.backup`, and `platform.sip` for infrastructure and application capabilities.
565
+ - `platform.storage` for provider-neutral storage classes, requests, capabilities, and resolved bindings.
566
+ - `platform.storagemigration` for fenced object-storage migration intent, status, consumer acknowledgements, canonical digests, and strict normalizers.
407
567
  - `platform.types` plus root re-exports for shared capability, provider, binding, credential, and endpoint metadata.
408
568
 
409
569
  ## Legacy Platformservice Contracts
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '19.8.0',
6
+ version: '20.1.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
@@ -66,6 +66,8 @@ export interface ICoreflowRuntimeCapabilities {
66
66
  immutableImageDeploymentVersion: 1;
67
67
  /** Coreflow can answer the versioned, node-scoped Corestore inventory probe. */
68
68
  corestoreInventoryVersion?: 1;
69
+ /** Coreflow can consume exact, cluster-scoped resolved secret manifests. */
70
+ secretManifestVersion?: 1;
69
71
  }
70
72
 
71
73
  /**
package/ts/data/index.ts CHANGED
@@ -19,6 +19,7 @@ export * from './isolatedrestore.js';
19
19
  export * from './isolatedrestore.golden.js';
20
20
  export * from './mail.js';
21
21
  export * from './registry.js';
22
+ export * from './secret.js';
22
23
  export * from './secretbundle.js';
23
24
  export * from './secretgroup.js';
24
25
  export * from './baremetal.js';
@@ -3,6 +3,13 @@ import type {
3
3
  IBackupArchiveObject,
4
4
  TIsolatedRestoreResourceMapping,
5
5
  } from './backup.js';
6
+ import {
7
+ canonicalizeStrictJson,
8
+ createCanonicalJsonSha256Hex,
9
+ createSha256Hex,
10
+ deepFreezeValue,
11
+ strictCanonicalJsonRules,
12
+ } from '../private/canonicaljson.js';
6
13
 
7
14
  /** Operations carried by a short-lived, single-operation restore grant. */
8
15
  export const isolatedRestoreGrantOperations = Object.freeze([
@@ -307,15 +314,7 @@ export class IsolatedRestoreContractError extends Error {
307
314
  }
308
315
 
309
316
  /** Cross-runtime rules for restore authority digests. */
310
- export const isolatedRestoreCanonicalJsonRules = Object.freeze({
311
- version: 1 as const,
312
- encoding: 'utf-8' as const,
313
- objectKeyOrdering: 'utf-16-code-unit-ascending' as const,
314
- arrayOrdering: 'preserved-dense' as const,
315
- unicodeNormalization: 'preserved' as const,
316
- numberEncoding: 'safe-integer-json-no-negative-zero' as const,
317
- digest: 'sha256-lowercase-hex' as const,
318
- });
317
+ export const isolatedRestoreCanonicalJsonRules = strictCanonicalJsonRules;
319
318
 
320
319
  const canonicalIdentifierPattern = /^[A-Za-z0-9][A-Za-z0-9._:@-]{0,255}$/;
321
320
  const canonicalScopedIdPattern = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,191}$/;
@@ -360,23 +359,7 @@ export const assertIsolatedRestoreControlIngressByteLength = (
360
359
  return byteLengthArg;
361
360
  };
362
361
 
363
- const deepFreeze = <T>(valueArg: T, seenArg = new WeakSet<object>()): T => {
364
- if (!valueArg || typeof valueArg !== 'object') {
365
- return valueArg;
366
- }
367
- const object = valueArg as object;
368
- if (ArrayBuffer.isView(object) || seenArg.has(object)) {
369
- return valueArg;
370
- }
371
- seenArg.add(object);
372
- for (const key of Reflect.ownKeys(object)) {
373
- const descriptor = Object.getOwnPropertyDescriptor(object, key);
374
- if (descriptor && Object.hasOwn(descriptor, 'value')) {
375
- deepFreeze(descriptor.value, seenArg);
376
- }
377
- }
378
- return Object.freeze(valueArg);
379
- };
362
+ const deepFreeze = deepFreezeValue;
380
363
 
381
364
  const verifiedIsolatedRestoreGrantCompacts = new WeakMap<object, string>();
382
365
  const verifiedIsolatedRestoreWriteBindings = new WeakSet<object>();
@@ -1419,64 +1402,7 @@ export const normalizeIsolatedRestoreControlStatusRequest = (
1419
1402
  };
1420
1403
 
1421
1404
  const canonicalizeJson = (valueArg: unknown): string => {
1422
- const ancestors = new Set<object>();
1423
-
1424
- const visit = (currentValueArg: unknown, fieldNameArg: string): string => {
1425
- if (currentValueArg === null) {
1426
- return 'null';
1427
- }
1428
- if (typeof currentValueArg === 'boolean') {
1429
- return currentValueArg ? 'true' : 'false';
1430
- }
1431
- if (typeof currentValueArg === 'string') {
1432
- assertUnicodeScalarString(currentValueArg, fieldNameArg);
1433
- return JSON.stringify(currentValueArg);
1434
- }
1435
- if (typeof currentValueArg === 'number') {
1436
- if (
1437
- !Number.isSafeInteger(currentValueArg) ||
1438
- Object.is(currentValueArg, -0)
1439
- ) {
1440
- return fail(`${fieldNameArg} must contain safe integers only`);
1441
- }
1442
- return String(currentValueArg);
1443
- }
1444
- if (typeof currentValueArg !== 'object') {
1445
- return fail(`${fieldNameArg} contains a non-JSON value`);
1446
- }
1447
- if (ancestors.has(currentValueArg)) {
1448
- return fail(`${fieldNameArg} contains a cycle`);
1449
- }
1450
-
1451
- ancestors.add(currentValueArg);
1452
- try {
1453
- if (Array.isArray(currentValueArg)) {
1454
- return `[${currentValueArg
1455
- .map((entryArg, indexArg) =>
1456
- visit(entryArg, `${fieldNameArg}[${indexArg}]`),
1457
- )
1458
- .join(',')}]`;
1459
- }
1460
-
1461
- const record = readRecord(currentValueArg, fieldNameArg);
1462
- const keys = Object.keys(record).sort((leftArg, rightArg) => {
1463
- return leftArg < rightArg ? -1 : leftArg > rightArg ? 1 : 0;
1464
- });
1465
- return `{${keys
1466
- .map((keyArg) => {
1467
- assertUnicodeScalarString(keyArg, `${fieldNameArg} key`);
1468
- return `${JSON.stringify(keyArg)}:${visit(
1469
- record[keyArg],
1470
- `${fieldNameArg}.${keyArg}`,
1471
- )}`;
1472
- })
1473
- .join(',')}}`;
1474
- } finally {
1475
- ancestors.delete(currentValueArg);
1476
- }
1477
- };
1478
-
1479
- return visit(valueArg, 'canonical JSON input');
1405
+ return canonicalizeStrictJson(valueArg, fail);
1480
1406
  };
1481
1407
 
1482
1408
  export const canonicalizeIsolatedRestoreResourceMappings = (
@@ -1618,20 +1544,13 @@ export const canonicalizeIsolatedRestoreAuthorityProjection = (
1618
1544
  };
1619
1545
 
1620
1546
  const createSha256 = async (contentsArg: Uint8Array): Promise<string> => {
1621
- const subtle = globalThis.crypto?.subtle;
1622
- if (!subtle) {
1623
- return fail('Web Crypto SHA-256 is unavailable in this runtime');
1624
- }
1625
- const digest = await subtle.digest('SHA-256', new Uint8Array(contentsArg));
1626
- return Array.from(new Uint8Array(digest), (byteArg) =>
1627
- byteArg.toString(16).padStart(2, '0'),
1628
- ).join('');
1547
+ return createSha256Hex(contentsArg, fail);
1629
1548
  };
1630
1549
 
1631
1550
  const createCanonicalJsonSha256 = async (
1632
1551
  canonicalJsonArg: string,
1633
1552
  ): Promise<string> => {
1634
- return createSha256(new TextEncoder().encode(canonicalJsonArg));
1553
+ return createCanonicalJsonSha256Hex(canonicalJsonArg, fail);
1635
1554
  };
1636
1555
 
1637
1556
  export const createIsolatedRestoreBytesSha256 = async (