@wtfalch/foundry 0.17.1 → 0.18.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.
@@ -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
@@ -698,32 +698,32 @@ export const PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY = 'files_management
698
698
  export const PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY = 'files_applied_hostname';
699
699
  export const PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY = 'files_applied_git_ref';
700
700
  /**
701
- * #478: the resource key `planNames` plans under and
701
+ * #478: the resource key `planEdge` plans under and
702
702
  * `FoundryPlatformStepExecutor` dispatches on -- the {@link PLATFORM_FILES_KEY}
703
- * shape for the names host (https://github.com/wtfalch/names). `runPlatformSteps`
704
- * records the app's uuid under `coolify:app:platform:names` once the step
705
- * succeeds, which is what stops `planNames` planning it again.
703
+ * shape for the edge host (https://github.com/wtfalch/edge). `runPlatformSteps`
704
+ * records the app's uuid under `coolify:app:platform:edge` once the step
705
+ * succeeds, which is what stops `planEdge` planning it again.
706
706
  */
707
- export const PLATFORM_NAMES_KEY = 'platform:names';
707
+ export const PLATFORM_EDGE_KEY = 'platform:edge';
708
708
  /**
709
- * #478: the held-key ref the names platform secret's full value is
709
+ * #478: the held-key ref the edge platform secret's full value is
710
710
  * delivered under, once, by {@link FoundryStepExecutorPorts.deliverSecret} --
711
711
  * never the host's app env, which gets only its SHA-256 hash
712
- * (`NAMES_PLATFORM_KEY_HASH`). Archon's own `NamesPort` reads the full
713
- * secret back from this ref to call names' `mintKey`/`revokeKey`/
712
+ * (`EDGE_PLATFORM_KEY_HASH`). Archon's own `EdgePort` reads the full
713
+ * secret back from this ref to call edge' `mintKey`/`revokeKey`/
714
714
  * `setReservedLabels`.
715
715
  */
716
- export const PLATFORM_NAMES_KEY_REF = 'names:platform:key';
716
+ export const PLATFORM_EDGE_KEY_REF = 'edge:platform:key';
717
717
  /**
718
- * #478: the held-key ref for names' own Cloudflare API token (tunnel edit +
718
+ * #478: the held-key ref for edge' own Cloudflare API token (tunnel edit +
719
719
  * DNS edit on one account) -- the issue's own "never Archon's token": this
720
- * is a narrower, names-only credential a human provisions and holds
721
- * separately from whatever broader token `PlatformNamesDeps.cloudflare`
720
+ * is a narrower, edge-only credential a human provisions and holds
721
+ * separately from whatever broader token `PlatformEdgeDeps.cloudflare`
722
722
  * uses to stand the host itself up on Coolify.
723
723
  */
724
- export const PLATFORM_NAMES_CLOUDFLARE_TOKEN_REF = 'cloudflare:names:apiToken';
724
+ export const PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF = 'cloudflare:edge:apiToken';
725
725
  /**
726
- * #478: scope keys (colon-free) for the names platform key, the
726
+ * #478: scope keys (colon-free) for the edge platform key, the
727
727
  * {@link PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY} pattern once:
728
728
  * the undelivered id is recorded and persisted the moment the secret is
729
729
  * minted, before delivery; the guard is recorded only once it was delivered
@@ -731,16 +731,16 @@ export const PLATFORM_NAMES_CLOUDFLARE_TOKEN_REF = 'cloudflare:names:apiToken';
731
731
  * means the secret is lost (never kept past that point): the planner
732
732
  * reports it and never mints again. Neither key holds the secret itself.
733
733
  */
734
- export const PLATFORM_NAMES_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY = 'names_platform_key_undelivered';
735
- export const PLATFORM_NAMES_PLATFORM_KEY_SET_SCOPE_KEY = 'names_platform_key_set';
734
+ export const PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY = 'edge_platform_key_undelivered';
735
+ export const PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY = 'edge_platform_key_set';
736
736
  /**
737
737
  * #478: the hostname and `gitRef` the last successful names run applied.
738
- * `planNames` plans the step again when either differs from the declared
738
+ * `planEdge` plans the step again when either differs from the declared
739
739
  * value; a re-run updates `git_branch` and the runner env and redeploys,
740
740
  * and mints no new platform key (the guards above hold).
741
741
  */
742
- export const PLATFORM_NAMES_APPLIED_HOSTNAME_SCOPE_KEY = 'names_applied_hostname';
743
- export const PLATFORM_NAMES_APPLIED_GIT_REF_SCOPE_KEY = 'names_applied_git_ref';
742
+ export const PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY = 'edge_applied_hostname';
743
+ export const PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY = 'edge_applied_git_ref';
744
744
  /**
745
745
  * #167: the platform's own diff engine, mirroring `diff.ts`'s
746
746
  * `computePlanDetail` for the one shared {@link DeclaredPlatform} record
@@ -755,7 +755,7 @@ export const PLATFORM_NAMES_APPLIED_GIT_REF_SCOPE_KEY = 'names_applied_git_ref';
755
755
  * Scope for #167: the frame plus one step, {@link planApexZone}. #173 adds
756
756
  * {@link planJobs}, #170 adds {@link planSfu}, #168 adds {@link planAuth},
757
757
  * #169 adds {@link planMail}, #174 adds {@link planIntegrations}, #171 adds
758
- * {@link planFiles}, #172 adds {@link planAi}, #478 adds {@link planNames}
758
+ * {@link planFiles}, #172 adds {@link planAi}, #478 adds {@link planEdge}
759
759
  * and #175 adds
760
760
  * {@link planArchon}, the same way #19 shipped org plan/apply against
761
761
  * three singletons before #27, #71, #80 and the rest added the remaining
@@ -805,10 +805,10 @@ export function planPlatform(platform, live, options = {}) {
805
805
  const ai = planAi(platform, recordedIds, blocked.ai);
806
806
  if (ai.step)
807
807
  plannedSteps.push(ai.step);
808
- // #478: independent of auth and files -- the names host needs neither.
809
- const names = planNames(platform, recordedIds, blocked.names);
810
- if (names.step)
811
- plannedSteps.push(names.step);
808
+ // #478: independent of auth and files -- the edge host needs neither.
809
+ const edge = planEdge(platform, recordedIds, blocked.edge);
810
+ if (edge.step)
811
+ plannedSteps.push(edge.step);
812
812
  // #175: last, on purpose. Archon's sign-in client lives in the sign-in
813
813
  // `planAuth` builds, and its env reads what every other service's step
814
814
  // records or delivers, so in one apply it runs after all of them.
@@ -842,9 +842,9 @@ export function planPlatform(platform, live, options = {}) {
842
842
  integrations.blocked
843
843
  ? null
844
844
  : versionChangeLine('integrations', recordedIds[PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY], platform.integrations.gitRef, 'git ref, redeploy'),
845
- names.blocked
845
+ edge.blocked
846
846
  ? null
847
- : versionChangeLine('names', recordedIds[PLATFORM_NAMES_APPLIED_GIT_REF_SCOPE_KEY], platform.names.gitRef, 'git ref, redeploy'),
847
+ : versionChangeLine('edge', recordedIds[PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY], platform.edge.gitRef, 'git ref, redeploy'),
848
848
  auth.blocked
849
849
  ? null
850
850
  : versionNotAppliedLine('zitadel', Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_AUTH_KEY)]), recordedIds[PLATFORM_AUTH_ZITADEL_APPLIED_IMAGE_TAG_SCOPE_KEY], VERSIONS.zitadel),
@@ -876,8 +876,8 @@ export function planPlatform(platform, live, options = {}) {
876
876
  unmanaged.push({ description: files.blocked, key: 'files' });
877
877
  if (ai.blocked)
878
878
  unmanaged.push({ description: ai.blocked, key: 'ai' });
879
- if (names.blocked)
880
- unmanaged.push({ description: names.blocked, key: 'names' });
879
+ if (edge.blocked)
880
+ unmanaged.push({ description: edge.blocked, key: 'edge' });
881
881
  if (archon.blocked)
882
882
  unmanaged.push({ description: archon.blocked, key: 'archon' });
883
883
  // #398: the old integrations key stays pinned until Archon holds the new
@@ -1747,13 +1747,13 @@ function planFiles(platform, recordedIds, executorBlocked, authReady, drifted) {
1747
1747
  };
1748
1748
  }
1749
1749
  /**
1750
- * #478: the names host (https://github.com/wtfalch/names) -- its database
1750
+ * #478: the edge host (https://github.com/wtfalch/edge) -- its database
1751
1751
  * (owner and runtime roles), its one Coolify app (no separate migrate app:
1752
1752
  * the Dockerfile migrates inside the runner's own entrypoint, see
1753
- * `provisionPlatformNames`'s own doc comment), foundry's own platform key
1753
+ * `provisionPlatformEdge`'s own doc comment), foundry's own platform key
1754
1754
  * (minted once, its hash alone written to the runner) and a Cloudflare
1755
- * tunnel + DNS record at `platform.names.hostname`. One `coolify:app` step
1756
- * keyed {@link PLATFORM_NAMES_KEY}, the {@link planFiles} bundling shape,
1755
+ * tunnel + DNS record at `platform.edge.hostname`. One `coolify:app` step
1756
+ * keyed {@link PLATFORM_EDGE_KEY}, the {@link planFiles} bundling shape,
1757
1757
  * public like {@link planMail}/{@link planAuth} rather than internal-only
1758
1758
  * like {@link planFiles}/{@link planAi}.
1759
1759
  *
@@ -1764,23 +1764,23 @@ function planFiles(platform, recordedIds, executorBlocked, authReady, drifted) {
1764
1764
  *
1765
1765
  * Blocked, not planned: a platform key minted but never delivered (lost,
1766
1766
  * `ServiceError`-free the way {@link planFiles}'s own management-key check
1767
- * is), or `executorBlocked` -- the host built no names dependencies, or
1767
+ * is), or `executorBlocked` -- the host built no edge dependencies, or
1768
1768
  * (CLAUDE.md's "Human steps") no Cloudflare API token is held yet under
1769
- * {@link PLATFORM_NAMES_CLOUDFLARE_TOKEN_REF}.
1769
+ * {@link PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF}.
1770
1770
  */
1771
- function planNames(platform, recordedIds, executorBlocked) {
1772
- if (!platform.names.enabled)
1771
+ function planEdge(platform, recordedIds, executorBlocked) {
1772
+ if (!platform.edge.enabled)
1773
1773
  return { step: null, blocked: null };
1774
- const undelivered = recordedIds[PLATFORM_NAMES_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY];
1775
- if (undelivered && !recordedIds[PLATFORM_NAMES_PLATFORM_KEY_SET_SCOPE_KEY]) {
1774
+ const undelivered = recordedIds[PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY];
1775
+ if (undelivered && !recordedIds[PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY]) {
1776
1776
  return {
1777
1777
  step: null,
1778
- blocked: `the names host is blocked, not planned: its platform key (${undelivered}) was minted but never delivered and cannot be shown again; remove the recorded key ${PLATFORM_NAMES_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY} to mint a new one`,
1778
+ blocked: `the edge host is blocked, not planned: its platform key (${undelivered}) was minted but never delivered and cannot be shown again; remove the recorded key ${PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY} to mint a new one`,
1779
1779
  };
1780
1780
  }
1781
- const applied = Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_NAMES_KEY)]) &&
1782
- recordedIds[PLATFORM_NAMES_APPLIED_HOSTNAME_SCOPE_KEY] === platform.names.hostname &&
1783
- recordedIds[PLATFORM_NAMES_APPLIED_GIT_REF_SCOPE_KEY] === platform.names.gitRef;
1781
+ const applied = Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_EDGE_KEY)]) &&
1782
+ recordedIds[PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY] === platform.edge.hostname &&
1783
+ recordedIds[PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY] === platform.edge.gitRef;
1784
1784
  if (applied)
1785
1785
  return { step: null, blocked: null };
1786
1786
  if (executorBlocked)
@@ -1790,11 +1790,11 @@ function planNames(platform, recordedIds, executorBlocked) {
1790
1790
  action: {
1791
1791
  system: 'coolify',
1792
1792
  kind: 'app',
1793
- key: PLATFORM_NAMES_KEY,
1794
- desired: { hostname: platform.names.hostname, gitRef: platform.names.gitRef },
1793
+ key: PLATFORM_EDGE_KEY,
1794
+ desired: { hostname: platform.edge.hostname, gitRef: platform.edge.gitRef },
1795
1795
  },
1796
1796
  step: {
1797
- description: `provision the names host at ${platform.names.hostname}, with its own database and foundry's platform key`,
1797
+ description: `provision the edge host at ${platform.edge.hostname}, with its own database and foundry's platform key`,
1798
1798
  destroys: false,
1799
1799
  migrates: false,
1800
1800
  },
@@ -88,16 +88,16 @@ export interface OrgPlatformReadiness {
88
88
  /** The jobs scheduler (#173) is enabled, applied, and its admin token is held. */
89
89
  jobsReady: boolean;
90
90
  /**
91
- * #317 (F7) and #478: the names service is declared `enabled`, its own
92
- * `coolify:app` deploy step has applied (`provisionPlatformNames`), and
93
- * the host's executor was built with a `NamesPort` -- the same
91
+ * #317 (F7) and #478: the edge service is declared `enabled`, its own
92
+ * `coolify:app` deploy step has applied (`provisionPlatformEdge`), and
93
+ * the host's executor was built with an `EdgePort` -- the same
94
94
  * `filesReady`/`aiReady` "declared and applied" shape now that #478 built
95
95
  * the deploy step #317 shipped ahead of (this field's own doc comment
96
96
  * used to say there was none; N1 is no longer deployed by hand).
97
97
  */
98
- namesReady: boolean;
99
- /** `https://<platform.names.hostname>`, which an entry with `uses.names` gets as `NAMES_URL`. `null` until `namesReady`. */
100
- namesUrl?: string | null;
98
+ edgeReady: boolean;
99
+ /** `https://<platform.edge.hostname>`, which an entry with `uses.edge` gets as `EDGE_URL`. `null` until `edgeReady`. */
100
+ edgeUrl?: string | null;
101
101
  /**
102
102
  * The ref of a platform key an applied service needs that is not held yet,
103
103
  * `null` when it is held or when the service is not applied. A ref only,
@@ -166,8 +166,8 @@ export declare function orgPlatformReadinessFrom(platform: DeclaredPlatform, pla
166
166
  heldRefs?: ReadonlySet<string>,
167
167
  /** True when the host wired no held-keys reader ({@link OrgPlatformReadiness.noKeyReader}). */
168
168
  noKeyReader?: boolean,
169
- /** #317: true when the host's executor was built with a `NamesPort` ({@link OrgPlatformReadiness.namesReady}). */
170
- namesPortPresent?: boolean): OrgPlatformReadiness;
169
+ /** #317: true when the host's executor was built with an `EdgePort` ({@link OrgPlatformReadiness.edgeReady}). */
170
+ edgePortPresent?: boolean): OrgPlatformReadiness;
171
171
  /**
172
172
  * #177: {@link AgoraHostValues} derived from the same platform state, rather
173
173
  * than requiring a bespoke host-supplied `AgoraHostPort` for values the
@@ -1,4 +1,4 @@
1
- import { JOBS_SCHEDULER_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_NAMES_KEY, platformMailMxHost, } from './platform-diff.js';
1
+ import { JOBS_SCHEDULER_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AUTH_KEY, PLATFORM_EDGE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, platformMailMxHost, } from './platform-diff.js';
2
2
  import { FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_KEY_REF, } from './platform-providers.js';
3
3
  /**
4
4
  * #443: the no-domain company mail apex -- `<slug>.oikos.host`, the same
@@ -32,7 +32,7 @@ export const DEFAULT_ORG_PLATFORM_READINESS = {
32
32
  callsReady: false,
33
33
  authReady: false,
34
34
  jobsReady: false,
35
- namesReady: false,
35
+ edgeReady: false,
36
36
  missingKeys: { files: null, ai: null, jobs: null },
37
37
  };
38
38
  /** The held-key refs an org step needs from the platform, one per service. */
@@ -80,13 +80,13 @@ export function orgPlatformReadinessFrom(platform, platformRecordedIds,
80
80
  heldRefs,
81
81
  /** True when the host wired no held-keys reader ({@link OrgPlatformReadiness.noKeyReader}). */
82
82
  noKeyReader = false,
83
- /** #317: true when the host's executor was built with a `NamesPort` ({@link OrgPlatformReadiness.namesReady}). */
84
- namesPortPresent = false) {
83
+ /** #317: true when the host's executor was built with an `EdgePort` ({@link OrgPlatformReadiness.edgeReady}). */
84
+ edgePortPresent = false) {
85
85
  const mailApplied = platform.mail.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_MAIL_KEY);
86
86
  const filesApplied = platform.files.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_FILES_KEY);
87
87
  const aiApplied = platform.ai.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_AI_KEY);
88
88
  const jobsApplied = platform.jobs.enabled && Boolean(platformRecordedIds[`coolify:jobs:${JOBS_SCHEDULER_KEY}`]);
89
- const namesApplied = platform.names.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_NAMES_KEY);
89
+ const edgeApplied = platform.edge.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_EDGE_KEY);
90
90
  const missing = (applied, ref) => applied && heldRefs !== undefined && !heldRefs.has(ref) ? ref : null;
91
91
  const missingKeys = {
92
92
  files: missing(filesApplied, ORG_PLATFORM_KEY_REFS.files),
@@ -113,8 +113,8 @@ namesPortPresent = false) {
113
113
  callsReady: platform.sfu.enabled,
114
114
  authReady: platform.auth.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_AUTH_KEY),
115
115
  jobsReady: jobsApplied && missingKeys.jobs === null,
116
- namesReady: namesApplied && namesPortPresent,
117
- namesUrl: namesApplied && namesPortPresent ? `https://${platform.names.hostname}` : null,
116
+ edgeReady: edgeApplied && edgePortPresent,
117
+ edgeUrl: edgeApplied && edgePortPresent ? `https://${platform.edge.hostname}` : null,
118
118
  missingKeys,
119
119
  ...(noKeyReader ? { noKeyReader } : {}),
120
120
  };