@wtfalch/foundry 0.61.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 (40) hide show
  1. package/dist/index.d.ts +5 -1
  2. package/dist/index.js +2 -0
  3. package/dist/migrations/0065_native_build_runs.sql +32 -0
  4. package/dist/migrations/0066_org_kosmos.sql +10 -0
  5. package/dist/modules/sign-in.js +1 -1
  6. package/dist/native-build.d.ts +70 -0
  7. package/dist/native-build.js +92 -0
  8. package/dist/org.d.ts +22 -3
  9. package/dist/org.js +25 -2
  10. package/dist/plan/agora.d.ts +1 -1
  11. package/dist/plan/agora.js +4 -0
  12. package/dist/plan/blue-green.d.ts +19 -7
  13. package/dist/plan/blue-green.js +35 -8
  14. package/dist/plan/deploy-health.js +9 -2
  15. package/dist/plan/desktop-callback.js +4 -1
  16. package/dist/plan/diff.js +180 -63
  17. package/dist/plan/edge-access.js +1 -0
  18. package/dist/plan/executor.d.ts +40 -18
  19. package/dist/plan/executor.js +153 -49
  20. package/dist/plan/kosmos.d.ts +56 -0
  21. package/dist/plan/kosmos.js +84 -0
  22. package/dist/plan/locked-hostnames.js +5 -2
  23. package/dist/plan/org-names.d.ts +3 -3
  24. package/dist/plan/org-names.js +3 -1
  25. package/dist/plan/org-postgres-migrate.js +1 -0
  26. package/dist/plan/own-limits.d.ts +6 -0
  27. package/dist/plan/own-limits.js +1 -0
  28. package/dist/plan/platform-providers.js +6 -0
  29. package/dist/plan/post-logout-uris.d.ts +1 -1
  30. package/dist/plan/post-logout-uris.js +5 -2
  31. package/dist/plan/providers-readers.js +20 -1
  32. package/dist/release/migration-compatibility.d.ts +66 -0
  33. package/dist/release/migration-compatibility.js +340 -0
  34. package/dist/requested-changes.js +2 -1
  35. package/dist/slug.d.ts +1 -1
  36. package/dist/slug.js +11 -2
  37. package/dist/store/postgres.js +13 -0
  38. package/dist/store/schema.d.ts +36 -0
  39. package/dist/store/schema.js +6 -0
  40. package/package.json +2 -2
package/dist/plan/diff.js CHANGED
@@ -1,5 +1,5 @@
1
- import { VERSIONS, floatingTagBlocked } from '../bootstrap/versions.js';
2
- import { APP_ENTRY_KEY, ORG_POSTGRES_SERVER_SCOPE_KEY, ORG_SERVICE_SCHEMAS, appsOn, companyGithubFor, heldEnvRef, mainEntry, ownerMailboxRecorded, ownerMailboxes, ownerNameProblem, ownsServices, } from '../org.js';
1
+ import { VERSIONS } from '../bootstrap/versions.js';
2
+ import { APP_ENTRY_KEY, ORG_POSTGRES_SERVER_SCOPE_KEY, ORG_SERVICE_SCHEMAS, appsOn, companyGithubFor, heldEnvRef, kosmosOn, mainEntry, ownerMailboxRecorded, ownerMailboxes, ownerNameProblem, ownsServices, } from '../org.js';
3
3
  import { entryHasDatabase } from '../worker-app-config.js';
4
4
  import { AGORA_ACTIVATED_ENV_SCOPE_KEY, AGORA_APP_PORTS_EXPOSES, CALLS_INTROSPECTION_SCOPE_KEY, CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY, CALLS_SERVICE_DEPLOYED_SCOPE_KEY, CHAT_CALLS_APPLIED_SCOPE_KEY, CHAT_CALLS_TOKEN_SCOPE_KEY, CHAT_CALLS_TOKEN_UNDELIVERED_SCOPE_KEY, CHAT_INTROSPECTION_SCOPE_KEY, NO_AGORA_HOST_VALUES, ZITADEL_ORG_SCOPE_KEY, agoraEnvDrift, agoraEnvRevision, agoraHostMissing, agoraHostnameFor, callsServiceHasName, callsServiceHasTokenEnv, callsTokenEnvMovedMessage, callsTokenHostFrom, chatCallsApplied, chatHasUpstreams, desiredAgoraEnv, entryClientIdScopeKey, withRecordedMessagingProject, } from './agora.js';
5
5
  import { planAppsDbRoute } from './apps-db-route.js';
@@ -26,6 +26,7 @@ import { gateIdRecordKey, hasWildcardGate, planGates } from './gates.js';
26
26
  import { githubSourceResourceKey, planGithubSource } from './github-source.js';
27
27
  import { appHostname, tunnelDnsHostnames } from './hostnames.js';
28
28
  import { planKeysRegistrar } from './keys-registrar.js';
29
+ import { desiredKosmosEnv, kosmosEnvDrift } from './kosmos.js';
29
30
  import { APP_GONE_KIND } from './live.js';
30
31
  import { lockedHostnames } from './locked-hostnames.js';
31
32
  import { LOGIN_BINDING_SCOPE_KEY, LOGIN_CLIENT_ORGS_SCOPE_KEY, OWN_CUSTOMERS_BASE_URI_SCOPE_KEY, agoraIssuerHostFor, clientOrganizationsState, companyLoginTunnelHost, customersClientOrganizations, inAppApps, loginBindingState, loginHostClientIds, loginHostServesAgora, loginHostnameFor, ownAuthApplied, ownCustomersAppState, pendingRemovalHosts, } from './login-host.js';
@@ -351,7 +352,7 @@ export function computePlanDetail(declaredOrg, live, recordedIds = {}, agoraHost
351
352
  const chat = planChat(org, byKey, claimed, recordedIds, agoraHost, issuer.step !== null);
352
353
  if (chat.step)
353
354
  plannedSteps.push(chat.step);
354
- const calls = planCalls(org, byKey, claimed, platformReadiness.callsReady, versionPin?.calls);
355
+ const calls = planCalls(org, byKey, claimed);
355
356
  if (calls.step)
356
357
  plannedSteps.push(calls.step);
357
358
  // An own org's media server, before the calls service that reads its addresses.
@@ -711,7 +712,16 @@ export function computePlanDetail(declaredOrg, live, recordedIds = {}, agoraHost
711
712
  }, customerGrantsShape);
712
713
  if (boule.step)
713
714
  plannedSteps.push(boule.step);
714
- // Boule's and Agora's blue/green: after their own steps, which make the app it copies.
715
+ // foundry#949: the company's kosmos, only once it turns it on. Its fixed ZITADEL client first, the same
716
+ // reason Boule's and Agora's do: `coolifyKosmos` reads this step's recorded client id.
717
+ const kosmosClient = planKosmosClient(org, byKey, claimed);
718
+ const kosmosGuarded = guardApp(kosmosClient);
719
+ if (kosmosGuarded)
720
+ plannedSteps.push(kosmosGuarded);
721
+ const kosmos = planKosmos(org, byKey, claimed, recordedIds, authIssuer, options.otherSlugs ?? []);
722
+ if (kosmos.step)
723
+ plannedSteps.push(kosmos.step);
724
+ // Boule's, Agora's and kosmos's blue/green: after their own steps, which make the app it copies.
715
725
  plannedSteps.push(...blueGreen.steps.filter((x) => x.phase === 'last').map(blueGreenDraft()));
716
726
  // Boule owners, after the Boule step: needs the app deployed and the seeding secret in its env.
717
727
  const bouleOwners = planBouleOwners(org, recordedIds, (byKey.get(resourceKey('coolify', 'boule', org.id))?.config.env ?? null));
@@ -977,6 +987,8 @@ export function computePlanDetail(declaredOrg, live, recordedIds = {}, agoraHost
977
987
  if (boule.mailBlocked) {
978
988
  unmanaged.push({ description: boule.mailBlocked, key: 'coolify:boule:mail' });
979
989
  }
990
+ if (kosmos.blocked)
991
+ unmanaged.push({ description: kosmos.blocked, key: 'coolify:kosmos' });
980
992
  // Same for an ai key minted but never delivered: planning a new mint would
981
993
  // orphan a second live key.
982
994
  if (aiKey.blocked)
@@ -1769,8 +1781,8 @@ function loginHostWaiting(org, host, recordedIds, bindingsWired, issuerPlanned)
1769
1781
  * actually need it -- CMS's, chat's and calls' entries follow their own
1770
1782
  * on/off switch, the same ones this file already gates their own
1771
1783
  * `coolify:*` steps on (`org.ts`'s `SiteConfig.enabled`, `ChatConfig.enabled`,
1772
- * `CallsConfig.enabled`); Boule's is unconditional, matching `org.ts`'s own
1773
- * "every org gets one" rule; the app schema follows whether any
1784
+ * `CallsConfig.enabled`); kosmos follows its own switch too (`KosmosApp.enabled`, foundry#949); Boule's is
1785
+ * unconditional, matching `org.ts`'s own "every org gets one" rule; the app schema follows whether any
1774
1786
  * `hosting.entries` entry declares `database: true` (`coolifyDatabase`'s own
1775
1787
  * gate) rather than `hosting.enabled` alone, since a hosting org with no
1776
1788
  * database-bearing entry has nothing for this schema to hold.
@@ -1778,6 +1790,8 @@ function loginHostWaiting(org, host, recordedIds, bindingsWired, issuerPlanned)
1778
1790
  function orgServiceSchemaEnabled(org, schema) {
1779
1791
  if (schema.schema === 'cms')
1780
1792
  return org.site.enabled;
1793
+ if (schema.schema === 'kosmos')
1794
+ return kosmosOn(org);
1781
1795
  if (schema.schema.startsWith('chat_'))
1782
1796
  return org.chat.enabled;
1783
1797
  if (schema.schema.startsWith('calls_'))
@@ -2197,75 +2211,47 @@ function planChat(org, byKey, claimed, recordedIds, reading, issuerPlanned) {
2197
2211
  * matrix-authentication-service, entirely separate from chat's
2198
2212
  * (`plan/calls-matrix.ts`'s own top doc comment: a call guest must never gain
2199
2213
  * chat membership). Unlike chat, off by default: `org.ts`'s
2200
- * `CallsConfig.enabled` doc comment. Same on/off, park-not-delete shape as
2201
- * {@link planApp}/{@link planDatabase}/{@link planTunnel} below
2202
- * (`planHostingToggle`, #7 corrections 1) -- turning it off parks Synapse and
2203
- * MAS, never either database (`executor.ts`'s `coolifyCalls`).
2214
+ * `CallsConfig.enabled` doc comment.
2204
2215
  *
2205
- * #177: a first-ever provision (no live resource yet) is blocked, not
2206
- * planned, while `platformReadiness.callsReady` is false -- an org's own
2207
- * Matrix stack is wired to the shared calls SFU (#122/#127), so there is
2208
- * nothing for it to authenticate calls against until the platform's own
2209
- * `sfu:livekit` step is declared. Parking, and an already-provisioned stack,
2210
- * are unaffected.
2216
+ * #430: calls run transit-only now (calls#93's `CALLS_ENCRYPTION=transit`),
2217
+ * so the calls service no longer talks to a per-org Matrix stack and this
2218
+ * never provisions a new one, nor re-provisions a parked one -- the live
2219
+ * resource is still claimed, so it is never reported unmanaged. An org
2220
+ * whose stack is already live and enabled is left exactly as it is;
2221
+ * removing it is a separate, human-confirmed step. Turning `calls.enabled`
2222
+ * off still parks an already-live stack unchanged (`executor.ts`'s
2223
+ * `coolifyCalls`, #7 corrections 1): data is retained, not deleted.
2211
2224
  */
2212
- function planCalls(org, byKey, claimed, callsReady, synapseTag) {
2225
+ function planCalls(org, byKey, claimed) {
2213
2226
  const key = resourceKey('coolify', 'calls', org.id);
2214
- if (org.calls.enabled && !byKey.get(key) && !callsReady) {
2215
- return {
2216
- step: null,
2217
- blocked: ownsServices(org)
2218
- ? `${org.slug}'s calls Matrix stack is blocked, not planned: its own media server (coolify:own-sfu) is not applied yet`
2219
- : `${org.slug}'s calls Matrix stack is blocked, not planned: the platform owner's calls SFU (the platform's) is off`,
2220
- };
2221
- }
2222
- // #248: the two images this stack's own apply would run, checked the same
2223
- // way the platform's SFU checks LiveKit/cloudflared and ZITADEL checks its
2224
- // own tag -- a re-apply of the same declaration must not silently run a
2225
- // different version with no diff shown anywhere.
2226
- if (org.calls.enabled && !byKey.get(key)) {
2227
- const synapseBlocked = floatingTagBlocked('Synapse', synapseTag ?? VERSIONS.synapse, 'v1.161.0');
2228
- if (synapseBlocked) {
2229
- return {
2230
- step: null,
2231
- blocked: `${org.slug}'s calls Matrix stack is blocked, not planned: ${synapseBlocked}`,
2232
- };
2233
- }
2234
- const masBlocked = floatingTagBlocked('matrix-authentication-service', VERSIONS.mas, '1.24.0');
2235
- if (masBlocked) {
2236
- return {
2237
- step: null,
2238
- blocked: `${org.slug}'s calls Matrix stack is blocked, not planned: ${masBlocked}`,
2239
- };
2240
- }
2241
- }
2227
+ const live = byKey.get(key);
2228
+ if (live)
2229
+ claimed.add(key);
2230
+ const liveEnabled = live !== undefined && live.config.enabled !== false;
2231
+ if (org.calls.enabled && !liveEnabled)
2232
+ return { step: null, blocked: null };
2242
2233
  const toggle = planHostingToggle('coolify', 'calls', org.calls.enabled, org, byKey, claimed, () => `provision ${org.slug}'s calls Matrix stack (Synapse + matrix-authentication-service)`, () => `park ${org.slug}'s calls Matrix stack (stops serving; data is retained, not deleted -- #7 corrections 1)`);
2243
- // A held Synapse tag rides in the provisioning step; a parking step carries none.
2244
- if (toggle && synapseTag && toggle.action.desired.enabled === true) {
2245
- toggle.action.desired = { ...toggle.action.desired, synapseTag };
2246
- }
2247
2234
  return { step: toggle, blocked: null };
2248
2235
  }
2249
2236
  /**
2250
2237
  * The org's own calls service (#145, part 2 of #128) -- `wtfalch/calls`'s
2251
- * `packages/service`, deployed alongside the calls Matrix stack {@link planCalls}
2252
- * provisions and wired to it and to the shared LiveKit SFU (foundry#122/#127).
2253
- * Gated on the same `org.calls.enabled` flag as the Matrix stack, not a flag
2254
- * of its own: a calls service with no Matrix stack behind it has nothing to
2255
- * authenticate a guest against, and a Matrix stack alone answers no browser,
2256
- * so the two are declared as one on/off switch even though they are two
2257
- * planner steps. Same on/off, park-not-delete shape as {@link planCalls}
2258
- * (`planHostingToggle`, #7 corrections 1) -- turning it off parks the app,
2259
- * never its database (`executor.ts`'s `coolifyCallsService`).
2238
+ * `packages/service`, wired to the shared LiveKit SFU (foundry#122/#127).
2239
+ * Same on/off shape as {@link planCalls} (`planHostingToggle`, #7
2240
+ * corrections 1) -- turning it off parks the app, never its database
2241
+ * (`executor.ts`'s `coolifyCallsService`).
2260
2242
  *
2261
- * #177: silently plans nothing (no second blocked message) while the Matrix
2262
- * stack it rides beside is itself blocked on `callsReady` -- {@link planCalls}
2263
- * already reports the platform's shared SFU as missing once.
2243
+ * #430: calls run transit-only (calls#93), so this no longer rides behind
2244
+ * the Matrix stack {@link planCalls} used to provision -- there is none to
2245
+ * ride behind any more -- and reports the shared SFU as missing itself
2246
+ * (#177) rather than relying on `planCalls`'s now-removed report.
2264
2247
  */
2265
2248
  function planCallsService(org, byKey, claimed, callsReady, recordedIds, callsBlocked, tokenHost) {
2266
2249
  const key = resourceKey('coolify', 'calls-service', org.id);
2267
- if (org.calls.enabled && !byKey.get(key) && !callsReady) {
2268
- return { step: null, blocked: null };
2250
+ if (org.calls.enabled && !byKey.get(key) && !callsReady && !ownsServices(org)) {
2251
+ return {
2252
+ step: null,
2253
+ blocked: `${org.slug}'s calls service is blocked, not planned: the platform owner's calls SFU (the platform's) is off`,
2254
+ };
2269
2255
  }
2270
2256
  // An own org's service names its own media server, so it waits for it even when already provisioned.
2271
2257
  if (org.calls.enabled && ownsServices(org) && !callsReady) {
@@ -3081,6 +3067,137 @@ function planBouleClient(org, byKey, claimed) {
3081
3067
  },
3082
3068
  };
3083
3069
  }
3070
+ /**
3071
+ * foundry#949: the fixed ZITADEL web client `kosmos`, once the company turns kosmos on. The same shape as
3072
+ * {@link planBouleClient} and {@link planAgoraClient}; `kosmosOn(org)` is false before any client is planned.
3073
+ */
3074
+ function planKosmosClient(org, byKey, claimed) {
3075
+ if (!kosmosOn(org))
3076
+ return null;
3077
+ const key = resourceKey('zitadel', 'app', 'kosmos');
3078
+ const live = byKey.get(key);
3079
+ if (live)
3080
+ claimed.add(key);
3081
+ const desired = { appType: 'web' };
3082
+ if (!live) {
3083
+ return {
3084
+ action: { system: 'zitadel', kind: 'app', key: 'kosmos', desired },
3085
+ step: {
3086
+ description: `provision the web ZITADEL app 'kosmos' for ${org.slug}'s kosmos sign-in`,
3087
+ destroys: false,
3088
+ migrates: false,
3089
+ },
3090
+ };
3091
+ }
3092
+ if (live.config.appType === 'web')
3093
+ return null;
3094
+ return {
3095
+ action: {
3096
+ system: 'zitadel',
3097
+ kind: 'app',
3098
+ key: 'kosmos',
3099
+ desired: { ...desired, recreate: true },
3100
+ },
3101
+ step: {
3102
+ description: `recreate ZITADEL app 'kosmos' for ${org.slug} as web (was ${String(live.config.appType)})`,
3103
+ destroys: true,
3104
+ migrates: false,
3105
+ },
3106
+ };
3107
+ }
3108
+ /**
3109
+ * foundry#949: the org's own kosmos, a per-org singleton keyed by the org id under its own `kosmos` kind, built
3110
+ * from https://github.com/wtfalch/kosmos's root Dockerfile (`executor.ts`'s `coolifyKosmos`), with a database of
3111
+ * its own. Unlike Boule and Agora, kosmos is set up only once the company turns it on (`kosmosOn(org)`); while
3112
+ * it is off, an org that never had it plans nothing, and a live app is parked (stopped, database kept), the
3113
+ * same rule a switched-off service always follows here.
3114
+ *
3115
+ * Once live, a step is planned again whenever the live app's sign-in env lacks a desired key or holds a
3116
+ * different value (`kosmos.ts`'s `desiredKosmosEnv`, the function the executor writes from). The only host
3117
+ * value is the issuer address (the one Agora and Boule sign in at); without it kosmos is blocked, not planned,
3118
+ * so every other step still applies.
3119
+ */
3120
+ function planKosmos(org, byKey, claimed, recordedIds, issuer, otherSlugs) {
3121
+ if (!kosmosOn(org)) {
3122
+ // Switched off: park a live app (stop it, keep the database), like CMS and calls. A stopped app is
3123
+ // already parked, so the plan reaches empty.
3124
+ const parked = planHostingToggle('coolify', 'kosmos', false, org, byKey, claimed, () => `provision ${org.slug}'s kosmos`, () => `park ${org.slug}'s kosmos (stops serving; its database is retained, not deleted)`, org.id, (live) => !/^(exited|stopped)/.test(String(live.config.status ?? '')));
3125
+ return { step: parked, blocked: null };
3126
+ }
3127
+ const key = resourceKey('coolify', 'kosmos', org.id);
3128
+ const live = byKey.get(key);
3129
+ if (live)
3130
+ claimed.add(key);
3131
+ // Never truncated: Postgres cuts an identifier at 63 bytes, and a DNS label
3132
+ // over 63 is refused by Coolify and Cloudflare -- the same check `planBoule` makes.
3133
+ const tooLong = [
3134
+ [`${org.slug}-kosmos`, 'the kosmos host label and app name'],
3135
+ [`${org.slug}_kosmos_db_rt`, 'the kosmos database role'],
3136
+ ].filter(([name]) => name.length > 63);
3137
+ if (tooLong.length > 0) {
3138
+ return {
3139
+ step: null,
3140
+ blocked: `${org.slug}'s kosmos is blocked, not planned: ${tooLong.map(([n, what]) => `${what} '${n}' is over 63 characters`).join(', ')}; shorten the slug`,
3141
+ };
3142
+ }
3143
+ if (otherSlugs.includes(`${org.slug}-kosmos`)) {
3144
+ return {
3145
+ step: null,
3146
+ blocked: `${org.slug}'s kosmos is blocked, not planned: another declared org has the slug '${org.slug}-kosmos', which is this kosmos's hostname`,
3147
+ };
3148
+ }
3149
+ if (!issuer) {
3150
+ return {
3151
+ step: null,
3152
+ blocked: `${org.slug}'s kosmos is blocked, not planned: the host supplies no issuer`,
3153
+ };
3154
+ }
3155
+ const action = {
3156
+ system: 'coolify',
3157
+ kind: 'kosmos',
3158
+ key: org.id,
3159
+ desired: { issuer },
3160
+ };
3161
+ if (!live) {
3162
+ return {
3163
+ step: {
3164
+ action,
3165
+ step: { description: `provision ${org.slug}'s kosmos`, destroys: false, migrates: false },
3166
+ },
3167
+ blocked: null,
3168
+ };
3169
+ }
3170
+ const liveEnv = (live.config.env ?? {});
3171
+ const { env } = desiredKosmosEnv(org, recordedIds, issuer);
3172
+ const drift = kosmosEnvDrift(env, liveEnv);
3173
+ // Switched back on while the app is still stopped: the step starts it (`coolifyKosmos` starts a stopped app).
3174
+ if (/^(exited|stopped)/.test(String(live.config.status ?? ''))) {
3175
+ return {
3176
+ step: {
3177
+ action,
3178
+ step: {
3179
+ description: `start ${org.slug}'s kosmos again (it was parked)`,
3180
+ destroys: false,
3181
+ migrates: false,
3182
+ },
3183
+ },
3184
+ blocked: null,
3185
+ };
3186
+ }
3187
+ if (drift.length === 0)
3188
+ return { step: null, blocked: null };
3189
+ return {
3190
+ step: {
3191
+ action,
3192
+ step: {
3193
+ description: `update ${org.slug}'s kosmos config (${drift.join(', ')} missing or different)`,
3194
+ destroys: false,
3195
+ migrates: false,
3196
+ },
3197
+ },
3198
+ blocked: null,
3199
+ };
3200
+ }
3084
3201
  /**
3085
3202
  * C3: which credentials the org's Boule is given, and why one is not. Archon's credential goes to every
3086
3203
  * Boule the host can mint it for; the customers directory credential only while `customers.enabled`. Each
@@ -35,6 +35,7 @@ export function createEdgeAccess(client, options = {}) {
35
35
  createGate: (input) => retrying(() => client.createGate(input)),
36
36
  replaceGateHostnames: (id, input) => retrying(() => client.replaceGateHostnames(id, input)),
37
37
  deleteGate: (id) => retrying(() => client.deleteGate(id)),
38
+ setGatePeople: (id, input) => retrying(() => client.setGatePeople(id, input)),
38
39
  };
39
40
  const writeSlot = options.writeSlot ?? limiter(EDGE_DNS_WRITE_CONCURRENCY);
40
41
  /** app id -> a seen live app with no edge row yet. */
@@ -1966,6 +1966,15 @@ export declare const BOULE_REPO = "wtfalch/boule";
1966
1966
  export declare const BOULE_APP_DOCKERFILE = "/Dockerfile";
1967
1967
  /** Boule's own `EXPOSE 3000` (its doc comment just above) -- the port its health check (#543) probes. */
1968
1968
  export declare const BOULE_PORT = 3000;
1969
+ /**
1970
+ * foundry#949: kosmos (https://github.com/wtfalch/kosmos), bootstrapped from the same `wtfalch/package-template`
1971
+ * service shape Boule is, with its own Dockerfile at the repository root, serving on 3000 (`EXPOSE 3000`) and
1972
+ * running its migrations through `apps/host/scripts/migrate.mjs` before it serves, the same shape Boule's
1973
+ * `migrate-and-start.sh` follows.
1974
+ */
1975
+ export declare const KOSMOS_REPO = "wtfalch/kosmos";
1976
+ export declare const KOSMOS_APP_DOCKERFILE = "/Dockerfile";
1977
+ export declare const KOSMOS_PORT = 3000;
1969
1978
  export declare const LOG_DIR = "/app/logs";
1970
1979
  /** The company's Coolify destination (its Docker network `co-<slug>`), recorded when its first resource is created. */
1971
1980
  export declare const COOLIFY_DESTINATION_SCOPE_KEY = "coolify_destination";
@@ -3145,16 +3154,18 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
3145
3154
  private coolifyChatRekey;
3146
3155
  /**
3147
3156
  * `plan/diff.ts`'s `planCallsService`: the org's own calls service --
3148
- * `wtfalch/calls`'s `packages/service`, wired to the calls Matrix stack
3149
- * {@link coolifyCalls} provisions and the shared LiveKit SFU (#122/#127).
3150
- * Static config checks run first, before any Coolify call (#128's own
3151
- * carried-over PR 344 review fix, restated in #145's own issue body): the
3152
- * calls Matrix stack must already be recorded, its MAS app must already
3153
- * carry its client id/secret, and the shared SFU's LiveKit credentials must
3154
- * already be resolvable -- none of the three is minted or guessed here.
3155
- * Park stops the app only, the same park-not-delete rule
3156
- * {@link coolifyCalls}/{@link coolifyApp} already follow (#7 corrections
3157
- * 1); its own database is never touched from that branch.
3157
+ * `wtfalch/calls`'s `packages/service`, wired to the shared LiveKit SFU
3158
+ * (#122/#127). #430: calls run transit-only (calls#93's
3159
+ * `CALLS_ENCRYPTION=transit`), so this no longer waits on or wires a
3160
+ * per-org Matrix stack at all -- an org whose stack is already live from
3161
+ * before this keeps running it, unused, until a human removes it
3162
+ * separately. Static config checks run first, before any Coolify call
3163
+ * (#128's own carried-over PR 344 review fix, restated in #145's own
3164
+ * issue body): the shared SFU's LiveKit credentials must already be
3165
+ * resolvable -- not minted or guessed here. Park stops the app only, the
3166
+ * same park-not-delete rule {@link coolifyCalls}/{@link coolifyApp}
3167
+ * already follow (#7 corrections 1); its own database is never touched
3168
+ * from that branch.
3158
3169
  */
3159
3170
  private coolifyCallsService;
3160
3171
  /**
@@ -3187,14 +3198,6 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
3187
3198
  * env and mints nothing.
3188
3199
  */
3189
3200
  private callsTokenEnvs;
3190
- /**
3191
- * Reads back the calls Matrix stack's MAS client id/secret from the MAS
3192
- * app's own envs ({@link CALLS_SECRET_ENV_KEYS}) -- never mints: minting
3193
- * either is {@link ensureCallsMatrixSecrets}'s own job, and a missing value
3194
- * here means that stack has not finished provisioning yet, not something
3195
- * this step can fix on its own.
3196
- */
3197
- private readCallsMasClientCredentials;
3198
3201
  /**
3199
3202
  * `null` once the calls service app already has a `CALLS_BINDING_SECRET` --
3200
3203
  * never rotated, the same rule {@link ensureAuthCookieSecret} follows for
@@ -3327,6 +3330,25 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
3327
3330
  * shape {@link coolifyAgora}'s own doc comment explains.
3328
3331
  */
3329
3332
  private coolifyBoule;
3333
+ /**
3334
+ * `plan/diff.ts`'s `planKosmos`: provisions the org's own kosmos
3335
+ * (https://github.com/wtfalch/kosmos) as one more Coolify application, from
3336
+ * that repo's root Dockerfile ({@link KOSMOS_REPO}), with a database of its
3337
+ * own, and pushes its sign-in env. The same shape as {@link coolifyBoule},
3338
+ * trimmed: kosmos is opt-in (`kosmosOn(org)` guards every caller, so this is
3339
+ * never reached while it is off), has no mail, no owner seeding and no
3340
+ * Archon credential.
3341
+ *
3342
+ * The sign-in env comes from `plan/kosmos.ts`'s `desiredKosmosEnv`, the same
3343
+ * function the plan compares the live app against; it never writes a
3344
+ * partial set. The database is `<slug>-kosmos-db`, split the same way
3345
+ * Boule's is: the superuser URL as `DATABASE_URL_OWNER` and a `<dbName>_rt`
3346
+ * role's URL as `DATABASE_URL`, both through `pushDatabaseUrl`.
3347
+ *
3348
+ * #504 (same reason {@link coolifyBoule}'s own comment gives): a brand new
3349
+ * app is never restarted, but it is deployed once ({@link KOSMOS_DEPLOYED_SCOPE_KEY}).
3350
+ */
3351
+ private coolifyKosmos;
3330
3352
  /**
3331
3353
  * C3: the Archon credential of an org's Boule. Archon's mint rotates on every call, so it is called only
3332
3354
  * when none was pushed or the recorded push time (no secret) is 335 days old. Otherwise the held value is