@wtfalch/foundry 0.60.0 → 0.62.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 (236) 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 +14 -5
  44. package/dist/index.js +7 -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/migrations/0065_native_build_runs.sql +32 -0
  49. package/dist/migrations/0066_org_kosmos.sql +10 -0
  50. package/dist/modules/ai.d.ts +3 -0
  51. package/dist/modules/ai.js +64 -0
  52. package/dist/modules/assistants.d.ts +3 -0
  53. package/dist/modules/assistants.js +72 -0
  54. package/dist/modules/audit-ledger.d.ts +29 -0
  55. package/dist/modules/audit-ledger.js +49 -0
  56. package/dist/modules/audit.d.ts +7 -0
  57. package/dist/modules/audit.js +44 -0
  58. package/dist/modules/database.d.ts +23 -0
  59. package/dist/modules/database.js +189 -0
  60. package/dist/modules/email.d.ts +3 -0
  61. package/dist/modules/email.js +18 -0
  62. package/dist/modules/entry-parts.d.ts +23 -0
  63. package/dist/modules/entry-parts.js +63 -0
  64. package/dist/modules/files.d.ts +3 -0
  65. package/dist/modules/files.js +64 -0
  66. package/dist/modules/index.d.ts +74 -0
  67. package/dist/modules/index.js +774 -0
  68. package/dist/modules/jobs.d.ts +3 -0
  69. package/dist/modules/jobs.js +23 -0
  70. package/dist/modules/organisations.d.ts +3 -0
  71. package/dist/modules/organisations.js +20 -0
  72. package/dist/modules/outputs.d.ts +9 -0
  73. package/dist/modules/outputs.js +38 -0
  74. package/dist/modules/sign-in.d.ts +3 -0
  75. package/dist/modules/sign-in.js +197 -0
  76. package/dist/modules/signed-records.d.ts +3 -0
  77. package/dist/modules/signed-records.js +32 -0
  78. package/dist/modules/types.d.ts +194 -0
  79. package/dist/modules/types.js +1 -0
  80. package/dist/modules/workspaces.d.ts +3 -0
  81. package/dist/modules/workspaces.js +75 -0
  82. package/dist/native-build.d.ts +70 -0
  83. package/dist/native-build.js +92 -0
  84. package/dist/org.d.ts +184 -24
  85. package/dist/org.js +444 -49
  86. package/dist/plan/agora.d.ts +1 -1
  87. package/dist/plan/agora.js +4 -0
  88. package/dist/plan/apps-db-route.d.ts +90 -0
  89. package/dist/plan/apps-db-route.js +168 -0
  90. package/dist/plan/apps-zones.d.ts +56 -0
  91. package/dist/plan/apps-zones.js +118 -0
  92. package/dist/plan/archon-blue-green.js +11 -1
  93. package/dist/plan/blue-green-apply.d.ts +37 -1
  94. package/dist/plan/blue-green-apply.js +55 -15
  95. package/dist/plan/blue-green.d.ts +60 -8
  96. package/dist/plan/blue-green.js +115 -10
  97. package/dist/plan/boule.d.ts +23 -1
  98. package/dist/plan/boule.js +35 -2
  99. package/dist/plan/broker-entries.d.ts +28 -3
  100. package/dist/plan/broker-entries.js +74 -3
  101. package/dist/plan/broker-registrar.d.ts +168 -0
  102. package/dist/plan/broker-registrar.js +440 -0
  103. package/dist/plan/broker-workspaces-database.d.ts +48 -0
  104. package/dist/plan/broker-workspaces-database.js +87 -0
  105. package/dist/plan/deploy-health.js +9 -2
  106. package/dist/plan/desktop-callback.js +15 -7
  107. package/dist/plan/diff.d.ts +8 -2
  108. package/dist/plan/diff.js +552 -104
  109. package/dist/plan/edge-access.d.ts +6 -0
  110. package/dist/plan/edge-access.js +1 -0
  111. package/dist/plan/edge-dns.d.ts +6 -0
  112. package/dist/plan/edge-dns.js +9 -7
  113. package/dist/plan/entry-ai-key.d.ts +1 -1
  114. package/dist/plan/entry-ai-key.js +2 -2
  115. package/dist/plan/entry-audit.d.ts +58 -0
  116. package/dist/plan/entry-audit.js +122 -0
  117. package/dist/plan/entry-customer-grants.d.ts +75 -0
  118. package/dist/plan/entry-customer-grants.js +154 -0
  119. package/dist/plan/entry-edge-key.d.ts +26 -1
  120. package/dist/plan/entry-edge-key.js +42 -3
  121. package/dist/plan/entry-jobs.d.ts +64 -0
  122. package/dist/plan/entry-jobs.js +92 -0
  123. package/dist/plan/entry-repo-key-apply.d.ts +55 -0
  124. package/dist/plan/entry-repo-key-apply.js +185 -0
  125. package/dist/plan/entry-repo-key.d.ts +92 -0
  126. package/dist/plan/entry-repo-key.js +235 -0
  127. package/dist/plan/entry-workspaces.d.ts +55 -0
  128. package/dist/plan/entry-workspaces.js +90 -0
  129. package/dist/plan/executor.d.ts +231 -19
  130. package/dist/plan/executor.js +1648 -129
  131. package/dist/plan/gates.d.ts +1 -1
  132. package/dist/plan/gates.js +62 -9
  133. package/dist/plan/github-deploy-keys.d.ts +78 -0
  134. package/dist/plan/github-deploy-keys.js +305 -0
  135. package/dist/plan/github-source.d.ts +53 -0
  136. package/dist/plan/github-source.js +140 -0
  137. package/dist/plan/held-key-refs.d.ts +12 -0
  138. package/dist/plan/held-key-refs.js +12 -0
  139. package/dist/plan/hostnames.d.ts +9 -0
  140. package/dist/plan/hostnames.js +28 -1
  141. package/dist/plan/index.d.ts +9 -0
  142. package/dist/plan/index.js +6 -0
  143. package/dist/plan/jobs-scheduler-http.js +3 -0
  144. package/dist/plan/keys-registrar-http.d.ts +32 -0
  145. package/dist/plan/keys-registrar-http.js +105 -0
  146. package/dist/plan/keys-registrar.d.ts +67 -0
  147. package/dist/plan/keys-registrar.js +103 -0
  148. package/dist/plan/kosmos.d.ts +56 -0
  149. package/dist/plan/kosmos.js +84 -0
  150. package/dist/plan/live.d.ts +1 -1
  151. package/dist/plan/locked-hostnames.d.ts +2 -2
  152. package/dist/plan/locked-hostnames.js +7 -4
  153. package/dist/plan/login-host.js +11 -5
  154. package/dist/plan/machine-paths.d.ts +36 -0
  155. package/dist/plan/machine-paths.js +137 -0
  156. package/dist/plan/mcp.d.ts +1 -1
  157. package/dist/plan/mcp.js +49 -17
  158. package/dist/plan/org-apps-admin.d.ts +108 -0
  159. package/dist/plan/org-apps-admin.js +434 -0
  160. package/dist/plan/org-names.d.ts +3 -3
  161. package/dist/plan/org-names.js +15 -2
  162. package/dist/plan/org-postgres-migrate.js +1 -0
  163. package/dist/plan/own-diff.d.ts +23 -0
  164. package/dist/plan/own-diff.js +53 -0
  165. package/dist/plan/own-hostnames.d.ts +2 -0
  166. package/dist/plan/own-hostnames.js +1 -0
  167. package/dist/plan/own-jobs-route.d.ts +47 -0
  168. package/dist/plan/own-jobs-route.js +91 -0
  169. package/dist/plan/own-limits.d.ts +6 -0
  170. package/dist/plan/own-limits.js +1 -0
  171. package/dist/plan/own-services.d.ts +4 -0
  172. package/dist/plan/own-services.js +4 -0
  173. package/dist/plan/plan-apply.d.ts +39 -0
  174. package/dist/plan/plan-apply.js +163 -9
  175. package/dist/plan/platform-diff.d.ts +2 -2
  176. package/dist/plan/platform-diff.js +7 -2
  177. package/dist/plan/platform-org.d.ts +30 -0
  178. package/dist/plan/platform-org.js +12 -1
  179. package/dist/plan/platform-providers.d.ts +2 -0
  180. package/dist/plan/platform-providers.js +13 -2
  181. package/dist/plan/post-logout-uris.d.ts +1 -1
  182. package/dist/plan/post-logout-uris.js +12 -3
  183. package/dist/plan/private-pages.d.ts +26 -0
  184. package/dist/plan/private-pages.js +39 -0
  185. package/dist/plan/providers-readers.js +20 -1
  186. package/dist/plan/proxy-labels.d.ts +12 -1
  187. package/dist/plan/proxy-labels.js +32 -2
  188. package/dist/plan/release-host.d.ts +6 -2
  189. package/dist/plan/release-host.js +36 -8
  190. package/dist/plan/stage-env.d.ts +19 -1
  191. package/dist/plan/stage-env.js +25 -1
  192. package/dist/plan/stage-guards.d.ts +20 -0
  193. package/dist/plan/stage-guards.js +24 -0
  194. package/dist/plan/stage-isolation.d.ts +24 -0
  195. package/dist/plan/stage-isolation.js +29 -1
  196. package/dist/plan/worker-app-database.d.ts +46 -42
  197. package/dist/plan/worker-app-database.js +150 -153
  198. package/dist/plan/worker-app.d.ts +5 -65
  199. package/dist/plan/worker-app.js +28 -105
  200. package/dist/plan/worker.d.ts +2 -2
  201. package/dist/plan/worker.js +2 -1
  202. package/dist/plan/workspace-client.d.ts +25 -0
  203. package/dist/plan/workspace-client.js +48 -0
  204. package/dist/plan/workspace-sign-in.d.ts +55 -0
  205. package/dist/plan/workspace-sign-in.js +273 -0
  206. package/dist/plan/workspace-worker.d.ts +4 -0
  207. package/dist/plan/workspace-worker.js +13 -0
  208. package/dist/plan/workspace.d.ts +101 -0
  209. package/dist/plan/workspace.js +305 -0
  210. package/dist/release/decide.d.ts +30 -1
  211. package/dist/release/decide.js +73 -11
  212. package/dist/release/health.d.ts +8 -3
  213. package/dist/release/health.js +58 -5
  214. package/dist/release/migration-compatibility.d.ts +66 -0
  215. package/dist/release/migration-compatibility.js +340 -0
  216. package/dist/release/run.d.ts +2 -5
  217. package/dist/release/run.js +4 -0
  218. package/dist/release/state.d.ts +3 -1
  219. package/dist/release/state.js +6 -0
  220. package/dist/release/types.d.ts +20 -0
  221. package/dist/requested-changes.d.ts +11 -2
  222. package/dist/requested-changes.js +13 -1
  223. package/dist/service-labels.d.ts +4 -0
  224. package/dist/service-labels.js +11 -0
  225. package/dist/slug.d.ts +2 -0
  226. package/dist/slug.js +20 -7
  227. package/dist/store/postgres.js +25 -2
  228. package/dist/store/schema.d.ts +73 -1
  229. package/dist/store/schema.js +10 -0
  230. package/dist/worker-app-config.d.ts +1 -11
  231. package/dist/worker-app-config.js +4 -17
  232. package/dist/worker-app-migrations.d.ts +105 -0
  233. package/dist/worker-app-migrations.js +166 -0
  234. package/dist/worker-app-upload.d.ts +103 -0
  235. package/dist/worker-app-upload.js +128 -0
  236. package/package.json +19 -2
@@ -8,6 +8,7 @@ import { OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY, ownAuthApplied } from './login-hos
8
8
  import { OWN_MAIL_SMTP_INTERNAL_SCOPE_KEY, OWN_MAIL_SMTP_PORT_BASE, OWN_MAIL_SMTP_PORT_SCOPE_KEY, isOwnMailSmtpPort, } from './mail-relay.js';
9
9
  import { mailStoreIsLegacy, mailStoreSwitchDescription } from './mail-store.js';
10
10
  import { ownHostnames } from './own-hostnames.js';
11
+ import { JOBS_PUBLIC_URL_SCOPE_KEY, OWN_JOBS_ROUTE_TUNNEL_SCOPE_KEY } from './own-jobs-route.js';
11
12
  import { OWN_FILES_R2_SCOPE_KEY, OWN_SERVICES_APPLIED_SCOPE_KEY, orgTarget, } from './own-services.js';
12
13
  import { OWN_SFU_APPLIED_HOSTNAME_SCOPE_KEY, OWN_SFU_APPLIED_NODE_IP_SCOPE_KEY, OWN_SFU_APPLIED_SLOT_SCOPE_KEY, OWN_SFU_APPLIED_VERSION_SCOPE_KEY, OWN_SFU_DEPLOY_PENDING_SCOPE_KEY, OWN_SFU_KEYS_UNDELIVERED_SCOPE_KEY, SFU_INTERNAL_URL_SCOPE_KEY, SFU_PUBLIC_URL_SCOPE_KEY, } from './own-sfu.js';
13
14
  import { AUTH_TUNNEL_ROUTES_VERSION, AUTH_ZITADEL_RUN_OPTIONS, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_INTERNAL_URL_SCOPE_KEY, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_MCP_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_TUNNEL_ROUTES_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_SCOPE_KEY, PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, orgDeclaresMcp, platformMailMxHost, } from './platform-diff.js';
@@ -67,6 +68,58 @@ export function planOwnJobs(options) {
67
68
  },
68
69
  };
69
70
  }
71
+ /** `unmanaged` key of the one line `planOwnJobsRoute` reports. */
72
+ export const OWN_JOBS_ROUTE_BLOCKED_KEY = 'own-jobs-route';
73
+ /**
74
+ * The public route of an own org's jobs scheduler: `jobs.<domain>`, serving only what an app calls. The
75
+ * owner company always publishes its scheduler, since every company on shared services registers there; any
76
+ * other own org publishes its own only while an entry declares `uses.jobs`. Planned again when the hostname
77
+ * moved. Every blocked line is fixed text: it is part of the stale-plan comparison.
78
+ */
79
+ export function planOwnJobsRoute(options) {
80
+ const { org, recordedIds, readiness, ownServices } = options;
81
+ if (!ownsServices(org))
82
+ return { step: null, blocked: null };
83
+ const owner = readiness.platformOwnerOrgId === org.id;
84
+ if (!owner && !org.hosting.entries.some((e) => e.uses?.jobs)) {
85
+ return { step: null, blocked: null };
86
+ }
87
+ const hostname = ownHostnames(org).jobs;
88
+ const schedulerApplied = Boolean(recordedIds[`coolify:own-jobs:${org.id}`]) &&
89
+ Boolean(recordedIds[JOBS_INTERNAL_URL_SCOPE_KEY]);
90
+ if (!schedulerApplied && !options.schedulerPlanned)
91
+ return { step: null, blocked: null };
92
+ if (recordedIds[JOBS_PUBLIC_URL_SCOPE_KEY] === `https://${hostname}` &&
93
+ recordedIds[OWN_JOBS_ROUTE_TUNNEL_SCOPE_KEY]) {
94
+ return { step: null, blocked: null };
95
+ }
96
+ const waiting = (reason) => ({
97
+ step: null,
98
+ blocked: `${org.slug}'s own jobs scheduler route is blocked, not planned: ${reason}`,
99
+ });
100
+ if (!ownServices)
101
+ return waiting('the host supplies no ownServices port');
102
+ if (!options.zoneActive)
103
+ return waiting(`the zone for ${ownHostnames(org).zone} is not active yet`);
104
+ const ownEdge = readiness.ownEdgePorts;
105
+ if (readiness.dnsBlocked && !(ownEdge?.dns && ownEdge.tunnels)) {
106
+ return waiting(readiness.dnsBlocked);
107
+ }
108
+ if (!ownEdge?.tunnels && !readiness.edgeReady) {
109
+ return waiting('the host supplies no tunnel port and edge is not ready');
110
+ }
111
+ return {
112
+ blocked: null,
113
+ step: {
114
+ action: { system: 'coolify', kind: 'own-jobs-route', key: org.id, desired: { hostname } },
115
+ step: {
116
+ description: `publish ${org.slug}'s own jobs scheduler at ${hostname}: its tunnel and connector and a proxied CNAME`,
117
+ destroys: false,
118
+ migrates: false,
119
+ },
120
+ },
121
+ };
122
+ }
70
123
  const BROKER_ENTRY_APPLIED_PREFIX = brokerEntryAppliedScopeKey('');
71
124
  /** `unmanaged` key of the one line `planOwnAuth` reports. */
72
125
  export const OWN_AUTH_BLOCKED_KEY = 'own-auth';
@@ -13,6 +13,8 @@ export interface OwnHostnames {
13
13
  files: string;
14
14
  /** `sfu.<domain>`, or `<slug>-sfu.oikos.host`. */
15
15
  sfu: string;
16
+ /** `jobs.<domain>`, or `<slug>-jobs.oikos.host`. */
17
+ jobs: string;
16
18
  /** `https://<auth>`. */
17
19
  issuerUrl: string;
18
20
  /** The mail domain: `<domain>`, or `<slug>.oikos.host` (names are `name@<slug>.oikos.host`). */
@@ -18,6 +18,7 @@ export function ownHostnames(org) {
18
18
  mail: mailOnDomain ? `mail.${domain}` : `${slug}-mail.${NO_DOMAIN_ZONE}`,
19
19
  files: name('files'),
20
20
  sfu: name('sfu'),
21
+ jobs: name('jobs'),
21
22
  issuerUrl: `https://${auth}`,
22
23
  mailDomain: mailOnDomain && domain ? domain : `${slug}.${NO_DOMAIN_ZONE}`,
23
24
  };
@@ -0,0 +1,47 @@
1
+ import type { CoolifyPort } from './executor.js';
2
+ import type { ServiceNetwork, ServiceTarget, StepScope } from './own-services.js';
3
+ /**
4
+ * A company's jobs scheduler on a public hostname: a tunnel that dials the scheduler by its internal address,
5
+ * a connector app, and a proxied CNAME for `jobs.<domain>`. An entry runs on its own company's network and
6
+ * cannot dial the scheduler's container name, so `uses.jobs` gives it `https://<hostname>` once this is
7
+ * recorded. The owner company (hq) runs the scheduler every shared-services company registers on; a company
8
+ * that runs its own services publishes its own the same way. No Coolify domain and no Access app: the
9
+ * scheduler authenticates every caller itself (a project's sync key, the admin token), and a gate in front
10
+ * would stop the apps.
11
+ *
12
+ * One `coolify:own-jobs-route` step. It records {@link JOBS_PUBLIC_URL_SCOPE_KEY} last, so a run that fails
13
+ * halfway is planned again, finds the tunnel and connector by name, and no entry is given a dead URL.
14
+ */
15
+ export declare const JOBS_PUBLIC_URL_SCOPE_KEY = "jobs_public_url";
16
+ export declare const OWN_JOBS_ROUTE_TUNNEL_SCOPE_KEY = "cloudflare_own_jobs_tunnel";
17
+ export declare const OWN_JOBS_ROUTE_CONNECTOR_APP_SCOPE_KEY = "coolify_own_jobs_connector_app";
18
+ /**
19
+ * The only paths the public route serves: what a project's app calls (`/sync`, `/timers`, `/timers/<name>`,
20
+ * `/deliveries`). `/admin/*` is not on the internet; foundry reaches it on the Coolify network.
21
+ */
22
+ export declare const JOBS_ENTRY_PATH_PATTERN = "^/(sync$|timers(/|$)|deliveries$)";
23
+ export declare const JOBS_ROUTE_CALL_TIMEOUT_MS = 60000;
24
+ /** The tunnel's name and the connector app's, one pair per company. */
25
+ export declare const jobsRouteNames: (target: Pick<ServiceTarget, "id">) => {
26
+ tunnel: string;
27
+ connector: string;
28
+ };
29
+ export interface JobsRoutePorts {
30
+ coolify: Pick<CoolifyPort, 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'createDockerfileApplication' | 'updateApplication' | 'bulkUpsertEnvs' | 'listEnvs' | 'deploy'>;
31
+ network: ServiceNetwork;
32
+ /** One outside call's limit in milliseconds; the connector step gets five times it. */
33
+ callTimeoutMs?: number;
34
+ }
35
+ /**
36
+ * Makes the route in this order: tunnel, its ingress, the connector app, the CNAME, then the recorded URL.
37
+ * Each is found by name or content first, so a second run makes nothing twice. A failure leaves what was made
38
+ * (found again on the next run) and no recorded URL. Errors carry the call's label only.
39
+ */
40
+ export declare function provisionJobsRoute(scope: StepScope, ports: JobsRoutePorts, target: ServiceTarget, input: {
41
+ hostname: string;
42
+ apex: string;
43
+ internalUrl: string;
44
+ projectUuid: string;
45
+ }): Promise<{
46
+ resourceId: string;
47
+ }>;
@@ -0,0 +1,91 @@
1
+ import { withTimeout } from './call-timeout.js';
2
+ import { ensureCloudflaredConnector } from './cloudflared-connector.js';
3
+ import { recordOwnDnsHostname } from './own-dns.js';
4
+ /**
5
+ * A company's jobs scheduler on a public hostname: a tunnel that dials the scheduler by its internal address,
6
+ * a connector app, and a proxied CNAME for `jobs.<domain>`. An entry runs on its own company's network and
7
+ * cannot dial the scheduler's container name, so `uses.jobs` gives it `https://<hostname>` once this is
8
+ * recorded. The owner company (hq) runs the scheduler every shared-services company registers on; a company
9
+ * that runs its own services publishes its own the same way. No Coolify domain and no Access app: the
10
+ * scheduler authenticates every caller itself (a project's sync key, the admin token), and a gate in front
11
+ * would stop the apps.
12
+ *
13
+ * One `coolify:own-jobs-route` step. It records {@link JOBS_PUBLIC_URL_SCOPE_KEY} last, so a run that fails
14
+ * halfway is planned again, finds the tunnel and connector by name, and no entry is given a dead URL.
15
+ */
16
+ export const JOBS_PUBLIC_URL_SCOPE_KEY = 'jobs_public_url';
17
+ export const OWN_JOBS_ROUTE_TUNNEL_SCOPE_KEY = 'cloudflare_own_jobs_tunnel';
18
+ export const OWN_JOBS_ROUTE_CONNECTOR_APP_SCOPE_KEY = 'coolify_own_jobs_connector_app';
19
+ /**
20
+ * The only paths the public route serves: what a project's app calls (`/sync`, `/timers`, `/timers/<name>`,
21
+ * `/deliveries`). `/admin/*` is not on the internet; foundry reaches it on the Coolify network.
22
+ */
23
+ export const JOBS_ENTRY_PATH_PATTERN = '^/(sync$|timers(/|$)|deliveries$)';
24
+ export const JOBS_ROUTE_CALL_TIMEOUT_MS = 60_000;
25
+ /** The tunnel's name and the connector app's, one pair per company. */
26
+ export const jobsRouteNames = (target) => ({
27
+ tunnel: `${target.id}-jobs-tunnel`,
28
+ connector: `${target.id}-jobs-cloudflared`,
29
+ });
30
+ /**
31
+ * Makes the route in this order: tunnel, its ingress, the connector app, the CNAME, then the recorded URL.
32
+ * Each is found by name or content first, so a second run makes nothing twice. A failure leaves what was made
33
+ * (found again on the next run) and no recorded URL. Errors carry the call's label only.
34
+ */
35
+ export async function provisionJobsRoute(scope, ports, target, input) {
36
+ const ms = ports.callTimeoutMs ?? JOBS_ROUTE_CALL_TIMEOUT_MS;
37
+ const at = `own step coolify:own-jobs-route for ${target.id}`;
38
+ const names = jobsRouteNames(target);
39
+ const record = async (key, id) => {
40
+ scope.recordScopeId(key, id);
41
+ await scope.persist();
42
+ };
43
+ const zoneId = await withTimeout(`${at}: zone lookup`, ms, () => ports.network.zoneId(input.apex));
44
+ if (!zoneId)
45
+ throw new Error(`${at}: no Cloudflare zone for ${input.apex}`);
46
+ const tunnels = await ports.network.tunnels();
47
+ const dns = await ports.network.dns();
48
+ const { environment } = await withTimeout(`${at}: environment`, ms, () => ports.coolify.findOrCreateEnvironment(input.projectUuid, 'production'));
49
+ const tunnel = (await withTimeout(`${at}: tunnel lookup`, ms, () => tunnels.findTunnelByName(names.tunnel))) ??
50
+ (await withTimeout(`${at}: tunnel create`, ms, () => tunnels.createTunnel(names.tunnel)));
51
+ await record(OWN_JOBS_ROUTE_TUNNEL_SCOPE_KEY, tunnel.id);
52
+ await withTimeout(`${at}: tunnel ingress`, ms, () => tunnels.putTunnelConfig(tunnel.id, {
53
+ ingress: [
54
+ { hostname: input.hostname, path: JOBS_ENTRY_PATH_PATTERN, service: input.internalUrl },
55
+ { service: 'http_status:404' },
56
+ ],
57
+ }));
58
+ await withTimeout(`${at}: connector app`, ms * 5, () => ensureCloudflaredConnector({
59
+ coolify: ports.coolify,
60
+ tunnels,
61
+ tunnelId: tunnel.id,
62
+ appName: names.connector,
63
+ projectUuid: input.projectUuid,
64
+ environment,
65
+ resolveServer: async () => {
66
+ const servers = await ports.coolify.listServers();
67
+ const server = servers.find((s) => s.is_usable && s.is_reachable) ?? servers[0];
68
+ if (!server)
69
+ throw new Error('no usable Coolify server is registered');
70
+ return server;
71
+ },
72
+ recordedConnectorAppId: scope.recordedIds()[OWN_JOBS_ROUTE_CONNECTOR_APP_SCOPE_KEY] ?? null,
73
+ recordAppId: (appId) => record(OWN_JOBS_ROUTE_CONNECTOR_APP_SCOPE_KEY, appId),
74
+ }));
75
+ const wanted = {
76
+ type: 'CNAME',
77
+ name: input.hostname,
78
+ content: `${tunnel.id}.cfargotunnel.com`,
79
+ proxied: true,
80
+ };
81
+ const [live] = await withTimeout(`${at}: dns lookup`, ms, () => dns.findDnsRecord(input.apex, input.hostname, 'CNAME'));
82
+ if (!live) {
83
+ await withTimeout(`${at}: dns create`, ms, () => dns.createDnsRecord(input.apex, wanted));
84
+ }
85
+ else if (live.content !== wanted.content) {
86
+ await withTimeout(`${at}: dns update`, ms, () => dns.updateDnsRecord(input.apex, live.id, wanted));
87
+ }
88
+ await recordOwnDnsHostname(scope, input.hostname);
89
+ await record(JOBS_PUBLIC_URL_SCOPE_KEY, `https://${input.hostname}`);
90
+ return { resourceId: tunnel.id };
91
+ }
@@ -107,6 +107,12 @@ export declare const LIMITS: {
107
107
  autoDeploy: false | "declared";
108
108
  capabilities: CapabilityTier;
109
109
  };
110
+ readonly kosmos: {
111
+ memory: string;
112
+ dbMemory: string;
113
+ autoDeploy: false | "declared";
114
+ capabilities: CapabilityTier;
115
+ };
110
116
  readonly companyOther: {
111
117
  memory: string;
112
118
  dbMemory: string;
@@ -29,6 +29,7 @@ export const LIMITS = {
29
29
  calls: profile('1g', '512m'),
30
30
  agora: profile('1g', '512m'),
31
31
  boule: profile('512m'),
32
+ kosmos: profile('512m'),
32
33
  companyOther: profile('1g', '512m'),
33
34
  platformArchon: profile('1g'),
34
35
  platformAi: profile('1g'),
@@ -27,6 +27,8 @@ export interface ServiceTarget {
27
27
  authZitadel: string;
28
28
  authLogin: string;
29
29
  authBroker: string;
30
+ /** The Coolify Postgres database of the broker's customer workspaces. */
31
+ workspacesDb: string;
30
32
  authTunnel: string;
31
33
  authCloudflared: string;
32
34
  mailApp: string;
@@ -61,6 +63,8 @@ export interface ServiceTarget {
61
63
  instanceBearer: string;
62
64
  authAdmin: string;
63
65
  brokerMailKey: string;
66
+ /** The connection string of the database for the broker's customer workspaces: foundry holds it after making the database, or the operator holds one first. */
67
+ brokerWorkspacesDatabase: string;
64
68
  mailAdminKey: string;
65
69
  filesManagement: string;
66
70
  filesIntrospection: string;
@@ -16,6 +16,7 @@ export function platformTarget() {
16
16
  authZitadel: 'platform-auth-zitadel',
17
17
  authLogin: 'platform-auth-login',
18
18
  authBroker: 'platform-auth-broker',
19
+ workspacesDb: 'platform-workspaces-db',
19
20
  authTunnel: 'platform-auth-tunnel',
20
21
  authCloudflared: 'platform-auth-cloudflared',
21
22
  mailApp: 'platform-mail-stalwart',
@@ -47,6 +48,7 @@ export function platformTarget() {
47
48
  instanceBearer: 'zitadel:platform:instanceBearer',
48
49
  authAdmin: 'zitadel:platform:admin',
49
50
  brokerMailKey: 'lettermint:platform:apiToken',
51
+ brokerWorkspacesDatabase: 'auth:platform:workspacesDatabaseUrl',
50
52
  mailAdminKey: 'stalwart:platform:admin',
51
53
  filesManagement: 'files:platform:managementKey',
52
54
  filesIntrospection: 'zitadel:platform:files:introspection',
@@ -71,6 +73,7 @@ export function orgTarget(org) {
71
73
  authZitadel: `${s}-auth-zitadel`,
72
74
  authLogin: `${s}-auth-login`,
73
75
  authBroker: `${s}-auth-broker`,
76
+ workspacesDb: `${s}-workspaces-db`,
74
77
  authTunnel: `${s}-auth-tunnel`,
75
78
  authCloudflared: `${s}-auth-cloudflared`,
76
79
  mailApp: `${s}-mail-stalwart`,
@@ -102,6 +105,7 @@ export function orgTarget(org) {
102
105
  instanceBearer: `zitadel:org:${s}:instanceBearer`,
103
106
  authAdmin: `zitadel:org:${s}:admin`,
104
107
  brokerMailKey: `lettermint:org:${s}:apiToken`,
108
+ brokerWorkspacesDatabase: `auth:org:${s}:workspacesDatabaseUrl`,
105
109
  mailAdminKey: `stalwart:org:${s}:admin`,
106
110
  filesManagement: `files:org:${s}:managementKey`,
107
111
  filesIntrospection: `zitadel:org:${s}:files:introspection`,
@@ -1,4 +1,6 @@
1
1
  import type { DestroyProgress, PlanApply } from '../apply.js';
2
+ import type { SealResult } from '../audit/seal.js';
3
+ import { type AuditSealingOptions } from '../audit/tick.js';
2
4
  import type { DeclaredOrg, HostingEntry, OrgId } from '../org.js';
3
5
  import type { Plan, PlanStep } from '../plan.js';
4
6
  import type { DeclaredPlatform } from '../platform.js';
@@ -12,6 +14,7 @@ import { type MoveGatePorts } from './backup-gate.js';
12
14
  import { type BlueGreenServer, type BlueGreenSubject } from './blue-green.js';
13
15
  import { type UnconfirmedUpgrade } from './confirm-upgrade.js';
14
16
  import { type StepAction } from './diff.js';
17
+ import type { GithubDeployKeysPort } from './github-deploy-keys.js';
15
18
  import { type LiveReaders, type LiveResource, type PlatformLiveReaders } from './live.js';
16
19
  import { type LoginHostDiscoveryPort } from './login-host.js';
17
20
  import { type OrgPostgresMigrationConfirmation } from './org-postgres-migrate.js';
@@ -249,6 +252,14 @@ export interface StepExecutor {
249
252
  * without it leaves the option as the answer.
250
253
  */
251
254
  ownBrokerPort?(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<boolean>;
255
+ /**
256
+ * Whether the host can make the broker's workspaces database: it can hold the URL and the broker and the held keys
257
+ * were just read and have none. Read only for an org whose entry declares workspaces and only while
258
+ * {@link brokerHasWorkspacesDatabase} is false. Optional: without it the plan only reports the entry blocked.
259
+ */
260
+ canProvisionWorkspacesDatabase?(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<boolean>;
261
+ /** Whether the broker this org's entries are written to has `WORKSPACES_DATABASE_URL`. Read only for an org whose entry declares workspaces. */
262
+ brokerHasWorkspacesDatabase?(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<boolean>;
252
263
  /**
253
264
  * Every secret this executor minted or read so far (peer password, bridge
254
265
  * secret, invite code and link). The host's run adds them to its redactor
@@ -462,6 +473,8 @@ export interface FoundryApplyOptions {
462
473
  typed: string;
463
474
  /** The removed entry's gate keys, read by the caller from the company before the change. */
464
475
  gates?: readonly string[];
476
+ /** The paths the entry's private pages had before the change, for an entry that still exists: their `pp-`/`wp-` gate deletes pass with its confirmation. */
477
+ privatePages?: readonly string[];
465
478
  };
466
479
  }
467
480
  export interface FoundryPlanApplyOptions {
@@ -550,6 +563,8 @@ export interface FoundryPlanApplyOptions {
550
563
  workerApps?: boolean;
551
564
  /** ADR 0002: the keys custody Worker address, when the executor has a `KeysCustodyPort`. A `keysCustody` secret is blocked without it. */
552
565
  workerAppKeysUrl?: string;
566
+ /** F7: the keys service's registrar address, when the executor has a `KeysRegistrarPort`. */
567
+ keysRegistrarUrl?: string;
553
568
  /** C2: true when the host's executor was built with a `WorkerAppDatabasePort`. */
554
569
  workerAppDatabase?: boolean;
555
570
  /** ADR 0002: the platform's Cloudflare account id. A container image outside its registry is blocked. */
@@ -604,6 +619,16 @@ export interface FoundryPlanApplyOptions {
604
619
  * the release runner. Without it no release runs, in an apply or in {@link FoundryPlanApply.runReleaseTick}.
605
620
  */
606
621
  releaseStore?: ReleaseStore;
622
+ /**
623
+ * F12: the GitHub deploy-key client (`createGithubDeployKeys`), to ask which deploy keys an entry's repo has.
624
+ * Without it an entry with `uses.repo` is reported blocked and nothing else changes.
625
+ */
626
+ githubDeployKeys?: GithubDeployKeysPort;
627
+ /**
628
+ * `uses.audit` (foundry#929): what {@link FoundryPlanApply.runAuditSealTick} seals with. Without it the tick
629
+ * seals nothing and logs that it was not given.
630
+ */
631
+ auditSealing?: AuditSealingOptions;
607
632
  /** Overridable for tests; defaults to `() => new Date()`. */
608
633
  now?: () => Date;
609
634
  /** #441: how long a mutation lease lasts before another caller may take it over. Defaults to 30 minutes. */
@@ -693,6 +718,7 @@ export declare class FoundryPlanApply implements PlanApply {
693
718
  private readonly edgeWorkers;
694
719
  private readonly workerApps;
695
720
  private readonly workerAppKeysUrl;
721
+ private readonly keysRegistrarUrl;
696
722
  private readonly workerAppDatabase;
697
723
  private readonly workerAppRegistryAccountId;
698
724
  private readonly ownerInviteMail;
@@ -709,6 +735,8 @@ export declare class FoundryPlanApply implements PlanApply {
709
735
  private readonly platformDeps;
710
736
  private readonly runner;
711
737
  private readonly releaseStore;
738
+ private readonly githubDeployKeys;
739
+ private readonly auditSealing;
712
740
  constructor(options: FoundryPlanApplyOptions);
713
741
  plan(id: OrgId): Promise<Plan>;
714
742
  /**
@@ -872,6 +900,13 @@ export declare class FoundryPlanApply implements PlanApply {
872
900
  target: string;
873
901
  error?: string;
874
902
  }>>;
903
+ /**
904
+ * The recurring audit sealing run (foundry#929): seals every ledger registered by an `audit:ledger` step with the
905
+ * platform signer and has the timestamp authority stamp the new checkpoints. Foundry has no scheduler, so the host
906
+ * calls this every `AUDIT_SEAL_EVERY_MINUTES` (15). One ledger failing never stops the others; each result and a
907
+ * log line say what happened and why when nothing did. It writes nothing to the org's records.
908
+ */
909
+ runAuditSealTick(): Promise<SealResult[]>;
875
910
  private runOrgReleaseTick;
876
911
  /** An own org with calls on: the host's answer about its slot's ports. A failure or a slow answer is "not open". */
877
912
  private readSfuPortsOpen;
@@ -891,6 +926,10 @@ export declare class FoundryPlanApply implements PlanApply {
891
926
  * org step that needs one is blocked.
892
927
  */
893
928
  private orgPlatformReadiness;
929
+ /** F12: asks GitHub about the repo of each entry with a deploy key to make, check or remove. Never throws; a failure is a plain line. */
930
+ private repoKeysReadiness;
931
+ /** Which refs of the platform GitHub App hold no value now. A resolver that throws counts as not held; only refs are kept. */
932
+ private githubAppKeysReadiness;
894
933
  /**
895
934
  * The org's `heldSecret` envs: the refs that hold a value now, which of those differ from the live
896
935
  * production env, which could not be read, and which fit no ref. A resolver that throws counts as