@wtfalch/foundry 0.59.0 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/dist/apps/cloudflare.d.ts +173 -0
  2. package/dist/apps/cloudflare.js +17 -0
  3. package/dist/apps/deploy.d.ts +116 -0
  4. package/dist/apps/deploy.js +791 -0
  5. package/dist/apps/github.d.ts +47 -0
  6. package/dist/apps/github.js +201 -0
  7. package/dist/apps/index.d.ts +9 -0
  8. package/dist/apps/index.js +9 -0
  9. package/dist/apps/migrations.d.ts +110 -0
  10. package/dist/apps/migrations.js +458 -0
  11. package/dist/audit/connector.d.ts +43 -0
  12. package/dist/audit/connector.js +22 -0
  13. package/dist/audit/evidence.d.ts +49 -0
  14. package/dist/audit/evidence.js +56 -0
  15. package/dist/audit/index.d.ts +9 -0
  16. package/dist/audit/index.js +9 -0
  17. package/dist/audit/migrations.d.ts +5 -0
  18. package/dist/audit/migrations.js +5 -0
  19. package/dist/audit/registry.d.ts +49 -0
  20. package/dist/audit/registry.js +44 -0
  21. package/dist/audit/seal.d.ts +49 -0
  22. package/dist/audit/seal.js +131 -0
  23. package/dist/audit/sealer-role.d.ts +33 -0
  24. package/dist/audit/sealer-role.js +56 -0
  25. package/dist/audit/signer.d.ts +40 -0
  26. package/dist/audit/signer.js +78 -0
  27. package/dist/audit/tick.d.ts +36 -0
  28. package/dist/audit/tick.js +87 -0
  29. package/dist/catalogue/copy-en.d.ts +1 -0
  30. package/dist/catalogue/copy-en.js +129 -0
  31. package/dist/catalogue/index.d.ts +9 -0
  32. package/dist/catalogue/index.js +21 -0
  33. package/dist/catalogue/integrations.d.ts +4 -0
  34. package/dist/catalogue/integrations.js +104 -0
  35. package/dist/catalogue/systems.d.ts +7 -0
  36. package/dist/catalogue/systems.js +31 -0
  37. package/dist/catalogue/types.d.ts +110 -0
  38. package/dist/catalogue/types.js +1 -0
  39. package/dist/entry-rules.d.ts +53 -0
  40. package/dist/entry-rules.js +71 -0
  41. package/dist/hosted-label.d.ts +13 -0
  42. package/dist/hosted-label.js +81 -0
  43. package/dist/index.d.ts +10 -5
  44. package/dist/index.js +5 -3
  45. package/dist/migrations/0062_hosting_github.sql +6 -0
  46. package/dist/migrations/0063_boule_apps.sql +7 -0
  47. package/dist/migrations/0064_requested_changes_boule_apps.sql +6 -0
  48. package/dist/modules/ai.d.ts +3 -0
  49. package/dist/modules/ai.js +64 -0
  50. package/dist/modules/assistants.d.ts +3 -0
  51. package/dist/modules/assistants.js +72 -0
  52. package/dist/modules/audit-ledger.d.ts +29 -0
  53. package/dist/modules/audit-ledger.js +49 -0
  54. package/dist/modules/audit.d.ts +7 -0
  55. package/dist/modules/audit.js +44 -0
  56. package/dist/modules/database.d.ts +23 -0
  57. package/dist/modules/database.js +189 -0
  58. package/dist/modules/email.d.ts +3 -0
  59. package/dist/modules/email.js +18 -0
  60. package/dist/modules/entry-parts.d.ts +23 -0
  61. package/dist/modules/entry-parts.js +63 -0
  62. package/dist/modules/files.d.ts +3 -0
  63. package/dist/modules/files.js +64 -0
  64. package/dist/modules/index.d.ts +74 -0
  65. package/dist/modules/index.js +774 -0
  66. package/dist/modules/jobs.d.ts +3 -0
  67. package/dist/modules/jobs.js +23 -0
  68. package/dist/modules/organisations.d.ts +3 -0
  69. package/dist/modules/organisations.js +20 -0
  70. package/dist/modules/outputs.d.ts +9 -0
  71. package/dist/modules/outputs.js +38 -0
  72. package/dist/modules/sign-in.d.ts +3 -0
  73. package/dist/modules/sign-in.js +197 -0
  74. package/dist/modules/signed-records.d.ts +3 -0
  75. package/dist/modules/signed-records.js +32 -0
  76. package/dist/modules/types.d.ts +194 -0
  77. package/dist/modules/types.js +1 -0
  78. package/dist/modules/workspaces.d.ts +3 -0
  79. package/dist/modules/workspaces.js +75 -0
  80. package/dist/org.d.ts +163 -22
  81. package/dist/org.js +419 -47
  82. package/dist/plan/apps-db-route.d.ts +90 -0
  83. package/dist/plan/apps-db-route.js +168 -0
  84. package/dist/plan/apps-zones.d.ts +56 -0
  85. package/dist/plan/apps-zones.js +118 -0
  86. package/dist/plan/archon-blue-green.js +11 -1
  87. package/dist/plan/blue-green-apply.d.ts +37 -1
  88. package/dist/plan/blue-green-apply.js +55 -15
  89. package/dist/plan/blue-green.d.ts +41 -1
  90. package/dist/plan/blue-green.js +80 -2
  91. package/dist/plan/boule.d.ts +23 -1
  92. package/dist/plan/boule.js +35 -2
  93. package/dist/plan/broker-entries.d.ts +28 -3
  94. package/dist/plan/broker-entries.js +74 -3
  95. package/dist/plan/broker-registrar.d.ts +168 -0
  96. package/dist/plan/broker-registrar.js +440 -0
  97. package/dist/plan/broker-workspaces-database.d.ts +48 -0
  98. package/dist/plan/broker-workspaces-database.js +87 -0
  99. package/dist/plan/desktop-callback.js +12 -7
  100. package/dist/plan/diff.d.ts +8 -2
  101. package/dist/plan/diff.js +373 -42
  102. package/dist/plan/edge-access.d.ts +6 -0
  103. package/dist/plan/edge-dns.d.ts +6 -0
  104. package/dist/plan/edge-dns.js +9 -7
  105. package/dist/plan/entry-ai-key.d.ts +1 -1
  106. package/dist/plan/entry-ai-key.js +2 -2
  107. package/dist/plan/entry-audit.d.ts +58 -0
  108. package/dist/plan/entry-audit.js +122 -0
  109. package/dist/plan/entry-customer-grants.d.ts +75 -0
  110. package/dist/plan/entry-customer-grants.js +154 -0
  111. package/dist/plan/entry-edge-key.d.ts +26 -1
  112. package/dist/plan/entry-edge-key.js +42 -3
  113. package/dist/plan/entry-jobs.d.ts +64 -0
  114. package/dist/plan/entry-jobs.js +92 -0
  115. package/dist/plan/entry-repo-key-apply.d.ts +55 -0
  116. package/dist/plan/entry-repo-key-apply.js +185 -0
  117. package/dist/plan/entry-repo-key.d.ts +92 -0
  118. package/dist/plan/entry-repo-key.js +235 -0
  119. package/dist/plan/entry-workspaces.d.ts +55 -0
  120. package/dist/plan/entry-workspaces.js +90 -0
  121. package/dist/plan/executor.d.ts +191 -1
  122. package/dist/plan/executor.js +1505 -90
  123. package/dist/plan/gates.d.ts +1 -1
  124. package/dist/plan/gates.js +62 -9
  125. package/dist/plan/github-deploy-keys.d.ts +78 -0
  126. package/dist/plan/github-deploy-keys.js +305 -0
  127. package/dist/plan/github-source.d.ts +53 -0
  128. package/dist/plan/github-source.js +140 -0
  129. package/dist/plan/held-key-refs.d.ts +12 -0
  130. package/dist/plan/held-key-refs.js +12 -0
  131. package/dist/plan/hostnames.d.ts +9 -0
  132. package/dist/plan/hostnames.js +28 -1
  133. package/dist/plan/index.d.ts +9 -0
  134. package/dist/plan/index.js +6 -0
  135. package/dist/plan/jobs-scheduler-http.js +3 -0
  136. package/dist/plan/keys-registrar-http.d.ts +32 -0
  137. package/dist/plan/keys-registrar-http.js +105 -0
  138. package/dist/plan/keys-registrar.d.ts +67 -0
  139. package/dist/plan/keys-registrar.js +103 -0
  140. package/dist/plan/live.d.ts +1 -1
  141. package/dist/plan/locked-hostnames.d.ts +2 -2
  142. package/dist/plan/locked-hostnames.js +2 -2
  143. package/dist/plan/login-host.js +11 -5
  144. package/dist/plan/machine-paths.d.ts +36 -0
  145. package/dist/plan/machine-paths.js +137 -0
  146. package/dist/plan/mcp.d.ts +1 -1
  147. package/dist/plan/mcp.js +49 -17
  148. package/dist/plan/org-apps-admin.d.ts +108 -0
  149. package/dist/plan/org-apps-admin.js +434 -0
  150. package/dist/plan/org-names.js +12 -1
  151. package/dist/plan/own-diff.d.ts +23 -0
  152. package/dist/plan/own-diff.js +53 -0
  153. package/dist/plan/own-hostnames.d.ts +2 -0
  154. package/dist/plan/own-hostnames.js +1 -0
  155. package/dist/plan/own-jobs-route.d.ts +47 -0
  156. package/dist/plan/own-jobs-route.js +91 -0
  157. package/dist/plan/own-services.d.ts +4 -0
  158. package/dist/plan/own-services.js +4 -0
  159. package/dist/plan/plan-apply.d.ts +39 -0
  160. package/dist/plan/plan-apply.js +163 -9
  161. package/dist/plan/platform-diff.d.ts +2 -2
  162. package/dist/plan/platform-diff.js +7 -2
  163. package/dist/plan/platform-org.d.ts +30 -0
  164. package/dist/plan/platform-org.js +12 -1
  165. package/dist/plan/platform-providers.d.ts +2 -0
  166. package/dist/plan/platform-providers.js +7 -2
  167. package/dist/plan/post-logout-uris.js +7 -1
  168. package/dist/plan/private-pages.d.ts +26 -0
  169. package/dist/plan/private-pages.js +39 -0
  170. package/dist/plan/proxy-labels.d.ts +12 -1
  171. package/dist/plan/proxy-labels.js +32 -2
  172. package/dist/plan/release-host.d.ts +6 -2
  173. package/dist/plan/release-host.js +36 -8
  174. package/dist/plan/stage-env.d.ts +19 -1
  175. package/dist/plan/stage-env.js +25 -1
  176. package/dist/plan/stage-guards.d.ts +20 -0
  177. package/dist/plan/stage-guards.js +24 -0
  178. package/dist/plan/stage-isolation.d.ts +24 -0
  179. package/dist/plan/stage-isolation.js +29 -1
  180. package/dist/plan/worker-app-database.d.ts +47 -40
  181. package/dist/plan/worker-app-database.js +150 -152
  182. package/dist/plan/worker-app.d.ts +6 -65
  183. package/dist/plan/worker-app.js +29 -105
  184. package/dist/plan/worker.d.ts +2 -2
  185. package/dist/plan/worker.js +2 -1
  186. package/dist/plan/workspace-client.d.ts +25 -0
  187. package/dist/plan/workspace-client.js +48 -0
  188. package/dist/plan/workspace-sign-in.d.ts +55 -0
  189. package/dist/plan/workspace-sign-in.js +273 -0
  190. package/dist/plan/workspace-worker.d.ts +4 -0
  191. package/dist/plan/workspace-worker.js +13 -0
  192. package/dist/plan/workspace.d.ts +101 -0
  193. package/dist/plan/workspace.js +305 -0
  194. package/dist/release/decide.d.ts +30 -1
  195. package/dist/release/decide.js +73 -11
  196. package/dist/release/health.d.ts +8 -3
  197. package/dist/release/health.js +58 -5
  198. package/dist/release/run.d.ts +2 -5
  199. package/dist/release/run.js +4 -0
  200. package/dist/release/state.d.ts +3 -1
  201. package/dist/release/state.js +6 -0
  202. package/dist/release/types.d.ts +20 -0
  203. package/dist/requested-changes.d.ts +11 -2
  204. package/dist/requested-changes.js +11 -0
  205. package/dist/service-labels.d.ts +4 -0
  206. package/dist/service-labels.js +11 -0
  207. package/dist/slug.d.ts +2 -0
  208. package/dist/slug.js +10 -6
  209. package/dist/store/postgres.js +12 -2
  210. package/dist/store/schema.d.ts +37 -1
  211. package/dist/store/schema.js +4 -0
  212. package/dist/worker-app-config.d.ts +6 -10
  213. package/dist/worker-app-config.js +22 -17
  214. package/dist/worker-app-migrations.d.ts +105 -0
  215. package/dist/worker-app-migrations.js +166 -0
  216. package/dist/worker-app-upload.d.ts +103 -0
  217. package/dist/worker-app-upload.js +128 -0
  218. package/package.json +18 -1
@@ -1,10 +1,13 @@
1
- import type { DeclaredOrg, HostingEntry } from '../org.js';
1
+ import { type DeclaredOrg, type HostingEntry } from '../org.js';
2
2
  import { type WorkerAppConfig } from '../worker-app-config.js';
3
+ import { WORKER_APP_DNS_RECORD, type WorkerAppContainer, type WorkerAppUpload, withCallTimeout, workerAppRegistryPrefix } from '../worker-app-upload.js';
3
4
  import type { DraftStep, StepAction } from './diff.js';
4
5
  import type { DnsPort } from './edge-dns.js';
5
6
  import type { ZonePort } from './edge-zones.js';
6
7
  import type { StepExecutionContext } from './plan-apply.js';
7
8
  import { type WorkerAppDatabaseDeps } from './worker-app-database.js';
9
+ export type { WorkerAppBinding, WorkerAppContainer, WorkerAppMigrations, WorkerAppUpload, } from '../worker-app-upload.js';
10
+ export { WORKER_APP_DNS_RECORD, withCallTimeout, workerAppRegistryPrefix };
8
11
  /**
9
12
  * ADR 0002: a hosting entry that declares `workerApp` runs as one Cloudflare
10
13
  * Worker with Durable Objects and Containers on the platform account. One step
@@ -46,11 +49,6 @@ export declare const WORKER_APP_ORG_HASH_LENGTH = 8;
46
49
  export declare const workerAppName: (org: DeclaredOrg, entry: HostingEntry) => string;
47
50
  /** The zone that holds the routes of an org with no domain of its own. */
48
51
  export declare const WORKER_APP_OIKOS_ZONE = "oikos.host";
49
- /** The record a Worker route needs on the org's own zone: Cloudflare's documented dummy, proxied. */
50
- export declare const WORKER_APP_DNS_RECORD: {
51
- readonly type: "AAAA";
52
- readonly content: "100::";
53
- };
54
52
  /** True once the Worker app holds the broker key (`authEnv` delivered it). */
55
53
  export declare const brokerKeyDelivered: (recorded: Readonly<Record<string, string>>, entryKey: string) => boolean;
56
54
  /**
@@ -69,57 +67,6 @@ export declare const WORKER_APP_AUTH_KEY_SECRET = "AUTH_APP_KEY";
69
67
  * and name are hashed: a held ref is at most 128 characters.
70
68
  */
71
69
  export declare const workerAppSecretRef: (orgId: string, entryKey: string, name: string) => string;
72
- export type WorkerAppBinding = {
73
- type: 'durable_object_namespace';
74
- name: string;
75
- class_name: string;
76
- } | {
77
- type: 'plain_text';
78
- name: string;
79
- text: string;
80
- } | {
81
- type: 'hyperdrive';
82
- name: string;
83
- id: string;
84
- };
85
- export interface WorkerAppMigrations {
86
- /** The tag the script holds now. Absent for a script with none. */
87
- oldTag?: string;
88
- newTag: string;
89
- steps: {
90
- newSqliteClasses?: string[];
91
- deletedClasses?: string[];
92
- renamedClasses?: {
93
- from: string;
94
- to: string;
95
- }[];
96
- }[];
97
- }
98
- export interface WorkerAppUpload {
99
- name: string;
100
- artifactUrl: string;
101
- artifactSha256: string;
102
- compatibilityDate: string;
103
- compatibilityFlags: string[];
104
- /** All of them: a redeploy replaces the list. Secrets survive a redeploy. */
105
- bindings: WorkerAppBinding[];
106
- /** Only the migrations not yet applied; absent when there are none. */
107
- migrations?: WorkerAppMigrations;
108
- /** Durable Object classes the upload marks container-enabled; absent when the entry declares no container. */
109
- containers?: string[];
110
- /** Workers Logs: always on, every request sampled. The adapter sends it as the upload metadata's `observability`. */
111
- observability: {
112
- enabled: true;
113
- head_sampling_rate: 1;
114
- };
115
- }
116
- export interface WorkerAppContainer {
117
- className: string;
118
- /** `<repository>@sha256:<digest>`. */
119
- image: string;
120
- instanceType: string;
121
- maxInstances: number;
122
- }
123
70
  /**
124
71
  * The Cloudflare calls a Worker app needs. Edge 0.13.0 has none of the Durable
125
72
  * Object, migration or container calls, so no adapter ships with this port yet
@@ -208,6 +155,7 @@ export interface WorkerAppWant {
208
155
  /** `workerApp.database`: the Hyperdrive binding and the tables the runtime role may only append to, sorted. */
209
156
  database?: {
210
157
  appendOnly: string[];
158
+ grants?: string[];
211
159
  migrationsSha256?: string;
212
160
  };
213
161
  /** Secret names and where each comes from, sorted. */
@@ -218,7 +166,7 @@ export interface WorkerAppWant {
218
166
  }[];
219
167
  }
220
168
  /** The zone apex the entry's route lives in. */
221
- export declare const workerAppApex: (org: DeclaredOrg) => string;
169
+ export declare const workerAppApex: (org: DeclaredOrg, entry?: HostingEntry) => string;
222
170
  /**
223
171
  * What the Worker must look like, or the inputs still missing (names only).
224
172
  * `issuer` is the sign-in issuer the plan read; `keysUrl` the custody Worker.
@@ -268,8 +216,6 @@ export interface WorkerAppPlanInput {
268
216
  accessReady: boolean;
269
217
  };
270
218
  }
271
- /** The registry prefix a container image of this platform must start with. */
272
- export declare const workerAppRegistryPrefix: (accountId: string) => string;
273
219
  /** Every entry key with a deployed or half-deployed Worker app recorded. */
274
220
  export declare function liveWorkerApps(orgId: string, recordedIds: Readonly<Record<string, string>>): Map<string, string>;
275
221
  /**
@@ -305,10 +251,6 @@ export interface WorkerAppDeps {
305
251
  /** An outside call that has not answered in this long fails the step; the next apply repairs it. */
306
252
  export declare const WORKER_APP_CALL_TIMEOUT_MS = 120000;
307
253
  export declare const WORKER_APP_KEYS_DELIVERY_ATTEMPTS = 3;
308
- /**
309
- * Races `task` against a timer. An error names the call and edge's error code, never the cause.
310
- */
311
- export declare function withCallTimeout<T>(ms: number, label: string, task: () => Promise<T>): Promise<T>;
312
254
  /**
313
255
  * Makes the Worker app true. Throws a fixed message on a failure, naming the
314
256
  * step and never a value or a provider's cause for anything that carried a
@@ -324,4 +266,3 @@ export declare function applyWorkerApp(deps: WorkerAppDeps, org: DeclaredOrg, ac
324
266
  * by org destroy.
325
267
  */
326
268
  export declare function teardownWorkerApp(deps: WorkerAppDeps, entryKey: string, scriptName: string | undefined, recorded: Readonly<Record<string, string>>, call: <T>(label: string, task: () => Promise<T>) => Promise<T>, orgId: string, context?: Pick<StepExecutionContext, 'recordScopeId' | 'persistScopeIds'> | null): Promise<void>;
327
- export {};
@@ -1,11 +1,14 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
- import { WORKER_APP_HYPERDRIVE_BINDING, WORKER_APP_KEYS_SECRET, WORKER_APP_KEYS_URL_VAR, workerAppHasBuild, workerAppHostId, } from '../worker-app-config.js';
2
+ import { APPS_DOMAIN } from '../org.js';
3
+ import { WORKER_APP_KEYS_SECRET, WORKER_APP_KEYS_URL_VAR, workerAppHasBuild, workerAppHostId, } from '../worker-app-config.js';
4
+ import { WORKER_APP_DNS_RECORD, pendingWorkerAppMigrations, strayContainerImage, withCallTimeout, workerAppRegistryPrefix, workerAppShape, workerAppUpload, } from '../worker-app-upload.js';
3
5
  import { entryAuthEnv } from './broker-entries.js';
4
6
  import { workerAppHostname } from './hostnames.js';
7
+ import { workspaceWorkerParent } from './workspace-worker.js';
5
8
  import { appStepKey } from './platform-diff.js';
6
9
  import { remoteDatabaseKeys } from './remote-database.js';
7
10
  import { applyWorkerAppDatabase, deleteWorkerAppDatabaseRoute, deleteWorkerAppHyperdrive, workerAppDbRouteMarkerScopeKey, workerAppHyperdriveScopeKey, workerAppMigrationsScopeKey, } from './worker-app-database.js';
8
- import { WORKER_COMPATIBILITY_DATE } from './worker.js';
11
+ export { WORKER_APP_DNS_RECORD, withCallTimeout, workerAppRegistryPrefix };
9
12
  /**
10
13
  * ADR 0002: a hosting entry that declares `workerApp` runs as one Cloudflare
11
14
  * Worker with Durable Objects and Containers on the platform account. One step
@@ -60,14 +63,11 @@ export const workerAppTraceScopeKeys = (entryKey) => [
60
63
  export const workerAppResourceKey = (stepKey) => `cloudflare:worker-app:${stepKey}`;
61
64
  export const WORKER_APP_ORG_HASH_LENGTH = 8;
62
65
  /** `${8 hex of the org id's sha256}-${entry key}-app`. */
63
- export const workerAppName = (org, entry) => `${createHash('sha256').update(org.id).digest('hex').slice(0, WORKER_APP_ORG_HASH_LENGTH)}-${entry.key}-app`;
66
+ export const workerAppName = (org, entry) => workspaceWorkerParent(entry) !== null
67
+ ? `${org.slug}-${workspaceWorkerParent(entry)}-work`
68
+ : `${createHash('sha256').update(org.id).digest('hex').slice(0, WORKER_APP_ORG_HASH_LENGTH)}-${entry.key}-app`;
64
69
  /** The zone that holds the routes of an org with no domain of its own. */
65
70
  export const WORKER_APP_OIKOS_ZONE = 'oikos.host';
66
- /** The record a Worker route needs on the org's own zone: Cloudflare's documented dummy, proxied. */
67
- export const WORKER_APP_DNS_RECORD = {
68
- type: 'AAAA',
69
- content: '100::',
70
- };
71
71
  /** True once the Worker app holds the broker key (`authEnv` delivered it). */
72
72
  export const brokerKeyDelivered = (recorded, entryKey) => Boolean(parseJson(recorded[workerAppSecretsScopeKey(entryKey)], {})
73
73
  .AUTH_APP_KEY);
@@ -107,7 +107,11 @@ export function custodyRenewalDue(recorded, entryKey, now) {
107
107
  }
108
108
  const sortedEntries = (record) => Object.entries(record ?? {}).sort(([a], [b]) => a.localeCompare(b));
109
109
  /** The zone apex the entry's route lives in. */
110
- export const workerAppApex = (org) => org.hosting.usesDomain && org.domain ? org.domain : WORKER_APP_OIKOS_ZONE;
110
+ export const workerAppApex = (org, entry) => entry && workspaceWorkerParent(entry) !== null
111
+ ? APPS_DOMAIN
112
+ : org.hosting.usesDomain && org.domain
113
+ ? org.domain
114
+ : WORKER_APP_OIKOS_ZONE;
111
115
  /** True when the want has a secret that must be held (a generated one, the broker key, a custody credential). */
112
116
  const holdsGenerated = (want) => want.secrets.length > 0;
113
117
  /**
@@ -160,32 +164,25 @@ export function wantedWorkerApp(org, entry, recorded, input) {
160
164
  }
161
165
  if (missing.length > 0)
162
166
  return { missing };
167
+ const shape = workerAppShape(app);
163
168
  return {
164
169
  want: {
165
170
  name: workerAppName(org, entry),
166
- artifactUrl: app.artifactUrl,
167
- artifactSha256: app.artifactSha256,
168
- compatibilityDate: app.compatibilityDate ?? WORKER_COMPATIBILITY_DATE,
169
- compatibilityFlags: [...(app.compatibilityFlags ?? [])].sort(),
171
+ artifactUrl: shape.artifactUrl,
172
+ artifactSha256: shape.artifactSha256,
173
+ compatibilityDate: shape.compatibilityDate,
174
+ compatibilityFlags: shape.compatibilityFlags,
170
175
  host: workerAppHostname(org, entry),
171
- apex: workerAppApex(org),
172
- classes: Object.entries(app.durableObjects.classes)
173
- .map(([binding, className]) => ({ binding, className }))
174
- .sort((a, b) => a.binding.localeCompare(b.binding)),
175
- migrations: app.durableObjects.migrations,
176
- containers: (app.containers ?? [])
177
- .map((c) => ({
178
- className: c.className,
179
- image: `${c.image.repository}@${c.image.digest}`,
180
- instanceType: c.instanceType,
181
- maxInstances: c.maxInstances,
182
- }))
183
- .sort((a, b) => a.className.localeCompare(b.className)),
176
+ apex: workerAppApex(org, entry),
177
+ classes: shape.classes,
178
+ migrations: shape.migrations,
179
+ containers: shape.containers,
184
180
  vars: sortedEntries(vars),
185
181
  ...(app.database
186
182
  ? {
187
183
  database: {
188
184
  appendOnly: [...(app.database.appendOnly ?? [])].sort(),
185
+ ...(app.database.grants?.length ? { grants: [...app.database.grants].sort() } : {}),
189
186
  ...(app.database.migrationsSha256
190
187
  ? { migrationsSha256: app.database.migrationsSha256 }
191
188
  : {}),
@@ -222,8 +219,6 @@ export function migrationPrefixError(applied, declared) {
222
219
  }
223
220
  /** The unapplied migrations after the last tag in `applied`. */
224
221
  export const unappliedMigrations = (declared, applied) => declared.slice(applied.length);
225
- /** The registry prefix a container image of this platform must start with. */
226
- export const workerAppRegistryPrefix = (accountId) => `registry.cloudflare.com/${accountId}/`;
227
222
  /** Every entry key with a deployed or half-deployed Worker app recorded. */
228
223
  export function liveWorkerApps(orgId, recordedIds) {
229
224
  const mainPrefix = `cloudflare:worker-app:${orgId}:`;
@@ -327,9 +322,7 @@ export function planWorkerApps(org, recordedIds, input) {
327
322
  continue;
328
323
  }
329
324
  const { want } = wanted;
330
- const stray = (app.containers ?? []).find((c) => input.registryAccountId === undefined ||
331
- !c.image.repository.startsWith(workerAppRegistryPrefix(input.registryAccountId)) ||
332
- c.image.repository.split('/').some((part) => /^\.+$/.test(part)));
325
+ const stray = strayContainerImage(app, input.registryAccountId);
333
326
  if (stray) {
334
327
  block(input.registryAccountId === undefined
335
328
  ? 'the host supplies no Cloudflare account id for the container registry'
@@ -399,31 +392,6 @@ export function planWorkerApps(org, recordedIds, input) {
399
392
  /** An outside call that has not answered in this long fails the step; the next apply repairs it. */
400
393
  export const WORKER_APP_CALL_TIMEOUT_MS = 120_000;
401
394
  export const WORKER_APP_KEYS_DELIVERY_ATTEMPTS = 3;
402
- /**
403
- * Races `task` against a timer. An error names the call and edge's error code, never the cause.
404
- */
405
- export function withCallTimeout(ms, label, task) {
406
- return new Promise((resolve, reject) => {
407
- const timer = setTimeout(() => reject(new Error(`${label}: no answer within ${ms} ms`)), ms);
408
- task().then((value) => {
409
- clearTimeout(timer);
410
- resolve(value);
411
- }, (error) => {
412
- clearTimeout(timer);
413
- // The cause is dropped: a provider error can echo a request body. The
414
- // HTTP status stays, because callers tell "already gone" by it. So does
415
- // edge's error code (a fixed lower-case enum, never a secret), to say why.
416
- const { status, code } = (error ?? {});
417
- const known = typeof code === 'string' && /^[a-z_]{1,40}$/.test(code) ? code : undefined;
418
- const failure = new Error(known ? `${label} failed (${known})` : `${label} failed`);
419
- if (typeof status === 'number')
420
- failure.status = status;
421
- if (known)
422
- failure.code = known;
423
- reject(failure);
424
- });
425
- });
426
- }
427
395
  const isGone = (error) => {
428
396
  const status = error?.status;
429
397
  return typeof status === 'number' && status === 404;
@@ -480,58 +448,14 @@ export async function applyWorkerApp(deps, org, action, context) {
480
448
  const recordedTags = parseJson(recorded0[workerAppTagsScopeKey(entryKey)], []);
481
449
  // The live tag is the fact; the record can be behind it after a crash between upload and record.
482
450
  const liveTag = await call('read the migration tag', () => workers.liveMigrationTag(want.name));
483
- let appliedCount;
484
- if (liveTag === undefined)
485
- appliedCount = 0;
486
- else {
487
- const index = declaredTags.indexOf(liveTag);
488
- if (index < 0) {
489
- throw new Error(`${at}: the live Worker holds migration '${liveTag}', which is not in the declared list; migrations are append-only and foundry will not guess`);
490
- }
491
- appliedCount = index + 1;
492
- }
451
+ const pending = pendingWorkerAppMigrations(want.migrations, liveTag);
452
+ if (!pending.ok)
453
+ throw new Error(`${at}: ${pending.reason}`);
493
454
  const recordedError = migrationPrefixError(recordedTags, declaredTags);
494
455
  if (recordedError)
495
456
  throw new Error(`${at}: ${recordedError}`);
496
- const pending = want.migrations.slice(appliedCount);
497
- const migrations = pending.length > 0
498
- ? {
499
- ...(liveTag !== undefined ? { oldTag: liveTag } : {}),
500
- newTag: pending[pending.length - 1]?.tag,
501
- steps: pending.map((m) => ({
502
- ...(m.newSqliteClasses ? { newSqliteClasses: m.newSqliteClasses } : {}),
503
- ...(m.deletedClasses ? { deletedClasses: m.deletedClasses } : {}),
504
- ...(m.renamedClasses ? { renamedClasses: m.renamedClasses } : {}),
505
- })),
506
- }
507
- : undefined;
508
- await call('upload the script', () => workers.uploadWorkerApp({
509
- name: want.name,
510
- artifactUrl: want.artifactUrl,
511
- artifactSha256: want.artifactSha256,
512
- compatibilityDate: want.compatibilityDate,
513
- compatibilityFlags: want.compatibilityFlags,
514
- bindings: [
515
- ...want.classes.map((c) => ({
516
- type: 'durable_object_namespace',
517
- name: c.binding,
518
- class_name: c.className,
519
- })),
520
- ...want.vars.map(([name, text]) => ({
521
- type: 'plain_text',
522
- name,
523
- text,
524
- })),
525
- ...(hyperdriveId
526
- ? [{ type: 'hyperdrive', name: WORKER_APP_HYPERDRIVE_BINDING, id: hyperdriveId }]
527
- : []),
528
- ],
529
- observability: { enabled: true, head_sampling_rate: 1 },
530
- ...(migrations ? { migrations } : {}),
531
- ...(want.containers.length > 0
532
- ? { containers: want.containers.map((c) => c.className) }
533
- : {}),
534
- }));
457
+ const migrations = pending.migrations;
458
+ await call('upload the script', () => workers.uploadWorkerApp(workerAppUpload(want.name, want, want.vars, migrations, hyperdriveId)));
535
459
  context.recordScopeId(workerAppTagsScopeKey(entryKey), JSON.stringify(declaredTags));
536
460
  await context.persistScopeIds();
537
461
  // A Worker that no longer declares a database no longer binds its config: delete it now.
@@ -1,4 +1,5 @@
1
1
  import type { DeclaredOrg, HostingEntry, WorkerConfig } from '../org.js';
2
+ import { WORKER_COMPATIBILITY_DATE } from '../worker-app-upload.js';
2
3
  import type { DraftStep } from './diff.js';
3
4
  /**
4
5
  * F8 (#318): one plain Cloudflare Worker per hosting entry that declares
@@ -91,7 +92,7 @@ export declare const workerName: (org: DeclaredOrg, entry: HostingEntry) => stri
91
92
  * like {@link RATE_LIMIT_FREE_PLAN_PERIOD_SECONDS} is a fixed Cloudflare
92
93
  * Free plan value rather than something an org declares.
93
94
  */
94
- export declare const WORKER_COMPATIBILITY_DATE = "2026-09-12";
95
+ export { WORKER_COMPATIBILITY_DATE };
95
96
  export type WorkerMode = 'apply' | 'retire';
96
97
  export interface WorkerWant {
97
98
  name: string;
@@ -131,4 +132,3 @@ export declare function planWorkers(org: DeclaredOrg, recordedIds: Readonly<Reco
131
132
  steps: DraftStep[];
132
133
  blocked: Blocked;
133
134
  };
134
- export {};
@@ -1,4 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { WORKER_COMPATIBILITY_DATE } from '../worker-app-upload.js';
2
3
  import { entryHostnames } from './hostnames.js';
3
4
  import { lockedHostnames } from './locked-hostnames.js';
4
5
  import { appStepKey } from './platform-diff.js';
@@ -123,7 +124,7 @@ export const workerName = (org, entry) =>
123
124
  * like {@link RATE_LIMIT_FREE_PLAN_PERIOD_SECONDS} is a fixed Cloudflare
124
125
  * Free plan value rather than something an org declares.
125
126
  */
126
- export const WORKER_COMPATIBILITY_DATE = '2026-09-12';
127
+ export { WORKER_COMPATIBILITY_DATE };
127
128
  /**
128
129
  * With a wildcard route (`*.<domain>/*`) the Worker would also capture every
129
130
  * hostname foundry provisions on the domain, so each gets a no-Worker route
@@ -0,0 +1,25 @@
1
+ import type { DeclaredOrg, HostingEntry } from '../org.js';
2
+ /** The name of an entry's workspace client and of the key its workspace broker entry is held under. */
3
+ export declare const workspaceClientKey: (entryKey: string) => string;
4
+ /** The company's ZITADEL organisation for the customers of its workspaces. The space keeps it apart from every slug. */
5
+ export declare const testCustomersOrgName: (slug: string) => string;
6
+ /** The recorded id of that organisation (the step's `system:kind:key`). */
7
+ export declare const testCustomersOrgKey: (orgId: string) => string;
8
+ /** `'1'` or `'0'`: whether the test customers organisation was last left open to self-registration. */
9
+ export declare const TEST_CUSTOMERS_SIGN_UP_SCOPE_KEY = "test_customers_sign_up";
10
+ /** `'email-code'` once the emailed-code metadata was written to the test customers organisation. */
11
+ export declare const TEST_CUSTOMERS_SIGN_IN_SCOPE_KEY = "test_customers_sign_in";
12
+ /** The server entries whose workspace has its own sign-in: a workspace and `uses.auth`. */
13
+ export declare function workspaceAuthEntries(org: DeclaredOrg): HostingEntry[];
14
+ /** The entry whose workspace client is named `clientKey`, or `undefined`. */
15
+ export declare const workspaceAuthEntryOf: (org: DeclaredOrg, clientKey: string) => HostingEntry | undefined;
16
+ /** The entries whose workspace signs in the test customers organisation. */
17
+ export declare const testCustomersEntries: (org: DeclaredOrg) => HostingEntry[];
18
+ /** The addresses a workspace client signs in at: the workspace host. */
19
+ export declare const workspaceClientHosts: (org: DeclaredOrg, entry: HostingEntry) => string[];
20
+ /**
21
+ * `org` (planned without its dark entries) plus the sign-in-only shape of the dark entries whose workspace has
22
+ * sign-in, so the login host's lists of customers clients match what the executor, which reads the org as
23
+ * declared, writes. The copies have no address and no Worker.
24
+ */
25
+ export declare function withDarkWorkspaceSignIn(org: DeclaredOrg, declared: DeclaredOrg): DeclaredOrg;
@@ -0,0 +1,48 @@
1
+ import { workspaceHost } from './hostnames.js';
2
+ /**
3
+ * What the sign-in of a server workspace is called, shared by the planners that must agree on it. A workspace of
4
+ * an entry with `uses.auth` signs in with its own ZITADEL client named `<entry key>-work`, and its customers
5
+ * sign in a separate "<slug> test customers" organisation, never the live customers one.
6
+ */
7
+ /** The suffix of the client, broker entry and held key that belong to an entry's workspace. */
8
+ const WORKSPACE_CLIENT_SUFFIX = '-work';
9
+ /** The name of an entry's workspace client and of the key its workspace broker entry is held under. */
10
+ export const workspaceClientKey = (entryKey) => `${entryKey}${WORKSPACE_CLIENT_SUFFIX}`;
11
+ /** The company's ZITADEL organisation for the customers of its workspaces. The space keeps it apart from every slug. */
12
+ export const testCustomersOrgName = (slug) => `${slug} test customers`;
13
+ /** The recorded id of that organisation (the step's `system:kind:key`). */
14
+ export const testCustomersOrgKey = (orgId) => `zitadel:customers-test:${orgId}`;
15
+ /** `'1'` or `'0'`: whether the test customers organisation was last left open to self-registration. */
16
+ export const TEST_CUSTOMERS_SIGN_UP_SCOPE_KEY = 'test_customers_sign_up';
17
+ /** `'email-code'` once the emailed-code metadata was written to the test customers organisation. */
18
+ export const TEST_CUSTOMERS_SIGN_IN_SCOPE_KEY = 'test_customers_sign_in';
19
+ /** The server entries whose workspace has its own sign-in: a workspace and `uses.auth`. */
20
+ export function workspaceAuthEntries(org) {
21
+ if (!org.hosting.enabled)
22
+ return [];
23
+ return org.hosting.entries.filter((entry) => entry.workspace !== undefined &&
24
+ entry.workerApp === undefined &&
25
+ entry.uses?.auth !== undefined);
26
+ }
27
+ /** The entry whose workspace client is named `clientKey`, or `undefined`. */
28
+ export const workspaceAuthEntryOf = (org, clientKey) => workspaceAuthEntries(org).find((entry) => workspaceClientKey(entry.key) === clientKey);
29
+ /** The entries whose workspace signs in the test customers organisation. */
30
+ export const testCustomersEntries = (org) => workspaceAuthEntries(org).filter((entry) => entry.uses?.auth?.users === 'customers');
31
+ /** The addresses a workspace client signs in at: the workspace host. */
32
+ export const workspaceClientHosts = (org, entry) => [
33
+ workspaceHost(org.slug, entry.key),
34
+ ];
35
+ /**
36
+ * `org` (planned without its dark entries) plus the sign-in-only shape of the dark entries whose workspace has
37
+ * sign-in, so the login host's lists of customers clients match what the executor, which reads the org as
38
+ * declared, writes. The copies have no address and no Worker.
39
+ */
40
+ export function withDarkWorkspaceSignIn(org, declared) {
41
+ const present = new Set(org.hosting.entries.map((entry) => entry.key));
42
+ const dark = workspaceAuthEntries(declared)
43
+ .filter((entry) => !present.has(entry.key))
44
+ .map((entry) => ({ ...entry, hostnames: [], gates: [] }));
45
+ if (dark.length === 0)
46
+ return org;
47
+ return { ...org, hosting: { ...org.hosting, entries: [...org.hosting.entries, ...dark] } };
48
+ }
@@ -0,0 +1,55 @@
1
+ import type { DeclaredOrg, HostingEntry } from '../org.js';
2
+ import type { UnmanagedItem } from '../plan.js';
3
+ import { type BrokerEntryInput, type BrokerEntryWant } from './broker-entries.js';
4
+ import type { DraftStep } from './diff.js';
5
+ import type { LiveResource } from './live.js';
6
+ /** The step `coolify:workspace-broker-entry` and its action key, for the planner and the executor to share. */
7
+ export declare const WORKSPACE_BROKER_ENTRY_KIND = "workspace-broker-entry";
8
+ /**
9
+ * The MCP resource of a workspace with `uses.auth.mcp`: its own endpoint on the workspace host, its own audience
10
+ * project in the test customers organisation (recorded by `zitadel:mcp-project` under the client's name), and the
11
+ * onboarding page on the workspace host. `null` without `mcp` or until the project is recorded.
12
+ */
13
+ export declare function workspaceMcp(org: DeclaredOrg, entry: HostingEntry, recorded: Readonly<Record<string, string>>): {
14
+ resource: string;
15
+ projectId: string;
16
+ loginHost: string;
17
+ onboardingUrl?: string;
18
+ } | null;
19
+ /**
20
+ * What the app's env says about who signs in at the workspace, as recorded so far, or `null` until the
21
+ * organisation, client and issuer are all known. Non-secret values only.
22
+ */
23
+ export declare function workspaceAuthEnv(org: DeclaredOrg, entry: HostingEntry, recorded: Readonly<Record<string, string>>, issuer: string | null): Record<string, string> | null;
24
+ /**
25
+ * The client ids of the company's live apps: every recorded client id except the workspaces' own. A stage that
26
+ * holds one of them could sign in as the live app.
27
+ */
28
+ export declare function liveClientIds(org: DeclaredOrg, recorded: Readonly<Record<string, string>>): string[];
29
+ /** What `ws.<key>.signin` records: the sign-in env above as JSON, or `null` while it is not all known. */
30
+ export declare function workspaceSignInMarker(org: DeclaredOrg, entry: HostingEntry, recorded: Readonly<Record<string, string>>, issuer: string | null): string | null;
31
+ /**
32
+ * The test customers organisation, planned once an entry's workspace signs in customers. Sign-up and the emailed
33
+ * code follow what the company declares for its live customers (`customers.signUp`, the entries' `signIn`), so
34
+ * testers meet the same sign-in the customers will. Idempotent on the recorded id and the two markers.
35
+ */
36
+ export declare function planTestCustomers(org: DeclaredOrg, live: readonly LiveResource[], claimed: Set<string>, recordedIds: Readonly<Record<string, string>>, metadataWired: boolean): {
37
+ step: DraftStep | null;
38
+ blocked: UnmanagedItem[];
39
+ };
40
+ /** One ZITADEL web client `<key>-work` per workspace with sign-in, planned once; its redirect addresses name the workspace host. */
41
+ export declare function planWorkspaceClients(org: DeclaredOrg, byKey: ReadonlyMap<string, LiveResource>, claimed: Set<string>): DraftStep[];
42
+ /** What a workspace broker entry must say, without its keys, or what is still missing. */
43
+ export declare function wantedWorkspaceBrokerEntry(org: DeclaredOrg, entry: HostingEntry, recorded: Readonly<Record<string, string>>): {
44
+ want: BrokerEntryWant;
45
+ } | {
46
+ missing: BrokerEntryInput[];
47
+ };
48
+ /**
49
+ * One broker `AUTH_APPS` entry per workspace with sign-in, written after the workspace step has put the stage's
50
+ * `AUTH_APP_KEY` in its env. Idempotent on the recorded marker of what the last write said.
51
+ */
52
+ export declare function planWorkspaceBrokerEntries(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>, plannedSteps: readonly DraftStep[], brokerWired: boolean, brokerWorkspacesDatabase: boolean, stageReady: (entry: HostingEntry) => boolean): {
53
+ steps: DraftStep[];
54
+ blocked: UnmanagedItem[];
55
+ };