@wtfalch/foundry 0.34.0 → 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 (88) 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 +20 -0
  6. package/dist/bootstrap/versions.js +46 -0
  7. package/dist/calls-sfu.js +3 -3
  8. package/dist/index.d.ts +9 -4
  9. package/dist/index.js +5 -2
  10. package/dist/migrations/0053_heartbeats_provider.sql +9 -0
  11. package/dist/migrations/0053_org_version_pin.sql +6 -0
  12. package/dist/org.d.ts +60 -14
  13. package/dist/org.js +118 -28
  14. package/dist/plan/backup-gate.d.ts +93 -0
  15. package/dist/plan/backup-gate.js +261 -0
  16. package/dist/plan/broker-entries.d.ts +9 -1
  17. package/dist/plan/broker-entries.js +20 -3
  18. package/dist/plan/build-record.d.ts +56 -0
  19. package/dist/plan/build-record.js +109 -0
  20. package/dist/plan/calls-matrix.js +3 -3
  21. package/dist/plan/cloudflared-connector.js +2 -2
  22. package/dist/plan/company-backups.d.ts +31 -0
  23. package/dist/plan/company-backups.js +101 -0
  24. package/dist/plan/deploy-health.d.ts +12 -2
  25. package/dist/plan/deploy-health.js +3 -1
  26. package/dist/plan/destinations.d.ts +73 -0
  27. package/dist/plan/destinations.js +217 -0
  28. package/dist/plan/diff.d.ts +17 -8
  29. package/dist/plan/diff.js +131 -131
  30. package/dist/plan/edge-access.d.ts +15 -0
  31. package/dist/plan/edge-access.js +21 -2
  32. package/dist/plan/executor.d.ts +166 -7
  33. package/dist/plan/executor.js +547 -106
  34. package/dist/plan/health-targets.d.ts +1 -3
  35. package/dist/plan/health-targets.js +36 -36
  36. package/dist/plan/held-key-refs.d.ts +5 -0
  37. package/dist/plan/held-key-refs.js +5 -0
  38. package/dist/plan/hostnames.d.ts +10 -0
  39. package/dist/plan/hostnames.js +14 -0
  40. package/dist/plan/index.d.ts +5 -0
  41. package/dist/plan/index.js +3 -0
  42. package/dist/plan/live.d.ts +6 -0
  43. package/dist/plan/live.js +3 -2
  44. package/dist/plan/locked-hostnames.d.ts +2 -1
  45. package/dist/plan/locked-hostnames.js +16 -3
  46. package/dist/plan/login-host.d.ts +9 -20
  47. package/dist/plan/login-host.js +10 -25
  48. package/dist/plan/mail-relay.d.ts +48 -29
  49. package/dist/plan/mail-relay.js +145 -68
  50. package/dist/plan/network-move.d.ts +20 -0
  51. package/dist/plan/network-move.js +41 -0
  52. package/dist/plan/oikos-ingress.d.ts +111 -0
  53. package/dist/plan/oikos-ingress.js +231 -0
  54. package/dist/plan/org-names.d.ts +8 -2
  55. package/dist/plan/org-names.js +57 -1
  56. package/dist/plan/own-diff.d.ts +9 -8
  57. package/dist/plan/own-diff.js +42 -34
  58. package/dist/plan/own-hostnames.d.ts +27 -0
  59. package/dist/plan/own-hostnames.js +24 -0
  60. package/dist/plan/own-limits.d.ts +198 -7
  61. package/dist/plan/own-limits.js +163 -32
  62. package/dist/plan/own-services.js +3 -1
  63. package/dist/plan/own-sfu.js +3 -2
  64. package/dist/plan/plan-apply.d.ts +47 -7
  65. package/dist/plan/plan-apply.js +184 -38
  66. package/dist/plan/platform-diff.d.ts +133 -17
  67. package/dist/plan/platform-diff.js +231 -68
  68. package/dist/plan/platform-org.d.ts +79 -10
  69. package/dist/plan/platform-org.js +93 -13
  70. package/dist/plan/platform-providers.d.ts +118 -73
  71. package/dist/plan/platform-providers.js +770 -231
  72. package/dist/plan/proxy-labels.d.ts +57 -11
  73. package/dist/plan/proxy-labels.js +94 -14
  74. package/dist/plan/run-options.d.ts +19 -0
  75. package/dist/plan/run-options.js +46 -0
  76. package/dist/plan/zitadel-login-policy.d.ts +1 -1
  77. package/dist/plan/zitadel-login-policy.js +76 -8
  78. package/dist/platform.d.ts +32 -0
  79. package/dist/platform.js +16 -0
  80. package/dist/provision.d.ts +2 -0
  81. package/dist/provision.js +3 -1
  82. package/dist/slug.d.ts +3 -1
  83. package/dist/slug.js +25 -3
  84. package/dist/store/postgres.js +43 -1
  85. package/dist/store/schema.d.ts +36 -0
  86. package/dist/store/schema.js +4 -0
  87. package/package.json +3 -3
  88. package/versions.digests.json +11 -0
@@ -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,
@@ -6,5 +6,10 @@
6
6
  export declare const JOBS_ADMIN_TOKEN_REF = "jobs:scheduler:adminToken";
7
7
  /** The ref the AI management key is delivered under; the host's `AiOrganisationPort` adapter reads it back. */
8
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";
9
14
  /** The ref the management private key (PKCS#8 PEM) is delivered under. */
10
15
  export declare const FILES_MANAGEMENT_KEY_REF = "files:platform:managementKey";
@@ -6,5 +6,10 @@
6
6
  export const JOBS_ADMIN_TOKEN_REF = 'jobs:scheduler:adminToken';
7
7
  /** The ref the AI management key is delivered under; the host's `AiOrganisationPort` adapter reads it back. */
8
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';
9
14
  /** The ref the management private key (PKCS#8 PEM) is delivered under. */
10
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
@@ -23,6 +23,8 @@ export { AdoptRefusedError } from './adopt.js';
23
23
  export type { AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, FoundryApplyOptions, } from './plan-apply.js';
24
24
  export { FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, UpgradeConfirmationRequiredError, planNeedsConfirmation, } from './plan-apply.js';
25
25
  export type { UnconfirmedUpgrade } from './confirm-upgrade.js';
26
+ export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './backup-gate.js';
27
+ export type { BackupGatePorts, MoveGatePorts } from './backup-gate.js';
26
28
  export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, filesPublicHostsValue, filesShareDomains, provisionFilesHost, provisionJobsHost, provisionMailServer, } from './platform-providers.js';
27
29
  export type { FilesHostInput, JobsHostDeps, JobsHostInput, MailServerInput, PlatformAuthDeps, PlatformAuthSmtp, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformDnsFactory, PlatformAccessFactory, PlatformAccessPort, PlatformTunnelsFactory, PlatformEdgeRuntime, } from './platform-providers.js';
28
30
  export type { PlatformMailDeps, PlatformMailAdminPort, PlatformMailRuntime, } from './platform-providers.js';
@@ -43,6 +45,8 @@ export type { BrokerPort } from './broker-entries.js';
43
45
  export { PlatformBrokerPort } from './broker-entries.js';
44
46
  export type { LoginHostBinding, LoginHostBindingsPort, LoginHostDiscoveryPort, } from './login-host.js';
45
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';
46
50
  export type { MailIdentityApp, MailIdentityAudienceCheck } from './mail-identity.js';
47
51
  export { checkMailIdentityAudience } from './mail-identity.js';
48
52
  export type { MailAllowlistApp } from './mail-identity-allowlist.js';
@@ -71,3 +75,4 @@ export { adaptZitadelListApps } from './executor.js';
71
75
  export { HttpJobsSchedulerClient } from './jobs-scheduler-http.js';
72
76
  export type { OrgAppVersionRef, OrgAppVersionStatus, WtfalchPackageDrift, PlatformVersionStatus, ReadPlatformVersionStatusOptions, StatusCoolifyPort, StatusGithubPort, GhExec as StatusGhExec, } from './version-status.js';
73
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';
@@ -10,6 +10,7 @@ export { OWN_FILES_R2_SCOPE_KEY, OWN_FILES_R2_UNDELIVERED_SCOPE_KEY, OWN_SERVICE
10
10
  export { PlatformOrgBlockedError, PlatformOrgPorts } from './platform-org-ports.js';
11
11
  export { AdoptRefusedError } from './adopt.js';
12
12
  export { FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, UpgradeConfirmationRequiredError, planNeedsConfirmation, } from './plan-apply.js';
13
+ export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './backup-gate.js';
13
14
  export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, filesPublicHostsValue, filesShareDomains, provisionFilesHost, provisionJobsHost, provisionMailServer, } from './platform-providers.js';
14
15
  // #171: how a host signs files management requests with the delivered key.
15
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';
@@ -22,6 +23,7 @@ export { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, MissingMailCop
22
23
  export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeLoginHostResolver, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
23
24
  export { PlatformBrokerPort } from './broker-entries.js';
24
25
  export { loginHostnameFor } from './login-host.js';
26
+ export { NO_DOMAIN_ZONE, ownHostnames } from './own-hostnames.js';
25
27
  export { checkMailIdentityAudience } from './mail-identity.js';
26
28
  export { checkMailAppsAllowlist } from './mail-identity-allowlist.js';
27
29
  export { FoundryStepExecutor, NotBuiltError, healthUrls } from './executor.js';
@@ -36,3 +38,4 @@ export { createEdgeTunnels } from './edge-tunnels.js';
36
38
  export { adaptZitadelListApps } from './executor.js';
37
39
  export { HttpJobsSchedulerClient } from './jobs-scheduler-http.js';
38
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';
@@ -121,6 +121,12 @@ export interface PlatformCloudflareReader {
121
121
  /** #173: the platform's own Coolify-hosted jobs scheduler (`platform-diff.ts`'s `planJobs`). */
122
122
  export interface PlatformCoolifyReader {
123
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
+ }>>;
124
130
  }
125
131
  export interface PlatformLiveReaders {
126
132
  cloudflare: PlatformCloudflareReader;
package/dist/plan/live.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ownsServices } from '../org.js';
2
+ import { ownHostnames } from './own-hostnames.js';
2
3
  import { OWN_SERVICES_APPLIED_SCOPE_KEY, orgTarget } from './own-services.js';
3
4
  /** Every live resource foundry can see for `orgId`, across all six systems. */
4
5
  export async function readLiveResources(orgId, readers, org, orgRecordedIds) {
@@ -16,11 +17,11 @@ export async function readLiveResources(orgId, readers, org, orgRecordedIds) {
16
17
  }
17
18
  /** An own org's ZITADEL is its own instance: nothing is read until it is applied, and never from the shared one. */
18
19
  async function readOwnZitadel(orgId, readers, org, recordedIds) {
19
- if (!readers.zitadelFor || !org.domain || !recordedIds[OWN_SERVICES_APPLIED_SCOPE_KEY])
20
+ if (!readers.zitadelFor || !recordedIds[OWN_SERVICES_APPLIED_SCOPE_KEY])
20
21
  return [];
21
22
  const refs = orgTarget(org).refs;
22
23
  return readers
23
- .zitadelFor({ issuerUrl: `https://auth.${org.domain}`, bearerRef: refs.instanceBearer })
24
+ .zitadelFor({ issuerUrl: ownHostnames(org).issuerUrl, bearerRef: refs.instanceBearer })
24
25
  .listResources(orgId);
25
26
  }
26
27
  /** Every live resource foundry can see for the platform, across every wired reader (#167, #173). */
@@ -6,6 +6,7 @@ export declare const OWN_SERVICE_LABELS: readonly ["auth", "mail", "files", "sfu
6
6
  * take one: Agora, Boule, the cms studio when the site is on, calls when it uses the
7
7
  * domain, the sign-in host when the issuer does, `auth.`, `mail.`, `files.` and
8
8
  * `sfu.` for an org that runs its own services, and every platform service
9
- * hostname that falls under the domain. Empty for an org with no domain.
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.
10
11
  */
11
12
  export declare function lockedHostnames(org: DeclaredOrg, platformHostnames: readonly string[]): Set<string>;
@@ -1,7 +1,9 @@
1
1
  import { ownsServices } from '../org.js';
2
2
  import { agoraHostnameFor } from './agora.js';
3
3
  import { bouleHostnameFor } from './boule.js';
4
+ import { cmsStudioHostnameFor, siteHostnameFor } from './hostnames.js';
4
5
  import { loginHostnameFor } from './login-host.js';
6
+ import { ownHostnames } from './own-hostnames.js';
5
7
  /** The labels an own org's services answer on, so no hosting entry may take them. */
6
8
  export const OWN_SERVICE_LABELS = ['auth', 'mail', 'files', 'sfu'];
7
9
  /**
@@ -9,17 +11,28 @@ export const OWN_SERVICE_LABELS = ['auth', 'mail', 'files', 'sfu'];
9
11
  * take one: Agora, Boule, the cms studio when the site is on, calls when it uses the
10
12
  * domain, the sign-in host when the issuer does, `auth.`, `mail.`, `files.` and
11
13
  * `sfu.` for an org that runs its own services, and every platform service
12
- * hostname that falls under the domain. Empty for an org with no domain.
14
+ * hostname that falls under the domain. An own org with no domain holds its
15
+ * oikos.host service names; any other org with no domain holds none.
13
16
  */
14
17
  export function lockedHostnames(org, platformHostnames) {
15
18
  const { domain } = org;
16
19
  const locked = new Set();
17
- if (!domain)
20
+ if (!domain) {
21
+ if (ownsServices(org)) {
22
+ const own = ownHostnames(org);
23
+ for (const hostname of [own.auth, own.mail, own.files, own.sfu])
24
+ locked.add(hostname);
25
+ }
26
+ if (org.site.enabled) {
27
+ locked.add(siteHostnameFor(org));
28
+ locked.add(cmsStudioHostnameFor(org));
29
+ }
18
30
  return locked;
31
+ }
19
32
  locked.add(agoraHostnameFor(org));
20
33
  locked.add(bouleHostnameFor(org));
21
34
  if (org.site.enabled)
22
- locked.add(`cms.${domain}`);
35
+ locked.add(cmsStudioHostnameFor(org));
23
36
  if (org.calls.enabled && org.calls.usesDomain)
24
37
  locked.add(`calls.${domain}`);
25
38
  if (org.issuer.usesDomain)
@@ -22,33 +22,20 @@ import { type DeclaredOrg } from '../org.js';
22
22
  * org's own `<slug>.oikos.host` can collide with this subdomain, the same
23
23
  * way `AGORA_SUFFIX` reserves `-agora`.
24
24
  *
25
- * #488: the platform's own org is not handled here: it signs in on the shared
26
- * ZITADEL instance's own host (`platformAuthHostname`), whatever
27
- * `issuer.usesDomain` says (William, 2026-10-06), and `planLoginHost` picks
28
- * that host itself. That host must never get a trusted
29
- * domain or a login binding of its own:
30
- * Archon's login-host write throws on a duplicate host, and `auth`'s own
31
- * `company-host.ts` denies every request through one, the shared one
32
- * included (`planLoginHost` and `zitadelLoginHost` skip both for it).
25
+ * Every company, the platform owner included, signs in on its own sign-in
26
+ * server at this host (`ownHostnames(org).auth` is this function): a domain is
27
+ * never required.
33
28
  */
34
- export declare function loginHostnameFor(org: DeclaredOrg): string;
35
- /**
36
- * The login host the login-host step plans for an org: the shared instance's
37
- * own host when `sharedHost` is given (the platform's own org), the company's
38
- * own host otherwise. `planLoginHost` and {@link agoraIssuerHostFor} both
39
- * read it here, so Agora's issuer and the login host cannot disagree.
40
- */
41
- export declare function orgLoginHost(org: DeclaredOrg, sharedHost: string | null): string;
29
+ export declare function loginHostnameFor(org: Pick<DeclaredOrg, 'slug' | 'domain' | 'issuer'>): string;
42
30
  /**
43
31
  * The login host Agora may take as its issuer, and whether the org's pair is
44
32
  * one Agora refuses. Agora accepts an issuer host on its own address's
45
33
  * registrable domain, or `<slug>-auth.oikos.host` for `<slug>-agora.oikos.host`.
46
- * `host` is null for the platform's own org (it keeps the platform issuer) and
47
- * for a domain with `issuer.usesDomain` off, where the login host
34
+ * `host` is null for a domain with `issuer.usesDomain` off, where the login host
48
35
  * `<slug>-auth.oikos.host` and the Agora address `agora.<domain>` are
49
36
  * refused together (`mismatch`).
50
37
  */
51
- export declare function agoraIssuerHostFor(org: DeclaredOrg, isPlatformOrg: boolean): {
38
+ export declare function agoraIssuerHostFor(org: DeclaredOrg): {
52
39
  host: string | null;
53
40
  mismatch: boolean;
54
41
  };
@@ -77,6 +64,8 @@ export declare function discoveryNamesHost(host: string, answer: {
77
64
  * leaves.
78
65
  */
79
66
  export declare function readLoginHostDiscovery(port: LoginHostDiscoveryPort | undefined, host: string, timeoutMs?: number): Promise<boolean>;
67
+ /** The hostname an own org's sign-in last applied at, so a changed domain plans the step again. */
68
+ export declare const OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY = "own_auth_applied_hostname";
80
69
  /**
81
70
  * True once the org's own sign-in step has applied (`coolify:own-auth:<org.id>`
82
71
  * in its recorded ids). An own org has no login-host step: its issuer host is
@@ -175,4 +164,4 @@ export declare function pendingRemovalState(hosts: readonly string[]): string;
175
164
  * the shared instance's and is routed by the platform auth tunnel. Otherwise
176
165
  * `null`, and always `null` for an own org. The tier 1 host rides the shared `*.oikos.host` tunnel.
177
166
  */
178
- export declare function companyLoginTunnelHost(org: DeclaredOrg, isPlatformOrg: boolean): string | null;
167
+ export declare function companyLoginTunnelHost(org: DeclaredOrg): string | null;
@@ -24,14 +24,9 @@ import { BOULE_CLIENT_ID_SCOPE_KEY } from './boule.js';
24
24
  * org's own `<slug>.oikos.host` can collide with this subdomain, the same
25
25
  * way `AGORA_SUFFIX` reserves `-agora`.
26
26
  *
27
- * #488: the platform's own org is not handled here: it signs in on the shared
28
- * ZITADEL instance's own host (`platformAuthHostname`), whatever
29
- * `issuer.usesDomain` says (William, 2026-10-06), and `planLoginHost` picks
30
- * that host itself. That host must never get a trusted
31
- * domain or a login binding of its own:
32
- * Archon's login-host write throws on a duplicate host, and `auth`'s own
33
- * `company-host.ts` denies every request through one, the shared one
34
- * included (`planLoginHost` and `zitadelLoginHost` skip both for it).
27
+ * Every company, the platform owner included, signs in on its own sign-in
28
+ * server at this host (`ownHostnames(org).auth` is this function): a domain is
29
+ * never required.
35
30
  */
36
31
  export function loginHostnameFor(org) {
37
32
  // `issuer.usesDomain` is the issuer's opt-in into the org's domain
@@ -42,27 +37,15 @@ export function loginHostnameFor(org) {
42
37
  }
43
38
  return `${org.slug}-auth.oikos.host`;
44
39
  }
45
- /**
46
- * The login host the login-host step plans for an org: the shared instance's
47
- * own host when `sharedHost` is given (the platform's own org), the company's
48
- * own host otherwise. `planLoginHost` and {@link agoraIssuerHostFor} both
49
- * read it here, so Agora's issuer and the login host cannot disagree.
50
- */
51
- export function orgLoginHost(org, sharedHost) {
52
- return sharedHost ?? loginHostnameFor(org);
53
- }
54
40
  /**
55
41
  * The login host Agora may take as its issuer, and whether the org's pair is
56
42
  * one Agora refuses. Agora accepts an issuer host on its own address's
57
43
  * registrable domain, or `<slug>-auth.oikos.host` for `<slug>-agora.oikos.host`.
58
- * `host` is null for the platform's own org (it keeps the platform issuer) and
59
- * for a domain with `issuer.usesDomain` off, where the login host
44
+ * `host` is null for a domain with `issuer.usesDomain` off, where the login host
60
45
  * `<slug>-auth.oikos.host` and the Agora address `agora.<domain>` are
61
46
  * refused together (`mismatch`).
62
47
  */
63
- export function agoraIssuerHostFor(org, isPlatformOrg) {
64
- if (isPlatformOrg)
65
- return { host: null, mismatch: false };
48
+ export function agoraIssuerHostFor(org) {
66
49
  if (org.domain && !org.issuer.usesDomain)
67
50
  return { host: null, mismatch: true };
68
51
  return { host: loginHostnameFor(org), mismatch: false };
@@ -110,6 +93,8 @@ export async function readLoginHostDiscovery(port, host, timeoutMs = LOGIN_HOST_
110
93
  clearTimeout(timer);
111
94
  }
112
95
  }
96
+ /** The hostname an own org's sign-in last applied at, so a changed domain plans the step again. */
97
+ export const OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY = 'own_auth_applied_hostname';
113
98
  /**
114
99
  * True once the org's own sign-in step has applied (`coolify:own-auth:<org.id>`
115
100
  * in its recorded ids). An own org has no login-host step: its issuer host is
@@ -211,9 +196,9 @@ export function pendingRemovalState(hosts) {
211
196
  * the shared instance's and is routed by the platform auth tunnel. Otherwise
212
197
  * `null`, and always `null` for an own org. The tier 1 host rides the shared `*.oikos.host` tunnel.
213
198
  */
214
- export function companyLoginTunnelHost(org, isPlatformOrg) {
215
- // An own org's sign-in stack publishes `auth.<domain>` through its own tunnel.
199
+ export function companyLoginTunnelHost(org) {
200
+ // An own org's sign-in stack publishes its host through its own tunnel.
216
201
  if (ownsServices(org))
217
202
  return null;
218
- return org.domain && org.issuer.usesDomain && !isPlatformOrg ? loginHostnameFor(org) : null;
203
+ return org.domain && org.issuer.usesDomain ? loginHostnameFor(org) : null;
219
204
  }