@serve.zone/interfaces 20.0.0 → 20.2.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 +24 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/coremail.d.ts +216 -0
- package/dist_ts/data/coremail.js +24 -0
- package/dist_ts/data/index.d.ts +1 -0
- package/dist_ts/data/index.js +2 -1
- package/dist_ts/data/isolatedrestore.js +7 -82
- package/dist_ts/platform/index.d.ts +3 -1
- package/dist_ts/platform/index.js +4 -2
- package/dist_ts/platform/storagemigration.d.ts +346 -0
- package/dist_ts/platform/storagemigration.golden.d.ts +49 -0
- package/dist_ts/platform/storagemigration.golden.js +207 -0
- package/dist_ts/platform/storagemigration.js +1554 -0
- package/dist_ts/private/canonicaljson.d.ts +14 -0
- package/dist_ts/private/canonicaljson.js +154 -0
- package/dist_ts/requests/coremail.d.ts +248 -0
- package/dist_ts/requests/coremail.js +2 -0
- package/dist_ts/requests/index.d.ts +2 -1
- package/dist_ts/requests/index.js +3 -2
- package/package.json +2 -2
- package/readme.md +156 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/coremail.ts +277 -0
- package/ts/data/index.ts +1 -0
- package/ts/data/isolatedrestore.ts +12 -93
- package/ts/platform/index.ts +3 -0
- package/ts/platform/storagemigration.golden.ts +241 -0
- package/ts/platform/storagemigration.ts +3082 -0
- package/ts/private/canonicaljson.ts +221 -0
- package/ts/requests/coremail.ts +346 -0
- package/ts/requests/index.ts +2 -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.
|
|
@@ -356,6 +480,35 @@ const provisioning: requests.gateway.IReq_ProvisionGatewayClientCredential['requ
|
|
|
356
480
|
|
|
357
481
|
`getGatewayClientContext` returns effective live policy. A `gatewayClient` role necessarily includes the credential ID, bound client ID/type, and `policyGeneration`; consumers should reject admin/operator or mismatched contexts rather than falling back to a caller-supplied owner ID. `getGatewayClientMailOverview` provides an owner-scoped domain and recent-message summary. `getGatewayClientMailDomainCount` derives ownership exclusively from the authenticating gateway credential and returns `{ count: number }` for its distinct configured mail domains. Cloudly therefore uses only `dcrouterGatewayApiToken`; the former `dcrouterOpsApiToken` setting is not part of `data.ICloudlySettings`.
|
|
358
482
|
|
|
483
|
+
### CoreMail contracts
|
|
484
|
+
|
|
485
|
+
`requests.coremail` is the shared contract boundary for authenticated workload
|
|
486
|
+
sessions, Coreflow reconciliation, and the CoreMail-to-dcrouter gateway
|
|
487
|
+
session. Only the three authentication handshakes carry reusable peer
|
|
488
|
+
credentials. Subsequent transfer operations may carry a scoped, short-lived,
|
|
489
|
+
one-time bearer capability, while every subsequent workload request derives
|
|
490
|
+
tenant, service, binding, capabilities, allowed senders, and credential version
|
|
491
|
+
from the server-owned TypedSocket peer.
|
|
492
|
+
|
|
493
|
+
Large content never travels inside TypedRequest JSON. Outbound body parts and
|
|
494
|
+
attachments use prepare/upload/complete operations with short-lived one-time
|
|
495
|
+
HTTP transfer grants. Inbound delivery uses bounded delivery listing followed
|
|
496
|
+
by prepare/fetch/complete and an explicit acknowledgement after the workload
|
|
497
|
+
has processed the exact byte count and SHA-256 digest. `data.coreMailLimits`
|
|
498
|
+
defines the 64 KiB control-frame boundary, bounded structured content, the
|
|
499
|
+
30 MiB serialized MIME ceiling, transfer deadlines, and five-minute grant
|
|
500
|
+
lifetime.
|
|
501
|
+
|
|
502
|
+
Coreflow applies `data.ICoreMailDesiredState` with a config-epoch
|
|
503
|
+
compare-and-set fence. It stages the digest-fenced JSON snapshot through a
|
|
504
|
+
bounded one-time HTTP upload, then applies it by reconciliation ID, so a large
|
|
505
|
+
binding set never bypasses the 64 KiB control-frame boundary. Desired bindings
|
|
506
|
+
contain password verifiers and secret references only, never plaintext
|
|
507
|
+
workload or dcrouter credentials.
|
|
508
|
+
Reconciliation status carries the exact CoreMail task, rollout generation, and
|
|
509
|
+
image digest so Coreflow can correlate every response with its authoritative
|
|
510
|
+
current task roster.
|
|
511
|
+
|
|
359
512
|
Request groups are exported by product area:
|
|
360
513
|
|
|
361
514
|
- `requests.admin`
|
|
@@ -366,6 +519,7 @@ Request groups are exported by product area:
|
|
|
366
519
|
- `requests.certificate`
|
|
367
520
|
- `requests.cluster`
|
|
368
521
|
- `requests.config`
|
|
522
|
+
- `requests.coremail`
|
|
369
523
|
- `requests.corestore`
|
|
370
524
|
- `requests.deployment`
|
|
371
525
|
- `requests.dns`
|
|
@@ -438,6 +592,8 @@ Available platform modules:
|
|
|
438
592
|
- `platform.pushnotification` is the deprecated legacy device-token push contract. New browser Web Push integrations use `requests.webpush`.
|
|
439
593
|
- `platform.letter` for physical letter workflows.
|
|
440
594
|
- `platform.ai`, `platform.database`, `platform.objectstorage`, `platform.logging`, `platform.backup`, and `platform.sip` for infrastructure and application capabilities.
|
|
595
|
+
- `platform.storage` for provider-neutral storage classes, requests, capabilities, and resolved bindings.
|
|
596
|
+
- `platform.storagemigration` for fenced object-storage migration intent, status, consumer acknowledgements, canonical digests, and strict normalizers.
|
|
441
597
|
- `platform.types` plus root re-exports for shared capability, provider, binding, credential, and endpoint metadata.
|
|
442
598
|
|
|
443
599
|
## Legacy Platformservice Contracts
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CoreMail contracts.
|
|
3
|
+
*
|
|
4
|
+
* Workload authority is always derived from an authenticated transport
|
|
5
|
+
* session. Application-facing request DTOs therefore carry message or
|
|
6
|
+
* delivery identifiers only and never caller-selected tenant, service, or
|
|
7
|
+
* binding authority.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type TCoreMailSha256 = `sha256:${string}`;
|
|
11
|
+
|
|
12
|
+
export type TCoreMailCapability = 'outbound' | 'inbound';
|
|
13
|
+
export type TCoreMailBindingState = 'active' | 'disabled';
|
|
14
|
+
export type TCoreMailCredentialState = 'current' | 'retiring';
|
|
15
|
+
export type TCoreMailPartKind = 'text' | 'html' | 'attachment';
|
|
16
|
+
export type TCoreMailTransferMethod = 'PUT' | 'GET';
|
|
17
|
+
export type TCoreMailSubmissionState =
|
|
18
|
+
| 'preparing'
|
|
19
|
+
| 'uploading'
|
|
20
|
+
| 'ready'
|
|
21
|
+
| 'accepted'
|
|
22
|
+
| 'queued'
|
|
23
|
+
| 'delivering'
|
|
24
|
+
| 'delivered'
|
|
25
|
+
| 'deferred'
|
|
26
|
+
| 'failed'
|
|
27
|
+
| 'deadLettered';
|
|
28
|
+
export type TCoreMailInboundDeliveryState = 'pending' | 'fetching' | 'fetched' | 'acknowledged';
|
|
29
|
+
export type TCoreMailInboundAckOutcome = 'processed' | 'discarded';
|
|
30
|
+
export type TCoreMailRecipientAction = 'accept' | 'reject' | 'defer';
|
|
31
|
+
export type TCoreMailReconciliationState = 'applying' | 'ready' | 'failed';
|
|
32
|
+
|
|
33
|
+
export type TCoreMailErrorCode =
|
|
34
|
+
| 'AUTHENTICATION_FAILED'
|
|
35
|
+
| 'AUTHENTICATION_EXPIRED'
|
|
36
|
+
| 'AUTHORITY_REVOKED'
|
|
37
|
+
| 'CAPABILITY_DENIED'
|
|
38
|
+
| 'NOT_FOUND'
|
|
39
|
+
| 'INVALID_REQUEST'
|
|
40
|
+
| 'INVALID_MAILBOX'
|
|
41
|
+
| 'INVALID_SENDER'
|
|
42
|
+
| 'INVALID_RECIPIENT'
|
|
43
|
+
| 'INVALID_HEADER'
|
|
44
|
+
| 'PAYLOAD_LIMIT_EXCEEDED'
|
|
45
|
+
| 'IDEMPOTENCY_CONFLICT'
|
|
46
|
+
| 'STATE_CONFLICT'
|
|
47
|
+
| 'TRANSFER_GRANT_EXPIRED'
|
|
48
|
+
| 'TRANSFER_GRANT_REPLAYED'
|
|
49
|
+
| 'TRANSFER_LENGTH_MISMATCH'
|
|
50
|
+
| 'TRANSFER_DIGEST_MISMATCH'
|
|
51
|
+
| 'TRANSFER_ENCODING_UNSUPPORTED'
|
|
52
|
+
| 'OBJECT_INTEGRITY_CONFLICT'
|
|
53
|
+
| 'DELIVERY_NOT_FETCHED'
|
|
54
|
+
| 'GATEWAY_UNAVAILABLE'
|
|
55
|
+
| 'RECONCILIATION_FENCE_MISMATCH';
|
|
56
|
+
|
|
57
|
+
export interface ICoreMailErrorData {
|
|
58
|
+
code: TCoreMailErrorCode;
|
|
59
|
+
retryable: boolean;
|
|
60
|
+
retryAfterMs?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ICoreMailMailbox {
|
|
64
|
+
address: string;
|
|
65
|
+
displayName?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ICoreMailEnvelope {
|
|
69
|
+
mailFrom: string;
|
|
70
|
+
rcptTo: string[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ICoreMailHeader {
|
|
74
|
+
name: string;
|
|
75
|
+
value: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ICoreMailContentDescriptor {
|
|
79
|
+
partId: string;
|
|
80
|
+
kind: TCoreMailPartKind;
|
|
81
|
+
contentType: string;
|
|
82
|
+
filename?: string;
|
|
83
|
+
contentId?: string;
|
|
84
|
+
sha256: TCoreMailSha256;
|
|
85
|
+
lengthBytes: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ICoreMailOutboundMessageDescriptor {
|
|
89
|
+
sender: ICoreMailMailbox;
|
|
90
|
+
recipients: {
|
|
91
|
+
to: ICoreMailMailbox[];
|
|
92
|
+
cc?: ICoreMailMailbox[];
|
|
93
|
+
bcc?: ICoreMailMailbox[];
|
|
94
|
+
};
|
|
95
|
+
replyTo?: ICoreMailMailbox;
|
|
96
|
+
subject: string;
|
|
97
|
+
/** Ordered headers. Transport-owned MIME headers are not accepted here. */
|
|
98
|
+
headers?: ICoreMailHeader[];
|
|
99
|
+
/** Ordered body and attachment descriptors. Bytes move through bounded HTTP transfers. */
|
|
100
|
+
parts: ICoreMailContentDescriptor[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface ICoreMailTransferGrant {
|
|
104
|
+
grantId: string;
|
|
105
|
+
method: TCoreMailTransferMethod;
|
|
106
|
+
/** Same-origin path. Callers use the authenticated CoreMail or gateway endpoint they already hold. */
|
|
107
|
+
path: string;
|
|
108
|
+
/** One-time bearer capability. It must never be logged or persisted by a consumer. */
|
|
109
|
+
bearerToken: string;
|
|
110
|
+
sha256: TCoreMailSha256;
|
|
111
|
+
lengthBytes: number;
|
|
112
|
+
contentType: string;
|
|
113
|
+
expiresAt: number;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface ICoreMailUploadGrant extends ICoreMailTransferGrant {
|
|
117
|
+
method: 'PUT';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface ICoreMailDownloadGrant extends ICoreMailTransferGrant {
|
|
121
|
+
method: 'GET';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface ICoreMailPartStatus {
|
|
125
|
+
partId: string;
|
|
126
|
+
state: 'missing' | 'uploading' | 'complete' | 'failed';
|
|
127
|
+
sha256: TCoreMailSha256;
|
|
128
|
+
lengthBytes: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface ICoreMailSubmission {
|
|
132
|
+
submissionId: string;
|
|
133
|
+
idempotencyKey: string;
|
|
134
|
+
submissionDigest: TCoreMailSha256;
|
|
135
|
+
state: TCoreMailSubmissionState;
|
|
136
|
+
parts: ICoreMailPartStatus[];
|
|
137
|
+
transportMessageId?: string;
|
|
138
|
+
attempts: number;
|
|
139
|
+
nextAttemptAt?: number;
|
|
140
|
+
acceptedAt?: number;
|
|
141
|
+
deliveredAt?: number;
|
|
142
|
+
createdAt: number;
|
|
143
|
+
updatedAt: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface ICoreMailInboundDelivery {
|
|
147
|
+
deliveryId: string;
|
|
148
|
+
transportDeliveryId: string;
|
|
149
|
+
state: TCoreMailInboundDeliveryState;
|
|
150
|
+
envelope: ICoreMailEnvelope;
|
|
151
|
+
rawMime: {
|
|
152
|
+
sha256: TCoreMailSha256;
|
|
153
|
+
lengthBytes: number;
|
|
154
|
+
contentType: 'message/rfc822';
|
|
155
|
+
};
|
|
156
|
+
messageId?: string;
|
|
157
|
+
subject?: string;
|
|
158
|
+
receivedAt: number;
|
|
159
|
+
updatedAt: number;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface ICoreMailBindingCredentialVerifier {
|
|
163
|
+
credentialId: string;
|
|
164
|
+
version: number;
|
|
165
|
+
state: TCoreMailCredentialState;
|
|
166
|
+
/** Password-verifier string only. Plaintext credential material is never part of desired state. */
|
|
167
|
+
verificationHash: string;
|
|
168
|
+
acceptUntil?: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface ICoreMailBindingDesiredState {
|
|
172
|
+
schemaVersion: 1;
|
|
173
|
+
bindingId: string;
|
|
174
|
+
serviceId: string;
|
|
175
|
+
tenantId: string;
|
|
176
|
+
revision: number;
|
|
177
|
+
state: TCoreMailBindingState;
|
|
178
|
+
capabilities: TCoreMailCapability[];
|
|
179
|
+
credentials: ICoreMailBindingCredentialVerifier[];
|
|
180
|
+
allowedSenders: string[];
|
|
181
|
+
inboundRecipients: string[];
|
|
182
|
+
defaultSender?: string;
|
|
183
|
+
limits?: {
|
|
184
|
+
messagesPerMinute?: number;
|
|
185
|
+
messagesPerDay?: number;
|
|
186
|
+
maxPendingInbound?: number;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface ICoreMailGatewayDesiredState {
|
|
191
|
+
endpointUrl: string;
|
|
192
|
+
credentialId: string;
|
|
193
|
+
credentialVersion: number;
|
|
194
|
+
/** Key in CoreMail's resolved runtime secret material, never a plaintext value. */
|
|
195
|
+
credentialSecretKey: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface ICoreMailDesiredState {
|
|
199
|
+
schemaVersion: 1;
|
|
200
|
+
configEpoch: number;
|
|
201
|
+
bindings: ICoreMailBindingDesiredState[];
|
|
202
|
+
gateway: ICoreMailGatewayDesiredState;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface ICoreMailReplicaIdentity {
|
|
206
|
+
taskId: string;
|
|
207
|
+
serviceId: string;
|
|
208
|
+
rolloutId: string;
|
|
209
|
+
rolloutGeneration: number;
|
|
210
|
+
imageDigest: TCoreMailSha256;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface ICoreMailBindingReconciliationStatus {
|
|
214
|
+
bindingId: string;
|
|
215
|
+
revision: number;
|
|
216
|
+
state: TCoreMailBindingState;
|
|
217
|
+
activeSessionsByCredentialVersion: Array<{
|
|
218
|
+
version: number;
|
|
219
|
+
count: number;
|
|
220
|
+
}>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface ICoreMailReconciliationStatus {
|
|
224
|
+
replica: ICoreMailReplicaIdentity;
|
|
225
|
+
state: TCoreMailReconciliationState;
|
|
226
|
+
appliedConfigEpoch: number;
|
|
227
|
+
appliedDesiredStateDigest: TCoreMailSha256;
|
|
228
|
+
bindings: ICoreMailBindingReconciliationStatus[];
|
|
229
|
+
updatedAt: number;
|
|
230
|
+
errorCode?: TCoreMailErrorCode;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface ICoreMailRecipientResolution {
|
|
234
|
+
recipient: string;
|
|
235
|
+
action: TCoreMailRecipientAction;
|
|
236
|
+
/** Opaque, short-lived handle returned only for accepted recipients. */
|
|
237
|
+
routingHandle?: string;
|
|
238
|
+
smtpCode?: number;
|
|
239
|
+
message?: string;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface ICoreMailGatewayMessageDescriptor {
|
|
243
|
+
envelope: ICoreMailEnvelope;
|
|
244
|
+
rawMime: {
|
|
245
|
+
sha256: TCoreMailSha256;
|
|
246
|
+
lengthBytes: number;
|
|
247
|
+
contentType: 'message/rfc822';
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface ICoreMailGatewayOutboundStatus {
|
|
252
|
+
transportMessageId: string;
|
|
253
|
+
state: Extract<
|
|
254
|
+
TCoreMailSubmissionState,
|
|
255
|
+
'accepted' | 'queued' | 'delivering' | 'delivered' | 'deferred' | 'failed' | 'deadLettered'
|
|
256
|
+
>;
|
|
257
|
+
attempts: number;
|
|
258
|
+
nextAttemptAt?: number;
|
|
259
|
+
smtpCode?: number;
|
|
260
|
+
updatedAt: number;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export const coreMailLimits = Object.freeze({
|
|
264
|
+
controlMessageBytes: 64 * 1024,
|
|
265
|
+
desiredStateBytes: 8 * 1024 * 1024,
|
|
266
|
+
textPartBytes: 1 * 1024 * 1024,
|
|
267
|
+
htmlPartBytes: 2 * 1024 * 1024,
|
|
268
|
+
attachmentCount: 16,
|
|
269
|
+
attachmentBytes: 10 * 1024 * 1024,
|
|
270
|
+
aggregateAttachmentBytes: 17 * 1024 * 1024,
|
|
271
|
+
mimeFramingBytes: 512 * 1024,
|
|
272
|
+
serializedMimeBytes: 30 * 1024 * 1024,
|
|
273
|
+
transferGrantTtlMs: 5 * 60 * 1000,
|
|
274
|
+
transferHeaderTimeoutMs: 10 * 1000,
|
|
275
|
+
transferIdleTimeoutMs: 15 * 1000,
|
|
276
|
+
transferOverallTimeoutMs: 2 * 60 * 1000,
|
|
277
|
+
} as const);
|
package/ts/data/index.ts
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1553
|
+
return createCanonicalJsonSha256Hex(canonicalJsonArg, fail);
|
|
1635
1554
|
};
|
|
1636
1555
|
|
|
1637
1556
|
export const createIsolatedRestoreBytesSha256 = async (
|
package/ts/platform/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import * as pushnotification from './pushnotification.js';
|
|
|
9
9
|
import * as sip from './sip.js';
|
|
10
10
|
import * as sms from './sms.js';
|
|
11
11
|
import * as storage from './storage.js';
|
|
12
|
+
import * as storagemigration from './storagemigration.js';
|
|
12
13
|
import * as types from './types.js';
|
|
13
14
|
|
|
14
15
|
export {
|
|
@@ -23,8 +24,10 @@ export {
|
|
|
23
24
|
sip,
|
|
24
25
|
sms,
|
|
25
26
|
storage,
|
|
27
|
+
storagemigration,
|
|
26
28
|
types,
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
export * from './storage.js';
|
|
32
|
+
export * from './storagemigration.js';
|
|
30
33
|
export * from './types.js';
|