@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/dist/index.mjs CHANGED
@@ -1,3 +1,222 @@
1
+ //#region src/authentication-recovery.ts
2
+ function platformAuthenticationRecoveryMessage(value) {
3
+ return JSON.stringify([
4
+ "void-platform-auth-recovery-v1",
5
+ value.installationId,
6
+ value.administratorUserId,
7
+ value.connectionId,
8
+ value.revision,
9
+ value.generation,
10
+ value.testId,
11
+ value.expiresAt
12
+ ]);
13
+ }
14
+ //#endregion
15
+ //#region src/authentication.ts
16
+ /** Platform login and perimeter configuration are independent of app authentication. */
17
+ const PLATFORM_LOGIN_KINDS = [
18
+ "github",
19
+ "google",
20
+ "oidc",
21
+ "cloudflare-access"
22
+ ];
23
+ /** Operator-side coverage verification, bound to one administrator and settings revision. */
24
+ function platformProtectionProofMessage(input) {
25
+ return `void-platform-protection-v1\n${JSON.stringify({
26
+ installationId: input.installationId,
27
+ origin: input.origin,
28
+ userId: input.userId,
29
+ sessionId: input.sessionId,
30
+ revision: input.revision,
31
+ lifecycleVersion: input.lifecycleVersion,
32
+ protection: parsePlatformAccessProtection(input.protection),
33
+ expiresAt: input.expiresAt
34
+ })}`;
35
+ }
36
+ function parsePlatformSignupAllowlistEntry(value) {
37
+ const entry = record(value, "Signup allowlist entry");
38
+ const note = entry.note;
39
+ if (note !== void 0 && (typeof note !== "string" || note.length > 1e3)) throw new Error("Signup allowlist notes must be strings of at most 1000 characters.");
40
+ const optionalNote = note === void 0 ? {} : { note };
41
+ if (entry.type === "identity") {
42
+ const connectionId = parsePlatformLoginId(entry.connectionId);
43
+ if (typeof entry.subject !== "string" || entry.subject.length === 0 || entry.subject.length > 255) throw new Error("Identity subjects must be nonempty strings of at most 255 characters.");
44
+ return {
45
+ type: "identity",
46
+ connectionId,
47
+ subject: entry.subject,
48
+ ...optionalNote
49
+ };
50
+ }
51
+ if (entry.type !== "github" && entry.type !== "email") throw new Error("Signup allowlist entry type must be github, email, or identity.");
52
+ if (typeof entry.pattern !== "string" || !entry.pattern || entry.pattern.length > 320 || /[\s,]/.test(entry.pattern)) throw new Error("Signup allowlist patterns must be nonempty strings of at most 320 characters.");
53
+ return {
54
+ type: entry.type,
55
+ pattern: entry.pattern,
56
+ ...optionalNote
57
+ };
58
+ }
59
+ /** Match one verified login identity against persisted signup grants. */
60
+ function isPlatformSignupAllowed(identity, entries) {
61
+ return entries.some((entry) => {
62
+ if (entry.type === "identity") return entry.connectionId === identity.connectionId && entry.subject === identity.subject;
63
+ if (entry.type === "github") return identity.provider === "github" && entry.pattern.toLowerCase() === identity.login.toLowerCase();
64
+ if (!identity.email) return false;
65
+ if (entry.pattern.startsWith("*@")) return identity.email.toLowerCase().endsWith(entry.pattern.slice(1).toLowerCase());
66
+ return entry.pattern.toLowerCase() === identity.email.toLowerCase();
67
+ });
68
+ }
69
+ function parsePlatformAuthenticationSetup(value) {
70
+ const input = record(value, "Authentication setup");
71
+ if (!Array.isArray(input.connections) || input.connections.length === 0 || input.connections.length > 32) throw new Error("Authentication setup requires between 1 and 32 connections.");
72
+ const connections = input.connections.map((value) => {
73
+ const entry = record(value, "Login connection");
74
+ string(entry.clientSecret, "Client secret", 8192);
75
+ return {
76
+ configuration: parsePlatformLoginConfiguration(entry.configuration),
77
+ clientSecret: entry.clientSecret
78
+ };
79
+ });
80
+ if (new Set(connections.map((entry) => entry.configuration.id)).size !== connections.length) throw new Error("Authentication setup connection IDs must be unique.");
81
+ const administratorConnectionId = parsePlatformLoginId(input.administratorConnectionId);
82
+ if (!connections.some((entry) => entry.configuration.id === administratorConnectionId)) throw new Error("The administrator login method must be included in setup.");
83
+ if (typeof input.bootstrapCredentialHash !== "string" || !/^[a-f0-9]{64}$/.test(input.bootstrapCredentialHash)) throw new Error("A SHA-256 bootstrap credential hash is required.");
84
+ const admission = parsePlatformAdmissionPolicy(input.admission);
85
+ const protection = parsePlatformAccessProtection(input.protection ?? null);
86
+ validatePlatformAdmissionPolicy(admission, connections.map((entry) => entry.configuration), protection);
87
+ if (input.resetPending !== void 0 && typeof input.resetPending !== "boolean") throw new Error("resetPending must be a boolean.");
88
+ return {
89
+ connections,
90
+ administratorConnectionId,
91
+ bootstrapCredentialHash: input.bootstrapCredentialHash,
92
+ admission,
93
+ protection,
94
+ ...input.resetPending === true ? { resetPending: true } : {}
95
+ };
96
+ }
97
+ const DEFAULT_PLATFORM_SESSION_SECONDS = 2592e3;
98
+ const GOOGLE_OIDC_ISSUER = "https://accounts.google.com";
99
+ const PLATFORM_AUTH_SETUP_PROOF_PREFIX = "void.platform.auth.setup.v1";
100
+ function record(value, name) {
101
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${name} must be an object.`);
102
+ return value;
103
+ }
104
+ function string(value, name, maximum = 512) {
105
+ if (typeof value !== "string" || !value.trim() || value.length > maximum || Array.from(value).some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)) throw new Error(`${name} must be a nonempty string of at most ${maximum} characters.`);
106
+ return value.trim();
107
+ }
108
+ function strings(value, name, maximum = 100) {
109
+ if (!Array.isArray(value) || value.length > maximum) throw new Error(`${name} must be an array of at most ${maximum} strings.`);
110
+ const result = value.map((entry) => string(entry, name));
111
+ if (new Set(result).size !== result.length) throw new Error(`${name} must not contain duplicates.`);
112
+ return result;
113
+ }
114
+ function parsePlatformLoginId(value) {
115
+ if (typeof value !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(value)) throw new Error("Login connection IDs must start with a letter and use 1–64 lowercase letters, digits, or hyphens.");
116
+ return value;
117
+ }
118
+ /** HTTPS syntax validation; outbound network policy must additionally validate resolved endpoints. */
119
+ function parsePlatformIdentityUrl(value, name, originOnly = false) {
120
+ const input = string(value, name, 2048);
121
+ let url;
122
+ try {
123
+ url = new URL(input);
124
+ } catch {
125
+ throw new Error(`${name} must be a credential-free HTTPS URL.`);
126
+ }
127
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash || originOnly && url.pathname !== "/") throw new Error(`${name} must be a credential-free HTTPS ${originOnly ? "origin" : "URL"} without query or fragment.`);
128
+ return originOnly ? url.origin : input;
129
+ }
130
+ function parsePlatformLoginDescriptor(value) {
131
+ const entry = record(value, "Login connection");
132
+ const id = parsePlatformLoginId(entry.id);
133
+ if (!PLATFORM_LOGIN_KINDS.includes(entry.kind)) throw new Error("Login kind must be github, google, oidc, or cloudflare-access.");
134
+ const kind = entry.kind;
135
+ if ((id === "github" || id === "google") && id !== kind) throw new Error(`Connection ID ${id} is reserved for ${id} login.`);
136
+ if ((kind === "github" || kind === "google") && id !== kind) throw new Error(`${kind} uses the fixed connection ID ${kind}.`);
137
+ return {
138
+ id,
139
+ kind,
140
+ label: string(entry.label, "Login label", 100)
141
+ };
142
+ }
143
+ function parsePlatformLoginConfiguration(value) {
144
+ const entry = record(value, "Login configuration");
145
+ const descriptor = parsePlatformLoginDescriptor(entry);
146
+ const clientId = string(entry.clientId, "Client ID");
147
+ const allowedDomains = strings(entry.allowedDomains ?? [], "Allowed domains").map((domain) => {
148
+ const normalized = domain.toLowerCase();
149
+ if (!/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/.test(normalized)) throw new Error("Allowed domains must be exact domain names, without wildcards.");
150
+ return normalized;
151
+ });
152
+ if (new Set(allowedDomains).size !== allowedDomains.length) throw new Error("Allowed domains must not contain duplicates.");
153
+ const requiredGroups = strings(entry.requiredGroups ?? [], "Required groups");
154
+ if (descriptor.kind !== "google" && allowedDomains.length > 0) throw new Error("Workspace domain restrictions are only supported by Google login.");
155
+ if ((descriptor.kind === "github" || descriptor.kind === "google") && requiredGroups.length > 0) throw new Error("Group restrictions require an OIDC or Cloudflare Access connection.");
156
+ let issuer;
157
+ if (descriptor.kind === "google") {
158
+ if (entry.issuer !== void 0 && entry.issuer !== "https://accounts.google.com") throw new Error("Google login uses the Google issuer.");
159
+ issuer = GOOGLE_OIDC_ISSUER;
160
+ } else if (descriptor.kind === "github") {
161
+ if (entry.issuer !== void 0) throw new Error("GitHub login does not accept an OIDC issuer.");
162
+ } else {
163
+ issuer = parsePlatformIdentityUrl(entry.issuer, "OIDC issuer");
164
+ if (descriptor.kind === "cloudflare-access") {
165
+ const url = new URL(issuer);
166
+ if (!/^[a-z0-9-]+\.cloudflareaccess\.com$/.test(url.hostname) || url.port || !url.pathname.startsWith("/cdn-cgi/access/sso/oidc/") || decodeURIComponent(url.pathname.slice(25).replace(/\/$/, "")) !== clientId) throw new Error("Cloudflare Access login requires its application issuer URL and matching client ID. Use generic OIDC for another issuer.");
167
+ }
168
+ }
169
+ return {
170
+ ...descriptor,
171
+ clientId,
172
+ ...issuer ? { issuer } : {},
173
+ allowedDomains,
174
+ requiredGroups
175
+ };
176
+ }
177
+ function parsePlatformAccessProtection(value) {
178
+ if (value === null) return null;
179
+ const entry = record(value, "Access protection");
180
+ const teamDomain = parsePlatformIdentityUrl(entry.teamDomain, "Access team domain", true);
181
+ const host = new URL(teamDomain).hostname;
182
+ if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.cloudflareaccess\.com$/.test(host) || new URL(teamDomain).port) throw new Error("Access team domain must be an HTTPS cloudflareaccess.com team origin.");
183
+ if (!Array.isArray(entry.applications) || entry.applications.length === 0 || entry.applications.length > 20) throw new Error("Access protection requires between 1 and 20 protected origins.");
184
+ const applications = entry.applications.map((value) => {
185
+ const application = record(value, "Access application");
186
+ return {
187
+ origin: parsePlatformIdentityUrl(application.origin, "Protected origin", true),
188
+ audience: string(application.audience, "Access application audience")
189
+ };
190
+ });
191
+ if (new Set(applications.map((app) => app.origin)).size !== applications.length) throw new Error("Each protected origin must have exactly one Access application.");
192
+ return {
193
+ teamDomain,
194
+ applications
195
+ };
196
+ }
197
+ function parsePlatformAdmissionPolicy(value) {
198
+ const entry = record(value, "Admission policy");
199
+ if (entry.mode === "restricted" || entry.mode === "public") return { mode: entry.mode };
200
+ if (entry.mode !== "company" || typeof entry.access !== "boolean") throw new Error("Admission policy must select restricted, company, or public signup.");
201
+ const connections = strings(entry.connections, "Company connections").map(parsePlatformLoginId);
202
+ if (!entry.access && connections.length === 0) throw new Error("Company admission requires a trusted connection or Access protection.");
203
+ return {
204
+ mode: "company",
205
+ connections,
206
+ access: entry.access
207
+ };
208
+ }
209
+ /** Run inside the settings mutation transaction, using the resulting enabled connections. */
210
+ function validatePlatformAdmissionPolicy(policy, enabledConnections, protection) {
211
+ if (policy.mode !== "company") return;
212
+ if (policy.access && !protection) throw new Error("Company admission through Access requires enabled protection.");
213
+ for (const id of policy.connections) {
214
+ const connection = enabledConnections.find((entry) => entry.id === id);
215
+ if (!connection) throw new Error(`Company admission requires enabled connection ${id}.`);
216
+ if (connection.kind === "github" || connection.kind === "google" && connection.allowedDomains.length === 0) throw new Error("Unrestricted social login does not establish company membership.");
217
+ }
218
+ }
219
+ //#endregion
1
220
  //#region src/manifest.ts
2
221
  const PLATFORM_DEPLOYMENT_SCHEMA_VERSION = 1;
3
222
  const CORE_CAPABILITIES = {
@@ -45,6 +264,7 @@ function createPlatformResourceNames(installationSlug) {
45
264
  dashboardWorker: `${prefix}-dashboard`,
46
265
  dispatchNamespace: `${prefix}-users`,
47
266
  dispatchWorker: `${prefix}-dispatch`,
267
+ emailGatewayWorker: `${prefix}-email-gateway`,
48
268
  hyperdrive: `${prefix}-database`,
49
269
  isrCacheKv: `${prefix}-isr-cache`,
50
270
  legacyAssetsKv: `${prefix}-legacy-assets`,
@@ -84,6 +304,7 @@ function createApplicationRoutes(application) {
84
304
  return routes;
85
305
  }
86
306
  function createPlatformDeploymentManifest(input) {
307
+ if (input.emailSharedZoneId === void 0 !== (input.emailSenderDomain === void 0)) throw new Error("Shared email configuration requires both emailSenderDomain and emailSharedZoneId.");
87
308
  const installationSlug = normalizeInstallationSlug(input.installationSlug);
88
309
  const baseDomain = normalizeApplicationDomain(input.application.baseDomain);
89
310
  const workersDev = input.application.workersDev;
@@ -100,6 +321,8 @@ function createPlatformDeploymentManifest(input) {
100
321
  };
101
322
  if (capabilities.managedBuilds && !capabilities.githubActions) throw new Error("Managed builds require the GitHub Actions capability.");
102
323
  const providers = input.authentication?.providers ?? ["github"];
324
+ if (providers.some((provider) => !PLATFORM_LOGIN_KINDS.includes(provider)) || new Set(providers).size !== providers.length) throw new Error("Authentication providers must be unique supported login kinds.");
325
+ const configurable = input.authentication?.configurable === true || input.authentication?.accessProtection === true || providers.some((provider) => provider === "oidc" || provider === "cloudflare-access");
103
326
  if (providers.length === 0) throw new Error("At least one authentication provider is required.");
104
327
  if (providers.includes("github") !== capabilities.githubOAuth) throw new Error("The GitHub provider and githubOAuth capability must agree.");
105
328
  if (providers.includes("google") !== capabilities.googleOAuth) throw new Error("The Google provider and googleOAuth capability must agree.");
@@ -111,8 +334,14 @@ function createPlatformDeploymentManifest(input) {
111
334
  ...workersDev ? { workersDev: { ...workersDev } } : {},
112
335
  ...input.application.zoneId ? { zoneId: requiredString$1(input.application.zoneId, "application.zoneId") } : {}
113
336
  },
114
- authentication: { providers: [...providers] },
337
+ authentication: {
338
+ providers: [...providers],
339
+ ...configurable ? { configurable: true } : {},
340
+ ...input.authentication?.accessProtection ? { accessProtection: true } : {}
341
+ },
115
342
  capabilities,
343
+ ...input.emailSharedZoneId !== void 0 ? { emailSharedZoneId: requiredString$1(input.emailSharedZoneId, "emailSharedZoneId") } : {},
344
+ ...input.emailSenderDomain !== void 0 ? { emailSenderDomain: normalizeApplicationDomain(input.emailSenderDomain) } : {},
116
345
  controlPlane: {
117
346
  ...CONTROL_PLANE_DEFAULTS,
118
347
  ...input.controlPlane
@@ -120,7 +349,7 @@ function createPlatformDeploymentManifest(input) {
120
349
  displayName: requiredString$1(input.displayName, "displayName").trim(),
121
350
  installationSlug,
122
351
  resources,
123
- schemaVersion: workersDev ? 2 : 1
352
+ schemaVersion: configurable ? 3 : workersDev ? 2 : 1
124
353
  };
125
354
  createApplicationRoutes(manifest.application);
126
355
  for (const [key, value] of Object.entries(manifest.controlPlane)) {
@@ -132,7 +361,7 @@ function createPlatformDeploymentManifest(input) {
132
361
  }
133
362
  function parsePlatformDeploymentManifest(value) {
134
363
  assertRecord$1(value, "Platform deployment manifest");
135
- if (value.schemaVersion !== 1 && value.schemaVersion !== 2) throw new Error(`Unsupported platform deployment schema version: ${String(value.schemaVersion)}.`);
364
+ if (value.schemaVersion !== 1 && value.schemaVersion !== 2 && value.schemaVersion !== 3) throw new Error(`Unsupported platform deployment schema version: ${String(value.schemaVersion)}.`);
136
365
  const application = value.application;
137
366
  const authentication = value.authentication;
138
367
  const rawCapabilities = value.capabilities;
@@ -140,7 +369,7 @@ function parsePlatformDeploymentManifest(value) {
140
369
  const rawResources = value.resources;
141
370
  assertRecord$1(application, "application");
142
371
  let workersDev;
143
- if (value.schemaVersion === 2) {
372
+ if (value.schemaVersion === 2 || value.schemaVersion === 3 && application.workersDev !== void 0) {
144
373
  assertRecord$1(application.workersDev, "application.workersDev");
145
374
  workersDev = {
146
375
  subdomain: requiredString$1(application.workersDev.subdomain, "workersDev.subdomain"),
@@ -159,10 +388,11 @@ function parsePlatformDeploymentManifest(value) {
159
388
  capabilities[key] = entry;
160
389
  }
161
390
  const providers = authentication.providers;
162
- if (!Array.isArray(providers) || providers.some((provider) => provider !== "github" && provider !== "google")) throw new Error("authentication.providers must contain only github or google.");
391
+ if (!Array.isArray(providers) || providers.some((provider) => !PLATFORM_LOGIN_KINDS.includes(provider) || value.schemaVersion !== 3 && provider !== "github" && provider !== "google")) throw new Error("authentication.providers must contain only github or google.");
392
+ if (value.schemaVersion === 3 && authentication.configurable !== true || value.schemaVersion !== 3 && (authentication.configurable !== void 0 || authentication.accessProtection !== void 0) || authentication.accessProtection !== void 0 && authentication.accessProtection !== true) throw new Error("Configurable authentication requires deployment schema version 3.");
163
393
  const resourceDefaults = createPlatformResourceNames(requiredString$1(value.installationSlug, "installationSlug"));
164
394
  const resources = {};
165
- for (const key of Object.keys(resourceDefaults)) resources[key] = requiredString$1(rawResources[key], `resources.${key}`);
395
+ for (const key of Object.keys(resourceDefaults)) resources[key] = requiredString$1(rawResources[key] ?? (key === "emailGatewayWorker" ? resourceDefaults[key] : void 0), `resources.${key}`);
166
396
  const readHostname = (key) => {
167
397
  const hostname = controlPlane[key];
168
398
  return hostname === null ? null : requiredString$1(hostname, `controlPlane.${key}`);
@@ -175,8 +405,14 @@ function parsePlatformDeploymentManifest(value) {
175
405
  ...workersDev ? { workersDev } : {},
176
406
  ...typeof application.zoneId === "string" ? { zoneId: application.zoneId } : {}
177
407
  },
178
- authentication: { providers },
408
+ authentication: {
409
+ providers,
410
+ ...authentication.configurable === true ? { configurable: true } : {},
411
+ ...authentication.accessProtection === true ? { accessProtection: true } : {}
412
+ },
179
413
  capabilities,
414
+ ...value.emailSharedZoneId !== void 0 ? { emailSharedZoneId: requiredString$1(value.emailSharedZoneId, "emailSharedZoneId") } : {},
415
+ ...value.emailSenderDomain !== void 0 ? { emailSenderDomain: normalizeApplicationDomain(requiredString$1(value.emailSenderDomain, "emailSenderDomain")) } : {},
180
416
  controlPlane: {
181
417
  apiHostname: readHostname("apiHostname"),
182
418
  dashboardHostname: readHostname("dashboardHostname"),
@@ -196,7 +432,7 @@ const PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY = "__void/platform/installation/
196
432
  const PLATFORM_SANDBOX_DRAIN_KEY = "__void/platform/sandbox-drain/v1";
197
433
  const PLATFORM_R2_OWNERSHIP_MARKER_KEY = "__void/platform/ownership/v1";
198
434
  const PLATFORM_RESOURCE_OWNERSHIP_SCHEMA_VERSION = 1;
199
- const PLATFORM_USAGE_PROTOCOL_VERSION = 1;
435
+ const PLATFORM_USAGE_PROTOCOL_VERSION = 2;
200
436
  const STATUS_TRANSITIONS = {
201
437
  draft: [
202
438
  "planned",
@@ -459,6 +695,7 @@ function parsePlatformInstallationManifest(value) {
459
695
  status: value.status,
460
696
  runtime,
461
697
  ...pendingDomain ? { pendingDomain } : {},
698
+ ...value.authenticationResources !== void 0 ? { authenticationResources: parseAuthenticationResources(value.authenticationResources) } : {},
462
699
  updatedAt
463
700
  }, {
464
701
  apiUrl: endpoints.apiUrl === null ? null : requiredString(endpoints.apiUrl, "endpoints.apiUrl"),
@@ -495,15 +732,33 @@ function parsePlatformInstallationManifest(value) {
495
732
  updatedAt
496
733
  };
497
734
  }
735
+ function parseAuthenticationResources(value) {
736
+ if (!Array.isArray(value) || value.length > 100) throw new Error("Invalid authentication resource inventory.");
737
+ const resources = value.map((entry) => {
738
+ assertRecord(entry, "Authentication resource");
739
+ if (entry.kind !== "application" && entry.kind !== "service-token" || entry.ownership !== "created" && entry.ownership !== "adopted") throw new Error("Invalid authentication resource kind or ownership.");
740
+ return {
741
+ key: requiredString(entry.key, "authentication resource key"),
742
+ kind: entry.kind,
743
+ accountId: requiredString(entry.accountId, "authentication resource account"),
744
+ id: requiredString(entry.id, "authentication resource ID"),
745
+ name: requiredString(entry.name, "authentication resource name"),
746
+ ownership: entry.ownership
747
+ };
748
+ });
749
+ if (new Set(resources.map((resource) => `${resource.accountId}:${resource.kind}:${resource.id}`)).size !== resources.length) throw new Error("Duplicate authentication resource identity.");
750
+ return resources;
751
+ }
498
752
  function parseUsageProtocolVersion$1(value) {
499
753
  if (value === void 0 || value === 0) return 0;
500
754
  if (value === 1) return 1;
501
- throw new Error("usageProtocolVersion must be 0 or 1.");
755
+ if (value === 2) return 2;
756
+ throw new Error("usageProtocolVersion must be 0, 1, or 2.");
502
757
  }
503
758
  function parseSafetyProtocols(value) {
504
759
  if (value === void 0) return { sandboxDrain: 0 };
505
760
  assertRecord(value, "safetyProtocols");
506
- if (value.sandboxDrain !== 0 && value.sandboxDrain !== 1 && value.sandboxDrain !== 2) throw new Error("safetyProtocols.sandboxDrain must be 0, 1, or 2.");
761
+ if (value.sandboxDrain !== 0 && value.sandboxDrain !== 1 && value.sandboxDrain !== 2 && value.sandboxDrain !== 3) throw new Error("safetyProtocols.sandboxDrain must be 0, 1, 2, or 3.");
507
762
  return { sandboxDrain: value.sandboxDrain };
508
763
  }
509
764
  function parsePlatformRuntimeProvenance(value) {
@@ -600,28 +855,34 @@ function createPlatformDiscoveryDocument(input) {
600
855
  const application = readPlatformApplication({ PLATFORM_APPLICATION: JSON.stringify(input.applicationRouting) });
601
856
  if (!application.workersDev || application.baseDomain !== input.applicationDomain || application.workersDev.installationId !== input.installationId) throw new Error("Application routing must match the discovery domain and installation identity.");
602
857
  }
603
- if (input.usageProtocolVersion !== void 0 && input.usageProtocolVersion !== 0 && input.usageProtocolVersion !== 1) throw new Error("usageProtocolVersion must be 0 or 1.");
604
- if (input.authenticationProviders.length === 0) throw new Error("At least one authentication provider is required.");
858
+ if (input.usageProtocolVersion !== void 0 && input.usageProtocolVersion !== 0 && input.usageProtocolVersion !== 1 && input.usageProtocolVersion !== 2) throw new Error("usageProtocolVersion must be 0, 1, or 2.");
859
+ const authentication = input.authentication ? parseAuthentication(input.authentication) : void 0;
860
+ if (!authentication && input.authenticationProviders.some((provider) => provider !== "github" && provider !== "google")) throw new Error("Named login methods require authentication discovery descriptors.");
861
+ if (new Set(input.authenticationProviders).size !== input.authenticationProviders.length) throw new Error("Authentication provider IDs must be unique.");
862
+ if (input.authenticationProviders.length === 0 && !authentication?.setupPending) throw new Error("At least one authentication provider is required.");
863
+ if (authentication && (authentication.methods.length !== input.authenticationProviders.length || authentication.methods.some((method, index) => method.id !== input.authenticationProviders[index]))) throw new Error("Authentication method descriptors must match the advertised provider IDs.");
605
864
  if (input.authenticationProviders.includes("github") !== input.capabilities.githubOAuth || input.authenticationProviders.includes("google") !== input.capabilities.googleOAuth) throw new Error("Authentication providers and capabilities must agree.");
606
865
  return {
607
866
  ...input,
608
867
  apiUrl: normalizeOrigin(input.apiUrl, "apiUrl"),
609
868
  applicationDomain: normalizeApplicationDomain(input.applicationDomain),
610
869
  authenticationProviders: [...input.authenticationProviders],
870
+ ...authentication ? { authentication } : {},
611
871
  displayName: input.displayName.trim(),
612
872
  installationId: input.installationId?.trim() || null,
613
873
  platformVersion: input.platformVersion.trim(),
614
874
  proxyUrl: normalizeOrigin(input.proxyUrl, "proxyUrl"),
615
- schemaVersion: input.applicationRouting?.workersDev ? 2 : 1,
875
+ schemaVersion: authentication ? 3 : input.applicationRouting?.workersDev ? 2 : 1,
616
876
  usageProtocolVersion: input.usageProtocolVersion ?? 0
617
877
  };
618
878
  }
619
879
  function parsePlatformDiscoveryDocument(value) {
620
880
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Platform discovery document must be an object.");
621
881
  const document = value;
622
- if (document.schemaVersion !== 1 && document.schemaVersion !== 2) throw new Error(`Unsupported platform discovery schema version: ${String(document.schemaVersion)}.`);
882
+ if (document.schemaVersion !== 1 && document.schemaVersion !== 2 && document.schemaVersion !== 3) throw new Error(`Unsupported platform discovery schema version: ${String(document.schemaVersion)}.`);
623
883
  const capabilities = document.capabilities;
624
884
  if (document.schemaVersion === 1 && document.applicationRouting !== void 0) throw new Error("Application routing requires discovery schema version 2.");
885
+ if (document.schemaVersion === 2 && document.applicationRouting === void 0) throw new Error("Discovery schema version 2 requires application routing.");
625
886
  if (!capabilities || typeof capabilities !== "object" || Array.isArray(capabilities)) throw new Error("capabilities must be an object.");
626
887
  const capabilityNames = [
627
888
  "ai",
@@ -640,7 +901,10 @@ function parsePlatformDiscoveryDocument(value) {
640
901
  parsedCapabilities[name] = enabled;
641
902
  }
642
903
  const providers = document.authenticationProviders;
643
- if (!Array.isArray(providers) || providers.some((provider) => provider !== "github" && provider !== "google")) throw new Error("authenticationProviders must contain only github or google.");
904
+ if (!Array.isArray(providers) || providers.some((provider) => typeof provider !== "string" || document.schemaVersion !== 3 && provider !== "github" && provider !== "google")) throw new Error("authenticationProviders must contain only github or google.");
905
+ if (document.schemaVersion !== 3 && document.authentication !== void 0) throw new Error("Configurable authentication requires discovery schema version 3.");
906
+ const authentication = document.schemaVersion === 3 ? parseAuthentication(document.authentication) : void 0;
907
+ if (authentication) providers.forEach(parsePlatformLoginId);
644
908
  const string = (name) => {
645
909
  const entry = document[name];
646
910
  if (typeof entry !== "string" || !entry.trim()) throw new Error(`${name} must be a non-empty string.`);
@@ -651,8 +915,9 @@ function parsePlatformDiscoveryDocument(value) {
651
915
  return createPlatformDiscoveryDocument({
652
916
  apiUrl,
653
917
  applicationDomain: string("applicationDomain"),
654
- ...document.schemaVersion === 2 ? { applicationRouting: readPlatformApplication({ PLATFORM_APPLICATION: JSON.stringify(document.applicationRouting ?? null) }) } : {},
918
+ ...document.applicationRouting !== void 0 ? { applicationRouting: readPlatformApplication({ PLATFORM_APPLICATION: JSON.stringify(document.applicationRouting ?? null) }) } : {},
655
919
  authenticationProviders: providers,
920
+ ...authentication ? { authentication } : {},
656
921
  capabilities: parsedCapabilities,
657
922
  displayName: string("displayName"),
658
923
  installationId: document.installationId === null ? null : string("installationId"),
@@ -661,18 +926,37 @@ function parsePlatformDiscoveryDocument(value) {
661
926
  usageProtocolVersion: parseUsageProtocolVersion(document.usageProtocolVersion)
662
927
  });
663
928
  }
929
+ function parseAuthentication(value) {
930
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Authentication discovery must be an object.");
931
+ const authentication = value;
932
+ if (!Array.isArray(authentication.methods) || authentication.methods.length > 32 || typeof authentication.protectedByAccess !== "boolean" || authentication.setupPending !== void 0 && typeof authentication.setupPending !== "boolean") throw new Error("Authentication discovery requires login methods and a protection flag.");
933
+ const methods = authentication.methods.map(parsePlatformLoginDescriptor);
934
+ if (new Set(methods.map((method) => method.id)).size !== methods.length) throw new Error("Authentication connection IDs must be unique.");
935
+ return {
936
+ methods,
937
+ protectedByAccess: authentication.protectedByAccess,
938
+ ...authentication.setupPending === true ? { setupPending: true } : {}
939
+ };
940
+ }
664
941
  function parseUsageProtocolVersion(value) {
665
942
  if (value === void 0 || value === 0) return 0;
666
943
  if (value === 1) return 1;
667
- throw new Error("usageProtocolVersion must be 0 or 1.");
944
+ if (value === 2) return 2;
945
+ throw new Error("usageProtocolVersion must be 0, 1, or 2.");
668
946
  }
669
947
  //#endregion
670
948
  //#region src/worker-config.ts
671
949
  function createPlatformWorkerConfigs(input) {
672
- const { accountId, apiUrl, deployment, healthProbeToken, installationId, ownershipNonce, platformVersion, proxyUrl, resources, runtimeEntrypoints } = input;
950
+ const { accountId, apiUrl, deployment, healthProbeToken, installationId, ownershipNonce, platformVersion, proxyUrl, emailGatewayUrl, resources, runtimeEntrypoints } = input;
673
951
  const publicEndpoints = input.publicEndpoints ?? true;
674
952
  const trafficState = input.trafficState ?? "enabled";
675
953
  const names = deployment.resources;
954
+ const sharedEmailIngress = deployment.emailSharedZoneId ? {
955
+ EMAIL_SHARED_ACCOUNT_ID: accountId,
956
+ EMAIL_SHARED_ZONE_ID: deployment.emailSharedZoneId,
957
+ EMAIL_SHARED_INGRESS_NAME: names.emailGatewayWorker
958
+ } : {};
959
+ const senderDomain = deployment.emailSenderDomain ? { EMAIL_SENDER_DOMAIN: deployment.emailSenderDomain } : {};
676
960
  const ownershipVars = (resourceKey, resourceName) => ({ PLATFORM_OWNERSHIP_MARKER: JSON.stringify(createPlatformResourceOwnershipMarker({
677
961
  accountId,
678
962
  installationId,
@@ -686,7 +970,7 @@ function createPlatformWorkerConfigs(input) {
686
970
  account_id: accountId,
687
971
  compatibility_date: "2026-02-24",
688
972
  observability: { enabled: true },
689
- preview_urls: publicEndpoints,
973
+ preview_urls: publicEndpoints && !deployment.authentication.accessProtection,
690
974
  workers_dev: publicEndpoints
691
975
  };
692
976
  const kv = [
@@ -711,9 +995,15 @@ function createPlatformWorkerConfigs(input) {
711
995
  binding: "STATIC_ASSETS_R2",
712
996
  bucket_name: names.assetsBucket
713
997
  }];
714
- const fetchWorkersDev = Boolean(deployment.application.workersDev) || [apiUrl, proxyUrl].some((url) => new URL(url).hostname.endsWith(".workers.dev"));
998
+ const fetchWorkersDev = Boolean(deployment.application.workersDev) || [
999
+ apiUrl,
1000
+ proxyUrl,
1001
+ ...deployment.capabilities.email ? [emailGatewayUrl] : []
1002
+ ].some((url) => new URL(url).hostname.endsWith(".workers.dev"));
715
1003
  const api = {
716
1004
  ...common,
1005
+ version_metadata: { binding: "VERSION_METADATA" },
1006
+ workers_dev: publicEndpoints && (!deployment.authentication.accessProtection || new URL(apiUrl).hostname.endsWith(".workers.dev")),
717
1007
  name: names.apiWorker,
718
1008
  main: runtimeEntrypoints.api,
719
1009
  no_bundle: true,
@@ -724,6 +1014,7 @@ function createPlatformWorkerConfigs(input) {
724
1014
  id: resources.hyperdrive
725
1015
  }],
726
1016
  kv_namespaces: kv,
1017
+ ...deployment.capabilities.email ? { send_email: [{ name: "EMAIL" }] } : {},
727
1018
  r2_buckets: r2,
728
1019
  dispatch_namespaces: dispatchNamespace,
729
1020
  durable_objects: { bindings: [
@@ -790,6 +1081,7 @@ function createPlatformWorkerConfigs(input) {
790
1081
  DISPATCH_WORKER_NAME: names.dispatchWorker,
791
1082
  LOGS_DATASET: names.logsDataset,
792
1083
  PLATFORM_CAPABILITIES: JSON.stringify(deployment.capabilities),
1084
+ ...input.authenticationDatabaseUncached ? { PLATFORM_AUTH_DATABASE_UNCACHED: "true" } : {},
793
1085
  PLATFORM_DISPLAY_NAME: deployment.displayName,
794
1086
  PLATFORM_ENV: "production",
795
1087
  PLATFORM_HEALTH_PROBE_TOKEN: healthProbeToken,
@@ -798,12 +1090,16 @@ function createPlatformWorkerConfigs(input) {
798
1090
  ...ownershipVars("api-worker", names.apiWorker),
799
1091
  PLATFORM_VERSION: platformVersion,
800
1092
  PROXY_URL: proxyUrl,
1093
+ EMAIL_GATEWAY_ORIGIN: emailGatewayUrl,
1094
+ ...sharedEmailIngress,
1095
+ ...senderDomain,
801
1096
  R2_STATIC_ASSETS_BUCKET: names.assetsBucket,
802
1097
  REQUESTS_DATASET: names.requestsDataset,
803
1098
  SITE_DOMAIN: deployment.application.baseDomain,
804
1099
  ...deployment.application.workersDev ? { PLATFORM_APPLICATION: JSON.stringify(deployment.application) } : {},
805
1100
  TAIL_SERVICE_NAME: names.tailWorker,
806
- VOID_PROXY_SERVICE: names.proxyWorker
1101
+ VOID_PROXY_SERVICE: names.proxyWorker,
1102
+ SANDBOX_CONTROL_SERVICE: names.apiWorker
807
1103
  }
808
1104
  };
809
1105
  if (publicEndpoints && deployment.controlPlane.apiHostname) api.routes = [{
@@ -812,6 +1108,7 @@ function createPlatformWorkerConfigs(input) {
812
1108
  }];
813
1109
  const proxy = {
814
1110
  ...common,
1111
+ workers_dev: publicEndpoints && (!deployment.authentication.accessProtection || new URL(proxyUrl).hostname.endsWith(".workers.dev")),
815
1112
  name: names.proxyWorker,
816
1113
  main: runtimeEntrypoints.proxy,
817
1114
  no_bundle: true,
@@ -826,12 +1123,14 @@ function createPlatformWorkerConfigs(input) {
826
1123
  service: names.apiWorker
827
1124
  }],
828
1125
  dispatch_namespaces: dispatchNamespace,
1126
+ ...deployment.capabilities.email ? { send_email: [{ name: "SEND_EMAIL" }] } : {},
829
1127
  vars: {
830
1128
  AI_GATEWAY_ID: names.aiGateway,
831
1129
  PLATFORM_CAPABILITIES: JSON.stringify(deployment.capabilities),
832
1130
  PLATFORM_HEALTH_PROBE_TOKEN: healthProbeToken,
833
1131
  PLATFORM_INSTALLATION_ID: installationId,
834
1132
  PLATFORM_TRAFFIC_STATE: trafficState,
1133
+ ...senderDomain,
835
1134
  ...ownershipVars("proxy-worker", names.proxyWorker)
836
1135
  }
837
1136
  };
@@ -842,6 +1141,34 @@ function createPlatformWorkerConfigs(input) {
842
1141
  return {
843
1142
  api,
844
1143
  proxy,
1144
+ emailGateway: {
1145
+ ...common,
1146
+ workers_dev: publicEndpoints,
1147
+ preview_urls: false,
1148
+ name: names.emailGatewayWorker,
1149
+ main: runtimeEntrypoints.emailGateway,
1150
+ no_bundle: true,
1151
+ compatibility_flags: ["nodejs_compat"],
1152
+ kv_namespaces: [{
1153
+ binding: "ROUTING",
1154
+ id: resources.routingKv
1155
+ }],
1156
+ services: [{
1157
+ binding: "API",
1158
+ service: names.apiWorker
1159
+ }],
1160
+ dispatch_namespaces: dispatchNamespace,
1161
+ ...deployment.capabilities.email ? { send_email: [{ name: "SEND_EMAIL" }] } : {},
1162
+ vars: {
1163
+ PLATFORM_CAPABILITIES: JSON.stringify(deployment.capabilities),
1164
+ PLATFORM_INSTALLATION_ID: installationId,
1165
+ PLATFORM_VERSION: platformVersion,
1166
+ PLATFORM_TRAFFIC_STATE: trafficState,
1167
+ ...sharedEmailIngress,
1168
+ ...senderDomain,
1169
+ ...ownershipVars("email-gateway-worker", names.emailGatewayWorker)
1170
+ }
1171
+ },
845
1172
  tail: {
846
1173
  ...common,
847
1174
  name: names.tailWorker,
@@ -892,4 +1219,4 @@ function createPlatformWorkerConfigs(input) {
892
1219
  };
893
1220
  }
894
1221
  //#endregion
895
- export { PLATFORM_APPLICATION_KEY, PLATFORM_DEPLOYMENT_SCHEMA_VERSION, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_DISCOVERY_SCHEMA_VERSION, PLATFORM_INSTALLATION_SCHEMA_VERSION, PLATFORM_PROJECT_INGRESS_SOURCE, PLATFORM_R2_OWNERSHIP_MARKER_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY, PLATFORM_RESOURCE_OWNERSHIP_SCHEMA_VERSION, PLATFORM_SANDBOX_DRAIN_KEY, PLATFORM_USAGE_PROTOCOL_VERSION, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformDiscoveryDocument, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, normalizeApplicationDomain, normalizeInstallationSlug, parsePlatformDeploymentManifest, parsePlatformDiscoveryDocument, parsePlatformInstallationManifest, parsePlatformResourceOwnershipMarker, platformProjectHostname, platformProjectHostnames, platformProjectIngressName, platformProjectWorkersDevHostname, platformWorkersDevAliasKey, readPlatformApplication, recordInstallationResource, recordInstallationStep, resolvePlatformApplicationEnvironment, setInstallationDesiredState, setInstallationEndpoints, transitionInstallation };
1222
+ export { DEFAULT_PLATFORM_SESSION_SECONDS, GOOGLE_OIDC_ISSUER, PLATFORM_APPLICATION_KEY, PLATFORM_AUTH_SETUP_PROOF_PREFIX, PLATFORM_DEPLOYMENT_SCHEMA_VERSION, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_DISCOVERY_SCHEMA_VERSION, PLATFORM_INSTALLATION_SCHEMA_VERSION, PLATFORM_LOGIN_KINDS, PLATFORM_PROJECT_INGRESS_SOURCE, PLATFORM_R2_OWNERSHIP_MARKER_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY, PLATFORM_RESOURCE_OWNERSHIP_SCHEMA_VERSION, PLATFORM_SANDBOX_DRAIN_KEY, PLATFORM_USAGE_PROTOCOL_VERSION, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformDiscoveryDocument, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, isPlatformSignupAllowed, normalizeApplicationDomain, normalizeInstallationSlug, parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformAuthenticationSetup, parsePlatformDeploymentManifest, parsePlatformDiscoveryDocument, parsePlatformIdentityUrl, parsePlatformInstallationManifest, parsePlatformLoginConfiguration, parsePlatformLoginDescriptor, parsePlatformLoginId, parsePlatformResourceOwnershipMarker, parsePlatformSignupAllowlistEntry, platformAuthenticationRecoveryMessage, platformProjectHostname, platformProjectHostnames, platformProjectIngressName, platformProjectWorkersDevHostname, platformProtectionProofMessage, platformWorkersDevAliasKey, readPlatformApplication, recordInstallationResource, recordInstallationStep, resolvePlatformApplicationEnvironment, setInstallationDesiredState, setInstallationEndpoints, transitionInstallation, validatePlatformAdmissionPolicy };
@@ -23,17 +23,19 @@ export type PlatformRuntimeDatabaseManifest = Readonly<{
23
23
  schemaDigest: string;
24
24
  }>;
25
25
  export type PlatformRuntimeManifest = Readonly<{
26
+ authenticationProtocolVersion?: 1;
26
27
  applicationRoutingVersion?: 1;
27
- artifacts: Readonly<Record<"api" | "dispatch" | "proxy" | "tail", PlatformRuntimeArtifact>>;
28
+ artifacts: Readonly<Record<"api" | "dispatch" | "proxy" | "tail", PlatformRuntimeArtifact> & Partial<Record<"emailGateway", PlatformRuntimeArtifact>>>;
29
+ emailProtocolVersion?: 1;
28
30
  database: PlatformRuntimeDatabaseManifest;
29
31
  platformVersion: string;
30
32
  safetyProtocols: Readonly<{
31
- sandboxDrain: 0 | 1 | 2;
33
+ sandboxDrain: 0 | 1 | 2 | 3;
32
34
  }>;
33
35
  schemaVersion: 2;
34
36
  sourceRevision: string | null;
35
37
  /** Queue payload contract shared by the API consumer and trusted producers. */
36
- usageProtocolVersion: 0 | 1;
38
+ usageProtocolVersion: 0 | 1 | 2;
37
39
  }>;
38
40
  export type PlatformRuntime = Readonly<{
39
41
  directory: string;