@void/platform 0.20.1 → 0.20.3

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 (32) hide show
  1. package/README.md +10 -1
  2. package/dist/email-protocol.d.mts +48 -0
  3. package/dist/email-protocol.mjs +278 -0
  4. package/dist/index.d.mts +154 -15
  5. package/dist/index.mjs +349 -22
  6. package/dist/installer.d.mts +5 -3
  7. package/dist/installer.mjs +14 -5
  8. package/dist/runtime/api/README.md +1 -1
  9. package/dist/runtime/api/index.js +79311 -65332
  10. package/dist/runtime/api/index.js.map +4 -4
  11. package/dist/runtime/dispatch/README.md +1 -1
  12. package/dist/runtime/dispatch/index.js +35 -13
  13. package/dist/runtime/dispatch/index.js.map +2 -2
  14. package/dist/runtime/drizzle/0041_military_beyonder.sql +119 -0
  15. package/dist/runtime/drizzle/0042_dark_the_anarchist.sql +1 -0
  16. package/dist/runtime/drizzle/0043_nostalgic_meggan.sql +21 -0
  17. package/dist/runtime/drizzle/0044_smart_mentor.sql +15 -0
  18. package/dist/runtime/drizzle/0045_platform_settings.sql +5 -0
  19. package/dist/runtime/drizzle/0046_email_domains_platform_credential.sql +8 -0
  20. package/dist/runtime/drizzle/0047_email_gateway.sql +226 -0
  21. package/dist/runtime/drizzle/0048_project_teams.sql +133 -0
  22. package/dist/runtime/drizzle/compatibility.json +4 -0
  23. package/dist/runtime/drizzle/meta/_journal.json +56 -0
  24. package/dist/runtime/emailGateway/README.md +1 -0
  25. package/dist/runtime/emailGateway/index.js +5893 -0
  26. package/dist/runtime/emailGateway/index.js.map +8 -0
  27. package/dist/runtime/manifest.json +71 -12
  28. package/dist/runtime/proxy/README.md +1 -1
  29. package/dist/runtime/proxy/index.js +1646 -2240
  30. package/dist/runtime/proxy/index.js.map +4 -4
  31. package/dist/runtime/tail/README.md +1 -1
  32. package/package.json +5 -1
package/README.md CHANGED
@@ -16,12 +16,19 @@ for administrator sessions and audit records. See
16
16
  [Platform Administration](../../docs/guide/platform-administration.md) to operate
17
17
  an installed platform from your terminal.
18
18
 
19
+ Platforms can use GitHub, Google, generic OIDC, Cloudflare Access, or a combination.
20
+ Access protection is independent from login methods and can admit company-approved
21
+ users automatically. Login settings, explicit account linking, and provider recovery
22
+ are available through Void; upgrades preserve live settings and reject runtimes
23
+ that cannot enforce their session revocation. See the installation and administration
24
+ guides for setup, scoped CI credentials, and recovery requirements.
25
+
19
26
  The package contains:
20
27
 
21
28
  - versioned deployment and installation manifests
22
29
  - deterministic resource naming and application-route generation
23
30
  - discovery-document and capability contracts
24
- - Cloudflare Worker configuration generation for the core API, proxy, tail, and dispatch Workers
31
+ - Cloudflare Worker configuration generation for the API, proxy, tail, dispatch, and email gateway Workers
25
32
  - A shared installation-owned traffic-disable protocol used by lifecycle-safe retained Workers
26
33
  - integrity-checked, deployable Worker bundles plus a schema-versioned PostgreSQL migration and rollback-compatibility manifest under the `./installer` export
27
34
 
@@ -31,6 +38,8 @@ Interactive lifecycle operations authenticate the human operator through Cloudfl
31
38
 
32
39
  Fresh installations require a unique JWT signing secret with at least 32 bytes of random material and a unique, canonical base64-encoded 32-byte project-encryption key from the operator's durable secret manager. The installer never makes an ephemeral workstation or CI registry the only custodian of these installation identity keys. Existing Workers preserve their deployed values during routine lifecycle operations.
33
40
 
41
+ Email-enabled installations also need an independent email signing key. The installer generates it in encrypted recovery state, or accepts `VOID_PLATFORM_EMAIL_SIGNING_SECRET` from the operator's secret manager. Keep that original value for recovery on another machine and for headless upgrades without persistent recovery files.
42
+
34
43
  Browser OAuth cannot manage AI Gateway. Installation previews mark an inaccessible gateway as requiring inspection; apply rechecks it with the already-required runtime token (AI Gateway: Edit) before provisioning. All other provider operations keep the operator credential. Later OAuth lifecycle operations can reuse that token from encrypted local recovery storage. On another workstation, use a management API token with AI Gateway access or supply the runtime token explicitly for that check.
35
44
 
36
45
  Runtime and installation manifests record the last usage protocol proven live across the complete Worker set. A protocol transition gates traffic before the first Worker update and opens it only after target health and immutable versions pass; interrupted transitions remain gated and resumable.
@@ -0,0 +1,48 @@
1
+ //#region src/email-protocol.d.ts
2
+ /** Worker-safe wire contract shared by the platform gateway and tenant entry. */
3
+ export declare const EMAIL_PROTOCOL_VERSION = 1;
4
+ export declare const EMAIL_RECEIPT_PATH = "/__void/email/receipt/v1";
5
+ export declare const EMAIL_MAX_ACTIONS: number;
6
+ export declare const EMAIL_MAX_METADATA_BYTES: number;
7
+ export declare const EMAIL_MAX_RESPONSE_BYTES: number;
8
+ export declare const EMAIL_MAX_REPLY_BYTES: number;
9
+ export declare const EMAIL_MAX_INBOUND_BYTES: number;
10
+ export declare const EMAIL_MAX_INBOUND_HEADERS: number;
11
+ export declare const EMAIL_MAX_INBOUND_HEADER_BYTES: number;
12
+ export type EmailWireAction = {
13
+ type: "reject";
14
+ reason: string;
15
+ } | {
16
+ type: "forward";
17
+ to: string;
18
+ headers?: Record<string, string>;
19
+ } | {
20
+ type: "reply";
21
+ from: string;
22
+ to: string;
23
+ raw: Uint8Array;
24
+ };
25
+ export type EmailReceiptClaims = {
26
+ version: 1;
27
+ receiptId: string;
28
+ projectId: string;
29
+ deploymentId: string;
30
+ recipient: string;
31
+ expiresAt: number;
32
+ };
33
+ export type EmailWireEnvelope = {
34
+ from: string;
35
+ to: string;
36
+ headers: Record<string, string>;
37
+ raw: Uint8Array;
38
+ rawSize: number;
39
+ viaDomain?: string;
40
+ };
41
+ export declare function encodeEmailEnvelope(envelope: EmailWireEnvelope): Uint8Array;
42
+ export declare function readEmailEnvelope(stream: ReadableStream<Uint8Array> | null, deadlineMs?: number): Promise<EmailWireEnvelope>;
43
+ export declare function signEmailReceipt(claims: EmailReceiptClaims, signingSecret: string): Promise<string>;
44
+ export declare function verifyEmailReceipt(token: string, signingSecret: string, expected: Pick<EmailReceiptClaims, "projectId" | "deploymentId" | "recipient">, now?: number): Promise<EmailReceiptClaims | null>;
45
+ export declare function encodeEmailActions(actions: ReadonlyArray<EmailWireAction>): ReadableStream<Uint8Array>;
46
+ /** Read each frame with fixed ceilings before parsing metadata or allocating a reply. */
47
+ export declare function readEmailActions(stream: ReadableStream<Uint8Array>, deadlineMs?: number): Promise<Array<EmailWireAction>>;
48
+ //#endregion
@@ -0,0 +1,278 @@
1
+ //#region src/email-protocol.ts
2
+ /** Worker-safe wire contract shared by the platform gateway and tenant entry. */
3
+ const EMAIL_PROTOCOL_VERSION = 1;
4
+ const EMAIL_RECEIPT_PATH = "/__void/email/receipt/v1";
5
+ const EMAIL_MAX_ACTIONS = 100;
6
+ const EMAIL_MAX_METADATA_BYTES = 8192;
7
+ const EMAIL_MAX_RESPONSE_BYTES = 8388608;
8
+ const EMAIL_MAX_REPLY_BYTES = 5242880;
9
+ const EMAIL_MAX_INBOUND_BYTES = 10485760;
10
+ const EMAIL_MAX_INBOUND_HEADERS = 1024;
11
+ const EMAIL_MAX_INBOUND_HEADER_BYTES = 131072;
12
+ const encoder = new TextEncoder();
13
+ const decoder = new TextDecoder("utf-8", { fatal: true });
14
+ function assertFlatJson(bytes, maxDepth, maxFields) {
15
+ let depth = 0;
16
+ let fields = 0;
17
+ let quoted = false;
18
+ let escaped = false;
19
+ for (const byte of bytes) if (quoted) {
20
+ if (escaped) escaped = false;
21
+ else if (byte === 92) escaped = true;
22
+ else if (byte === 34) quoted = false;
23
+ } else if (byte === 34) quoted = true;
24
+ else if (byte === 123 || byte === 91) {
25
+ if (++depth > maxDepth) throw new Error("email metadata too deep");
26
+ } else if (byte === 125 || byte === 93) {
27
+ if (--depth < 0) throw new Error("invalid email metadata");
28
+ } else if (byte === 58 && ++fields > maxFields) throw new Error("too many email metadata fields");
29
+ if (quoted || depth !== 0) throw new Error("invalid email metadata");
30
+ }
31
+ function encodeEmailEnvelope(envelope) {
32
+ const metadata = encoder.encode(JSON.stringify({
33
+ from: envelope.from,
34
+ to: envelope.to,
35
+ headers: envelope.headers,
36
+ rawSize: envelope.rawSize,
37
+ ...envelope.viaDomain ? { viaDomain: envelope.viaDomain } : {}
38
+ }));
39
+ if (metadata.byteLength > 131072 || envelope.raw.byteLength > 10485760) throw new Error("email envelope too large");
40
+ const bytes = new Uint8Array(4 + metadata.byteLength + envelope.raw.byteLength);
41
+ new DataView(bytes.buffer).setUint32(0, metadata.byteLength);
42
+ bytes.set(metadata, 4);
43
+ bytes.set(envelope.raw, 4 + metadata.byteLength);
44
+ return bytes;
45
+ }
46
+ async function readEmailEnvelope(stream, deadlineMs = 15e3) {
47
+ if (!stream) throw new Error("missing email envelope");
48
+ const reader = stream.getReader();
49
+ const limit = 10616836;
50
+ let bytes = /* @__PURE__ */ new Uint8Array(1024);
51
+ let total = 0;
52
+ const deadline = Date.now() + deadlineMs;
53
+ try {
54
+ for (;;) {
55
+ const remaining = deadline - Date.now();
56
+ if (remaining <= 0) throw new Error("email envelope timeout");
57
+ let timer;
58
+ const chunk = await Promise.race([reader.read(), new Promise((_, reject) => {
59
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error("email envelope timeout")), remaining);
60
+ })]).finally(() => {
61
+ if (timer !== void 0) clearTimeout(timer);
62
+ });
63
+ if (chunk.done) break;
64
+ const nextTotal = total + chunk.value.byteLength;
65
+ if (nextTotal > limit) throw new Error("email envelope too large");
66
+ if (nextTotal > bytes.byteLength) {
67
+ let capacity = bytes.byteLength;
68
+ while (capacity < nextTotal) capacity = Math.min(limit, capacity * 2);
69
+ const grown = new Uint8Array(capacity);
70
+ grown.set(bytes.subarray(0, total));
71
+ bytes = grown;
72
+ }
73
+ bytes.set(chunk.value, total);
74
+ total = nextTotal;
75
+ }
76
+ } catch (error) {
77
+ reader.cancel().catch(() => {});
78
+ throw error;
79
+ } finally {
80
+ try {
81
+ reader.releaseLock();
82
+ } catch {}
83
+ }
84
+ if (total < 4) throw new Error("truncated email envelope");
85
+ const metadataLength = new DataView(bytes.buffer).getUint32(0);
86
+ if (metadataLength > 131072 || total < 4 + metadataLength) throw new Error("invalid email envelope");
87
+ const metadataBytes = bytes.subarray(4, 4 + metadataLength);
88
+ assertFlatJson(metadataBytes, 2, 1032);
89
+ const metadata = JSON.parse(decoder.decode(metadataBytes));
90
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) throw new Error("invalid email envelope");
91
+ const value = metadata;
92
+ const raw = bytes.subarray(4 + metadataLength, total);
93
+ if (raw.byteLength > 10485760 || typeof value.from !== "string" || value.from.length > 320 || typeof value.to !== "string" || value.to.length > 320 || typeof value.rawSize !== "number" || value.rawSize !== raw.byteLength || !value.headers || typeof value.headers !== "object" || Array.isArray(value.headers) || Object.keys(value.headers).length > 1024 || Object.values(value.headers).some((header) => typeof header !== "string") || value.viaDomain !== void 0 && (typeof value.viaDomain !== "string" || value.viaDomain.length > 253)) throw new Error("invalid email envelope");
94
+ return {
95
+ from: value.from,
96
+ to: value.to,
97
+ headers: value.headers,
98
+ raw,
99
+ rawSize: raw.byteLength,
100
+ ...value.viaDomain ? { viaDomain: value.viaDomain } : {}
101
+ };
102
+ }
103
+ function base64url(bytes) {
104
+ let binary = "";
105
+ for (const byte of bytes) binary += String.fromCharCode(byte);
106
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
107
+ }
108
+ function unbase64url(value) {
109
+ if (!/^[A-Za-z0-9_-]+$/.test(value) || value.length > 1024) throw new Error("invalid receipt");
110
+ const binary = atob(value.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - value.length % 4) % 4));
111
+ return Uint8Array.from(binary, (char) => char.charCodeAt(0));
112
+ }
113
+ async function receiptKey(secret, usage) {
114
+ if (!secret) throw new Error("Email signing key is not configured");
115
+ return crypto.subtle.importKey("raw", encoder.encode(secret), {
116
+ name: "HMAC",
117
+ hash: "SHA-256"
118
+ }, false, [usage]);
119
+ }
120
+ async function signEmailReceipt(claims, signingSecret) {
121
+ const payload = encoder.encode(JSON.stringify(claims));
122
+ const signature = new Uint8Array(await crypto.subtle.sign("HMAC", await receiptKey(signingSecret, "sign"), payload));
123
+ return `${base64url(payload)}.${base64url(signature)}`;
124
+ }
125
+ async function verifyEmailReceipt(token, signingSecret, expected, now = Date.now()) {
126
+ try {
127
+ if (token.length > 1536) return null;
128
+ const parts = token.split(".");
129
+ if (parts.length !== 2) return null;
130
+ const payload = unbase64url(parts[0]);
131
+ const signature = unbase64url(parts[1]);
132
+ if (payload.byteLength > 768 || signature.byteLength !== 32) return null;
133
+ if (!await crypto.subtle.verify("HMAC", await receiptKey(signingSecret, "verify"), Uint8Array.from(signature), Uint8Array.from(payload))) return null;
134
+ const claims = JSON.parse(decoder.decode(payload));
135
+ if (typeof claims !== "object" || claims === null) return null;
136
+ const value = claims;
137
+ if (value.version !== 1 || typeof value.receiptId !== "string" || !/^[a-f0-9-]{16,64}$/.test(value.receiptId) || value.projectId !== expected.projectId || value.deploymentId !== expected.deploymentId || value.recipient !== expected.recipient || typeof value.expiresAt !== "number" || !Number.isSafeInteger(value.expiresAt) || value.expiresAt < now || value.expiresAt > now + 6e4) return null;
138
+ return value;
139
+ } catch {
140
+ return null;
141
+ }
142
+ }
143
+ function frame(kind, payload) {
144
+ const bytes = new Uint8Array(5 + payload.byteLength);
145
+ new DataView(bytes.buffer).setUint32(0, payload.byteLength);
146
+ bytes[4] = kind;
147
+ bytes.set(payload, 5);
148
+ return bytes;
149
+ }
150
+ function encodeEmailActionMetadata(action) {
151
+ return encoder.encode(JSON.stringify(action.type === "reply" ? {
152
+ type: action.type,
153
+ from: action.from,
154
+ to: action.to
155
+ } : action));
156
+ }
157
+ function emailActionWireSizeFromMetadata(action, metadata) {
158
+ return 5 + metadata.byteLength + (action.type === "reply" ? 5 + action.raw.byteLength : 0);
159
+ }
160
+ function encodeEmailActions(actions) {
161
+ if (actions.length > 100) throw new Error("too many email actions");
162
+ let index = 0;
163
+ let pending = null;
164
+ let total = 0;
165
+ return new ReadableStream({ pull(controller) {
166
+ if (pending) {
167
+ controller.enqueue(pending);
168
+ pending = null;
169
+ return;
170
+ }
171
+ if (index === actions.length) {
172
+ controller.enqueue(frame(0, /* @__PURE__ */ new Uint8Array()));
173
+ controller.close();
174
+ return;
175
+ }
176
+ const action = actions[index++];
177
+ const metadata = encodeEmailActionMetadata(action);
178
+ if (metadata.byteLength > 8192) throw new Error("email action metadata too large");
179
+ if (action.type === "reply") {
180
+ if (action.raw.byteLength > 5242880) throw new Error("email reply too large");
181
+ pending = frame(2, action.raw);
182
+ }
183
+ total += emailActionWireSizeFromMetadata(action, metadata);
184
+ if (total + 5 > 8388608) throw new Error("email actions too large");
185
+ controller.enqueue(frame(1, metadata));
186
+ } });
187
+ }
188
+ /** Read each frame with fixed ceilings before parsing metadata or allocating a reply. */
189
+ async function readEmailActions(stream, deadlineMs = 15e3) {
190
+ const reader = stream.getReader();
191
+ const actions = [];
192
+ let stash = /* @__PURE__ */ new Uint8Array(0);
193
+ let total = 0;
194
+ const deadline = Date.now() + deadlineMs;
195
+ async function readChunk() {
196
+ const remaining = deadline - Date.now();
197
+ if (remaining <= 0) throw new Error("email response timeout");
198
+ let timer;
199
+ return Promise.race([reader.read(), new Promise((_, reject) => {
200
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error("email response timeout")), remaining);
201
+ })]).finally(() => {
202
+ if (timer !== void 0) clearTimeout(timer);
203
+ });
204
+ }
205
+ async function exact(length) {
206
+ const result = new Uint8Array(length);
207
+ let offset = 0;
208
+ while (offset < length) {
209
+ if (stash.byteLength === 0) {
210
+ const chunk = await readChunk();
211
+ if (chunk.done) throw new Error("truncated email response");
212
+ total += chunk.value.byteLength;
213
+ if (total > 8388608) throw new Error("email response too large");
214
+ stash = chunk.value;
215
+ }
216
+ const used = Math.min(stash.byteLength, length - offset);
217
+ result.set(stash.subarray(0, used), offset);
218
+ stash = stash.subarray(used);
219
+ offset += used;
220
+ }
221
+ return result;
222
+ }
223
+ try {
224
+ for (;;) {
225
+ const head = await exact(5);
226
+ const length = new DataView(head.buffer).getUint32(0);
227
+ const kind = head[4];
228
+ if (kind === 0) {
229
+ if (length !== 0 || stash.byteLength !== 0) throw new Error("invalid email response tail");
230
+ if (!(await readChunk()).done) throw new Error("invalid email response tail");
231
+ if (actions.some((action) => action.type === "reject") && actions.length !== 1) throw new Error("email reject cannot accompany delivery actions");
232
+ return actions;
233
+ }
234
+ if (kind !== 1 || length > 8192 || actions.length >= 100) throw new Error("invalid email action frame");
235
+ const metadataBytes = await exact(length);
236
+ assertFlatJson(metadataBytes, 2, 40);
237
+ const value = JSON.parse(decoder.decode(metadataBytes));
238
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid email action");
239
+ const action = value;
240
+ if (action.type === "reply") {
241
+ if (typeof action.from !== "string" || typeof action.to !== "string" || Object.keys(action).length !== 3) throw new Error("invalid email reply");
242
+ const rawHead = await exact(5);
243
+ const rawLength = new DataView(rawHead.buffer).getUint32(0);
244
+ if (rawHead[4] !== 2 || rawLength > 5242880) throw new Error("invalid email reply frame");
245
+ actions.push({
246
+ type: "reply",
247
+ from: action.from,
248
+ to: action.to,
249
+ raw: await exact(rawLength)
250
+ });
251
+ } else if (action.type === "forward") {
252
+ if (typeof action.to !== "string" || Object.keys(action).length > 3 || action.headers !== void 0 && (typeof action.headers !== "object" || action.headers === null || Array.isArray(action.headers))) throw new Error("invalid email forward");
253
+ const headers = action.headers;
254
+ if (headers && (Object.keys(headers).length > 32 || Object.values(headers).some((v) => typeof v !== "string"))) throw new Error("invalid email headers");
255
+ actions.push({
256
+ type: "forward",
257
+ to: action.to,
258
+ ...headers ? { headers } : {}
259
+ });
260
+ } else if (action.type === "reject") {
261
+ if (typeof action.reason !== "string" || Object.keys(action).length !== 2) throw new Error("invalid email reject");
262
+ actions.push({
263
+ type: "reject",
264
+ reason: action.reason
265
+ });
266
+ } else throw new Error("unknown email action");
267
+ }
268
+ } catch (error) {
269
+ reader.cancel().catch(() => {});
270
+ throw error;
271
+ } finally {
272
+ try {
273
+ reader.releaseLock();
274
+ } catch {}
275
+ }
276
+ }
277
+ //#endregion
278
+ export { EMAIL_MAX_ACTIONS, EMAIL_MAX_INBOUND_BYTES, EMAIL_MAX_INBOUND_HEADERS, EMAIL_MAX_INBOUND_HEADER_BYTES, EMAIL_MAX_METADATA_BYTES, EMAIL_MAX_REPLY_BYTES, EMAIL_MAX_RESPONSE_BYTES, EMAIL_PROTOCOL_VERSION, EMAIL_RECEIPT_PATH, encodeEmailActions, encodeEmailEnvelope, readEmailActions, readEmailEnvelope, signEmailReceipt, verifyEmailReceipt };
package/dist/index.d.mts CHANGED
@@ -1,3 +1,115 @@
1
+ //#region src/authentication-recovery.d.ts
2
+ /** Owner recovery capabilities authorize a provider test, never a platform session. */
3
+ type PlatformAuthenticationRecovery = Readonly<{
4
+ installationId: string;
5
+ administratorUserId: string;
6
+ connectionId: string;
7
+ revision: number;
8
+ generation: number;
9
+ testId: string;
10
+ expiresAt: string;
11
+ signature: string;
12
+ }>;
13
+ export declare function platformAuthenticationRecoveryMessage(value: Omit<PlatformAuthenticationRecovery, "signature">): string;
14
+ //#endregion
15
+ //#region src/authentication.d.ts
16
+ /** Platform login and perimeter configuration are independent of app authentication. */
17
+ export declare const PLATFORM_LOGIN_KINDS: readonly ["github", "google", "oidc", "cloudflare-access"];
18
+ type PlatformLoginKind = (typeof PLATFORM_LOGIN_KINDS)[number];
19
+ type PlatformLoginConfiguration = Readonly<{
20
+ id: string;
21
+ kind: PlatformLoginKind;
22
+ label: string;
23
+ clientId: string;
24
+ /** Exact OIDC issuer; GitHub does not use OIDC. */
25
+ issuer?: string;
26
+ /** Google Workspace hosted domains, checked against the signed hd claim. */
27
+ allowedDomains: ReadonlyArray<string>;
28
+ /** An identity must belong to at least one configured group. */
29
+ requiredGroups: ReadonlyArray<string>;
30
+ }>;
31
+ type PlatformLoginDescriptor = Readonly<{
32
+ id: string;
33
+ kind: PlatformLoginKind;
34
+ label: string;
35
+ }>;
36
+ type PlatformAccessProtection = Readonly<{
37
+ teamDomain: string;
38
+ /** Each externally reachable platform origin has its own expected application audience. */
39
+ applications: ReadonlyArray<Readonly<{
40
+ origin: string;
41
+ audience: string;
42
+ }>>;
43
+ }>;
44
+ /** Operator-side coverage verification, bound to one administrator and settings revision. */
45
+ export declare function platformProtectionProofMessage(input: {
46
+ installationId: string;
47
+ origin: string;
48
+ userId: string;
49
+ sessionId: string;
50
+ revision: number;
51
+ lifecycleVersion: string;
52
+ protection: PlatformAccessProtection | null;
53
+ expiresAt: string;
54
+ }): string;
55
+ type PlatformAdmissionPolicy = Readonly<{
56
+ mode: "restricted" | "public";
57
+ }> | Readonly<{
58
+ mode: "company";
59
+ /** Explicitly trusted connections whose own configured restrictions must pass. */
60
+ connections: ReadonlyArray<string>;
61
+ /** A verified human Access identity qualifies, independently of the login method. */
62
+ access: boolean;
63
+ }>;
64
+ /** One persisted grant used by invited/allowlisted platform signup. */
65
+ type PlatformSignupAllowlistEntry = Readonly<{
66
+ type: "github" | "email";
67
+ pattern: string;
68
+ note?: string;
69
+ }> | Readonly<{
70
+ type: "identity";
71
+ /** Stable login connection namespace. */
72
+ connectionId: string;
73
+ /** Exact, case-sensitive provider subject. */
74
+ subject: string;
75
+ note?: string;
76
+ }>;
77
+ type PlatformSignupIdentity = Readonly<{
78
+ connectionId: string;
79
+ subject: string;
80
+ provider: string;
81
+ login: string;
82
+ /** Only pass a provider email after its verification claim has been validated. */
83
+ email: string | null;
84
+ }>;
85
+ export declare function parsePlatformSignupAllowlistEntry(value: unknown): PlatformSignupAllowlistEntry;
86
+ /** Match one verified login identity against persisted signup grants. */
87
+ export declare function isPlatformSignupAllowed(identity: PlatformSignupIdentity, entries: ReadonlyArray<PlatformSignupAllowlistEntry>): boolean;
88
+ type PlatformAuthenticationSetup = Readonly<{
89
+ connections: ReadonlyArray<Readonly<{
90
+ configuration: PlatformLoginConfiguration;
91
+ clientSecret: string;
92
+ }>>;
93
+ administratorConnectionId: string;
94
+ bootstrapCredentialHash: string;
95
+ admission: PlatformAdmissionPolicy;
96
+ protection: PlatformAccessProtection | null;
97
+ resetPending?: boolean;
98
+ }>;
99
+ export declare function parsePlatformAuthenticationSetup(value: unknown): PlatformAuthenticationSetup;
100
+ export declare const DEFAULT_PLATFORM_SESSION_SECONDS: number;
101
+ export declare const GOOGLE_OIDC_ISSUER = "https://accounts.google.com";
102
+ export declare const PLATFORM_AUTH_SETUP_PROOF_PREFIX = "void.platform.auth.setup.v1";
103
+ export declare function parsePlatformLoginId(value: unknown): string;
104
+ /** HTTPS syntax validation; outbound network policy must additionally validate resolved endpoints. */
105
+ export declare function parsePlatformIdentityUrl(value: unknown, name: string, originOnly?: boolean): string;
106
+ export declare function parsePlatformLoginDescriptor(value: unknown): PlatformLoginDescriptor;
107
+ export declare function parsePlatformLoginConfiguration(value: unknown): PlatformLoginConfiguration;
108
+ export declare function parsePlatformAccessProtection(value: unknown): PlatformAccessProtection | null;
109
+ export declare function parsePlatformAdmissionPolicy(value: unknown): PlatformAdmissionPolicy;
110
+ /** Run inside the settings mutation transaction, using the resulting enabled connections. */
111
+ export declare function validatePlatformAdmissionPolicy(policy: PlatformAdmissionPolicy, enabledConnections: ReadonlyArray<PlatformLoginConfiguration>, protection: PlatformAccessProtection | null): void;
112
+ //#endregion
1
113
  //#region src/application.d.ts
2
114
  /** Public application addressing, independent of the platform API's origin. */
3
115
  type PlatformApplication = Readonly<{
@@ -61,6 +173,7 @@ type PlatformResourceNames = Readonly<{
61
173
  dashboardWorker: string;
62
174
  dispatchNamespace: string;
63
175
  dispatchWorker: string;
176
+ emailGatewayWorker: string;
64
177
  hyperdrive: string;
65
178
  isrCacheKv: string;
66
179
  legacyAssetsKv: string;
@@ -73,15 +186,21 @@ type PlatformResourceNames = Readonly<{
73
186
  }>;
74
187
  type PlatformDeploymentManifest = Readonly<{
75
188
  application: PlatformApplication;
189
+ /** Zone that owns the installation's shared sender mail domain. */
190
+ emailSharedZoneId?: string;
191
+ /** Shared sender domain registered for managed email. */
192
+ emailSenderDomain?: string;
76
193
  authentication: Readonly<{
77
- providers: ReadonlyArray<"github" | "google">;
194
+ providers: ReadonlyArray<PlatformLoginKind>;
195
+ configurable?: true;
196
+ accessProtection?: true;
78
197
  }>;
79
198
  capabilities: PlatformCapabilities;
80
199
  controlPlane: PlatformControlPlane;
81
200
  displayName: string;
82
201
  installationSlug: string;
83
202
  resources: PlatformResourceNames;
84
- schemaVersion: typeof PLATFORM_DEPLOYMENT_SCHEMA_VERSION | 2;
203
+ schemaVersion: typeof PLATFORM_DEPLOYMENT_SCHEMA_VERSION | 2 | 3;
85
204
  }>;
86
205
  type PlatformDeploymentManifestInput = Readonly<{
87
206
  application: Readonly<{
@@ -91,8 +210,12 @@ type PlatformDeploymentManifestInput = Readonly<{
91
210
  workersDev?: PlatformApplication["workersDev"];
92
211
  zoneId?: string;
93
212
  }>;
213
+ emailSharedZoneId?: string;
214
+ emailSenderDomain?: string;
94
215
  authentication?: Readonly<{
95
- providers?: ReadonlyArray<"github" | "google">;
216
+ providers?: ReadonlyArray<PlatformLoginKind>;
217
+ configurable?: true;
218
+ accessProtection?: true;
96
219
  }>;
97
220
  capabilities?: Partial<PlatformCapabilities>;
98
221
  controlPlane?: Partial<PlatformControlPlane>;
@@ -119,7 +242,7 @@ export declare const PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY = "__void/platfor
119
242
  export declare const PLATFORM_SANDBOX_DRAIN_KEY = "__void/platform/sandbox-drain/v1";
120
243
  export declare const PLATFORM_R2_OWNERSHIP_MARKER_KEY = "__void/platform/ownership/v1";
121
244
  export declare const PLATFORM_RESOURCE_OWNERSHIP_SCHEMA_VERSION: 1;
122
- export declare const PLATFORM_USAGE_PROTOCOL_VERSION: 1;
245
+ export declare const PLATFORM_USAGE_PROTOCOL_VERSION: 2;
123
246
  type InstallationStatus = "draft" | "planned" | "provisioning" | "waiting-for-dns" | "waiting-for-oauth" | "deploying" | "verifying" | "installed" | "disabling" | "disabled" | "uninstalling" | "uninstalled" | "failed";
124
247
  type ResourceOwnership = "created" | "adopted" | "external";
125
248
  type InstallationResourceKind = "ai-gateway" | "analytics-dataset" | "dispatch-namespace" | "dns-record" | "hyperdrive" | "kv-namespace" | "queue" | "r2-bucket" | "worker" | "worker-custom-domain" | "worker-route" | "zone";
@@ -148,6 +271,14 @@ type PlatformRuntimeProvenance = Readonly<{
148
271
  sourceRevision: string | null;
149
272
  }>;
150
273
  type PlatformInstallationManifest = Readonly<{
274
+ authenticationResources?: ReadonlyArray<Readonly<{
275
+ key: string;
276
+ kind: "application" | "service-token";
277
+ accountId: string;
278
+ id: string;
279
+ name: string;
280
+ ownership: "created" | "adopted";
281
+ }>>;
151
282
  account: Readonly<{
152
283
  id: string;
153
284
  name: string;
@@ -166,14 +297,14 @@ type PlatformInstallationManifest = Readonly<{
166
297
  resources: ReadonlyArray<InstallationResource>;
167
298
  runtime: PlatformRuntimeProvenance | null;
168
299
  safetyProtocols: Readonly<{
169
- sandboxDrain: 0 | 1 | 2;
300
+ sandboxDrain: 0 | 1 | 2 | 3;
170
301
  }>;
171
302
  schemaVersion: typeof PLATFORM_INSTALLATION_SCHEMA_VERSION;
172
303
  status: InstallationStatus;
173
304
  steps: ReadonlyArray<InstallationStep>;
174
305
  updatedAt: string;
175
306
  /** Last usage protocol proven live across the installed Worker set. */
176
- usageProtocolVersion: 0 | 1;
307
+ usageProtocolVersion: 0 | 1 | 2;
177
308
  }>;
178
309
  type PlatformResourceOwnershipMarker = Readonly<{
179
310
  accountId: string;
@@ -206,9 +337,9 @@ export declare function createPlatformInstallationManifest(input: {
206
337
  ownershipNonce: string;
207
338
  platformVersion: string;
208
339
  safetyProtocols?: Readonly<{
209
- sandboxDrain: 0 | 1 | 2;
340
+ sandboxDrain: 0 | 1 | 2 | 3;
210
341
  }>;
211
- usageProtocolVersion?: 0 | 1;
342
+ usageProtocolVersion?: 0 | 1 | 2;
212
343
  }): PlatformInstallationManifest;
213
344
  export declare function setInstallationDesiredState(manifest: PlatformInstallationManifest, desiredState: PlatformInstallationManifest["desiredState"], updatedAt: string): PlatformInstallationManifest;
214
345
  export declare function setInstallationEndpoints(manifest: PlatformInstallationManifest, endpoints: PlatformInstallationEndpoints, updatedAt: string): PlatformInstallationManifest;
@@ -219,21 +350,27 @@ export declare function parsePlatformInstallationManifest(value: unknown): Platf
219
350
  //#endregion
220
351
  //#region src/discovery.d.ts
221
352
  export declare const PLATFORM_DISCOVERY_SCHEMA_VERSION: 1;
353
+ type PlatformAuthenticationDiscovery = Readonly<{
354
+ methods: ReadonlyArray<PlatformLoginDescriptor>;
355
+ protectedByAccess: boolean;
356
+ setupPending?: boolean;
357
+ }>;
222
358
  type PlatformDiscoveryDocument = Readonly<{
223
359
  apiUrl: string;
224
360
  applicationDomain: string;
225
361
  applicationRouting?: PlatformApplication;
226
- authenticationProviders: ReadonlyArray<"github" | "google">;
362
+ authenticationProviders: ReadonlyArray<string>;
363
+ authentication?: PlatformAuthenticationDiscovery;
227
364
  capabilities: PlatformCapabilities;
228
365
  displayName: string;
229
366
  installationId: string | null;
230
367
  platformVersion: string;
231
368
  proxyUrl: string;
232
- schemaVersion: typeof PLATFORM_DISCOVERY_SCHEMA_VERSION | 2;
233
- usageProtocolVersion: 0 | 1;
369
+ schemaVersion: typeof PLATFORM_DISCOVERY_SCHEMA_VERSION | 2 | 3;
370
+ usageProtocolVersion: 0 | 1 | 2;
234
371
  }>;
235
372
  export declare function createPlatformDiscoveryDocument(input: Omit<PlatformDiscoveryDocument, "schemaVersion" | "usageProtocolVersion"> & {
236
- usageProtocolVersion?: 0 | 1;
373
+ usageProtocolVersion?: 0 | 1 | 2;
237
374
  }): PlatformDiscoveryDocument;
238
375
  export declare function parsePlatformDiscoveryDocument(value: unknown): PlatformDiscoveryDocument;
239
376
  //#endregion
@@ -245,8 +382,9 @@ type PlatformProvisionedResourceIds = Readonly<{
245
382
  routingKv: string;
246
383
  zone: string;
247
384
  }>;
248
- type PlatformWorkerConfigSet = Readonly<Record<"api" | "dispatch" | "proxy" | "tail", Record<string, unknown>>>;
385
+ type PlatformWorkerConfigSet = Readonly<Record<"api" | "dispatch" | "proxy" | "tail" | "emailGateway", Record<string, unknown>>>;
249
386
  export declare function createPlatformWorkerConfigs(input: {
387
+ authenticationDatabaseUncached?: boolean;
250
388
  accountId: string;
251
389
  apiUrl: string;
252
390
  deployment: PlatformDeploymentManifest;
@@ -256,9 +394,10 @@ export declare function createPlatformWorkerConfigs(input: {
256
394
  platformVersion: string;
257
395
  publicEndpoints?: boolean;
258
396
  proxyUrl: string;
397
+ emailGatewayUrl: string;
259
398
  resources: PlatformProvisionedResourceIds;
260
- runtimeEntrypoints: Readonly<Record<"api" | "dispatch" | "proxy" | "tail", string>>;
399
+ runtimeEntrypoints: Readonly<Record<"api" | "dispatch" | "proxy" | "tail" | "emailGateway", string>>;
261
400
  trafficState?: "enabled" | "sandbox-drain" | "staging";
262
401
  }): PlatformWorkerConfigSet;
263
402
  //#endregion
264
- export type { InstallationResource, InstallationResourceKind, InstallationStatus, InstallationStep, PlatformApplication, PlatformApplicationProject, PlatformApplicationRoute, PlatformCapabilities, PlatformControlPlane, PlatformDeploymentManifest, PlatformDeploymentManifestInput, PlatformDiscoveryDocument, PlatformInstallationEndpoints, PlatformInstallationManifest, PlatformProvisionedResourceIds, PlatformResourceNames, PlatformResourceOwnershipMarker, PlatformRuntimeProvenance, PlatformWorkerConfigSet, ResourceOwnership };
403
+ export type { InstallationResource, InstallationResourceKind, InstallationStatus, InstallationStep, PlatformAccessProtection, PlatformAdmissionPolicy, PlatformApplication, PlatformApplicationProject, PlatformApplicationRoute, PlatformAuthenticationDiscovery, PlatformAuthenticationRecovery, PlatformAuthenticationSetup, PlatformCapabilities, PlatformControlPlane, PlatformDeploymentManifest, PlatformDeploymentManifestInput, PlatformDiscoveryDocument, PlatformInstallationEndpoints, PlatformInstallationManifest, PlatformLoginConfiguration, PlatformLoginDescriptor, PlatformLoginKind, PlatformProvisionedResourceIds, PlatformResourceNames, PlatformResourceOwnershipMarker, PlatformRuntimeProvenance, PlatformSignupAllowlistEntry, PlatformSignupIdentity, PlatformWorkerConfigSet, ResourceOwnership };