@serve.zone/interfaces 31.5.0 → 31.7.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.
@@ -0,0 +1,111 @@
1
+ import type { ICorestoreDatabaseBackupReceipt } from './runtime.corestore.js';
2
+ import {
3
+ corestoreDatabaseBackupRuntimeLimits,
4
+ normalizeCorestoreDatabaseBackupReceipt,
5
+ } from './runtime.corestore.js';
6
+
7
+ /**
8
+ * Carrying a Corestore database closure through the cluster relay.
9
+ *
10
+ * A cluster is outbound-only, so Cloudly never reads Corestore itself: the
11
+ * relay exports the closure from its cluster's Corestore and streams it to
12
+ * Cloudly, and streams it back for a restore. The bytes travel as a
13
+ * VirtualStream on the relay's own session; this descriptor is what the parent
14
+ * TypedRequest carries about them.
15
+ *
16
+ * No version suffix and no schema field: the protocol version is the installed
17
+ * `@serve.zone/interfaces` version.
18
+ */
19
+
20
+ /**
21
+ * What a peer states about the closure it is about to send or serve.
22
+ *
23
+ * `size` is mandatory, and it is the reason this descriptor exists at all:
24
+ * Corestore's database backup restore requires one exact `Content-Length`,
25
+ * identity transfer encoding and no content encoding, and it refuses a body
26
+ * whose byte length differs from its receipt. A stream that only learned its
27
+ * length at EOF could therefore never be restored — the receiver has to open
28
+ * the Corestore request before the first byte arrives.
29
+ *
30
+ * `sha256` is the digest of those exact bytes, and `receipt` is Corestore's own
31
+ * closure receipt, which binds the backup, the source service, the snapshot and
32
+ * the closure's identity. The two are cross-checked here rather than trusted
33
+ * separately: a descriptor whose `size` or `sha256` disagrees with its receipt
34
+ * describes a closure nobody can restore.
35
+ */
36
+ export interface ICorestoreDatabaseClosureDescriptor {
37
+ /** Exact byte length of the closure, as Corestore's `Content-Length` needs it. */
38
+ size: number;
39
+ /** Lowercase hex SHA-256 over those exact bytes. */
40
+ sha256: string;
41
+ /** Corestore's own receipt for this closure; its identity and integrity authority. */
42
+ receipt: ICorestoreDatabaseBackupReceipt;
43
+ }
44
+
45
+ const bareSha256Pattern = /^[a-f0-9]{64}$/;
46
+
47
+ const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> => (
48
+ Boolean(valueArg)
49
+ && typeof valueArg === 'object'
50
+ && !Array.isArray(valueArg)
51
+ && (Object.getPrototypeOf(valueArg) === Object.prototype
52
+ || Object.getPrototypeOf(valueArg) === null)
53
+ );
54
+
55
+ const hasExactKeys = (
56
+ valueArg: Record<string, unknown>,
57
+ keysArg: readonly string[],
58
+ ): boolean => {
59
+ const actualKeys = Object.keys(valueArg).sort();
60
+ const expectedKeys = [...keysArg].sort();
61
+ return actualKeys.length === expectedKeys.length
62
+ && actualKeys.every((keyArg, indexArg) => keyArg === expectedKeys[indexArg]);
63
+ };
64
+
65
+ /**
66
+ * Validates one closure descriptor and names every reason it is refused.
67
+ *
68
+ * Shape and self-consistency only: whether the closure exists, whether the
69
+ * backup is this cluster's and whether the target database may be replaced are
70
+ * decisions for the party that holds them. A descriptor that passes here has
71
+ * claimed none of them. No reason quotes closure bytes, because a reason
72
+ * travels into logs and the closure is tenant data.
73
+ */
74
+ export const validateCorestoreDatabaseClosureDescriptor = (
75
+ valueArg: unknown,
76
+ pathArg = 'corestore database closure descriptor',
77
+ ): string[] => {
78
+ if (!isRecord(valueArg) || !hasExactKeys(valueArg, ['size', 'sha256', 'receipt'])) {
79
+ return [`${pathArg} must use its exact schema`];
80
+ }
81
+ const errors: string[] = [];
82
+ const maximumBytes = corestoreDatabaseBackupRuntimeLimits.maximumClosureBytes;
83
+ if (!Number.isSafeInteger(valueArg.size)
84
+ || (valueArg.size as number) < 1
85
+ || (valueArg.size as number) > maximumBytes) {
86
+ errors.push(`${pathArg} size must be an integer from 1 to ${maximumBytes}`);
87
+ }
88
+ if (typeof valueArg.sha256 !== 'string' || !bareSha256Pattern.test(valueArg.sha256)) {
89
+ errors.push(`${pathArg} sha256 must be a lowercase hex SHA-256 digest`);
90
+ }
91
+ let receipt: ICorestoreDatabaseBackupReceipt | undefined;
92
+ try {
93
+ receipt = normalizeCorestoreDatabaseBackupReceipt(valueArg.receipt);
94
+ } catch (error) {
95
+ // Corestore's own normalizer names the field it refused and never the
96
+ // material behind it, so its reason is worth more than its class.
97
+ errors.push(`${pathArg} receipt is not canonical: ${(error as Error).message}`);
98
+ }
99
+ if (!receipt || errors.length > 0) {
100
+ return errors;
101
+ }
102
+ if (valueArg.size !== receipt.closure.archiveBytes) {
103
+ errors.push(
104
+ `${pathArg} size must equal the ${receipt.closure.archiveBytes} bytes its receipt states`,
105
+ );
106
+ }
107
+ if (valueArg.sha256 !== receipt.closure.sha256) {
108
+ errors.push(`${pathArg} sha256 must equal the digest its receipt states`);
109
+ }
110
+ return errors;
111
+ };
@@ -1,6 +1,7 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import type {
3
3
  IActiveSecretRecipientMetadata,
4
+ ICorestoreDatabaseAllocationReference,
4
5
  IIdentityCredential,
5
6
  ISecretVersionReference,
6
7
  } from './data/index.js';
@@ -1281,14 +1282,12 @@ export const corestoreDatabaseBackupRuntimeLimits = Object.freeze({
1281
1282
  maximumServiceNameBytes: 1024,
1282
1283
  });
1283
1284
 
1284
- export interface ICorestoreDatabaseAllocationReference {
1285
- purpose: 'scratch-rehearsal';
1286
- rehearsalId: string;
1287
- allocationId: string;
1288
- generation: number;
1289
- bindingSha256: string;
1290
- receiptSha256: string;
1291
- }
1285
+ /**
1286
+ * Declared in the data tree, because an isolated restore's canonical resource
1287
+ * mappings bind the exact same reference and that tree carries no runtime
1288
+ * import. Re-exported here so this module's readers keep one import site.
1289
+ */
1290
+ export type { ICorestoreDatabaseAllocationReference };
1292
1291
 
1293
1292
  export interface ICorestoreDatabaseBackupSnapshotTags {
1294
1293
  corestore: 'resource';
package/ts/runtime.ts CHANGED
@@ -42,6 +42,7 @@ export * from './runtime.workloadinit.js';
42
42
  export * from './runtime.networkprojection.js';
43
43
  export * from './runtime.corestore.js';
44
44
  export * from './runtime.corestore.databaseexportpackage.js';
45
+ export * from './runtime.corestore.relay.js';
45
46
  export * from './runtime.cloudlylegacydeploymentsettlement.js';
46
47
  export * from './runtime.cloudlylegacydeploymentsettlement.offlineexport.js';
47
48