@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.
- package/dist/bootstrap/declare.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/migrations/0045_names_to_edge.sql +107 -0
- package/dist/org.d.ts +17 -17
- package/dist/org.js +48 -58
- package/dist/plan/diff.d.ts +3 -1
- package/dist/plan/diff.js +16 -16
- package/dist/plan/entry-edge-key.d.ts +79 -0
- package/dist/plan/{entry-names-key.js → entry-edge-key.js} +90 -66
- package/dist/plan/executor.d.ts +48 -37
- package/dist/plan/executor.js +166 -98
- package/dist/plan/index.d.ts +2 -2
- package/dist/plan/index.js +1 -1
- package/dist/plan/live.d.ts +3 -3
- package/dist/plan/plan-apply.d.ts +6 -5
- package/dist/plan/plan-apply.js +40 -38
- package/dist/plan/platform-diff.d.ts +22 -22
- package/dist/plan/platform-diff.js +45 -45
- package/dist/plan/platform-org.d.ts +8 -8
- package/dist/plan/platform-org.js +7 -7
- package/dist/plan/platform-providers.d.ts +14 -14
- package/dist/plan/platform-providers.js +107 -107
- package/dist/plan/step-system.js +1 -1
- package/dist/plan/{edge-worker.d.ts → worker.d.ts} +53 -33
- package/dist/plan/{edge-worker.js → worker.js} +92 -54
- package/dist/platform.d.ts +3 -3
- package/dist/platform.js +1 -1
- package/dist/store/postgres.js +7 -7
- package/dist/store/schema.d.ts +6 -6
- package/dist/store/schema.js +4 -4
- package/package.json +1 -1
- package/dist/plan/entry-names-key.d.ts +0 -73
package/dist/plan/executor.js
CHANGED
|
@@ -10,16 +10,16 @@ import { CHAT_REPO, CHAT_SERVICE_DOCKERFILE, CHAT_SERVICE_PORT, CHAT_SYNAPSE_DOC
|
|
|
10
10
|
import { ensureCloudflaredConnector } from './cloudflared-connector.js';
|
|
11
11
|
import { CORE_FILES_COLLECTION_NAME, FILES_CORE_COLLECTION_SCOPE_KEY, ZITADEL_FILES_PROJECT_SCOPE_KEY, } from './core-files.js';
|
|
12
12
|
import { DEFAULT_AI_LIMIT_MICROS } from './diff.js';
|
|
13
|
-
import { entryEdgeAppliedScopeKey, entryEdgeRetiredScopeKey, entryEdgeRouteIdScopeKey, entryEdgeRoutePatternScopeKey, entryEdgeRoutePendingScopeKey, entryEdgeRouteZoneScopeKey, entryEdgeUploadedScopeKey, } from './edge-worker.js';
|
|
14
13
|
import { AI_ENTRY_RENEW_WINDOW_DAYS, entryAiAppliedScopeKey, entryAiClampedScopeKey, entryAiExpiresScopeKey, entryAiPendingExpiresScopeKey, entryAiRetiredScopeKey, entryAiUndeliveredScopeKey, entryAiUnrevokedScopeKey, entryAiWebhookPendingScopeKey, entryAiWrittenScopeKey, parseIdList, } from './entry-ai-key.js';
|
|
14
|
+
import { EDGE_ENTRY_KEY_DAYS, EDGE_RESERVED_LABELS_APPLIED_SCOPE_KEY, ENTRY_EDGE_KEY_DELIVERY_ATTEMPTS, entryEdgeMintedAtScopeKey, entryEdgeRetiredScopeKey, entryEdgeUndeliveredScopeKey, } from './entry-edge-key.js';
|
|
15
15
|
import { FILES_AGORA_KEY_SCOPE_KEY, FILES_AGORA_UNDELIVERED_SCOPE_KEY, FILES_KEY_DAYS, FILES_KEY_RENEW_WINDOW_DAYS, FILES_ORG_OWNER_SCOPE_KEY, FILES_ROOT_EXPIRES_SCOPE_KEY, FILES_ROOT_PREVIOUS_SCOPE_KEY, FILES_ROOT_UNDELIVERED_SCOPE_KEY, FILES_ROOT_UNREVOKED_SCOPE_KEY, entryFilesAppliedScopeKey, entryFilesExpiresScopeKey, entryFilesMinterScopeKey, entryFilesPendingExpiresScopeKey, entryFilesRetiredScopeKey, entryFilesUndeliveredScopeKey, entryFilesUnrevokedScopeKey, entryFilesWrittenScopeKey, filesOwnerUserId, parseIdList as parseFilesIdList, } from './entry-files-key.js';
|
|
16
|
-
import { ENTRY_NAMES_KEY_DELIVERY_ATTEMPTS, NAMES_ENTRY_KEY_DAYS, NAMES_RESERVED_LABELS_APPLIED_SCOPE_KEY, entryNamesMintedAtScopeKey, entryNamesRetiredScopeKey, entryNamesUndeliveredScopeKey, } from './entry-names-key.js';
|
|
17
16
|
import { appHostname, coolifyDomains, entryHostnames, tunnelDnsHostnames, tunnelIngressHostnames, } from './hostnames.js';
|
|
18
17
|
import { LOGIN_BINDING_SCOPE_KEY, LOGIN_HOST_PENDING_REMOVAL_SCOPE_KEY, loginBindingState, loginHostClientIds, pendingRemovalHosts, pendingRemovalState, } from './login-host.js';
|
|
19
18
|
import { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, parseMailCopyCompleted, runMailCopyStep, } from './mail-copy.js';
|
|
20
19
|
import { CALLS_MAS_APPLIED_IMAGE_TAG_SCOPE_KEY, CALLS_SYNAPSE_APPLIED_IMAGE_TAG_SCOPE_KEY, ORG_MAIL_APPLIED_APEX_SCOPE_KEY, ORG_MAIL_APPLIED_HOST_SCOPE_KEY, appDomainsAppliedScopeKey, appEnvAppliedScopeKey, appNameFor, appSettingsAppliedScopeKey, appSettingsOf, appStepKey, appliedAppDomains, appliedAppSettings, dbEnvAppliedScopeKey, resolveAppEnv, serviceDbEnvAppliedScopeKey, } from './platform-diff.js';
|
|
21
20
|
import { PLATFORM_AUTH_BROKER_MAIL_KEY_REF } from './platform-providers.js';
|
|
22
21
|
import { appLabelsAppliedScopeKey, entryLabelHash, entryLabelSpec, entryProxyLabels, sameLabelSets, } from './proxy-labels.js';
|
|
22
|
+
import { entryWorkerAppliedScopeKey, entryWorkerExclusionsScopeKey, entryWorkerRetiredScopeKey, entryWorkerRouteIdScopeKey, entryWorkerRoutePatternScopeKey, entryWorkerRoutePendingScopeKey, entryWorkerRouteZoneScopeKey, entryWorkerUploadedScopeKey, } from './worker.js';
|
|
23
23
|
/**
|
|
24
24
|
* #274 fix round 4: bridges `@wtfalch/providers`' real `ZitadelClient.
|
|
25
25
|
* listApps` -- a plain array, with no `loginBaseUri` or completeness signal
|
|
@@ -508,11 +508,21 @@ const CALLS_MAS_DB_SCOPE_KEY = 'coolify_calls_mas_database';
|
|
|
508
508
|
* Synapse refuses to start against a database whose `LC_COLLATE`/`LC_CTYPE`
|
|
509
509
|
* is not `C` (https://element-hq.github.io/synapse/latest/postgres.html);
|
|
510
510
|
* Coolify's official postgres image otherwise creates one with `en_US.utf8`.
|
|
511
|
-
* `
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
511
|
+
* `createPostgresDatabase` forwards this to Coolify's own
|
|
512
|
+
* `postgres_initdb_args` field -- but Coolify stores that field and reads it
|
|
513
|
+
* back unchanged without ever handing it to `initdb`: its Postgres start
|
|
514
|
+
* action builds the container's env from `postgres_user`/`postgres_password`/
|
|
515
|
+
* `postgres_db` and runtime envs only
|
|
516
|
+
* (https://github.com/coollabsio/coolify/blob/main/app/Actions/Database/StartPostgresql.php).
|
|
517
|
+
* A database created this way still comes up `en_US.utf8` and crash-loops
|
|
518
|
+
* every Synapse app that reads it; live evidence and the real fix (not yet
|
|
519
|
+
* chosen) are tracked in #494. This constant and the field it feeds stay in
|
|
520
|
+
* place, inert, until that lands -- collation cannot be migrated onto an
|
|
521
|
+
* existing database under any of the candidate fixes either. MAS has no
|
|
522
|
+
* such requirement, so this is passed for the Synapse database only. Not
|
|
523
|
+
* using Synapse's own `allow_unsafe_locale` escape hatch regardless of
|
|
524
|
+
* which fix lands: that trades a loud startup refusal for silent index
|
|
525
|
+
* corruption after a future glibc upgrade.
|
|
516
526
|
*/
|
|
517
527
|
const CALLS_SYNAPSE_DB_INITDB_ARGS = '--locale=C --encoding=UTF8';
|
|
518
528
|
/**
|
|
@@ -678,10 +688,10 @@ export class FoundryStepExecutor {
|
|
|
678
688
|
return action.key.includes(':')
|
|
679
689
|
? this.aiEntryKey(action, org, context)
|
|
680
690
|
: this.aiKey(action, org, context);
|
|
681
|
-
case '
|
|
682
|
-
return this.
|
|
683
|
-
case '
|
|
684
|
-
return this.
|
|
691
|
+
case 'edge:key':
|
|
692
|
+
return this.edgeEntryKey(action, org, context);
|
|
693
|
+
case 'edge:reserved-labels':
|
|
694
|
+
return this.edgeReservedLabels(action, org, context);
|
|
685
695
|
case 'coolify:mail':
|
|
686
696
|
return this.coolifyMail(action, org, context);
|
|
687
697
|
case 'coolify:mail-copy':
|
|
@@ -850,10 +860,10 @@ export class FoundryStepExecutor {
|
|
|
850
860
|
return { resourceId: created.id };
|
|
851
861
|
}
|
|
852
862
|
/**
|
|
853
|
-
* `plan/diff.ts`'s `
|
|
854
|
-
* `
|
|
863
|
+
* `plan/diff.ts`'s `planWorkers` (#318, F8): deploys one entry's
|
|
864
|
+
* `worker` Worker and routes it on the org's own zone. Throws by name
|
|
855
865
|
* without a {@link WorkersPort}, the same `jobsRegistration` shape --
|
|
856
|
-
* `
|
|
866
|
+
* `planWorkers` reports the step blocked instead while the host has
|
|
857
867
|
* none, so this only fires against a stale plan. The route is checked
|
|
858
868
|
* before upload: a foreign route already on the pattern is refused, never
|
|
859
869
|
* adopted or replaced (`cloudflareAccess`'s own precedent for a foreign
|
|
@@ -879,24 +889,25 @@ export class FoundryStepExecutor {
|
|
|
879
889
|
if (desired.mode === 'retire') {
|
|
880
890
|
const recorded = context.recordedIds();
|
|
881
891
|
const mainId = recorded[`cloudflare:worker:${action.key}`];
|
|
882
|
-
const scriptName = mainId || recorded[
|
|
883
|
-
await this.
|
|
884
|
-
context.recordScopeId(
|
|
885
|
-
context.recordScopeId(
|
|
886
|
-
context.recordScopeId(
|
|
887
|
-
context.recordScopeId(
|
|
888
|
-
context.recordScopeId(
|
|
889
|
-
context.recordScopeId(
|
|
892
|
+
const scriptName = mainId || recorded[entryWorkerUploadedScopeKey(entryKey)];
|
|
893
|
+
await this.teardownWorker(entryKey, scriptName, recorded, workers, at);
|
|
894
|
+
context.recordScopeId(entryWorkerRouteIdScopeKey(entryKey), '');
|
|
895
|
+
context.recordScopeId(entryWorkerRouteZoneScopeKey(entryKey), '');
|
|
896
|
+
context.recordScopeId(entryWorkerRoutePatternScopeKey(entryKey), '');
|
|
897
|
+
context.recordScopeId(entryWorkerRoutePendingScopeKey(entryKey), '');
|
|
898
|
+
context.recordScopeId(entryWorkerExclusionsScopeKey(entryKey), '');
|
|
899
|
+
context.recordScopeId(entryWorkerAppliedScopeKey(entryKey), '');
|
|
900
|
+
context.recordScopeId(entryWorkerUploadedScopeKey(entryKey), '');
|
|
890
901
|
// `runSteps` only ever writes the main recorded id from a truthy
|
|
891
902
|
// `resourceId` (`plan-apply.ts`); this step returns none, so without
|
|
892
903
|
// this marker the planner's "gone" scan would retire the same,
|
|
893
904
|
// never-cleared id forever.
|
|
894
905
|
if (mainId)
|
|
895
|
-
context.recordScopeId(
|
|
906
|
+
context.recordScopeId(entryWorkerRetiredScopeKey(entryKey), mainId);
|
|
896
907
|
return {};
|
|
897
908
|
}
|
|
898
909
|
if (!org.domain) {
|
|
899
|
-
throw new Error(`${at}:
|
|
910
|
+
throw new Error(`${at}: worker needs org.domain, and validateHosting already refuses it without one`);
|
|
900
911
|
}
|
|
901
912
|
const zone = await this.ports.cloudflare.findZoneByApex(org.domain);
|
|
902
913
|
if (!zone) {
|
|
@@ -919,8 +930,8 @@ export class FoundryStepExecutor {
|
|
|
919
930
|
// the same deterministic name) is foundry's own script to overwrite,
|
|
920
931
|
// never a stranger's.
|
|
921
932
|
const recordedBeforeUpload = context.recordedIds();
|
|
922
|
-
const everUploadedByFoundry = Boolean(recordedBeforeUpload[
|
|
923
|
-
recordedBeforeUpload[
|
|
933
|
+
const everUploadedByFoundry = Boolean(recordedBeforeUpload[entryWorkerAppliedScopeKey(entryKey)] ||
|
|
934
|
+
recordedBeforeUpload[entryWorkerUploadedScopeKey(entryKey)] ||
|
|
924
935
|
recordedBeforeUpload[`cloudflare:worker:${action.key}`]);
|
|
925
936
|
if (!everUploadedByFoundry && (await workers.scriptExists(name))) {
|
|
926
937
|
throw new Error(`${at}: a Worker script named '${name}' already exists and foundry never created it; refuses to overwrite it`);
|
|
@@ -931,8 +942,8 @@ export class FoundryStepExecutor {
|
|
|
931
942
|
// the `scriptExists` guard above. The retired marker clears here too,
|
|
932
943
|
// not only on success: once a redeploy has touched the script, a later
|
|
933
944
|
// undeclare must retire it even if this apply never finishes.
|
|
934
|
-
context.recordScopeId(
|
|
935
|
-
context.recordScopeId(
|
|
945
|
+
context.recordScopeId(entryWorkerUploadedScopeKey(entryKey), name);
|
|
946
|
+
context.recordScopeId(entryWorkerRetiredScopeKey(entryKey), '');
|
|
936
947
|
await context.persistScopeIds();
|
|
937
948
|
await workers.uploadScript({
|
|
938
949
|
name,
|
|
@@ -941,9 +952,12 @@ export class FoundryStepExecutor {
|
|
|
941
952
|
compatibilityDate: action.desired.compatibilityDate,
|
|
942
953
|
bindings: bindings.length > 0 ? bindings : undefined,
|
|
943
954
|
});
|
|
955
|
+
// Before the wildcard route exists, so a hostname foundry serves is never
|
|
956
|
+
// caught by the Worker, not even briefly.
|
|
957
|
+
await this.syncWorkerExclusions(entryKey, zone.id, org.domain, desired.exclusions ?? [], workers, context, at);
|
|
944
958
|
const recorded = context.recordedIds();
|
|
945
|
-
const previousRouteId = recorded[
|
|
946
|
-
const previousPending = recorded[
|
|
959
|
+
const previousRouteId = recorded[entryWorkerRouteIdScopeKey(entryKey)];
|
|
960
|
+
const previousPending = recorded[entryWorkerRoutePendingScopeKey(entryKey)];
|
|
947
961
|
const zoneRecord = { id: zone.id, apex: org.domain };
|
|
948
962
|
let routeId = existingRoute?.id;
|
|
949
963
|
if (!routeId) {
|
|
@@ -951,7 +965,7 @@ export class FoundryStepExecutor {
|
|
|
951
965
|
// never got to record is still found (`findRoute`) and deleted on
|
|
952
966
|
// retire or destroy (#318 re-review).
|
|
953
967
|
const pending = { ...zoneRecord, pattern: route, script: name };
|
|
954
|
-
context.recordScopeId(
|
|
968
|
+
context.recordScopeId(entryWorkerRoutePendingScopeKey(entryKey), JSON.stringify(pending));
|
|
955
969
|
await context.persistScopeIds();
|
|
956
970
|
const created = await workers.createRoute(zone.id, route, name);
|
|
957
971
|
routeId = created.id;
|
|
@@ -961,30 +975,75 @@ export class FoundryStepExecutor {
|
|
|
961
975
|
// own zone rather than left live beside the new one. A pending route
|
|
962
976
|
// from an earlier, interrupted apply under a different pattern likewise.
|
|
963
977
|
if (previousRouteId && previousRouteId !== routeId) {
|
|
964
|
-
const previousZone = await this.
|
|
978
|
+
const previousZone = await this.liveWorkerZone(recorded[entryWorkerRouteZoneScopeKey(entryKey)], at);
|
|
965
979
|
if (previousZone)
|
|
966
980
|
await workers.deleteRoute(previousZone, previousRouteId); // Idempotent.
|
|
967
981
|
}
|
|
968
982
|
if (previousPending) {
|
|
969
|
-
await this.
|
|
983
|
+
await this.deletePendingWorkerRoute(previousPending, routeId, workers, at);
|
|
970
984
|
}
|
|
971
|
-
context.recordScopeId(
|
|
972
|
-
context.recordScopeId(
|
|
973
|
-
context.recordScopeId(
|
|
974
|
-
context.recordScopeId(
|
|
975
|
-
context.recordScopeId(
|
|
985
|
+
context.recordScopeId(entryWorkerRouteIdScopeKey(entryKey), routeId);
|
|
986
|
+
context.recordScopeId(entryWorkerRouteZoneScopeKey(entryKey), JSON.stringify(zoneRecord));
|
|
987
|
+
context.recordScopeId(entryWorkerRoutePatternScopeKey(entryKey), route);
|
|
988
|
+
context.recordScopeId(entryWorkerRoutePendingScopeKey(entryKey), '');
|
|
989
|
+
context.recordScopeId(entryWorkerAppliedScopeKey(entryKey), JSON.stringify({
|
|
976
990
|
name,
|
|
977
991
|
artifactUrl: desired.artifactUrl,
|
|
978
992
|
artifactSha256: desired.artifactSha256,
|
|
979
993
|
route,
|
|
980
994
|
bindings: desired.bindings,
|
|
995
|
+
exclusions: desired.exclusions,
|
|
981
996
|
}));
|
|
982
|
-
context.recordScopeId(
|
|
997
|
+
context.recordScopeId(entryWorkerUploadedScopeKey(entryKey), '');
|
|
983
998
|
// The retired marker was already cleared before the upload above: a
|
|
984
999
|
// redeclare reuses this entry's deterministic script name, so a marker
|
|
985
1000
|
// left equal to the (identical) main id would read as already retired.
|
|
986
1001
|
return { resourceId: name };
|
|
987
1002
|
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Makes the no-Worker routes recorded for an entry match `patterns`: deletes
|
|
1005
|
+
* the ones no longer wanted (all of them if the zone changed), creates the
|
|
1006
|
+
* missing ones without a script. A route already on a pattern that is bound
|
|
1007
|
+
* to a Worker is foreign and refused; one with no script is adopted. Records
|
|
1008
|
+
* and persists after each change.
|
|
1009
|
+
*/
|
|
1010
|
+
async syncWorkerExclusions(entryKey, zoneId, apex, patterns, workers, context, at) {
|
|
1011
|
+
const key = entryWorkerExclusionsScopeKey(entryKey);
|
|
1012
|
+
const raw = context.recordedIds()[key];
|
|
1013
|
+
let state = raw
|
|
1014
|
+
? JSON.parse(raw)
|
|
1015
|
+
: { id: zoneId, apex, routes: {} };
|
|
1016
|
+
const save = async () => {
|
|
1017
|
+
context.recordScopeId(key, Object.keys(state.routes).length > 0 ? JSON.stringify(state) : '');
|
|
1018
|
+
await context.persistScopeIds();
|
|
1019
|
+
};
|
|
1020
|
+
if (state.id !== zoneId) {
|
|
1021
|
+
const oldZone = await this.liveWorkerZone(JSON.stringify({ id: state.id, apex: state.apex }), at);
|
|
1022
|
+
if (oldZone) {
|
|
1023
|
+
for (const id of Object.values(state.routes))
|
|
1024
|
+
await workers.deleteRoute(oldZone, id);
|
|
1025
|
+
}
|
|
1026
|
+
state = { id: zoneId, apex, routes: {} };
|
|
1027
|
+
await save();
|
|
1028
|
+
}
|
|
1029
|
+
for (const [pattern, id] of Object.entries(state.routes)) {
|
|
1030
|
+
if (patterns.includes(pattern))
|
|
1031
|
+
continue;
|
|
1032
|
+
await workers.deleteRoute(zoneId, id); // Idempotent.
|
|
1033
|
+
delete state.routes[pattern];
|
|
1034
|
+
await save();
|
|
1035
|
+
}
|
|
1036
|
+
for (const pattern of patterns) {
|
|
1037
|
+
if (state.routes[pattern])
|
|
1038
|
+
continue;
|
|
1039
|
+
const found = await workers.findRoute(zoneId, pattern);
|
|
1040
|
+
if (found?.script) {
|
|
1041
|
+
throw new Error(`${at}: route '${pattern}' is already bound to Worker '${found.script}'; foundry does not replace it`);
|
|
1042
|
+
}
|
|
1043
|
+
state.routes[pattern] = found ? found.id : (await workers.createRoute(zoneId, pattern)).id;
|
|
1044
|
+
await save();
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
988
1047
|
/**
|
|
989
1048
|
* The zone id a recorded route lives in, or `undefined` only when that
|
|
990
1049
|
* zone is *positively* gone -- its apex resolves to no zone, or to a
|
|
@@ -998,7 +1057,7 @@ export class FoundryStepExecutor {
|
|
|
998
1057
|
* live on the old one, the exact bug a recorded-per-route zone exists to
|
|
999
1058
|
* avoid (#318 re-review).
|
|
1000
1059
|
*/
|
|
1001
|
-
async
|
|
1060
|
+
async liveWorkerZone(recordedZone, at) {
|
|
1002
1061
|
if (!recordedZone) {
|
|
1003
1062
|
throw new Error(`${at}: a route is recorded but its zone is not, so foundry cannot tell which zone holds it; refuses to forget it`);
|
|
1004
1063
|
}
|
|
@@ -1007,9 +1066,9 @@ export class FoundryStepExecutor {
|
|
|
1007
1066
|
return zone && zone.id === id ? id : undefined;
|
|
1008
1067
|
}
|
|
1009
1068
|
/** Deletes the route an interrupted apply was creating, if `findRoute` shows it exists, points at that apply's script, and is not `keepRouteId`. */
|
|
1010
|
-
async
|
|
1069
|
+
async deletePendingWorkerRoute(pendingRaw, keepRouteId, workers, at) {
|
|
1011
1070
|
const pending = JSON.parse(pendingRaw);
|
|
1012
|
-
const zoneId = await this.
|
|
1071
|
+
const zoneId = await this.liveWorkerZone(JSON.stringify({ id: pending.id, apex: pending.apex }), at);
|
|
1013
1072
|
if (!zoneId)
|
|
1014
1073
|
return;
|
|
1015
1074
|
const found = await workers.findRoute(zoneId, pending.pattern);
|
|
@@ -1022,16 +1081,25 @@ export class FoundryStepExecutor {
|
|
|
1022
1081
|
* zone, then any route an interrupted apply created but never recorded,
|
|
1023
1082
|
* then the script. Records nothing -- the caller does.
|
|
1024
1083
|
*/
|
|
1025
|
-
async
|
|
1026
|
-
const routeId = recorded[
|
|
1084
|
+
async teardownWorker(entryKey, scriptName, recorded, workers, at) {
|
|
1085
|
+
const routeId = recorded[entryWorkerRouteIdScopeKey(entryKey)];
|
|
1027
1086
|
if (routeId) {
|
|
1028
|
-
const zoneId = await this.
|
|
1087
|
+
const zoneId = await this.liveWorkerZone(recorded[entryWorkerRouteZoneScopeKey(entryKey)], at);
|
|
1029
1088
|
if (zoneId)
|
|
1030
1089
|
await workers.deleteRoute(zoneId, routeId); // Idempotent: a no-op if already gone.
|
|
1031
1090
|
}
|
|
1032
|
-
const pending = recorded[
|
|
1091
|
+
const pending = recorded[entryWorkerRoutePendingScopeKey(entryKey)];
|
|
1033
1092
|
if (pending)
|
|
1034
|
-
await this.
|
|
1093
|
+
await this.deletePendingWorkerRoute(pending, undefined, workers, at);
|
|
1094
|
+
const exclusions = recorded[entryWorkerExclusionsScopeKey(entryKey)];
|
|
1095
|
+
if (exclusions) {
|
|
1096
|
+
const state = JSON.parse(exclusions);
|
|
1097
|
+
const zoneId = await this.liveWorkerZone(JSON.stringify({ id: state.id, apex: state.apex }), at);
|
|
1098
|
+
if (zoneId) {
|
|
1099
|
+
for (const id of Object.values(state.routes))
|
|
1100
|
+
await workers.deleteRoute(zoneId, id);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1035
1103
|
if (scriptName)
|
|
1036
1104
|
await workers.deleteScript(scriptName); // Idempotent: a no-op if already gone.
|
|
1037
1105
|
}
|
|
@@ -4816,24 +4884,24 @@ export class FoundryStepExecutor {
|
|
|
4816
4884
|
context.recordScopeId(listKey, JSON.stringify(ids));
|
|
4817
4885
|
}
|
|
4818
4886
|
/**
|
|
4819
|
-
* F7 (#317): mints one entry's
|
|
4820
|
-
* takes no separate minter key id, unlike ai/files), writes `
|
|
4821
|
-
* and `
|
|
4822
|
-
* the key when `uses.
|
|
4887
|
+
* F7 (#317): mints one entry's edge key directly below the org (edge
|
|
4888
|
+
* takes no separate minter key id, unlike ai/files), writes `EDGE_URL`
|
|
4889
|
+
* and `EDGE_API_KEY` to the entry's app env, then restarts it. Retires
|
|
4890
|
+
* the key when `uses.edge` is removed. Follows the "one mint" rule
|
|
4823
4891
|
* (CLAUDE.md "Platform steps"): the id is recorded as undelivered and
|
|
4824
4892
|
* persisted before delivery is attempted, so a crash or a failed write
|
|
4825
|
-
* never leads to a second mint -- `
|
|
4893
|
+
* never leads to a second mint -- `planEntryEdgeKeys` blocks instead.
|
|
4826
4894
|
*/
|
|
4827
|
-
async
|
|
4895
|
+
async edgeEntryKey(action, org, context) {
|
|
4828
4896
|
const entryKey = action.key.slice(org.id.length + 1);
|
|
4829
|
-
const at = `
|
|
4830
|
-
const
|
|
4831
|
-
if (!
|
|
4832
|
-
throw new Error(`${at}: this executor was built without
|
|
4897
|
+
const at = `edge:key for ${org.slug} entry '${entryKey}'`;
|
|
4898
|
+
const edge = this.ports.edge;
|
|
4899
|
+
if (!edge)
|
|
4900
|
+
throw new Error(`${at}: this executor was built without an EdgePort (#317)`);
|
|
4833
4901
|
const desired = action.desired;
|
|
4834
4902
|
if (desired.mode === 'retire') {
|
|
4835
4903
|
const recorded = context.recordedIds();
|
|
4836
|
-
const mainId = recorded[`
|
|
4904
|
+
const mainId = recorded[`edge:key:${action.key}`];
|
|
4837
4905
|
// A key minted but never delivered never gets a main id (the mint
|
|
4838
4906
|
// step throws before `runSteps` sees a `resourceId`), so it can
|
|
4839
4907
|
// coexist with a separate, already-delivered id from an earlier mint
|
|
@@ -4843,10 +4911,10 @@ export class FoundryStepExecutor {
|
|
|
4843
4911
|
// here always picked A (truthy), leaving B live for up to 365 days
|
|
4844
4912
|
// with nothing recorded (#317 re-review) -- both ids are revoked,
|
|
4845
4913
|
// independently, whenever both are present.
|
|
4846
|
-
const undeliveredId = recorded[
|
|
4914
|
+
const undeliveredId = recorded[entryEdgeUndeliveredScopeKey(entryKey)];
|
|
4847
4915
|
const idsToRevoke = [...new Set([mainId, undeliveredId].filter((id) => !!id))];
|
|
4848
4916
|
const keyId = mainId || undeliveredId;
|
|
4849
|
-
// Revoke first: the key going dead in
|
|
4917
|
+
// Revoke first: the key going dead in edge is what retire is for,
|
|
4850
4918
|
// and the one thing that must still happen even when the entry's app
|
|
4851
4919
|
// is itself already gone. Only once that has succeeded (or 404'd as
|
|
4852
4920
|
// already gone) is the env cleanup attempted, itself tolerant of a
|
|
@@ -4856,7 +4924,7 @@ export class FoundryStepExecutor {
|
|
|
4856
4924
|
// stranding a live key on every retry (#317 review).
|
|
4857
4925
|
for (const id of idsToRevoke) {
|
|
4858
4926
|
try {
|
|
4859
|
-
await
|
|
4927
|
+
await edge.revokeKey(org.id, id);
|
|
4860
4928
|
}
|
|
4861
4929
|
catch (error) {
|
|
4862
4930
|
// A 404 means it is already gone -- recorded as retired below,
|
|
@@ -4871,7 +4939,7 @@ export class FoundryStepExecutor {
|
|
|
4871
4939
|
const appId = recorded[`coolify:app:${action.key}`];
|
|
4872
4940
|
if (appId) {
|
|
4873
4941
|
// A revoked key's env values are dead, not merely stale -- remove
|
|
4874
|
-
// them so nothing reads a credential
|
|
4942
|
+
// them so nothing reads a credential edge has already revoked. A
|
|
4875
4943
|
// 404 on either call means that app or that env is already gone:
|
|
4876
4944
|
// nothing to clean up there, and the key is already revoked above,
|
|
4877
4945
|
// so it is not a reason to fail the step. Each delete tolerates its
|
|
@@ -4887,7 +4955,7 @@ export class FoundryStepExecutor {
|
|
|
4887
4955
|
}
|
|
4888
4956
|
};
|
|
4889
4957
|
const envs = await tolerateNotFound(() => this.ports.coolify.listEnvs(appId), []);
|
|
4890
|
-
for (const envKey of ['
|
|
4958
|
+
for (const envKey of ['EDGE_URL', 'EDGE_API_KEY']) {
|
|
4891
4959
|
const env = envs.find((e) => e.key === envKey && !e.is_preview);
|
|
4892
4960
|
if (env?.uuid) {
|
|
4893
4961
|
const uuid = env.uuid;
|
|
@@ -4895,21 +4963,21 @@ export class FoundryStepExecutor {
|
|
|
4895
4963
|
}
|
|
4896
4964
|
}
|
|
4897
4965
|
}
|
|
4898
|
-
context.recordScopeId(
|
|
4899
|
-
context.recordScopeId(
|
|
4966
|
+
context.recordScopeId(entryEdgeUndeliveredScopeKey(entryKey), '');
|
|
4967
|
+
context.recordScopeId(entryEdgeMintedAtScopeKey(entryKey), '');
|
|
4900
4968
|
if (!keyId)
|
|
4901
4969
|
return {};
|
|
4902
|
-
context.recordScopeId(
|
|
4970
|
+
context.recordScopeId(entryEdgeRetiredScopeKey(entryKey), keyId);
|
|
4903
4971
|
return {};
|
|
4904
4972
|
}
|
|
4905
|
-
const entry = org.hosting.entries.find((e) => e.key === entryKey && e.uses?.
|
|
4973
|
+
const entry = org.hosting.entries.find((e) => e.key === entryKey && e.uses?.edge);
|
|
4906
4974
|
if (!entry)
|
|
4907
|
-
throw new Error(`${at}: the entry no longer declares uses.
|
|
4975
|
+
throw new Error(`${at}: the entry no longer declares uses.edge`);
|
|
4908
4976
|
const appId = context.recordedIds()[`coolify:app:${action.key}`];
|
|
4909
4977
|
if (!appId)
|
|
4910
4978
|
throw new Error(`${at}: the entry's app is not recorded yet`);
|
|
4911
4979
|
if (!org.domain) {
|
|
4912
|
-
throw new Error(`${at}:
|
|
4980
|
+
throw new Error(`${at}: edge needs org.domain, and validateEntryEdge already refuses it without one`);
|
|
4913
4981
|
}
|
|
4914
4982
|
// A key left over from a previous mint whose delivery never succeeded
|
|
4915
4983
|
// is revoked first, not left blocking every later apply forever:
|
|
@@ -4917,39 +4985,39 @@ export class FoundryStepExecutor {
|
|
|
4917
4985
|
// (CLAUDE.md "No secret leaves"), so the only way to actually recover
|
|
4918
4986
|
// is to revoke the stale id and mint a fresh one -- a 404 counts as
|
|
4919
4987
|
// already revoked, same as any other retire (#317 re-review).
|
|
4920
|
-
const staleUndelivered = context.recordedIds()[
|
|
4988
|
+
const staleUndelivered = context.recordedIds()[entryEdgeUndeliveredScopeKey(entryKey)];
|
|
4921
4989
|
if (staleUndelivered) {
|
|
4922
4990
|
try {
|
|
4923
|
-
await
|
|
4991
|
+
await edge.revokeKey(org.id, staleUndelivered);
|
|
4924
4992
|
}
|
|
4925
4993
|
catch (error) {
|
|
4926
4994
|
if (!isNotFoundError(error))
|
|
4927
4995
|
throw error;
|
|
4928
4996
|
}
|
|
4929
|
-
context.recordScopeId(
|
|
4997
|
+
context.recordScopeId(entryEdgeUndeliveredScopeKey(entryKey), '');
|
|
4930
4998
|
await context.persistScopeIds();
|
|
4931
4999
|
}
|
|
4932
|
-
const minted = await
|
|
5000
|
+
const minted = await edge.mintKey({
|
|
4933
5001
|
organisationId: org.id,
|
|
4934
5002
|
domain: org.domain,
|
|
4935
5003
|
name: desired.name,
|
|
4936
|
-
expiresDays:
|
|
5004
|
+
expiresDays: EDGE_ENTRY_KEY_DAYS,
|
|
4937
5005
|
});
|
|
4938
|
-
context.recordScopeId(
|
|
5006
|
+
context.recordScopeId(entryEdgeUndeliveredScopeKey(entryKey), minted.id);
|
|
4939
5007
|
await context.persistScopeIds();
|
|
4940
5008
|
const envs = [
|
|
4941
|
-
{ key: '
|
|
4942
|
-
{ key: '
|
|
5009
|
+
{ key: 'EDGE_URL', value: desired.edgeUrl, literal: true },
|
|
5010
|
+
{ key: 'EDGE_API_KEY', value: minted.secret, literal: true },
|
|
4943
5011
|
];
|
|
4944
5012
|
let lastError;
|
|
4945
|
-
for (let attempt = 1; attempt <=
|
|
5013
|
+
for (let attempt = 1; attempt <= ENTRY_EDGE_KEY_DELIVERY_ATTEMPTS; attempt++) {
|
|
4946
5014
|
try {
|
|
4947
5015
|
await this.ports.coolify.bulkUpsertEnvs(appId, envs);
|
|
4948
5016
|
await this.ports.coolify.restart(appId);
|
|
4949
|
-
context.recordScopeId(
|
|
5017
|
+
context.recordScopeId(entryEdgeUndeliveredScopeKey(entryKey), '');
|
|
4950
5018
|
// The only trace of this key's age, since nothing here renews it
|
|
4951
5019
|
// (#317 review's warning-row ask reads this to flag an expiring key).
|
|
4952
|
-
context.recordScopeId(
|
|
5020
|
+
context.recordScopeId(entryEdgeMintedAtScopeKey(entryKey), new Date(context.now?.() ?? Date.now()).toISOString());
|
|
4953
5021
|
return { resourceId: minted.id };
|
|
4954
5022
|
}
|
|
4955
5023
|
catch (e) {
|
|
@@ -4957,21 +5025,21 @@ export class FoundryStepExecutor {
|
|
|
4957
5025
|
}
|
|
4958
5026
|
}
|
|
4959
5027
|
// Names the key id only; the underlying error is dropped in case it echoes the secret.
|
|
4960
|
-
throw new Error(`${at}: key ${minted.id} was minted but could not be written to the app's env after ${
|
|
5028
|
+
throw new Error(`${at}: key ${minted.id} was minted but could not be written to the app's env after ${ENTRY_EDGE_KEY_DELIVERY_ATTEMPTS} attempts (${lastError instanceof Error ? lastError.name : 'error'}); the next apply revokes it and mints a fresh one automatically`);
|
|
4961
5029
|
}
|
|
4962
5030
|
/**
|
|
4963
|
-
* F7 (#317): `plan/entry-
|
|
4964
|
-
* tells the
|
|
4965
|
-
* hold, so
|
|
5031
|
+
* F7 (#317): `plan/entry-edge-key.ts`'s `planEdgeReservedLabels` --
|
|
5032
|
+
* tells the edge service every label this org's own entries already
|
|
5033
|
+
* hold, so edge refuses a customer claiming one of them.
|
|
4966
5034
|
*/
|
|
4967
|
-
async
|
|
4968
|
-
const
|
|
4969
|
-
if (!
|
|
4970
|
-
throw new Error(`
|
|
5035
|
+
async edgeReservedLabels(action, org, context) {
|
|
5036
|
+
const edge = this.ports.edge;
|
|
5037
|
+
if (!edge) {
|
|
5038
|
+
throw new Error(`edge:reserved-labels for ${org.slug}: this executor was built without an EdgePort (#317)`);
|
|
4971
5039
|
}
|
|
4972
5040
|
const labels = action.desired.labels;
|
|
4973
|
-
await
|
|
4974
|
-
context.recordScopeId(
|
|
5041
|
+
await edge.setReservedLabels(org.id, labels);
|
|
5042
|
+
context.recordScopeId(EDGE_RESERVED_LABELS_APPLIED_SCOPE_KEY, JSON.stringify(labels));
|
|
4975
5043
|
return {};
|
|
4976
5044
|
}
|
|
4977
5045
|
/**
|
|
@@ -5375,7 +5443,7 @@ export class FoundryStepExecutor {
|
|
|
5375
5443
|
return;
|
|
5376
5444
|
}
|
|
5377
5445
|
case 'cloudflare:worker': {
|
|
5378
|
-
// #318: an entry's
|
|
5446
|
+
// #318: an entry's Worker (`<orgId>:<entry>`) and its route.
|
|
5379
5447
|
// No port means no way to delete either -- thrown, not silently
|
|
5380
5448
|
// skipped, so it is reported blocked rather than left live with
|
|
5381
5449
|
// nothing recorded ids point at any more.
|
|
@@ -5388,20 +5456,20 @@ export class FoundryStepExecutor {
|
|
|
5388
5456
|
// never recorded, then the script -- the same teardown retire runs.
|
|
5389
5457
|
// `target.id` is `''` for a route-only partial deploy.
|
|
5390
5458
|
const entryKey = target.key.slice(org.id.length + 1);
|
|
5391
|
-
await this.
|
|
5459
|
+
await this.teardownWorker(entryKey, target.id || undefined, recordedIds, workers, `cloudflare:worker '${target.key}' for ${org.slug}`);
|
|
5392
5460
|
return;
|
|
5393
5461
|
}
|
|
5394
|
-
case '
|
|
5395
|
-
// F7 (#317): an entry's key (`<orgId>:<entry>`);
|
|
5462
|
+
case 'edge:key': {
|
|
5463
|
+
// F7 (#317): an entry's key (`<orgId>:<entry>`); edge has no org-root key.
|
|
5396
5464
|
// No port means no way to revoke it -- thrown, not silently
|
|
5397
5465
|
// skipped, so the key is reported blocked rather than left live
|
|
5398
5466
|
// with nothing saying so.
|
|
5399
|
-
const
|
|
5400
|
-
if (!
|
|
5401
|
-
throw new Error(`
|
|
5467
|
+
const edge = this.ports.edge;
|
|
5468
|
+
if (!edge) {
|
|
5469
|
+
throw new Error(`edge:key '${target.key}' for ${org.slug}: this executor was built without an EdgePort (#317); cannot revoke it`);
|
|
5402
5470
|
}
|
|
5403
5471
|
try {
|
|
5404
|
-
await
|
|
5472
|
+
await edge.revokeKey(org.id, target.id);
|
|
5405
5473
|
}
|
|
5406
5474
|
catch (error) {
|
|
5407
5475
|
// Already gone -- the same already-retired key a retire step
|
package/dist/plan/index.d.ts
CHANGED
|
@@ -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,
|
|
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,
|
|
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';
|
package/dist/plan/index.js
CHANGED
|
@@ -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,
|
|
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';
|
package/dist/plan/live.d.ts
CHANGED
|
@@ -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. `'
|
|
30
|
-
* (#317) is the same: a
|
|
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' | '
|
|
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>;
|
|
@@ -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 `
|
|
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 `
|
|
351
|
-
* Optional, and false without it: each entry's `uses.
|
|
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
|
-
|
|
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
|
|
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`
|