@wtfalch/foundry 0.33.1 → 0.35.1

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 (106) hide show
  1. package/dist/bootstrap/bootstrap.d.ts +1 -1
  2. package/dist/bootstrap/bootstrap.js +49 -11
  3. package/dist/bootstrap/declare.d.ts +12 -5
  4. package/dist/bootstrap/declare.js +33 -18
  5. package/dist/bootstrap/versions.d.ts +24 -0
  6. package/dist/bootstrap/versions.js +46 -0
  7. package/dist/calls-sfu.d.ts +10 -0
  8. package/dist/calls-sfu.js +44 -3
  9. package/dist/index.d.ts +21 -8
  10. package/dist/index.js +11 -4
  11. package/dist/migrations/0051_org_services.sql +13 -0
  12. package/dist/migrations/0052_platform_mail_relay.sql +8 -0
  13. package/dist/migrations/0053_heartbeats_provider.sql +9 -0
  14. package/dist/migrations/0053_org_version_pin.sql +6 -0
  15. package/dist/org.d.ts +76 -12
  16. package/dist/org.js +136 -20
  17. package/dist/plan/backup-gate.d.ts +93 -0
  18. package/dist/plan/backup-gate.js +261 -0
  19. package/dist/plan/broker-entries.d.ts +9 -1
  20. package/dist/plan/broker-entries.js +20 -3
  21. package/dist/plan/build-record.d.ts +56 -0
  22. package/dist/plan/build-record.js +109 -0
  23. package/dist/plan/calls-matrix.d.ts +9 -0
  24. package/dist/plan/calls-matrix.js +18 -1
  25. package/dist/plan/cloudflared-connector.d.ts +7 -1
  26. package/dist/plan/cloudflared-connector.js +15 -2
  27. package/dist/plan/company-backups.d.ts +31 -0
  28. package/dist/plan/company-backups.js +101 -0
  29. package/dist/plan/deploy-health.d.ts +12 -2
  30. package/dist/plan/deploy-health.js +3 -1
  31. package/dist/plan/destinations.d.ts +73 -0
  32. package/dist/plan/destinations.js +217 -0
  33. package/dist/plan/diff.d.ts +46 -8
  34. package/dist/plan/diff.js +367 -126
  35. package/dist/plan/edge-access.d.ts +15 -0
  36. package/dist/plan/edge-access.js +21 -2
  37. package/dist/plan/executor.d.ts +276 -5
  38. package/dist/plan/executor.js +1111 -109
  39. package/dist/plan/health-check-settings.d.ts +28 -0
  40. package/dist/plan/health-check-settings.js +36 -0
  41. package/dist/plan/health-gaps.d.ts +33 -0
  42. package/dist/plan/health-gaps.js +89 -0
  43. package/dist/plan/health-targets.d.ts +1 -3
  44. package/dist/plan/health-targets.js +36 -36
  45. package/dist/plan/held-key-refs.d.ts +15 -0
  46. package/dist/plan/held-key-refs.js +15 -0
  47. package/dist/plan/hostnames.d.ts +10 -0
  48. package/dist/plan/hostnames.js +14 -0
  49. package/dist/plan/index.d.ts +19 -6
  50. package/dist/plan/index.js +10 -3
  51. package/dist/plan/live.d.ts +11 -2
  52. package/dist/plan/live.js +16 -2
  53. package/dist/plan/locked-hostnames.d.ts +7 -3
  54. package/dist/plan/locked-hostnames.js +25 -4
  55. package/dist/plan/login-host.d.ts +61 -12
  56. package/dist/plan/login-host.js +99 -11
  57. package/dist/plan/mail-relay.d.ts +139 -0
  58. package/dist/plan/mail-relay.js +400 -0
  59. package/dist/plan/network-move.d.ts +20 -0
  60. package/dist/plan/network-move.js +41 -0
  61. package/dist/plan/oikos-ingress.d.ts +111 -0
  62. package/dist/plan/oikos-ingress.js +231 -0
  63. package/dist/plan/org-names.d.ts +30 -0
  64. package/dist/plan/org-names.js +108 -0
  65. package/dist/plan/own-diff.d.ts +170 -0
  66. package/dist/plan/own-diff.js +440 -0
  67. package/dist/plan/own-dns.d.ts +6 -0
  68. package/dist/plan/own-dns.js +15 -0
  69. package/dist/plan/own-hostnames.d.ts +27 -0
  70. package/dist/plan/own-hostnames.js +24 -0
  71. package/dist/plan/own-limits.d.ts +213 -0
  72. package/dist/plan/own-limits.js +232 -0
  73. package/dist/plan/own-services.d.ts +193 -0
  74. package/dist/plan/own-services.js +169 -0
  75. package/dist/plan/own-sfu.d.ts +69 -0
  76. package/dist/plan/own-sfu.js +256 -0
  77. package/dist/plan/plan-apply.d.ts +121 -10
  78. package/dist/plan/plan-apply.js +357 -18
  79. package/dist/plan/platform-diff.d.ts +155 -11
  80. package/dist/plan/platform-diff.js +271 -66
  81. package/dist/plan/platform-org.d.ts +107 -4
  82. package/dist/plan/platform-org.js +175 -3
  83. package/dist/plan/platform-providers.d.ts +474 -81
  84. package/dist/plan/platform-providers.js +1407 -560
  85. package/dist/plan/proxy-labels.d.ts +57 -11
  86. package/dist/plan/proxy-labels.js +94 -14
  87. package/dist/plan/run-options.d.ts +19 -0
  88. package/dist/plan/run-options.js +46 -0
  89. package/dist/plan/sign-in-org.d.ts +1 -1
  90. package/dist/plan/sign-in-org.js +20 -2
  91. package/dist/plan/zitadel-login-policy.d.ts +1 -1
  92. package/dist/plan/zitadel-login-policy.js +77 -9
  93. package/dist/plan/zitadel-system-api.d.ts +22 -1
  94. package/dist/plan/zitadel-system-api.js +49 -8
  95. package/dist/platform.d.ts +40 -0
  96. package/dist/platform.js +16 -0
  97. package/dist/provision.d.ts +2 -0
  98. package/dist/provision.js +3 -1
  99. package/dist/slug.d.ts +3 -1
  100. package/dist/slug.js +26 -3
  101. package/dist/store/postgres.js +93 -2
  102. package/dist/store/schema.d.ts +87 -0
  103. package/dist/store/schema.js +9 -0
  104. package/package.json +3 -3
  105. package/versions.digests.json +11 -0
  106. package/versions.json +3 -1
@@ -45,3 +45,31 @@ export declare const DEFAULT_HEALTH_CHECK_START_PERIOD_SECONDS = 30;
45
45
  * Dockerfile's `EXPOSE`) names, not a public one.
46
46
  */
47
47
  export declare function healthCheckSettings(path: string, port: number, startPeriodSeconds?: number): HealthCheckPatch;
48
+ /**
49
+ * For an app built from a Dockerfile that has its own HEALTHCHECK: Coolify's check off. Coolify then keeps the
50
+ * Dockerfile's check, and its rolling update waits on that check's state (with Coolify's check on it would replace
51
+ * the Dockerfile's, so two checks would never both be live). Coolify reads the Dockerfile only while its own check is
52
+ * off, so this goes on before the first deploy.
53
+ */
54
+ export declare const IMAGE_HEALTHCHECK_SETTINGS: {
55
+ readonly health_check_enabled: false;
56
+ };
57
+ /** The four `updateApplication` fields that turn on a Coolify health check that runs a command inside the container. */
58
+ export interface CommandHealthCheckPatch {
59
+ health_check_enabled: true;
60
+ health_check_type: 'cmd';
61
+ health_check_command: string;
62
+ health_check_start_period: number;
63
+ }
64
+ /**
65
+ * The same fields as {@link healthCheckSettings}, for an image whose HTTP
66
+ * check cannot work: no `curl`, or an app that answers 401 on every route.
67
+ * Coolify's own HTTP check calls `localhost`, which `wget` in an Alpine image
68
+ * resolves to `::1` first, so a server that listens on IPv4 only is never
69
+ * reached. A command names `127.0.0.1` itself.
70
+ */
71
+ export declare function commandHealthCheckSettings(command: string, startPeriodSeconds?: number): CommandHealthCheckPatch;
72
+ /** A GET on `127.0.0.1` that fails on any status of 400 or more; the `wget` of a busybox image. */
73
+ export declare const wgetProbe: (port: number | string, path: string) => string;
74
+ /** A TCP connect to `127.0.0.1`, for a server with no route that answers without a token. */
75
+ export declare const tcpProbe: (port: number | string) => string;
@@ -45,3 +45,39 @@ export function healthCheckSettings(path, port, startPeriodSeconds = DEFAULT_HEA
45
45
  health_check_start_period: startPeriodSeconds,
46
46
  };
47
47
  }
48
+ /**
49
+ * For an app built from a Dockerfile that has its own HEALTHCHECK: Coolify's check off. Coolify then keeps the
50
+ * Dockerfile's check, and its rolling update waits on that check's state (with Coolify's check on it would replace
51
+ * the Dockerfile's, so two checks would never both be live). Coolify reads the Dockerfile only while its own check is
52
+ * off, so this goes on before the first deploy.
53
+ */
54
+ export const IMAGE_HEALTHCHECK_SETTINGS = { health_check_enabled: false };
55
+ /**
56
+ * What Coolify accepts as `health_check_command`: letters, digits, space and
57
+ * `- _ . / : = @ , +`, at most 1000 characters (its `generate_healthcheck_commands`
58
+ * falls back to the HTTP check on anything else, without an error). It runs as
59
+ * `sh -c`, so the image needs a shell and the tool the command names.
60
+ */
61
+ const COOLIFY_COMMAND = /^[a-zA-Z0-9 \-_./:=@,+]+$/;
62
+ /**
63
+ * The same fields as {@link healthCheckSettings}, for an image whose HTTP
64
+ * check cannot work: no `curl`, or an app that answers 401 on every route.
65
+ * Coolify's own HTTP check calls `localhost`, which `wget` in an Alpine image
66
+ * resolves to `::1` first, so a server that listens on IPv4 only is never
67
+ * reached. A command names `127.0.0.1` itself.
68
+ */
69
+ export function commandHealthCheckSettings(command, startPeriodSeconds = DEFAULT_HEALTH_CHECK_START_PERIOD_SECONDS) {
70
+ if (!COOLIFY_COMMAND.test(command) || command.length > 1000) {
71
+ throw new Error(`health check command ${JSON.stringify(command)} is not one Coolify accepts`);
72
+ }
73
+ return {
74
+ health_check_enabled: true,
75
+ health_check_type: 'cmd',
76
+ health_check_command: command,
77
+ health_check_start_period: startPeriodSeconds,
78
+ };
79
+ }
80
+ /** A GET on `127.0.0.1` that fails on any status of 400 or more; the `wget` of a busybox image. */
81
+ export const wgetProbe = (port, path) => `wget -q -O /dev/null http://127.0.0.1:${port}${path}`;
82
+ /** A TCP connect to `127.0.0.1`, for a server with no route that answers without a token. */
83
+ export const tcpProbe = (port) => `nc -z 127.0.0.1 ${port}`;
@@ -0,0 +1,33 @@
1
+ /** An app as Coolify keeps it: its name and everything create and update sent. */
2
+ export interface StoredApp {
3
+ name: string;
4
+ fields: Record<string, unknown>;
5
+ }
6
+ /**
7
+ * Apps that run once and then idle or exit, so Docker has no service to check. Matched by what the app is built
8
+ * from (repo and Dockerfile target), never by its name, so no look-alike name is exempt.
9
+ */
10
+ export declare const EXIT_JOBS: ReadonlyArray<{
11
+ repo: string;
12
+ target: string;
13
+ reason: string;
14
+ }>;
15
+ /**
16
+ * Apps built from a Dockerfile in another repo that carries a HEALTHCHECK line. Foundry cannot read that file
17
+ * here, so each entry names the app by repo and Dockerfile and says where the line was seen. With the Coolify
18
+ * check off, Coolify keeps the image's check and its rolling update follows it.
19
+ */
20
+ export declare const REPO_DOCKERFILE_CHECKED: ReadonlyArray<{
21
+ repo: string;
22
+ dockerfile?: string;
23
+ target?: string;
24
+ seen: string;
25
+ }>;
26
+ /**
27
+ * A check that can fail. Either Coolify's own, on and with something to probe (a command that does something,
28
+ * or HTTP on a port; Coolify falls back to the exposed port and to `/`), or Coolify's off over a Dockerfile that
29
+ * carries its own HEALTHCHECK, which Coolify then keeps.
30
+ */
31
+ export declare function hasRealCheck(fields: Record<string, unknown>): boolean;
32
+ /** The names of the long-running apps with no real check, not counting the exit jobs. */
33
+ export declare function healthGaps(apps: ReadonlyArray<StoredApp>): string[];
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Apps that run once and then idle or exit, so Docker has no service to check. Matched by what the app is built
3
+ * from (repo and Dockerfile target), never by its name, so no look-alike name is exempt.
4
+ */
5
+ export const EXIT_JOBS = [
6
+ {
7
+ repo: 'wtfalch/files',
8
+ target: 'migration',
9
+ reason: 'runs the migrations once; its image HEALTHCHECK reports done',
10
+ },
11
+ {
12
+ repo: 'wtfalch/ai',
13
+ target: 'migration',
14
+ reason: 'runs the migrations once; its image HEALTHCHECK reports done',
15
+ },
16
+ {
17
+ repo: 'wtfalch/integrations',
18
+ target: 'migration',
19
+ reason: 'runs the migrations once; its image HEALTHCHECK reports done',
20
+ },
21
+ {
22
+ repo: 'wtfalch/integrations',
23
+ target: 'worker',
24
+ reason: 'a finite pass (work-once.ts) that exits 0; nothing waits on it',
25
+ },
26
+ ];
27
+ /**
28
+ * Apps built from a Dockerfile in another repo that carries a HEALTHCHECK line. Foundry cannot read that file
29
+ * here, so each entry names the app by repo and Dockerfile and says where the line was seen. With the Coolify
30
+ * check off, Coolify keeps the image's check and its rolling update follows it.
31
+ */
32
+ export const REPO_DOCKERFILE_CHECKED = [
33
+ { repo: 'wtfalch/auth', dockerfile: '/login/Dockerfile', seen: 'login image, docker inspect' },
34
+ { repo: 'wtfalch/auth', dockerfile: '/broker/Dockerfile', seen: 'broker image, docker inspect' },
35
+ { repo: 'wtfalch/ai', target: 'worker', seen: 'ai-worker image, docker inspect' },
36
+ { repo: 'wtfalch/integrations', target: 'runner', seen: 'integrations/Dockerfile line 49' },
37
+ {
38
+ repo: 'wtfalch/chat',
39
+ dockerfile: '/apps/chat-service/synapse-module/Dockerfile',
40
+ seen: 'chat Synapse image, docker inspect',
41
+ },
42
+ ];
43
+ /** Commands that succeed whatever the container is doing. */
44
+ const NO_OP = /^(?:true|:|exit 0|\/bin\/true|\/usr\/bin\/true|sh -c true)$/;
45
+ /** A `HEALTHCHECK` instruction that is not `HEALTHCHECK NONE`. */
46
+ const DOCKERFILE_HEALTHCHECK = /^\s*HEALTHCHECK\s+(?!NONE\b)/im;
47
+ const same = (a, b) => b === undefined || a === b;
48
+ function exitJob(f) {
49
+ return EXIT_JOBS.some((e) => f.git_repository === e.repo && f.dockerfile_target_build === e.target);
50
+ }
51
+ function repoDockerfileChecked(f) {
52
+ return REPO_DOCKERFILE_CHECKED.some((e) => f.git_repository === e.repo &&
53
+ same(f.dockerfile_location, e.dockerfile) &&
54
+ same(f.dockerfile_target_build, e.target));
55
+ }
56
+ /**
57
+ * A check that can fail. Either Coolify's own, on and with something to probe (a command that does something,
58
+ * or HTTP on a port; Coolify falls back to the exposed port and to `/`), or Coolify's off over a Dockerfile that
59
+ * carries its own HEALTHCHECK, which Coolify then keeps.
60
+ */
61
+ export function hasRealCheck(fields) {
62
+ if (fields.health_check_enabled === true) {
63
+ if (fields.health_check_type === 'cmd') {
64
+ const command = fields.health_check_command;
65
+ return typeof command === 'string' && command.trim() !== '' && !NO_OP.test(command.trim());
66
+ }
67
+ const exposed = typeof fields.ports_exposes === 'string' && fields.ports_exposes.trim() !== '';
68
+ return typeof fields.health_check_port === 'number' || exposed;
69
+ }
70
+ if (fields.health_check_enabled === false) {
71
+ const dockerfile = fields.dockerfile;
72
+ if (typeof dockerfile === 'string') {
73
+ // An inline Dockerfile may arrive base64 (a PATCH), as plain text on create.
74
+ const text = DOCKERFILE_HEALTHCHECK.test(dockerfile)
75
+ ? dockerfile
76
+ : Buffer.from(dockerfile, 'base64').toString('utf8');
77
+ return DOCKERFILE_HEALTHCHECK.test(text);
78
+ }
79
+ return repoDockerfileChecked(fields);
80
+ }
81
+ return false;
82
+ }
83
+ /** The names of the long-running apps with no real check, not counting the exit jobs. */
84
+ export function healthGaps(apps) {
85
+ return apps
86
+ .filter((a) => !exitJob(a.fields) && !hasRealCheck(a.fields))
87
+ .map((a) => a.name)
88
+ .sort();
89
+ }
@@ -71,9 +71,7 @@ export interface HealthTarget {
71
71
  * the broker whenever the org has them; then whichever of hosting or site
72
72
  * the org runs, if either.
73
73
  */
74
- export declare function healthTargets(org: DeclaredOrg, options?: {
75
- platform?: DeclaredPlatform | null;
76
- }): HealthTarget[];
74
+ export declare function healthTargets(org: DeclaredOrg): HealthTarget[];
77
75
  /**
78
76
  * The seven platform services #541 names (`platform:auth`, `platform:archon`,
79
77
  * `platform:mail`, `platform:ai`, `platform:files`, `platform:integrations`,
@@ -1,10 +1,9 @@
1
1
  import { appEntry } from '../org.js';
2
2
  import { agoraHostnameFor } from './agora.js';
3
3
  import { bouleHostnameFor } from './boule.js';
4
- import { isPlatformOwnOrg } from './diff.js';
5
4
  import { callsHostnameFor, cmsHostnameFor } from './executor.js';
6
5
  import { isBehindGate } from './gates.js';
7
- import { entryHostnames } from './hostnames.js';
6
+ import { entryHostnames, siteHostnameFor } from './hostnames.js';
8
7
  import { loginHostnameFor } from './login-host.js';
9
8
  function okExpect() {
10
9
  return { status: 200 };
@@ -25,12 +24,8 @@ const STALWART_READY_PATH = '/healthz/ready';
25
24
  const CHAT_SERVICE_HEALTH_PATH = '/_chat/health';
26
25
  const CALLS_HEALTH_PATH = '/healthz';
27
26
  /** Boule, Agora and the company's own login host: every org gets these, with no flag to check (the "Why" this issue opens with). */
28
- function alwaysOnTargets(org, platform) {
29
- // The platform's own org signs in on the shared instance host, not its own
30
- // `loginHostnameFor` (#488; `login-host.ts`, `diff.ts`'s `planLoginHost`).
31
- const loginHost = platform && isPlatformOwnOrg(org, platform.ownerOrgId)
32
- ? platform.auth.hostname
33
- : loginHostnameFor(org);
27
+ function alwaysOnTargets(org) {
28
+ const loginHost = loginHostnameFor(org);
34
29
  return [
35
30
  {
36
31
  service: 'boule',
@@ -62,14 +57,10 @@ function alwaysOnTargets(org, platform) {
62
57
  ];
63
58
  }
64
59
  /**
65
- * The shared sign-in broker (`broker-entries.ts`), when this company depends
66
- * on it: any hosting entry declaring `uses.auth` signs in through it. The
67
- * broker is the platform's one app, not a per-company address `org` alone
68
- * can build -- its real, reachable URL is `platformHealthTargets`'s own
69
- * `'auth-broker'` target, under `platform.auth.hostname`. This target still
70
- * belongs in a company's own list (#541: "Include ... the broker when the
71
- * company has them") so a status page scoped to one company shows every
72
- * service that company's sign-in actually depends on, even a shared one.
60
+ * The company's own sign-in broker (`broker-entries.ts`), when it depends on
61
+ * it: any hosting entry declaring `uses.auth` signs in through it. Its
62
+ * `/healthy` is not routed to the outside, so no URL is built here (#541:
63
+ * "Include ... the broker when the company has them").
73
64
  */
74
65
  function brokerTarget(org) {
75
66
  const usesBroker = org.hosting.entries.some((entry) => Boolean(entry.uses?.auth));
@@ -82,14 +73,13 @@ function brokerTarget(org) {
82
73
  depth: 'shallow',
83
74
  restartSafe: false,
84
75
  behindAccess: false,
85
- reason: "the sign-in broker is the platform's one shared app (broker-entries.ts), not a per-company address this function can build from `org` alone -- see platformHealthTargets's 'auth-broker' target",
76
+ reason: "the company's own sign-in broker answers on its internal network only (broker-entries.ts): its /healthy is not routed to the outside, so there is no URL to probe",
86
77
  };
87
78
  }
88
79
  /**
89
- * Mail, when this company's mail is not off. Every org's mail is served by
90
- * the one shared Stalwart instance (`executor.ts`'s `mailOrgFor`), never a
91
- * per-org app, so there is no per-company hostname to build here either --
92
- * `platformHealthTargets`'s own `'mail'` target is the real, reachable one.
80
+ * Mail, when this company's mail is not off. The company runs its own
81
+ * Stalwart (`own-mail`), reached only by the shared relay on the internal
82
+ * network, so there is no public URL to build here.
93
83
  */
94
84
  function mailTarget(org) {
95
85
  if (org.mail.mode === 'off')
@@ -101,7 +91,7 @@ function mailTarget(org) {
101
91
  depth: 'deep',
102
92
  restartSafe: false,
103
93
  behindAccess: false,
104
- reason: "mail is the platform's one shared Stalwart instance, not a per-company address this function can build from `org` alone -- see platformHealthTargets's 'mail' target",
94
+ reason: "the company's own Stalwart is reached by the shared relay on the internal network only, so it has no public address to probe",
105
95
  };
106
96
  }
107
97
  /**
@@ -200,11 +190,11 @@ function appTargets(org) {
200
190
  if (org.hosting.enabled && appEntry(org.hosting)) {
201
191
  return org.hosting.entries.map((entry) => hostingEntryTarget(org, entry));
202
192
  }
203
- if (org.site.enabled && org.domain) {
193
+ if (org.site.enabled) {
204
194
  return [
205
195
  {
206
196
  service: 'site',
207
- url: `https://${org.domain}${DEFAULT_API_HEALTH_PATH}`,
197
+ url: `https://${siteHostnameFor(org)}${DEFAULT_API_HEALTH_PATH}`,
208
198
  expect: okExpect(),
209
199
  depth: 'deep',
210
200
  restartSafe: true,
@@ -228,8 +218,8 @@ function appTargets(org) {
228
218
  * the broker whenever the org has them; then whichever of hosting or site
229
219
  * the org runs, if either.
230
220
  */
231
- export function healthTargets(org, options = {}) {
232
- const targets = alwaysOnTargets(org, options.platform ?? null);
221
+ export function healthTargets(org) {
222
+ const targets = alwaysOnTargets(org);
233
223
  const broker = brokerTarget(org);
234
224
  if (broker)
235
225
  targets.push(broker);
@@ -308,8 +298,10 @@ function internalOnlyTarget(service, reason) {
308
298
  * one anyway.
309
299
  */
310
300
  export function platformHealthTargets(platform) {
301
+ // The platform's own sign-in, mail and files are off (each company runs its own), so a target
302
+ // for a service that is not enabled would point at a host that does not exist.
311
303
  return [
312
- ...authTargets(platform),
304
+ ...(platform.auth.enabled ? authTargets(platform) : []),
313
305
  {
314
306
  service: 'archon',
315
307
  // `provisionPlatformArchon` always puts a Cloudflare Access app in front
@@ -322,16 +314,24 @@ export function platformHealthTargets(platform) {
322
314
  restartSafe: false,
323
315
  behindAccess: true,
324
316
  },
325
- {
326
- service: 'mail',
327
- url: `https://${platform.mail.hostname}${STALWART_READY_PATH}`,
328
- expect: okExpect(),
329
- depth: 'deep',
330
- restartSafe: false,
331
- behindAccess: false,
332
- },
317
+ ...(platform.mail.enabled
318
+ ? [
319
+ {
320
+ service: 'mail',
321
+ url: `https://${platform.mail.hostname}${STALWART_READY_PATH}`,
322
+ expect: okExpect(),
323
+ depth: 'deep',
324
+ restartSafe: false,
325
+ behindAccess: false,
326
+ },
327
+ ]
328
+ : []),
333
329
  internalOnlyTarget('ai', "platform:ai's runner is created with no Coolify domains, tunnel, DNS record or Access app (platform-providers.ts) -- nothing public to probe yet"),
334
- internalOnlyTarget('files', "platform:files' runner is created with no domains either, the same as platform:auth's apps (platform-providers.ts) -- nothing public to probe yet"),
330
+ ...(platform.files.enabled
331
+ ? [
332
+ internalOnlyTarget('files', "platform:files' runner is created with no domains either, the same as platform:auth's apps (platform-providers.ts) -- nothing public to probe yet"),
333
+ ]
334
+ : []),
335
335
  {
336
336
  service: 'integrations',
337
337
  url: null,
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The held-key refs the platform's steps deliver under and an org step reads back. A leaf module:
3
+ * platform-providers.ts and platform-org.ts import each other, so a constant read at load lives here.
4
+ */
5
+ /** The ref the scheduler's admin token is delivered under: the host's real `JobsSchedulerPort` adapter reads it back. */
6
+ export declare const JOBS_ADMIN_TOKEN_REF = "jobs:scheduler:adminToken";
7
+ /** The ref the AI management key is delivered under; the host's `AiOrganisationPort` adapter reads it back. */
8
+ export declare const PLATFORM_AI_MANAGEMENT_KEY_REF = "ai:platform:managementKey";
9
+ /**
10
+ * The install's edge label token (`proxy-labels.ts` `EDGE_LABEL`): minted once, held, and written into the
11
+ * labels of every container foundry routes, so Traefik can be told to route only those.
12
+ */
13
+ export declare const PROXY_LABEL_TOKEN_REF = "proxy:labels:token";
14
+ /** The ref the management private key (PKCS#8 PEM) is delivered under. */
15
+ export declare const FILES_MANAGEMENT_KEY_REF = "files:platform:managementKey";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The held-key refs the platform's steps deliver under and an org step reads back. A leaf module:
3
+ * platform-providers.ts and platform-org.ts import each other, so a constant read at load lives here.
4
+ */
5
+ /** The ref the scheduler's admin token is delivered under: the host's real `JobsSchedulerPort` adapter reads it back. */
6
+ export const JOBS_ADMIN_TOKEN_REF = 'jobs:scheduler:adminToken';
7
+ /** The ref the AI management key is delivered under; the host's `AiOrganisationPort` adapter reads it back. */
8
+ export const PLATFORM_AI_MANAGEMENT_KEY_REF = 'ai:platform:managementKey';
9
+ /**
10
+ * The install's edge label token (`proxy-labels.ts` `EDGE_LABEL`): minted once, held, and written into the
11
+ * labels of every container foundry routes, so Traefik can be told to route only those.
12
+ */
13
+ export const PROXY_LABEL_TOKEN_REF = 'proxy:labels:token';
14
+ /** The ref the management private key (PKCS#8 PEM) is delivered under. */
15
+ export const FILES_MANAGEMENT_KEY_REF = 'files:platform:managementKey';
@@ -38,6 +38,16 @@ export declare function tunnelDomainList(names: readonly string[]): string[];
38
38
  export declare function coolifyTunnelDomains(names: readonly string[]): string;
39
39
  /** What `domains` held on an `app` entry's Coolify app before entries had hostnames. */
40
40
  export declare function legacyAppHostname(org: DeclaredOrg): string;
41
+ /**
42
+ * The public address of the org's cms site: its domain, or `<slug>.oikos.host`
43
+ * for a company without one (served by the shared `*.oikos.host` wildcard).
44
+ */
45
+ export declare function siteHostnameFor(org: DeclaredOrg): string;
46
+ /**
47
+ * The cms studio's address: `cms.<domain>`, or `<slug>-cms.oikos.host` for a
48
+ * company without a domain (one label, so the free wildcard certificate covers it).
49
+ */
50
+ export declare function cmsStudioHostnameFor(org: DeclaredOrg): string;
41
51
  /**
42
52
  * `share.<domain>` when hosting is on and an entry declares
43
53
  * `uses.files.shareHost`, else `null`. The files service answers public
@@ -63,6 +63,20 @@ export function coolifyTunnelDomains(names) {
63
63
  export function legacyAppHostname(org) {
64
64
  return org.hosting.usesDomain && org.domain ? org.domain : `${org.slug}.oikos.host`;
65
65
  }
66
+ /**
67
+ * The public address of the org's cms site: its domain, or `<slug>.oikos.host`
68
+ * for a company without one (served by the shared `*.oikos.host` wildcard).
69
+ */
70
+ export function siteHostnameFor(org) {
71
+ return org.site.usesDomain && org.domain ? org.domain : `${org.slug}.oikos.host`;
72
+ }
73
+ /**
74
+ * The cms studio's address: `cms.<domain>`, or `<slug>-cms.oikos.host` for a
75
+ * company without a domain (one label, so the free wildcard certificate covers it).
76
+ */
77
+ export function cmsStudioHostnameFor(org) {
78
+ return org.site.usesDomain && org.domain ? `cms.${org.domain}` : `${org.slug}-cms.oikos.host`;
79
+ }
66
80
  /**
67
81
  * `share.<domain>` when hosting is on and an entry declares
68
82
  * `uses.files.shareHost`, else `null`. The files service answers public
@@ -4,10 +4,18 @@ export type { StepAction, PlannedStep, PlanDetail, OrgPostgresPlan } from './dif
4
4
  export { computePlanDetail, toPlan } from './diff.js';
5
5
  export type { StepSystem } from './step-system.js';
6
6
  export { stepSystemOf } from './step-system.js';
7
- export type { PlatformPlanDetail } from './platform-diff.js';
8
- export { planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_SFU_APPLIED_SCOPE_KEY, } from './platform-diff.js';
7
+ export type { PlatformPlanDetail, MailRoute } from './platform-diff.js';
8
+ export { planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_RELAY_KEY, PLATFORM_MAIL_RELAY_ROUTES_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_SFU_APPLIED_SCOPE_KEY, } from './platform-diff.js';
9
9
  export type { HeldKeyResolver, OrgPlatformReadiness } from './platform-org.js';
10
- export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './platform-org.js';
10
+ export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesForOwn, agoraHostValuesFromPlatform, heldOwnKeyRefs, heldPlatformKeyRefs, orgPlatformReadinessFor, orgPlatformReadinessFrom, } from './platform-org.js';
11
+ export type { PlanOwnAuthOptions, PlanOwnFilesOptions, PlanOwnFilesR2Options, PlanOwnJobsOptions, } from './own-diff.js';
12
+ export { OWN_AUTH_APPLIED_GIT_REF_SCOPE_KEY, OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY, OWN_AUTH_BLOCKED_KEY, OWN_FILES_BLOCKED_KEY, OWN_FILES_R2_BLOCKED_KEY, OWN_JOBS_BLOCKED_KEY, OWN_MAIL_BLOCKED_KEY, planOwnAuth, planOwnFiles, planOwnFilesR2, planOwnJobs, planOwnMail, } from './own-diff.js';
13
+ export type { PlanOwnSfuOptions } from './own-diff.js';
14
+ export { OWN_SFU_BLOCKED_KEY, OWN_SFU_FIREWALL_KEY, planOwnSfu } from './own-diff.js';
15
+ export type { SfuAppDeps, SfuAppInput } from './own-sfu.js';
16
+ export { provisionSfuApp, sfuConfig, sfuPortsMappings } from './own-sfu.js';
17
+ export type { OwnAuthInstance, FilesKeysMint, FilesKeysMinted, OwnFilesDeps, OwnFilesR2Key, OwnFilesR2Keys, OwnServicePorts, OwnServicesPort, OwnServicesProvision, OwnSfuDeps, ProjectSource, ServiceNetwork, ServiceTarget, StepScope, } from './own-services.js';
18
+ export { OWN_FILES_R2_SCOPE_KEY, OWN_FILES_R2_UNDELIVERED_SCOPE_KEY, OWN_SERVICES_APPLIED_SCOPE_KEY, OwnServicesBlockedError, orgNetwork, orgTarget, platformNetwork, platformTarget, scopeFromOrg, scopeFromPlatform, } from './own-services.js';
11
19
  export type { PlatformOrgClientFactories } from './platform-org-ports.js';
12
20
  export { PlatformOrgBlockedError, PlatformOrgPorts } from './platform-org-ports.js';
13
21
  export type { AdoptCandidate, AdoptInput, AdoptPlan, AdoptSnapshot } from './adopt.js';
@@ -15,8 +23,10 @@ export { AdoptRefusedError } from './adopt.js';
15
23
  export type { AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, FoundryApplyOptions, } from './plan-apply.js';
16
24
  export { FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, UpgradeConfirmationRequiredError, planNeedsConfirmation, } from './plan-apply.js';
17
25
  export type { UnconfirmedUpgrade } from './confirm-upgrade.js';
18
- export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, filesPublicHostsValue, filesShareDomains, } from './platform-providers.js';
19
- export type { PlatformAuthDeps, PlatformAuthSmtp, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformDnsFactory, PlatformAccessFactory, PlatformAccessPort, PlatformTunnelsFactory, PlatformEdgeRuntime, } from './platform-providers.js';
26
+ export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './backup-gate.js';
27
+ export type { BackupGatePorts, MoveGatePorts } from './backup-gate.js';
28
+ export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, filesPublicHostsValue, filesShareDomains, provisionFilesHost, provisionJobsHost, provisionMailServer, } from './platform-providers.js';
29
+ export type { FilesHostInput, JobsHostDeps, JobsHostInput, MailServerInput, PlatformAuthDeps, PlatformAuthSmtp, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformDnsFactory, PlatformAccessFactory, PlatformAccessPort, PlatformTunnelsFactory, PlatformEdgeRuntime, } from './platform-providers.js';
20
30
  export type { PlatformMailDeps, PlatformMailAdminPort, PlatformMailRuntime, } from './platform-providers.js';
21
31
  export { FILES_INTROSPECTION_SECRET_REF, FILES_MANAGEMENT_ISSUER, FILES_MANAGEMENT_KEY_ID, FILES_MANAGEMENT_KEY_REF, PLATFORM_AUTH_INSTANCE_BEARER_REF, } from './platform-providers.js';
22
32
  export { AI_MANAGEMENT_ISSUER, AI_MANAGEMENT_KEY_ID, AI_SIGNING_KEY_REF, AiManagementKeyExistsError, createAiManagementKeyMinter, PLATFORM_AI_MANAGEMENT_KEY_REF, PLATFORM_AI_MANAGEMENT_PERMISSIONS, } from './platform-providers.js';
@@ -33,8 +43,10 @@ export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, Provi
33
43
  export type { AgoraHostPort, AgoraHostValues } from './agora.js';
34
44
  export type { BrokerPort } from './broker-entries.js';
35
45
  export { PlatformBrokerPort } from './broker-entries.js';
36
- export type { LoginHostBinding, LoginHostBindingsPort } from './login-host.js';
46
+ export type { LoginHostBinding, LoginHostBindingsPort, LoginHostDiscoveryPort, } from './login-host.js';
37
47
  export { loginHostnameFor } from './login-host.js';
48
+ export { NO_DOMAIN_ZONE, ownHostnames } from './own-hostnames.js';
49
+ export type { OwnHostnames } from './own-hostnames.js';
38
50
  export type { MailIdentityApp, MailIdentityAudienceCheck } from './mail-identity.js';
39
51
  export { checkMailIdentityAudience } from './mail-identity.js';
40
52
  export type { MailAllowlistApp } from './mail-identity-allowlist.js';
@@ -63,3 +75,4 @@ export { adaptZitadelListApps } from './executor.js';
63
75
  export { HttpJobsSchedulerClient } from './jobs-scheduler-http.js';
64
76
  export type { OrgAppVersionRef, OrgAppVersionStatus, WtfalchPackageDrift, PlatformVersionStatus, ReadPlatformVersionStatusOptions, StatusCoolifyPort, StatusGithubPort, GhExec as StatusGhExec, } from './version-status.js';
65
77
  export { readPlatformVersionStatus, wtfalchVersionsFromPnpmLockfile, providersStatusCoolifyPort, ghCliStatusGithubPort, } from './version-status.js';
78
+ export { COMPANY_DESTINATION_NAME_PREFIX, COMPANY_NETWORKS, DEFAULT_MAX_DESTINATIONS, DestinationCapacityError, PLATFORM_NETWORK, companyNetworkName, ensureDestination, findDestination, placed, placedOnPlatform, validateNetworkName, } from './destinations.js';
@@ -1,12 +1,17 @@
1
1
  export { readLiveResources, readLivePlatformResources } from './live.js';
2
2
  export { computePlanDetail, toPlan } from './diff.js';
3
3
  export { stepSystemOf } from './step-system.js';
4
- export { planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_SFU_APPLIED_SCOPE_KEY, } from './platform-diff.js';
5
- export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './platform-org.js';
4
+ export { planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_RELAY_KEY, PLATFORM_MAIL_RELAY_ROUTES_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_SFU_APPLIED_SCOPE_KEY, } from './platform-diff.js';
5
+ export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesForOwn, agoraHostValuesFromPlatform, heldOwnKeyRefs, heldPlatformKeyRefs, orgPlatformReadinessFor, orgPlatformReadinessFrom, } from './platform-org.js';
6
+ export { OWN_AUTH_APPLIED_GIT_REF_SCOPE_KEY, OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY, OWN_AUTH_BLOCKED_KEY, OWN_FILES_BLOCKED_KEY, OWN_FILES_R2_BLOCKED_KEY, OWN_JOBS_BLOCKED_KEY, OWN_MAIL_BLOCKED_KEY, planOwnAuth, planOwnFiles, planOwnFilesR2, planOwnJobs, planOwnMail, } from './own-diff.js';
7
+ export { OWN_SFU_BLOCKED_KEY, OWN_SFU_FIREWALL_KEY, planOwnSfu } from './own-diff.js';
8
+ export { provisionSfuApp, sfuConfig, sfuPortsMappings } from './own-sfu.js';
9
+ export { OWN_FILES_R2_SCOPE_KEY, OWN_FILES_R2_UNDELIVERED_SCOPE_KEY, OWN_SERVICES_APPLIED_SCOPE_KEY, OwnServicesBlockedError, orgNetwork, orgTarget, platformNetwork, platformTarget, scopeFromOrg, scopeFromPlatform, } from './own-services.js';
6
10
  export { PlatformOrgBlockedError, PlatformOrgPorts } from './platform-org-ports.js';
7
11
  export { AdoptRefusedError } from './adopt.js';
8
12
  export { FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, UpgradeConfirmationRequiredError, planNeedsConfirmation, } from './plan-apply.js';
9
- export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, filesPublicHostsValue, filesShareDomains, } from './platform-providers.js';
13
+ export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './backup-gate.js';
14
+ export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, filesPublicHostsValue, filesShareDomains, provisionFilesHost, provisionJobsHost, provisionMailServer, } from './platform-providers.js';
10
15
  // #171: how a host signs files management requests with the delivered key.
11
16
  export { FILES_INTROSPECTION_SECRET_REF, FILES_MANAGEMENT_ISSUER, FILES_MANAGEMENT_KEY_ID, FILES_MANAGEMENT_KEY_REF, PLATFORM_AUTH_INSTANCE_BEARER_REF, } from './platform-providers.js';
12
17
  export { AI_MANAGEMENT_ISSUER, AI_MANAGEMENT_KEY_ID, AI_SIGNING_KEY_REF, AiManagementKeyExistsError, createAiManagementKeyMinter, PLATFORM_AI_MANAGEMENT_KEY_REF, PLATFORM_AI_MANAGEMENT_PERMISSIONS, } from './platform-providers.js';
@@ -18,6 +23,7 @@ export { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, MissingMailCop
18
23
  export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeLoginHostResolver, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
19
24
  export { PlatformBrokerPort } from './broker-entries.js';
20
25
  export { loginHostnameFor } from './login-host.js';
26
+ export { NO_DOMAIN_ZONE, ownHostnames } from './own-hostnames.js';
21
27
  export { checkMailIdentityAudience } from './mail-identity.js';
22
28
  export { checkMailAppsAllowlist } from './mail-identity-allowlist.js';
23
29
  export { FoundryStepExecutor, NotBuiltError, healthUrls } from './executor.js';
@@ -32,3 +38,4 @@ export { createEdgeTunnels } from './edge-tunnels.js';
32
38
  export { adaptZitadelListApps } from './executor.js';
33
39
  export { HttpJobsSchedulerClient } from './jobs-scheduler-http.js';
34
40
  export { readPlatformVersionStatus, wtfalchVersionsFromPnpmLockfile, providersStatusCoolifyPort, ghCliStatusGithubPort, } from './version-status.js';
41
+ export { COMPANY_DESTINATION_NAME_PREFIX, COMPANY_NETWORKS, DEFAULT_MAX_DESTINATIONS, DestinationCapacityError, PLATFORM_NETWORK, companyNetworkName, ensureDestination, findDestination, placed, placedOnPlatform, validateNetworkName, } from './destinations.js';
@@ -1,4 +1,5 @@
1
- import type { OrgId } from '../org.js';
1
+ import { type DeclaredOrg, type OrgId } from '../org.js';
2
+ import { type OwnAuthInstance } from './own-services.js';
2
3
  /**
3
4
  * One resource as seen live in one of foundry's seven systems.
4
5
  *
@@ -87,6 +88,8 @@ export interface CmsReader {
87
88
  }
88
89
  export interface LiveReaders {
89
90
  zitadel: ZitadelReader;
91
+ /** The reader for one own org's ZITADEL instance. Without it an own org's ZITADEL is not read. */
92
+ zitadelFor?: (instance: OwnAuthInstance) => ZitadelReader;
90
93
  coolify: CoolifyReader;
91
94
  cloudflare: CloudflareReader;
92
95
  files: FilesReader;
@@ -94,7 +97,7 @@ export interface LiveReaders {
94
97
  cms: CmsReader;
95
98
  }
96
99
  /** Every live resource foundry can see for `orgId`, across all six systems. */
97
- export declare function readLiveResources(orgId: OrgId, readers: LiveReaders): Promise<LiveResource[]>;
100
+ export declare function readLiveResources(orgId: OrgId, readers: LiveReaders, org?: DeclaredOrg, orgRecordedIds?: Readonly<Record<string, string>>): Promise<LiveResource[]>;
98
101
  /**
99
102
  * #167: one narrow interface per system reading the shared platform's own
100
103
  * resources (`platform.ts`'s `DeclaredPlatform`) -- the platform-scoped
@@ -118,6 +121,12 @@ export interface PlatformCloudflareReader {
118
121
  /** #173: the platform's own Coolify-hosted jobs scheduler (`platform-diff.ts`'s `planJobs`). */
119
122
  export interface PlatformCoolifyReader {
120
123
  listResources(): Promise<LiveResource[]>;
124
+ /** Optional: with it the platform plan reads each recorded app's running commit and notes a build that differs (`build-record.ts`). */
125
+ listApplicationDeployments?(uuid: string, take?: number): Promise<Array<{
126
+ deployment_uuid: string;
127
+ commit?: string;
128
+ status: string;
129
+ }>>;
121
130
  }
122
131
  export interface PlatformLiveReaders {
123
132
  cloudflare: PlatformCloudflareReader;
package/dist/plan/live.js CHANGED
@@ -1,7 +1,12 @@
1
+ import { ownsServices } from '../org.js';
2
+ import { ownHostnames } from './own-hostnames.js';
3
+ import { OWN_SERVICES_APPLIED_SCOPE_KEY, orgTarget } from './own-services.js';
1
4
  /** Every live resource foundry can see for `orgId`, across all six systems. */
2
- export async function readLiveResources(orgId, readers) {
5
+ export async function readLiveResources(orgId, readers, org, orgRecordedIds) {
3
6
  const [zitadel, coolify, cloudflare, files, ai, cms] = await Promise.all([
4
- readers.zitadel.listResources(orgId),
7
+ org && ownsServices(org)
8
+ ? readOwnZitadel(orgId, readers, org, orgRecordedIds ?? {})
9
+ : readers.zitadel.listResources(orgId),
5
10
  readers.coolify.listResources(orgId),
6
11
  readers.cloudflare.listResources(orgId),
7
12
  readers.files.listResources(orgId),
@@ -10,6 +15,15 @@ export async function readLiveResources(orgId, readers) {
10
15
  ]);
11
16
  return [...zitadel, ...coolify, ...cloudflare, ...files, ...ai, ...cms];
12
17
  }
18
+ /** An own org's ZITADEL is its own instance: nothing is read until it is applied, and never from the shared one. */
19
+ async function readOwnZitadel(orgId, readers, org, recordedIds) {
20
+ if (!readers.zitadelFor || !recordedIds[OWN_SERVICES_APPLIED_SCOPE_KEY])
21
+ return [];
22
+ const refs = orgTarget(org).refs;
23
+ return readers
24
+ .zitadelFor({ issuerUrl: ownHostnames(org).issuerUrl, bearerRef: refs.instanceBearer })
25
+ .listResources(orgId);
26
+ }
13
27
  /** Every live resource foundry can see for the platform, across every wired reader (#167, #173). */
14
28
  export async function readLivePlatformResources(readers) {
15
29
  const [cloudflare, coolify] = await Promise.all([
@@ -1,8 +1,12 @@
1
- import type { DeclaredOrg } from '../org.js';
1
+ import { type DeclaredOrg } from '../org.js';
2
+ /** The labels an own org's services answer on, so no hosting entry may take them. */
3
+ export declare const OWN_SERVICE_LABELS: readonly ["auth", "mail", "files", "sfu"];
2
4
  /**
3
5
  * The names under the org's domain that foundry owns, so no hosting entry may
4
6
  * take one: Agora, Boule, the cms studio when the site is on, calls when it uses the
5
- * domain, the sign-in host when the issuer does, and every platform service
6
- * hostname that falls under the domain. Empty for an org with no domain.
7
+ * domain, the sign-in host when the issuer does, `auth.`, `mail.`, `files.` and
8
+ * `sfu.` for an org that runs its own services, and every platform service
9
+ * hostname that falls under the domain. An own org with no domain holds its
10
+ * oikos.host service names; any other org with no domain holds none.
7
11
  */
8
12
  export declare function lockedHostnames(org: DeclaredOrg, platformHostnames: readonly string[]): Set<string>;