@wtfalch/foundry 0.17.1 → 0.19.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 (44) hide show
  1. package/dist/bootstrap/declare.d.ts +11 -2
  2. package/dist/bootstrap/declare.js +20 -11
  3. package/dist/index.d.ts +4 -2
  4. package/dist/index.js +2 -1
  5. package/dist/migrations/0045_names_to_edge.sql +107 -0
  6. package/dist/org.d.ts +17 -17
  7. package/dist/org.js +48 -58
  8. package/dist/plan/agora.d.ts +25 -0
  9. package/dist/plan/agora.js +25 -0
  10. package/dist/plan/boule.d.ts +8 -0
  11. package/dist/plan/boule.js +8 -0
  12. package/dist/plan/diff.d.ts +3 -1
  13. package/dist/plan/diff.js +115 -52
  14. package/dist/plan/entry-edge-key.d.ts +79 -0
  15. package/dist/plan/{entry-names-key.js → entry-edge-key.js} +90 -66
  16. package/dist/plan/executor.d.ts +87 -42
  17. package/dist/plan/executor.js +277 -120
  18. package/dist/plan/index.d.ts +2 -2
  19. package/dist/plan/index.js +1 -1
  20. package/dist/plan/live.d.ts +3 -3
  21. package/dist/plan/login-host.d.ts +15 -11
  22. package/dist/plan/login-host.js +16 -16
  23. package/dist/plan/plan-apply.d.ts +6 -5
  24. package/dist/plan/plan-apply.js +40 -38
  25. package/dist/plan/platform-diff.d.ts +22 -22
  26. package/dist/plan/platform-diff.js +45 -45
  27. package/dist/plan/platform-org-ports.d.ts +1 -0
  28. package/dist/plan/platform-org-ports.js +3 -0
  29. package/dist/plan/platform-org.d.ts +8 -8
  30. package/dist/plan/platform-org.js +7 -7
  31. package/dist/plan/platform-providers.d.ts +17 -16
  32. package/dist/plan/platform-providers.js +136 -118
  33. package/dist/plan/providers-readers.d.ts +34 -11
  34. package/dist/plan/providers-readers.js +72 -15
  35. package/dist/plan/step-system.js +1 -1
  36. package/dist/plan/{edge-worker.d.ts → worker.d.ts} +53 -33
  37. package/dist/plan/{edge-worker.js → worker.js} +92 -54
  38. package/dist/platform.d.ts +3 -3
  39. package/dist/platform.js +1 -1
  40. package/dist/store/postgres.js +7 -7
  41. package/dist/store/schema.d.ts +6 -6
  42. package/dist/store/schema.js +4 -4
  43. package/package.json +1 -1
  44. package/dist/plan/entry-names-key.d.ts +0 -73
@@ -5,7 +5,7 @@ export { computePlanDetail, toPlan } from './diff.js';
5
5
  export type { StepSystem } from './step-system.js';
6
6
  export { stepSystemOf } from './step-system.js';
7
7
  export type { PlatformPlanDetail } from './platform-diff.js';
8
- export { planPlatform, toPlatformPlan, 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_NAMES_KEY, PLATFORM_NAMES_KEY_REF, PLATFORM_NAMES_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
8
+ export { planPlatform, toPlatformPlan, 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_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
9
9
  export type { HeldKeyResolver, OrgPlatformReadiness } from './platform-org.js';
10
10
  export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './platform-org.js';
11
11
  export type { PlatformOrgClientFactories } from './platform-org-ports.js';
@@ -15,7 +15,7 @@ export { AdoptRefusedError } from './adopt.js';
15
15
  export type { AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, } from './plan-apply.js';
16
16
  export { FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, planNeedsConfirmation, } from './plan-apply.js';
17
17
  export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, filesPublicHostsValue, filesShareDomains, } from './platform-providers.js';
18
- export type { PlatformAuthDeps, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformNamesDeps, PlatformNamesRuntime, } from './platform-providers.js';
18
+ export type { PlatformAuthDeps, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformEdgeRuntime, } from './platform-providers.js';
19
19
  export type { PlatformMailDeps, PlatformMailAdminPort, PlatformMailRuntime, } from './platform-providers.js';
20
20
  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';
21
21
  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';
@@ -1,7 +1,7 @@
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_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_NAMES_KEY, PLATFORM_NAMES_KEY_REF, PLATFORM_NAMES_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
4
+ export { planPlatform, toPlatformPlan, 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_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, } from './platform-diff.js';
5
5
  export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './platform-org.js';
6
6
  export { PlatformOrgBlockedError, PlatformOrgPorts } from './platform-org-ports.js';
7
7
  export { AdoptRefusedError } from './adopt.js';
@@ -26,11 +26,11 @@ export interface LiveResource {
26
26
  * `platform-diff.ts`'s `planSfu` always plans its step when
27
27
  * `DeclaredPlatform.sfu.enabled` is true, rather than diffing against a
28
28
  * live resource the way every other system here does. It is still part of
29
- * this union so `StepAction.system` (`diff.ts`) can name it. `'names'`
30
- * (#317) is the same: a names key has no live lookup of its own, the same
29
+ * this union so `StepAction.system` (`diff.ts`) can name it. `'edge'`
30
+ * (#317) is the same: a edge key has no live lookup of its own, the same
31
31
  * shape `ai`/`files` entry keys already follow.
32
32
  */
33
- system: 'zitadel' | 'coolify' | 'cloudflare' | 'files' | 'ai' | 'cms' | 'jobs' | 'sfu' | 'names';
33
+ system: 'zitadel' | 'coolify' | 'cloudflare' | 'files' | 'ai' | 'cms' | 'jobs' | 'sfu' | 'edge';
34
34
  kind: string;
35
35
  key: string;
36
36
  config: Record<string, unknown>;
@@ -22,18 +22,22 @@ 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: `auth.<domain>` is refused when it would equal `platformAuthHostname`
26
- * -- the platform's own org, declared with-domain on the platform's own
27
- * domain, would otherwise compute the shared ZITADEL instance's own login
28
- * host (`platform.auth.hostname`, e.g. `auth.companyincriminated.com` ->
29
- * `auth.companyincluded.com`). Archon's login-host write throws on that
30
- * duplicate host (`archon`'s `validateLoginHostRegistry`); if it were ever
31
- * written instead, `auth`'s own `company-host.ts` denies every request
32
- * through a duplicate host, the shared one included. `platformAuthHostname`
33
- * is `null` when the caller has none to compare against (no platform
34
- * declared, or sign-in not ready yet), in which case this never refuses.
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).
35
33
  */
36
- export declare function loginHostnameFor(org: DeclaredOrg, platformAuthHostname?: string | null): string;
34
+ export declare function loginHostnameFor(org: DeclaredOrg): string;
35
+ /**
36
+ * Scope id set to `'1'` once an org's recorded login host is the platform's
37
+ * shared instance host: that host's trusted domain belongs to the shared
38
+ * instance, so `destroy` must not remove it.
39
+ */
40
+ export declare const LOGIN_HOST_SHARED_SCOPE_KEY = "zitadel_login_host_shared";
37
41
  /**
38
42
  * One entry in zitadel-login's own `LOGIN_HOST_BINDINGS`
39
43
  * (`auth/docs/company-login-hosts.md`) -- a single-line JSON document naming
@@ -23,30 +23,30 @@ import { BOULE_CLIENT_ID_SCOPE_KEY } from './boule.js';
23
23
  * org's own `<slug>.oikos.host` can collide with this subdomain, the same
24
24
  * way `AGORA_SUFFIX` reserves `-agora`.
25
25
  *
26
- * #488: `auth.<domain>` is refused when it would equal `platformAuthHostname`
27
- * -- the platform's own org, declared with-domain on the platform's own
28
- * domain, would otherwise compute the shared ZITADEL instance's own login
29
- * host (`platform.auth.hostname`, e.g. `auth.companyincriminated.com` ->
30
- * `auth.companyincluded.com`). Archon's login-host write throws on that
31
- * duplicate host (`archon`'s `validateLoginHostRegistry`); if it were ever
32
- * written instead, `auth`'s own `company-host.ts` denies every request
33
- * through a duplicate host, the shared one included. `platformAuthHostname`
34
- * is `null` when the caller has none to compare against (no platform
35
- * declared, or sign-in not ready yet), in which case this never refuses.
26
+ * #488: the platform's own org is not handled here: it signs in on the shared
27
+ * ZITADEL instance's own host (`platformAuthHostname`), whatever
28
+ * `issuer.usesDomain` says (William, 2026-10-06), and `planLoginHost` picks
29
+ * that host itself. That host must never get a trusted
30
+ * domain or a login binding of its own:
31
+ * Archon's login-host write throws on a duplicate host, and `auth`'s own
32
+ * `company-host.ts` denies every request through one, the shared one
33
+ * included (`planLoginHost` and `zitadelLoginHost` skip both for it).
36
34
  */
37
- export function loginHostnameFor(org, platformAuthHostname = null) {
35
+ export function loginHostnameFor(org) {
38
36
  // `issuer.usesDomain` is the issuer's opt-in into the org's domain
39
37
  // (`org.ts`'s `IssuerConfig`): a domain alone never moves it, so a domain
40
38
  // with `usesDomain` false stays on the tier 1 host.
41
39
  if (org.domain && org.issuer.usesDomain) {
42
- const host = `auth.${org.domain}`;
43
- if (platformAuthHostname && host === platformAuthHostname) {
44
- return `${org.slug}-auth.oikos.host`;
45
- }
46
- return host;
40
+ return `auth.${org.domain}`;
47
41
  }
48
42
  return `${org.slug}-auth.oikos.host`;
49
43
  }
44
+ /**
45
+ * Scope id set to `'1'` once an org's recorded login host is the platform's
46
+ * shared instance host: that host's trusted domain belongs to the shared
47
+ * instance, so `destroy` must not remove it.
48
+ */
49
+ export const LOGIN_HOST_SHARED_SCOPE_KEY = 'zitadel_login_host_shared';
50
50
  /**
51
51
  * Scope id holding what the binding was last written with
52
52
  * ({@link loginBindingState}). Client ids are not secrets. The planner
@@ -335,7 +335,7 @@ export interface FoundryPlanApplyOptions {
335
335
  broker?: boolean;
336
336
  /**
337
337
  * #318: true when the host's executor was built with a `WorkersPort`.
338
- * Optional, and false without it: each entry's `edge` Worker is then
338
+ * Optional, and false without it: each entry's `worker` Worker is then
339
339
  * reported blocked instead of planned.
340
340
  */
341
341
  edgeWorkers?: boolean;
@@ -347,11 +347,11 @@ export interface FoundryPlanApplyOptions {
347
347
  filesOrganisation?: boolean;
348
348
  aiOrganisation?: boolean;
349
349
  /**
350
- * #317: true when the host's executor was built with a `NamesPort`.
351
- * Optional, and false without it: each entry's `uses.names` step is then
350
+ * #317: true when the host's executor was built with a `EdgePort`.
351
+ * Optional, and false without it: each entry's `uses.edge` step is then
352
352
  * reported blocked instead of planned to fail.
353
353
  */
354
- names?: boolean;
354
+ edge?: boolean;
355
355
  /** Overridable for tests; defaults to `() => new Date()`. */
356
356
  now?: () => Date;
357
357
  /** #441: how long a mutation lease lasts before another caller may take it over. Defaults to 30 minutes. */
@@ -431,7 +431,7 @@ export declare class FoundryPlanApply implements PlanApply {
431
431
  private readonly edgeWorkers;
432
432
  private readonly hostFiles;
433
433
  private readonly hostAi;
434
- private readonly hostNames;
434
+ private readonly hostEdge;
435
435
  private readonly now;
436
436
  private readonly leaseTtlMs;
437
437
  private readonly appliedVersions;
@@ -580,6 +580,7 @@ export declare class FoundryPlanApply implements PlanApply {
580
580
  * which only the plan can read.
581
581
  */
582
582
  private invalidHosting;
583
+ private platformServiceHostnames;
583
584
  /**
584
585
  * #177: an org's plan reads the shared platform's own addresses and
585
586
  * readiness through the very `platform` deps `planPlatform`/`applyPlatform`
@@ -7,15 +7,15 @@ import { StaleLeaseError, } from '../store.js';
7
7
  import { AdoptRefusedError, adoptCandidates, adoptedKeysUnreadable, adoptedScopeKey, parseAdoptSnapshot, planAdoptFrom, } from './adopt.js';
8
8
  import { NO_AGORA_HOST_VALUES, readAgoraHost, } from './agora.js';
9
9
  import { AI_KEY_UNDELIVERED_SCOPE_KEY, JOBS_REGISTRATION_UNDELIVERED_SCOPE_KEY, computePlanDetail, toPlan, } from './diff.js';
10
- import { edgeWorkerTraceScopeKeys, liveEdgeWorkers } from './edge-worker.js';
11
10
  import { entryAiUndeliveredScopeKey } from './entry-ai-key.js';
11
+ import { entryEdgeRetiredScopeKey, entryEdgeUndeliveredScopeKey } from './entry-edge-key.js';
12
12
  import { FILES_ROOT_UNDELIVERED_SCOPE_KEY, entryFilesUndeliveredScopeKey, } from './entry-files-key.js';
13
- import { entryNamesRetiredScopeKey, entryNamesUndeliveredScopeKey } from './entry-names-key.js';
14
13
  import { readLivePlatformResources, readLiveResources, } from './live.js';
15
14
  import { lockedHostnames } from './locked-hostnames.js';
16
15
  import { PLATFORM_ARCHON_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_KEY, appStepKey, planPlatform as computePlatformPlanDetail, toPlatformPlan, } from './platform-diff.js';
17
16
  import { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './platform-org.js';
18
17
  import { PLATFORM_AUTH_BROKER_MAIL_KEY_REF } from './platform-providers.js';
18
+ import { liveWorkers, workerTraceScopeKeys } from './worker.js';
19
19
  /** #441: the lease scope that serializes every mutation of one company, and the one for the shared platform. */
20
20
  const companyScope = (orgId) => `company:${orgId}`;
21
21
  const PLATFORM_SCOPE = 'platform';
@@ -100,7 +100,7 @@ const HOSTNAME_STEPS = new Set([
100
100
  'cloudflare:access',
101
101
  'jobs:registration',
102
102
  // #318 (F8): the route is itself hostname-shaped, validated against the
103
- // org's own domain by `validateEntryEdge`.
103
+ // org's own domain by `validateEntryWorker`.
104
104
  'cloudflare:worker',
105
105
  ]);
106
106
  /**
@@ -124,7 +124,7 @@ export class FoundryPlanApply {
124
124
  edgeWorkers;
125
125
  hostFiles;
126
126
  hostAi;
127
- hostNames;
127
+ hostEdge;
128
128
  now;
129
129
  leaseTtlMs;
130
130
  appliedVersions;
@@ -145,7 +145,7 @@ export class FoundryPlanApply {
145
145
  this.edgeWorkers = options.edgeWorkers ?? false;
146
146
  this.hostFiles = options.filesOrganisation ?? false;
147
147
  this.hostAi = options.aiOrganisation ?? false;
148
- this.hostNames = options.names ?? false;
148
+ this.hostEdge = options.edge ?? false;
149
149
  this.now = options.now ?? (() => new Date());
150
150
  this.leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;
151
151
  this.appliedVersions = options.appliedVersions;
@@ -543,7 +543,7 @@ export class FoundryPlanApply {
543
543
  // #318: its partial-deploy traces too, or a retry after a later
544
544
  // step fails would read this Worker as live again.
545
545
  const entryKey = target.key.slice(id.length + 1);
546
- for (const scopeKey of edgeWorkerTraceScopeKeys(entryKey)) {
546
+ for (const scopeKey of workerTraceScopeKeys(entryKey)) {
547
547
  delete serviceIds[scopeKey];
548
548
  }
549
549
  }
@@ -869,6 +869,7 @@ export class FoundryPlanApply {
869
869
  this.orgArchonUrl(),
870
870
  ]);
871
871
  const otherSlugs = (await this.store.list()).filter((o) => o.id !== id).map((o) => o.slug);
872
+ const platformHostnames = await this.platformServiceHostnames();
872
873
  const detail = computePlanDetail(org, live, applied?.serviceIds ?? {}, agoraHost, this.jobsScheduler, platformReadiness, {
873
874
  loginHostBindings: this.loginHostBindings,
874
875
  broker: this.broker,
@@ -876,6 +877,7 @@ export class FoundryPlanApply {
876
877
  otherSlugs,
877
878
  now: () => this.now().getTime(),
878
879
  archonUrl,
880
+ platformHostnames,
879
881
  });
880
882
  const invalid = await this.invalidHosting(org);
881
883
  if (invalid) {
@@ -895,15 +897,15 @@ export class FoundryPlanApply {
895
897
  * which only the plan can read.
896
898
  */
897
899
  async invalidHosting(org) {
898
- const platform = await this.platformDeps?.store.get();
899
- const platformHostnames = platform
900
- ? PLATFORM_SERVICE_KEYS.map((k) => platform[k].hostname)
901
- : [];
902
900
  return validateHosting(org.hosting, {
903
901
  domain: org.domain,
904
- locked: lockedHostnames(org, platformHostnames),
902
+ locked: lockedHostnames(org, await this.platformServiceHostnames()),
905
903
  });
906
904
  }
905
+ async platformServiceHostnames() {
906
+ const platform = await this.platformDeps?.store.get();
907
+ return platform ? PLATFORM_SERVICE_KEYS.map((k) => platform[k].hostname) : [];
908
+ }
907
909
  /**
908
910
  * #177: an org's plan reads the shared platform's own addresses and
909
911
  * readiness through the very `platform` deps `planPlatform`/`applyPlatform`
@@ -927,7 +929,7 @@ export class FoundryPlanApply {
927
929
  refs.add(ORG_PLATFORM_KEY_REFS.ai);
928
930
  if (this.jobsScheduler)
929
931
  refs.add(ORG_PLATFORM_KEY_REFS.jobs);
930
- const readiness = orgPlatformReadinessFrom(platform, recorded, refs, !heldKeys, this.hostNames);
932
+ const readiness = orgPlatformReadinessFrom(platform, recorded, refs, !heldKeys, this.hostEdge);
931
933
  // Entry env from the platform's mail key or a shared secret (#T8).
932
934
  let mailKeyHeld = false;
933
935
  try {
@@ -1138,7 +1140,7 @@ function destroyTargets(serviceIds, orgId) {
1138
1140
  'zitadel:login-host:',
1139
1141
  'cloudflare:tunnel:',
1140
1142
  'cloudflare:access:',
1141
- // #318: an entry's edge Worker and its route.
1143
+ // #318: an entry's Worker and its route.
1142
1144
  'cloudflare:worker:',
1143
1145
  'coolify:database:',
1144
1146
  'coolify:app:',
@@ -1147,18 +1149,18 @@ function destroyTargets(serviceIds, orgId) {
1147
1149
  'ai:key:',
1148
1150
  // F6b: an entry's files key, `<orgId>:<entry>`; the org's own key has no `:` and is left alone.
1149
1151
  'files:key:',
1150
- // F7 (#317): an entry's names key, `<orgId>:<entry>` -- names has no org-root key.
1151
- 'names:key:',
1152
+ // F7 (#317): an entry's edge key, `<orgId>:<entry>` -- edge has no org-root key.
1153
+ 'edge:key:',
1152
1154
  ];
1153
1155
  const targets = [];
1154
1156
  const adopted = adoptedResourceIds(serviceIds);
1155
1157
  for (const prefix of ORDERED_PREFIXES) {
1156
1158
  if (prefix === 'cloudflare:worker:') {
1157
- // #318 re-review: not the main ids alone -- `liveEdgeWorkers` also
1159
+ // #318 re-review: not the main ids alone -- `liveWorkers` also
1158
1160
  // finds an upload-only or route-only partial deploy, and leaves out an
1159
1161
  // entry whose Worker is already retired (nothing live, so a host with
1160
1162
  // no WorkersPort is not blocked from destroying the org by it).
1161
- for (const [entryKey, scriptName] of liveEdgeWorkers(orgId, serviceIds)) {
1163
+ for (const [entryKey, scriptName] of liveWorkers(orgId, serviceIds)) {
1162
1164
  targets.push({
1163
1165
  system: 'cloudflare',
1164
1166
  kind: 'worker',
@@ -1181,16 +1183,16 @@ function destroyTargets(serviceIds, orgId) {
1181
1183
  // A key a retire step already revoked before org destroy ever ran
1182
1184
  // (#317 review): `runSteps` never clears this main id on a step that
1183
1185
  // returns no resourceId, so without this check destroy would target
1184
- // an already-revoked key and get stuck on names' 404 forever.
1185
- if (prefix === 'names:key:') {
1186
+ // an already-revoked key and get stuck on edge' 404 forever.
1187
+ if (prefix === 'edge:key:') {
1186
1188
  // `resourceKey.slice(prefix.length)` is `<orgId>:<entryKey>`, but
1187
- // `entryNamesRetiredScopeKey` takes the bare entry key alone (as
1188
- // every caller in entry-names-key.ts/executor.ts gives it) --
1189
+ // `entryEdgeRetiredScopeKey` takes the bare entry key alone (as
1190
+ // every caller in entry-edge-key.ts/executor.ts gives it) --
1189
1191
  // entry keys hold no `:` of their own, so the first one found is
1190
1192
  // exactly the org id/entry key boundary.
1191
1193
  const fullKey = resourceKey.slice(prefix.length);
1192
1194
  const entryKey = fullKey.slice(fullKey.indexOf(':') + 1);
1193
- if (serviceIds[entryNamesRetiredScopeKey(entryKey)] === id)
1195
+ if (serviceIds[entryEdgeRetiredScopeKey(entryKey)] === id)
1194
1196
  continue;
1195
1197
  }
1196
1198
  const [system, kind] = prefix.split(':');
@@ -1198,7 +1200,7 @@ function destroyTargets(serviceIds, orgId) {
1198
1200
  }
1199
1201
  }
1200
1202
  // F7 (#317): a key minted but never delivered never gets the main
1201
- // `names:key:<org>:<entry>` id recorded (the mint step throws before
1203
+ // `edge:key:<org>:<entry>` id recorded (the mint step throws before
1202
1204
  // `runSteps` sees a `resourceId`), so the loop above never finds it to
1203
1205
  // revoke on destroy. Skipped if a later mint already succeeded and holds
1204
1206
  // its own, still-live main id -- entry keys hold no `:`, so matching the
@@ -1207,19 +1209,19 @@ function destroyTargets(serviceIds, orgId) {
1207
1209
  // marker A); re-declare, mint B, fail delivery three times (undelivered
1208
1210
  // B, main id stays A). Without this check, A's stale-but-truthy main id
1209
1211
  // hid B from destroy, leaking it the same way the gone-scan in
1210
- // `entry-names-key.ts` once did (#317 re-review).
1211
- const undeliveredNamesPrefix = 'names_key_undelivered_';
1212
+ // `entry-edge-key.ts` once did (#317 re-review).
1213
+ const undeliveredEdgePrefix = 'edge_key_undelivered_';
1212
1214
  for (const [resourceKey, id] of Object.entries(serviceIds)) {
1213
- if (!id || !resourceKey.startsWith(undeliveredNamesPrefix))
1215
+ if (!id || !resourceKey.startsWith(undeliveredEdgePrefix))
1214
1216
  continue;
1215
- const entryKey = resourceKey.slice(undeliveredNamesPrefix.length);
1217
+ const entryKey = resourceKey.slice(undeliveredEdgePrefix.length);
1216
1218
  const hasLaterMainId = Object.entries(serviceIds).some(([k, v]) => v &&
1217
- k.startsWith('names:key:') &&
1219
+ k.startsWith('edge:key:') &&
1218
1220
  k.endsWith(`:${entryKey}`) &&
1219
- serviceIds[entryNamesRetiredScopeKey(entryKey)] !== v);
1221
+ serviceIds[entryEdgeRetiredScopeKey(entryKey)] !== v);
1220
1222
  if (hasLaterMainId)
1221
1223
  continue;
1222
- targets.push({ system: 'names', kind: 'key', key: entryKey, id });
1224
+ targets.push({ system: 'edge', kind: 'key', key: entryKey, id });
1223
1225
  }
1224
1226
  for (const [resourceKey, id] of Object.entries(serviceIds)) {
1225
1227
  if (!resourceKey.startsWith('zitadel:app:'))
@@ -1302,7 +1304,7 @@ const STEP_FIELDS = {
1302
1304
  'cloudflare:zone': 'hosting.usesDomain',
1303
1305
  'cloudflare:access': 'hosting.accessEmails',
1304
1306
  'cloudflare:ratelimit': 'hosting.usesDomain',
1305
- 'cloudflare:worker': 'hosting.entries.edge',
1307
+ 'cloudflare:worker': 'hosting.entries.worker',
1306
1308
  };
1307
1309
  function stepField(action) {
1308
1310
  // An entry's ai key (F6a) is keyed `<orgId>:<entry>`; the org's own is `<orgId>`.
@@ -1312,9 +1314,9 @@ function stepField(action) {
1312
1314
  // The org's own files key (F6b) is keyed `<orgId>`, an entry's `<orgId>:<entry>`.
1313
1315
  if (action.system === 'files' && action.kind === 'key')
1314
1316
  return 'hosting.entries.uses.files';
1315
- // An entry's names key (F7): always `<orgId>:<entry>`, names has no org-root key.
1316
- if (action.system === 'names' && action.kind === 'key')
1317
- return 'hosting.entries.uses.names';
1317
+ // An entry's edge key (F7): always `<orgId>:<entry>`, edge has no org-root key.
1318
+ if (action.system === 'edge' && action.kind === 'key')
1319
+ return 'hosting.entries.uses.edge';
1318
1320
  // An org declares several issuer apps, each keyed by its name (#71).
1319
1321
  if (action.system === 'zitadel' && action.kind === 'app') {
1320
1322
  return `hosting.issuerApps.${action.key}`;
@@ -1363,15 +1365,15 @@ function failedFields(org, detail, completed, serviceIds) {
1363
1365
  }
1364
1366
  }
1365
1367
  }
1366
- // The same for an entry's names key (F7): minted, never delivered is no
1368
+ // The same for an entry's edge key (F7): minted, never delivered is no
1367
1369
  // longer blocked forever on its own (the next apply revokes it and
1368
1370
  // mints again, #317 re-review) -- but while that retry step is itself
1369
- // not planned this run (no names service wired, say), the leftover
1371
+ // not planned this run (no edge service wired, say), the leftover
1370
1372
  // undelivered id must still read as unresolved rather than the org
1371
1373
  // converging silently around it.
1372
1374
  for (const entry of org.hosting.entries) {
1373
- if (entry.uses?.names && serviceIds[entryNamesUndeliveredScopeKey(entry.key)]) {
1374
- failed.add('hosting.entries.uses.names');
1375
+ if (entry.uses?.edge && serviceIds[entryEdgeUndeliveredScopeKey(entry.key)]) {
1376
+ failed.add('hosting.entries.uses.edge');
1375
1377
  }
1376
1378
  }
1377
1379
  // The same for a jobs registration whose credentials never reached the host.
@@ -622,32 +622,32 @@ export declare const PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY = "files_ma
622
622
  export declare const PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY = "files_applied_hostname";
623
623
  export declare const PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY = "files_applied_git_ref";
624
624
  /**
625
- * #478: the resource key `planNames` plans under and
625
+ * #478: the resource key `planEdge` plans under and
626
626
  * `FoundryPlatformStepExecutor` dispatches on -- the {@link PLATFORM_FILES_KEY}
627
- * shape for the names host (https://github.com/wtfalch/names). `runPlatformSteps`
628
- * records the app's uuid under `coolify:app:platform:names` once the step
629
- * succeeds, which is what stops `planNames` planning it again.
627
+ * shape for the edge host (https://github.com/wtfalch/edge). `runPlatformSteps`
628
+ * records the app's uuid under `coolify:app:platform:edge` once the step
629
+ * succeeds, which is what stops `planEdge` planning it again.
630
630
  */
631
- export declare const PLATFORM_NAMES_KEY = "platform:names";
631
+ export declare const PLATFORM_EDGE_KEY = "platform:edge";
632
632
  /**
633
- * #478: the held-key ref the names platform secret's full value is
633
+ * #478: the held-key ref the edge platform secret's full value is
634
634
  * delivered under, once, by {@link FoundryStepExecutorPorts.deliverSecret} --
635
635
  * never the host's app env, which gets only its SHA-256 hash
636
- * (`NAMES_PLATFORM_KEY_HASH`). Archon's own `NamesPort` reads the full
637
- * secret back from this ref to call names' `mintKey`/`revokeKey`/
636
+ * (`EDGE_PLATFORM_KEY_HASH`). Archon's own `EdgePort` reads the full
637
+ * secret back from this ref to call edge' `mintKey`/`revokeKey`/
638
638
  * `setReservedLabels`.
639
639
  */
640
- export declare const PLATFORM_NAMES_KEY_REF = "names:platform:key";
640
+ export declare const PLATFORM_EDGE_KEY_REF = "edge:platform:key";
641
641
  /**
642
- * #478: the held-key ref for names' own Cloudflare API token (tunnel edit +
642
+ * #478: the held-key ref for edge' own Cloudflare API token (tunnel edit +
643
643
  * DNS edit on one account) -- the issue's own "never Archon's token": this
644
- * is a narrower, names-only credential a human provisions and holds
645
- * separately from whatever broader token `PlatformNamesDeps.cloudflare`
644
+ * is a narrower, edge-only credential a human provisions and holds
645
+ * separately from whatever broader token `PlatformEdgeDeps.cloudflare`
646
646
  * uses to stand the host itself up on Coolify.
647
647
  */
648
- export declare const PLATFORM_NAMES_CLOUDFLARE_TOKEN_REF = "cloudflare:names:apiToken";
648
+ export declare const PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF = "cloudflare:edge:apiToken";
649
649
  /**
650
- * #478: scope keys (colon-free) for the names platform key, the
650
+ * #478: scope keys (colon-free) for the edge platform key, the
651
651
  * {@link PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY} pattern once:
652
652
  * the undelivered id is recorded and persisted the moment the secret is
653
653
  * minted, before delivery; the guard is recorded only once it was delivered
@@ -655,16 +655,16 @@ export declare const PLATFORM_NAMES_CLOUDFLARE_TOKEN_REF = "cloudflare:names:api
655
655
  * means the secret is lost (never kept past that point): the planner
656
656
  * reports it and never mints again. Neither key holds the secret itself.
657
657
  */
658
- export declare const PLATFORM_NAMES_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY = "names_platform_key_undelivered";
659
- export declare const PLATFORM_NAMES_PLATFORM_KEY_SET_SCOPE_KEY = "names_platform_key_set";
658
+ export declare const PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY = "edge_platform_key_undelivered";
659
+ export declare const PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY = "edge_platform_key_set";
660
660
  /**
661
661
  * #478: the hostname and `gitRef` the last successful names run applied.
662
- * `planNames` plans the step again when either differs from the declared
662
+ * `planEdge` plans the step again when either differs from the declared
663
663
  * value; a re-run updates `git_branch` and the runner env and redeploys,
664
664
  * and mints no new platform key (the guards above hold).
665
665
  */
666
- export declare const PLATFORM_NAMES_APPLIED_HOSTNAME_SCOPE_KEY = "names_applied_hostname";
667
- export declare const PLATFORM_NAMES_APPLIED_GIT_REF_SCOPE_KEY = "names_applied_git_ref";
666
+ export declare const PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY = "edge_applied_hostname";
667
+ export declare const PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY = "edge_applied_git_ref";
668
668
  /**
669
669
  * Options for {@link planPlatform}, replacing four positional arguments that
670
670
  * shifted on every merge that added a platform service (#186, #187, #188) --
@@ -703,8 +703,8 @@ export interface PlanPlatformOptions {
703
703
  ai?: string;
704
704
  archon?: string;
705
705
  mail?: string;
706
- /** #478: why the names host is blocked, or absent when it is not. */
707
- names?: string;
706
+ /** #478: why the edge host is blocked, or absent when it is not. */
707
+ edge?: string;
708
708
  /**
709
709
  * #257: true once a service's database exists but the host built the
710
710
  * platform executor with no backup dependencies -- every such service's
@@ -756,7 +756,7 @@ export interface PlanPlatformOptions {
756
756
  * Scope for #167: the frame plus one step, {@link planApexZone}. #173 adds
757
757
  * {@link planJobs}, #170 adds {@link planSfu}, #168 adds {@link planAuth},
758
758
  * #169 adds {@link planMail}, #174 adds {@link planIntegrations}, #171 adds
759
- * {@link planFiles}, #172 adds {@link planAi}, #478 adds {@link planNames}
759
+ * {@link planFiles}, #172 adds {@link planAi}, #478 adds {@link planEdge}
760
760
  * and #175 adds
761
761
  * {@link planArchon}, the same way #19 shipped org plan/apply against
762
762
  * three singletons before #27, #71, #80 and the rest added the remaining