@wtfalch/foundry 0.19.1 → 0.20.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 +8 -6
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/plan/cloudflared-connector.d.ts +3 -2
- package/dist/plan/cloudflared-connector.js +2 -2
- package/dist/plan/diff.js +4 -4
- package/dist/plan/edge-access.d.ts +68 -0
- package/dist/plan/edge-access.js +123 -0
- package/dist/plan/edge-dns.d.ts +89 -0
- package/dist/plan/edge-dns.js +227 -0
- package/dist/plan/edge-ratelimits.d.ts +54 -0
- package/dist/plan/edge-ratelimits.js +79 -0
- package/dist/plan/edge-tunnels.d.ts +44 -0
- package/dist/plan/edge-tunnels.js +108 -0
- package/dist/plan/edge-workers.d.ts +31 -0
- package/dist/plan/edge-workers.js +165 -0
- package/dist/plan/edge-zones.d.ts +37 -0
- package/dist/plan/edge-zones.js +64 -0
- package/dist/plan/entry-edge-key.d.ts +10 -0
- package/dist/plan/entry-edge-key.js +38 -8
- package/dist/plan/executor.d.ts +112 -143
- package/dist/plan/executor.js +189 -87
- package/dist/plan/index.d.ts +15 -3
- package/dist/plan/index.js +8 -2
- package/dist/plan/plan-apply.js +5 -1
- package/dist/plan/platform-diff.d.ts +9 -0
- package/dist/plan/platform-diff.js +32 -8
- package/dist/plan/platform-providers.d.ts +53 -6
- package/dist/plan/platform-providers.js +87 -44
- package/dist/plan/providers-readers.d.ts +23 -1
- package/dist/plan/providers-readers.js +29 -0
- package/package.json +2 -1
package/dist/plan/executor.js
CHANGED
|
@@ -10,8 +10,13 @@ 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 { createEdgeAccess, } from './edge-access.js';
|
|
14
|
+
import { EDGE_DNS_WRITE_CONCURRENCY, createEdgeDns, limiter, } from './edge-dns.js';
|
|
15
|
+
import { createEdgeRateLimits, } from './edge-ratelimits.js';
|
|
16
|
+
import { createEdgeTunnels } from './edge-tunnels.js';
|
|
17
|
+
import { createEdgeZones } from './edge-zones.js';
|
|
13
18
|
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';
|
|
19
|
+
import { BOULE_EDGE_ENTRY_KEY, EDGE_ENTRY_KEY_DAYS, EDGE_RESERVED_LABELS_APPLIED_SCOPE_KEY, ENTRY_EDGE_KEY_DELIVERY_ATTEMPTS, edgeKeyAppRecordKey, entryEdgeMintedAtScopeKey, entryEdgeRetiredScopeKey, entryEdgeUndeliveredScopeKey, } from './entry-edge-key.js';
|
|
15
20
|
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
21
|
import { appHostname, coolifyDomains, entryHostnames, tunnelDnsHostnames, tunnelIngressHostnames, } from './hostnames.js';
|
|
17
22
|
import { LOGIN_BINDING_SCOPE_KEY, LOGIN_HOST_PENDING_REMOVAL_SCOPE_KEY, LOGIN_HOST_SHARED_SCOPE_KEY, loginBindingState, loginHostClientIds, pendingRemovalHosts, pendingRemovalState, } from './login-host.js';
|
|
@@ -479,6 +484,16 @@ const MAIL_ROLE_MAILBOXES_SCOPE_KEY = 'mail_role_mailboxes';
|
|
|
479
484
|
const AI_KEY_UNDELIVERED_SCOPE_KEY = 'ai_key_undelivered';
|
|
480
485
|
/** How many times `aiKey` tries `deliverSecret` before giving up. */
|
|
481
486
|
const AI_KEY_DELIVERY_ATTEMPTS = 3;
|
|
487
|
+
/**
|
|
488
|
+
* #515: the limit `aiBudget` last applied, a flat scope id (no `:`) alongside
|
|
489
|
+
* `AI_KEY_UNDELIVERED_SCOPE_KEY`'s own. AI's `updateBudget` offers no read
|
|
490
|
+
* back, so nothing else can tell `diff.ts`'s `planAiBudget` the limit is
|
|
491
|
+
* already live -- without this, every plan for an applied org proposed
|
|
492
|
+
* setting it again. `providers-readers.ts`'s `ProvidersAiReader` keeps its
|
|
493
|
+
* own copy of this literal, the same "restated, not shared" choice this
|
|
494
|
+
* codebase already makes for `cloudflare_zone`/`zitadel_org`.
|
|
495
|
+
*/
|
|
496
|
+
const AI_BUDGET_LIMIT_SCOPE_KEY = 'ai_budget_limit_micros';
|
|
482
497
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
483
498
|
/** What a failed files call says about itself: its HTTP status, never its message, which can echo a value. */
|
|
484
499
|
function filesErrorCause(error) {
|
|
@@ -640,6 +655,12 @@ export const databaseStartWait = {
|
|
|
640
655
|
// ---------------------------------------------------------------------------
|
|
641
656
|
export class FoundryStepExecutor {
|
|
642
657
|
ports;
|
|
658
|
+
edgeDns;
|
|
659
|
+
edgeTunnels;
|
|
660
|
+
edgeAccess;
|
|
661
|
+
edgeRateLimits;
|
|
662
|
+
edgeZones;
|
|
663
|
+
edgeWriteSlot;
|
|
643
664
|
constructor(ports) {
|
|
644
665
|
this.ports = ports;
|
|
645
666
|
}
|
|
@@ -686,7 +707,7 @@ export class FoundryStepExecutor {
|
|
|
686
707
|
case 'ai:organisation':
|
|
687
708
|
return this.aiOrganisation(action, org);
|
|
688
709
|
case 'ai:budget':
|
|
689
|
-
return this.aiBudget(action, org);
|
|
710
|
+
return this.aiBudget(action, org, context);
|
|
690
711
|
case 'ai:key':
|
|
691
712
|
// The org's own key is keyed `<orgId>`, an entry's `<orgId>:<entry>` (F6a).
|
|
692
713
|
return action.key.includes(':')
|
|
@@ -774,7 +795,7 @@ export class FoundryStepExecutor {
|
|
|
774
795
|
// `everyone` is Cloudflare's own shape for "match every visitor".
|
|
775
796
|
return { decision: 'bypass', include: [{ everyone: {} }] };
|
|
776
797
|
case 'service-token': {
|
|
777
|
-
const token = await this.
|
|
798
|
+
const token = await this.accessPort().findServiceTokenByName(rule.serviceTokenName);
|
|
778
799
|
if (!token) {
|
|
779
800
|
throw new Error(`cloudflare:access for ${org.slug}: no Cloudflare Access service token named '${rule.serviceTokenName}'`);
|
|
780
801
|
}
|
|
@@ -788,7 +809,7 @@ export class FoundryStepExecutor {
|
|
|
788
809
|
*/
|
|
789
810
|
async cloudflareAccessApp(org, opts) {
|
|
790
811
|
const { appName, domain, policyName, policy } = opts;
|
|
791
|
-
const cf = this.
|
|
812
|
+
const cf = this.accessPort();
|
|
792
813
|
const onDomain = (await cf.listAccessApps()).filter((a) => a.domain === domain);
|
|
793
814
|
const foreign = onDomain.find((a) => a.name !== appName);
|
|
794
815
|
if (foreign) {
|
|
@@ -812,12 +833,10 @@ export class FoundryStepExecutor {
|
|
|
812
833
|
* gate), so `org.domain` is set and that zone is the org's alone, never
|
|
813
834
|
* shared with another org the way `oikos.host` is.
|
|
814
835
|
*
|
|
815
|
-
* Rate limiting rules
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
* `rules[]` and matching `RATE_LIMIT_RULE_DESCRIPTION`, the same shape
|
|
820
|
-
* `cloudflareAccess` above finds its own Access app by name. The Free
|
|
836
|
+
* Rate limiting rules go through edge (`RateLimitPort`) -- so "find by
|
|
837
|
+
* name" here means listing the zone's rules and matching
|
|
838
|
+
* `RATE_LIMIT_RULE_DESCRIPTION`, the same shape `cloudflareAccess` above
|
|
839
|
+
* finds its own Access app by name. The Free
|
|
821
840
|
* plan allows exactly one rule per zone, so a rule already there under a
|
|
822
841
|
* different description is refused, never adopted or replaced, mirroring
|
|
823
842
|
* `cloudflareAccess`'s own refusal when a foreign Access app already
|
|
@@ -827,40 +846,33 @@ export class FoundryStepExecutor {
|
|
|
827
846
|
if (!org.domain) {
|
|
828
847
|
throw new Error(`cloudflare:ratelimit for ${org.slug}: hosting.usesDomain is set but domain is null`);
|
|
829
848
|
}
|
|
830
|
-
const cf = this.
|
|
849
|
+
const cf = this.zonePort();
|
|
831
850
|
const zone = await cf.findZoneByApex(org.domain);
|
|
832
851
|
if (!zone) {
|
|
833
852
|
throw new Error(`cloudflare:ratelimit for ${org.slug}: no Cloudflare zone found for ${org.domain} -- cloudflare:zone must provision it first`);
|
|
834
853
|
}
|
|
835
|
-
const
|
|
836
|
-
const
|
|
837
|
-
const rules = existing?.rules ?? [];
|
|
854
|
+
const rateLimits = this.rateLimitPort();
|
|
855
|
+
const rules = await rateLimits.listRateLimitRules(zone.id);
|
|
838
856
|
const own = rules.find((r) => r.description === RATE_LIMIT_RULE_DESCRIPTION);
|
|
839
|
-
if (own)
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
}
|
|
845
|
-
const updated = await cf.request('PUT', path, {
|
|
846
|
-
rules: [
|
|
847
|
-
{
|
|
848
|
-
description: RATE_LIMIT_RULE_DESCRIPTION,
|
|
849
|
-
expression: SIGN_IN_PATH_EXPRESSION,
|
|
850
|
-
action: 'block',
|
|
851
|
-
ratelimit: {
|
|
852
|
-
characteristics: ['cf.colo.id', 'ip.src'],
|
|
853
|
-
period: RATE_LIMIT_FREE_PLAN_PERIOD_SECONDS,
|
|
854
|
-
requests_per_period: RATE_LIMIT_REQUESTS_PER_PERIOD,
|
|
855
|
-
mitigation_timeout: RATE_LIMIT_FREE_PLAN_PERIOD_SECONDS,
|
|
856
|
-
},
|
|
857
|
-
},
|
|
858
|
-
],
|
|
859
|
-
});
|
|
860
|
-
const created = updated.rules?.[0];
|
|
861
|
-
if (!created) {
|
|
862
|
-
throw new Error(`cloudflare:ratelimit for ${org.slug}: PUT ${path} did not return the created rule`);
|
|
857
|
+
if (!own) {
|
|
858
|
+
const foreign = rules[0];
|
|
859
|
+
if (foreign) {
|
|
860
|
+
throw new Error(`cloudflare:ratelimit for ${org.slug}: zone ${org.domain} already has a rate limiting rule ('${foreign.description ?? foreign.id}') foundry did not create; the Free plan allows only one rule per zone, so foundry does not adopt or replace it`);
|
|
861
|
+
}
|
|
863
862
|
}
|
|
863
|
+
// Edge adopts a live rule with no edge row (made before edge owned rate
|
|
864
|
+
// limits) and then makes the rule once: a repeat returns it unchanged.
|
|
865
|
+
const created = await rateLimits.createRateLimitRule(zone.id, {
|
|
866
|
+
description: RATE_LIMIT_RULE_DESCRIPTION,
|
|
867
|
+
expression: SIGN_IN_PATH_EXPRESSION,
|
|
868
|
+
action: 'block',
|
|
869
|
+
ratelimit: {
|
|
870
|
+
characteristics: ['cf.colo.id', 'ip.src'],
|
|
871
|
+
period: RATE_LIMIT_FREE_PLAN_PERIOD_SECONDS,
|
|
872
|
+
requests_per_period: RATE_LIMIT_REQUESTS_PER_PERIOD,
|
|
873
|
+
mitigation_timeout: RATE_LIMIT_FREE_PLAN_PERIOD_SECONDS,
|
|
874
|
+
},
|
|
875
|
+
});
|
|
864
876
|
return { resourceId: created.id };
|
|
865
877
|
}
|
|
866
878
|
/**
|
|
@@ -913,7 +925,7 @@ export class FoundryStepExecutor {
|
|
|
913
925
|
if (!org.domain) {
|
|
914
926
|
throw new Error(`${at}: worker needs org.domain, and validateHosting already refuses it without one`);
|
|
915
927
|
}
|
|
916
|
-
const zone = await this.
|
|
928
|
+
const zone = await this.zonePort().findZoneByApex(org.domain);
|
|
917
929
|
if (!zone) {
|
|
918
930
|
throw new Error(`${at}: no Cloudflare zone found for ${org.domain} -- cloudflare:zone must provision it first`);
|
|
919
931
|
}
|
|
@@ -1066,7 +1078,7 @@ export class FoundryStepExecutor {
|
|
|
1066
1078
|
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`);
|
|
1067
1079
|
}
|
|
1068
1080
|
const { id, apex } = JSON.parse(recordedZone);
|
|
1069
|
-
const zone = await this.
|
|
1081
|
+
const zone = await this.zonePort().findZoneByApex(apex);
|
|
1070
1082
|
return zone && zone.id === id ? id : undefined;
|
|
1071
1083
|
}
|
|
1072
1084
|
/** Deletes the route an interrupted apply was creating, if `findRoute` shows it exists, points at that apply's script, and is not `keepRouteId`. */
|
|
@@ -2481,11 +2493,11 @@ export class FoundryStepExecutor {
|
|
|
2481
2493
|
scopedProjectIds.push(platformLettermintProjectId);
|
|
2482
2494
|
}
|
|
2483
2495
|
await this.ports.lettermint.restrictDomainToProjects(domain.id, scopedProjectIds);
|
|
2484
|
-
const zone = await this.
|
|
2496
|
+
const zone = await this.zonePort().findZoneByApex(mailDomain);
|
|
2485
2497
|
if (!zone) {
|
|
2486
2498
|
throw new Error(`Lettermint for ${org.slug}: no Cloudflare zone found for ${mailDomain}`);
|
|
2487
2499
|
}
|
|
2488
|
-
await this.publishLettermintDnsRecords(zone.
|
|
2500
|
+
await this.publishLettermintDnsRecords(zone.name, domain.dnsRecords);
|
|
2489
2501
|
await this.ports.lettermint.verifyDomain(domain.id);
|
|
2490
2502
|
}
|
|
2491
2503
|
/**
|
|
@@ -3934,6 +3946,87 @@ export class FoundryStepExecutor {
|
|
|
3934
3946
|
throw new Error('ai: this executor was built without an ai port (#177)');
|
|
3935
3947
|
return port;
|
|
3936
3948
|
}
|
|
3949
|
+
/**
|
|
3950
|
+
* Every DNS record call runs through edge (`edge.dns`, the platform
|
|
3951
|
+
* credential), adopting live records before it first changes them. `dns` on
|
|
3952
|
+
* the ports is a seam for a host or test that supplies its own {@link DnsPort}.
|
|
3953
|
+
*/
|
|
3954
|
+
dnsPort() {
|
|
3955
|
+
if (this.ports.dns)
|
|
3956
|
+
return this.ports.dns;
|
|
3957
|
+
if (!this.ports.edge) {
|
|
3958
|
+
throw new Error('dns: this executor was built without an EdgePort (F4.dns)');
|
|
3959
|
+
}
|
|
3960
|
+
this.edgeWriteSlot ??= limiter(EDGE_DNS_WRITE_CONCURRENCY);
|
|
3961
|
+
this.edgeDns ??= createEdgeDns(this.ports.edge.dns, { writeSlot: this.edgeWriteSlot });
|
|
3962
|
+
return this.edgeDns;
|
|
3963
|
+
}
|
|
3964
|
+
/**
|
|
3965
|
+
* Every tunnel call runs through edge (`edge.tunnels`, the platform
|
|
3966
|
+
* credential), adopting a live tunnel before it first configures, reads the
|
|
3967
|
+
* token of or deletes it. Shares edge's write slot with {@link dnsPort}.
|
|
3968
|
+
*/
|
|
3969
|
+
tunnelPort() {
|
|
3970
|
+
if (this.ports.tunnels)
|
|
3971
|
+
return this.ports.tunnels;
|
|
3972
|
+
if (!this.ports.edge) {
|
|
3973
|
+
throw new Error('tunnels: this executor was built without an EdgePort (F4.tunnels)');
|
|
3974
|
+
}
|
|
3975
|
+
this.edgeWriteSlot ??= limiter(EDGE_DNS_WRITE_CONCURRENCY);
|
|
3976
|
+
this.edgeTunnels ??= createEdgeTunnels(this.ports.edge.tunnels, {
|
|
3977
|
+
writeSlot: this.edgeWriteSlot,
|
|
3978
|
+
});
|
|
3979
|
+
return this.edgeTunnels;
|
|
3980
|
+
}
|
|
3981
|
+
/**
|
|
3982
|
+
* Every Access call runs through edge (`edge.access`, the platform
|
|
3983
|
+
* credential), adopting a live app before it first adds a policy to or
|
|
3984
|
+
* deletes it. Shares edge's write slot with {@link dnsPort}.
|
|
3985
|
+
*/
|
|
3986
|
+
accessPort() {
|
|
3987
|
+
if (this.ports.access)
|
|
3988
|
+
return this.ports.access;
|
|
3989
|
+
if (!this.ports.edge) {
|
|
3990
|
+
throw new Error('access: this executor was built without an EdgePort (F4.access)');
|
|
3991
|
+
}
|
|
3992
|
+
this.edgeWriteSlot ??= limiter(EDGE_DNS_WRITE_CONCURRENCY);
|
|
3993
|
+
this.edgeAccess ??= createEdgeAccess(this.ports.edge.access, {
|
|
3994
|
+
writeSlot: this.edgeWriteSlot,
|
|
3995
|
+
});
|
|
3996
|
+
return this.edgeAccess;
|
|
3997
|
+
}
|
|
3998
|
+
/**
|
|
3999
|
+
* Every rate limiting rule call runs through edge (`edge.ratelimits`, the
|
|
4000
|
+
* platform credential), adopting the live rule before it first creates it.
|
|
4001
|
+
* Shares edge's write slot with {@link dnsPort}.
|
|
4002
|
+
*/
|
|
4003
|
+
rateLimitPort() {
|
|
4004
|
+
if (this.ports.ratelimits)
|
|
4005
|
+
return this.ports.ratelimits;
|
|
4006
|
+
if (!this.ports.edge) {
|
|
4007
|
+
throw new Error('ratelimits: this executor was built without an EdgePort (F4.ratelimits)');
|
|
4008
|
+
}
|
|
4009
|
+
this.edgeWriteSlot ??= limiter(EDGE_DNS_WRITE_CONCURRENCY);
|
|
4010
|
+
this.edgeRateLimits ??= createEdgeRateLimits(this.ports.edge.ratelimits, {
|
|
4011
|
+
writeSlot: this.edgeWriteSlot,
|
|
4012
|
+
});
|
|
4013
|
+
return this.edgeRateLimits;
|
|
4014
|
+
}
|
|
4015
|
+
/**
|
|
4016
|
+
* Every zone lookup and create runs through edge (`edge.zones`, the
|
|
4017
|
+
* platform credential), adopting a live zone before it first creates it.
|
|
4018
|
+
* Shares edge's write slot with {@link dnsPort}.
|
|
4019
|
+
*/
|
|
4020
|
+
zonePort() {
|
|
4021
|
+
if (this.ports.zones)
|
|
4022
|
+
return this.ports.zones;
|
|
4023
|
+
if (!this.ports.edge) {
|
|
4024
|
+
throw new Error('zones: this executor was built without an EdgePort (F4.zones)');
|
|
4025
|
+
}
|
|
4026
|
+
this.edgeWriteSlot ??= limiter(EDGE_DNS_WRITE_CONCURRENCY);
|
|
4027
|
+
this.edgeZones ??= createEdgeZones(this.ports.edge.zones, { writeSlot: this.edgeWriteSlot });
|
|
4028
|
+
return this.edgeZones;
|
|
4029
|
+
}
|
|
3937
4030
|
async jobsPort(org) {
|
|
3938
4031
|
const port = this.ports.jobsScheduler ?? (await this.ports.platformOrg?.jobs());
|
|
3939
4032
|
if (!port) {
|
|
@@ -4002,7 +4095,7 @@ export class FoundryStepExecutor {
|
|
|
4002
4095
|
}
|
|
4003
4096
|
// Cleared, not deleted (#7 corrections 1): an ingress with only the
|
|
4004
4097
|
// mandatory catch-all routes nothing.
|
|
4005
|
-
await this.
|
|
4098
|
+
await this.tunnelPort().putTunnelConfig(tunnelId, {
|
|
4006
4099
|
ingress: [{ service: 'http_status:404' }],
|
|
4007
4100
|
});
|
|
4008
4101
|
// #235: park stops the connector too -- an ingress with nothing
|
|
@@ -4028,8 +4121,9 @@ export class FoundryStepExecutor {
|
|
|
4028
4121
|
// domain declared still leaves `destroy` a way to recognise the old
|
|
4029
4122
|
// id's CNAMEs as this tunnel's own rather than a foreign record.
|
|
4030
4123
|
const recordedTunnelId = context.recordedIds()[`cloudflare:tunnel:${action.key}`];
|
|
4031
|
-
const
|
|
4032
|
-
const
|
|
4124
|
+
const tunnels = this.tunnelPort();
|
|
4125
|
+
const existing = await tunnels.findTunnelByName(tunnelName);
|
|
4126
|
+
const tunnel = existing ?? (await tunnels.createTunnel(tunnelName));
|
|
4033
4127
|
const previousIds = parseTunnelPreviousIds(context.recordedIds());
|
|
4034
4128
|
if (recordedTunnelId && recordedTunnelId !== tunnel.id) {
|
|
4035
4129
|
previousIds.add(recordedTunnelId);
|
|
@@ -4056,7 +4150,7 @@ export class FoundryStepExecutor {
|
|
|
4056
4150
|
hostname,
|
|
4057
4151
|
service: COOLIFY_PROXY_ORIGIN,
|
|
4058
4152
|
}));
|
|
4059
|
-
await this.
|
|
4153
|
+
await this.tunnelPort().putTunnelConfig(tunnel.id, {
|
|
4060
4154
|
ingress: [...ingress, { service: 'http_status:404' }],
|
|
4061
4155
|
});
|
|
4062
4156
|
// #235: a connector for this org's own tunnel, the same call every
|
|
@@ -4070,7 +4164,7 @@ export class FoundryStepExecutor {
|
|
|
4070
4164
|
const { environment } = await this.ports.coolify.findOrCreateEnvironment(projectUuid, 'production');
|
|
4071
4165
|
await ensureCloudflaredConnector({
|
|
4072
4166
|
coolify: this.ports.coolify,
|
|
4073
|
-
|
|
4167
|
+
tunnels: this.tunnelPort(),
|
|
4074
4168
|
tunnelId: tunnel.id,
|
|
4075
4169
|
appName: `${org.slug}-cloudflared`,
|
|
4076
4170
|
projectUuid,
|
|
@@ -4201,7 +4295,7 @@ export class FoundryStepExecutor {
|
|
|
4201
4295
|
* nothing of this domain's left in that set either way.
|
|
4202
4296
|
*/
|
|
4203
4297
|
async deleteStaleTunnelDnsRecords(tunnelId, previousIds, previousDomain, previousHostnames, context) {
|
|
4204
|
-
const zone = await this.
|
|
4298
|
+
const zone = await this.zonePort().findZoneByApex(previousDomain);
|
|
4205
4299
|
if (!zone)
|
|
4206
4300
|
return;
|
|
4207
4301
|
const dnsTargets = new Set([`${tunnelId}.cfargotunnel.com`]);
|
|
@@ -4209,13 +4303,13 @@ export class FoundryStepExecutor {
|
|
|
4209
4303
|
if (id !== tunnelId)
|
|
4210
4304
|
dnsTargets.add(`${id}.cfargotunnel.com`);
|
|
4211
4305
|
}
|
|
4212
|
-
const
|
|
4306
|
+
const dns = this.dnsPort();
|
|
4213
4307
|
const records = previousHostnames.length > 0
|
|
4214
|
-
? (await Promise.all(previousHostnames.map((hostname) =>
|
|
4215
|
-
: await
|
|
4308
|
+
? (await Promise.all(previousHostnames.map((hostname) => dns.findDnsRecord(zone.name, hostname, 'CNAME')))).flat()
|
|
4309
|
+
: await dns.listDnsRecords(zone.name, { type: 'CNAME' });
|
|
4216
4310
|
for (const record of records) {
|
|
4217
4311
|
if (dnsTargets.has(record.content)) {
|
|
4218
|
-
await
|
|
4312
|
+
await dns.deleteDnsRecord(zone.name, record.id);
|
|
4219
4313
|
}
|
|
4220
4314
|
}
|
|
4221
4315
|
if (previousHostnames.length > 0) {
|
|
@@ -4283,8 +4377,8 @@ export class FoundryStepExecutor {
|
|
|
4283
4377
|
* both read this set.
|
|
4284
4378
|
*/
|
|
4285
4379
|
async publishTunnelDnsRecords(org, domain, tunnelId, previousIds, previousHostnames, context) {
|
|
4286
|
-
const
|
|
4287
|
-
const zone = await
|
|
4380
|
+
const dns = this.dnsPort();
|
|
4381
|
+
const zone = await this.zonePort().findZoneByApex(domain);
|
|
4288
4382
|
if (!zone) {
|
|
4289
4383
|
throw new Error(`cloudflare:tunnel for ${org.slug}: no Cloudflare zone found for ${domain} to point its hostnames at -- cloudflare:zone must provision it first`);
|
|
4290
4384
|
}
|
|
@@ -4296,14 +4390,14 @@ export class FoundryStepExecutor {
|
|
|
4296
4390
|
const toCreate = [];
|
|
4297
4391
|
const toRepoint = [];
|
|
4298
4392
|
for (const hostname of hostnames) {
|
|
4299
|
-
const [a] = await
|
|
4300
|
-
const [aaaa] = await
|
|
4393
|
+
const [a] = await dns.findDnsRecord(zone.name, hostname, 'A');
|
|
4394
|
+
const [aaaa] = await dns.findDnsRecord(zone.name, hostname, 'AAAA');
|
|
4301
4395
|
const foreign = a ?? aaaa;
|
|
4302
4396
|
if (foreign) {
|
|
4303
4397
|
conflicts.push(`${hostname} already has a ${foreign.type} record`);
|
|
4304
4398
|
continue;
|
|
4305
4399
|
}
|
|
4306
|
-
const [cname] = await
|
|
4400
|
+
const [cname] = await dns.findDnsRecord(zone.name, hostname, 'CNAME');
|
|
4307
4401
|
if (cname && cname.content !== dnsTarget) {
|
|
4308
4402
|
if (previousTargets.has(cname.content)) {
|
|
4309
4403
|
toRepoint.push({ hostname, recordId: cname.id });
|
|
@@ -4335,7 +4429,7 @@ export class FoundryStepExecutor {
|
|
|
4335
4429
|
for (const hostname of settled)
|
|
4336
4430
|
recordTracked(hostname);
|
|
4337
4431
|
for (const hostname of toCreate) {
|
|
4338
|
-
await
|
|
4432
|
+
await dns.createDnsRecord(zone.name, {
|
|
4339
4433
|
type: 'CNAME',
|
|
4340
4434
|
name: hostname,
|
|
4341
4435
|
content: dnsTarget,
|
|
@@ -4344,7 +4438,7 @@ export class FoundryStepExecutor {
|
|
|
4344
4438
|
recordTracked(hostname);
|
|
4345
4439
|
}
|
|
4346
4440
|
for (const { hostname, recordId } of toRepoint) {
|
|
4347
|
-
await
|
|
4441
|
+
await dns.updateDnsRecord(zone.name, recordId, {
|
|
4348
4442
|
type: 'CNAME',
|
|
4349
4443
|
name: hostname,
|
|
4350
4444
|
content: dnsTarget,
|
|
@@ -4363,9 +4457,9 @@ export class FoundryStepExecutor {
|
|
|
4363
4457
|
// tracking under `domain`, so it is dropped from the shared set either
|
|
4364
4458
|
// way.
|
|
4365
4459
|
for (const hostname of droppedHostnames) {
|
|
4366
|
-
const [cname] = await
|
|
4460
|
+
const [cname] = await dns.findDnsRecord(zone.name, hostname, 'CNAME');
|
|
4367
4461
|
if (cname && (cname.content === dnsTarget || previousTargets.has(cname.content))) {
|
|
4368
|
-
await
|
|
4462
|
+
await dns.deleteDnsRecord(zone.name, cname.id);
|
|
4369
4463
|
}
|
|
4370
4464
|
tracked.delete(trackedTunnelDnsEntry(domain, hostname));
|
|
4371
4465
|
context.recordScopeId(CLOUDFLARE_TUNNEL_DNS_TRACKED_SCOPE_KEY, trackedTunnelDnsState(tracked));
|
|
@@ -4399,7 +4493,7 @@ export class FoundryStepExecutor {
|
|
|
4399
4493
|
*/
|
|
4400
4494
|
async cloudflareZone(action, org, context) {
|
|
4401
4495
|
const domain = action.key;
|
|
4402
|
-
const cf = this.
|
|
4496
|
+
const cf = this.zonePort();
|
|
4403
4497
|
const existing = await cf.findZoneByApex(domain);
|
|
4404
4498
|
if (existing) {
|
|
4405
4499
|
const owner = await this.ports.declaredOrgStore.findOrgIdForZone(existing.id);
|
|
@@ -4600,12 +4694,12 @@ export class FoundryStepExecutor {
|
|
|
4600
4694
|
* publishes it.
|
|
4601
4695
|
*/
|
|
4602
4696
|
async publishMailDnsRecords(org, result) {
|
|
4603
|
-
const zone = await this.
|
|
4697
|
+
const zone = await this.zonePort().findZoneByApex(result.domain);
|
|
4604
4698
|
if (!zone) {
|
|
4605
4699
|
throw new Error(`coolify:mail for ${org.slug}: no Cloudflare zone found for ${result.domain}`);
|
|
4606
4700
|
}
|
|
4607
4701
|
for (const record of result.records) {
|
|
4608
|
-
await this.upsertMailDnsRecord(zone.
|
|
4702
|
+
await this.upsertMailDnsRecord(zone.name, record);
|
|
4609
4703
|
}
|
|
4610
4704
|
}
|
|
4611
4705
|
/**
|
|
@@ -4619,7 +4713,7 @@ export class FoundryStepExecutor {
|
|
|
4619
4713
|
* a record someone proxied by hand is put back. An `MX` record carries the
|
|
4620
4714
|
* priority it was given, which Cloudflare requires.
|
|
4621
4715
|
*
|
|
4622
|
-
* Found by an exact name+type match (`
|
|
4716
|
+
* Found by an exact name+type match (`DnsPort.findDnsRecord`'s own
|
|
4623
4717
|
* doc comment) so a stale record of a different type is never taken for a
|
|
4624
4718
|
* match. A name can hold several `TXT` records (an apex often holds a site
|
|
4625
4719
|
* verification beside SPF), so a `TXT` record only replaces one that
|
|
@@ -4628,8 +4722,8 @@ export class FoundryStepExecutor {
|
|
|
4628
4722
|
* whatever it was.
|
|
4629
4723
|
*/
|
|
4630
4724
|
async upsertMailDnsRecord(zoneId, record) {
|
|
4631
|
-
const
|
|
4632
|
-
const found = await
|
|
4725
|
+
const dns = this.dnsPort();
|
|
4726
|
+
const found = await dns.findDnsRecord(zoneId, record.name, record.type);
|
|
4633
4727
|
const existing = record.type === 'TXT'
|
|
4634
4728
|
? found.find((r) => txtRecordTag(r.content) === txtRecordTag(record.value))
|
|
4635
4729
|
: found[0];
|
|
@@ -4641,10 +4735,10 @@ export class FoundryStepExecutor {
|
|
|
4641
4735
|
...(record.priority === undefined ? {} : { priority: record.priority }),
|
|
4642
4736
|
};
|
|
4643
4737
|
if (existing) {
|
|
4644
|
-
await
|
|
4738
|
+
await dns.updateDnsRecord(zoneId, existing.id, rec);
|
|
4645
4739
|
}
|
|
4646
4740
|
else {
|
|
4647
|
-
await
|
|
4741
|
+
await dns.createDnsRecord(zoneId, rec);
|
|
4648
4742
|
}
|
|
4649
4743
|
}
|
|
4650
4744
|
/**
|
|
@@ -4700,13 +4794,13 @@ export class FoundryStepExecutor {
|
|
|
4700
4794
|
* `dkim_secondary`, none of them SPF -- so that merge has no case left to
|
|
4701
4795
|
* handle here and is dropped rather than kept as dead code.
|
|
4702
4796
|
*/
|
|
4703
|
-
async publishLettermintDnsRecords(
|
|
4797
|
+
async publishLettermintDnsRecords(zone, records) {
|
|
4704
4798
|
for (const record of records) {
|
|
4705
4799
|
if (record.type === 'TXT' && record.value.toLowerCase().startsWith('v=dmarc1')) {
|
|
4706
|
-
await this.ensureLettermintDmarcRecord(this.
|
|
4800
|
+
await this.ensureLettermintDmarcRecord(this.dnsPort(), zone, record);
|
|
4707
4801
|
continue;
|
|
4708
4802
|
}
|
|
4709
|
-
await this.upsertMailDnsRecord(
|
|
4803
|
+
await this.upsertMailDnsRecord(zone, record);
|
|
4710
4804
|
}
|
|
4711
4805
|
}
|
|
4712
4806
|
/**
|
|
@@ -4714,11 +4808,11 @@ export class FoundryStepExecutor {
|
|
|
4714
4808
|
* already set (`p=quarantine`/`p=reject`) should not be relaxed back to
|
|
4715
4809
|
* Lettermint's own `p=none` default.
|
|
4716
4810
|
*/
|
|
4717
|
-
async ensureLettermintDmarcRecord(
|
|
4718
|
-
const hasDmarc = (await
|
|
4811
|
+
async ensureLettermintDmarcRecord(dns, zoneId, record) {
|
|
4812
|
+
const hasDmarc = (await dns.findDnsRecord(zoneId, record.name, 'TXT')).some((r) => txtRecordTag(r.content) === 'v=dmarc1');
|
|
4719
4813
|
if (hasDmarc)
|
|
4720
4814
|
return;
|
|
4721
|
-
await
|
|
4815
|
+
await dns.createDnsRecord(zoneId, {
|
|
4722
4816
|
type: 'TXT',
|
|
4723
4817
|
name: record.name,
|
|
4724
4818
|
content: record.value,
|
|
@@ -4845,7 +4939,7 @@ export class FoundryStepExecutor {
|
|
|
4845
4939
|
await ai.setOrganisationOffers(org.id, action.desired.offers ?? []);
|
|
4846
4940
|
return { resourceId: provisioned.organisationId };
|
|
4847
4941
|
}
|
|
4848
|
-
async aiBudget(action, org) {
|
|
4942
|
+
async aiBudget(action, org, context) {
|
|
4849
4943
|
// #484: ai/packages/sdk/src/types.ts:134-137's UpdateBudget.limitMicros
|
|
4850
4944
|
// is a required string -- there is no "uncapped" this API can express
|
|
4851
4945
|
// yet (Finding, this campaign's report) -- so a null `desired.limitMicros`
|
|
@@ -4856,6 +4950,10 @@ export class FoundryStepExecutor {
|
|
|
4856
4950
|
const limitMicros = action.desired.limitMicros ?? DEFAULT_AI_LIMIT_MICROS;
|
|
4857
4951
|
const ai = await this.aiPort();
|
|
4858
4952
|
await ai.updateBudget(org.id, { keyId: null, limitMicros });
|
|
4953
|
+
// #515: recorded only once `updateBudget` has actually succeeded, so a
|
|
4954
|
+
// crash or a thrown call before this line leaves nothing recorded and
|
|
4955
|
+
// the step replans, rather than recording a limit that was never applied.
|
|
4956
|
+
context.recordScopeId(AI_BUDGET_LIMIT_SCOPE_KEY, limitMicros);
|
|
4859
4957
|
return { resourceId: org.id };
|
|
4860
4958
|
}
|
|
4861
4959
|
/**
|
|
@@ -5115,7 +5213,7 @@ export class FoundryStepExecutor {
|
|
|
5115
5213
|
throw error;
|
|
5116
5214
|
}
|
|
5117
5215
|
}
|
|
5118
|
-
const appId = recorded[
|
|
5216
|
+
const appId = recorded[edgeKeyAppRecordKey(org, entryKey)];
|
|
5119
5217
|
if (appId) {
|
|
5120
5218
|
// A revoked key's env values are dead, not merely stale -- remove
|
|
5121
5219
|
// them so nothing reads a credential edge has already revoked. A
|
|
@@ -5149,10 +5247,14 @@ export class FoundryStepExecutor {
|
|
|
5149
5247
|
context.recordScopeId(entryEdgeRetiredScopeKey(entryKey), keyId);
|
|
5150
5248
|
return {};
|
|
5151
5249
|
}
|
|
5152
|
-
|
|
5153
|
-
|
|
5250
|
+
if (entryKey === BOULE_EDGE_ENTRY_KEY) {
|
|
5251
|
+
if (!org.domain)
|
|
5252
|
+
throw new Error(`${at}: Boule's edge key needs org.domain`);
|
|
5253
|
+
}
|
|
5254
|
+
else if (!org.hosting.entries.some((e) => e.key === entryKey && e.uses?.edge)) {
|
|
5154
5255
|
throw new Error(`${at}: the entry no longer declares uses.edge`);
|
|
5155
|
-
|
|
5256
|
+
}
|
|
5257
|
+
const appId = context.recordedIds()[edgeKeyAppRecordKey(org, entryKey)];
|
|
5156
5258
|
if (!appId)
|
|
5157
5259
|
throw new Error(`${at}: the entry's app is not recorded yet`);
|
|
5158
5260
|
if (!org.domain) {
|
|
@@ -5590,15 +5692,15 @@ export class FoundryStepExecutor {
|
|
|
5590
5692
|
}
|
|
5591
5693
|
for (const [dnsDomain, dnsHostnames] of hostnamesByDomain) {
|
|
5592
5694
|
try {
|
|
5593
|
-
const zone = await this.
|
|
5695
|
+
const zone = await this.zonePort().findZoneByApex(dnsDomain);
|
|
5594
5696
|
if (zone) {
|
|
5595
|
-
const
|
|
5697
|
+
const dns = this.dnsPort();
|
|
5596
5698
|
const records = dnsHostnames.length > 0
|
|
5597
|
-
? (await Promise.all(dnsHostnames.map((hostname) =>
|
|
5598
|
-
: await
|
|
5699
|
+
? (await Promise.all(dnsHostnames.map((hostname) => dns.findDnsRecord(zone.name, hostname, 'CNAME')))).flat()
|
|
5700
|
+
: await dns.listDnsRecords(zone.name, { type: 'CNAME' });
|
|
5599
5701
|
for (const record of records) {
|
|
5600
5702
|
if (dnsTargets.has(record.content)) {
|
|
5601
|
-
await
|
|
5703
|
+
await dns.deleteDnsRecord(zone.name, record.id);
|
|
5602
5704
|
}
|
|
5603
5705
|
}
|
|
5604
5706
|
}
|
|
@@ -5608,11 +5710,11 @@ export class FoundryStepExecutor {
|
|
|
5608
5710
|
throw error;
|
|
5609
5711
|
}
|
|
5610
5712
|
}
|
|
5611
|
-
await this.
|
|
5713
|
+
await this.tunnelPort().deleteTunnel(target.id);
|
|
5612
5714
|
return;
|
|
5613
5715
|
}
|
|
5614
5716
|
case 'cloudflare:access':
|
|
5615
|
-
await this.
|
|
5717
|
+
await this.accessPort().deleteAccessApp(target.id);
|
|
5616
5718
|
return;
|
|
5617
5719
|
case 'ai:key': {
|
|
5618
5720
|
// F6a: an entry's key (`<orgId>:<entry>`). REVOKE SEAM (ai#157): a
|
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, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_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_INTERNAL_URL_SCOPE_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, PlatformEdgeDeps, PlatformEdgeRuntime, } from './platform-providers.js';
|
|
18
|
+
export type { PlatformAuthDeps, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, PlatformEdgeDeps, PlatformDnsFactory, PlatformAccessFactory, PlatformAccessPort, PlatformTunnelsFactory, 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';
|
|
@@ -27,7 +27,7 @@ export { MissingCallsSfuConnectionError, applyCallsSfuPlatformStep } from './pla
|
|
|
27
27
|
export type { MailCopyJob, MailCopyPort, MailCopyStepResult } from './mail-copy.js';
|
|
28
28
|
export { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, MissingMailCopyAccountsError, MissingMailCopySourceTokenError, MissingMailCopyStalwartTokenError, parseMailCopyCompleted, runMailCopyStep, } from './mail-copy.js';
|
|
29
29
|
export type { ScopeResolver, RecordedIds } from './providers-readers.js';
|
|
30
|
-
export { ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
|
|
30
|
+
export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
|
|
31
31
|
export type { AgoraHostPort, AgoraHostValues } from './agora.js';
|
|
32
32
|
export type { BrokerPort } from './broker-entries.js';
|
|
33
33
|
export { PlatformBrokerPort } from './broker-entries.js';
|
|
@@ -39,6 +39,18 @@ export type { MailAllowlistApp } from './mail-identity-allowlist.js';
|
|
|
39
39
|
export { checkMailAppsAllowlist } from './mail-identity-allowlist.js';
|
|
40
40
|
export type { CoolifyPort, CloudflarePort, ZitadelPort, CallsSfuPort, JobsSchedulerPort, FilesOrganisationPort, AiOrganisationPort, FoundryStepExecutorPorts, PlatformOrgPortSource, } from './executor.js';
|
|
41
41
|
export { FoundryStepExecutor, NotBuiltError, healthUrls } from './executor.js';
|
|
42
|
+
export { createEdgeDns, EDGE_DNS_WRITE_CONCURRENCY } from './edge-dns.js';
|
|
43
|
+
export type { DnsPort, DnsRecordInput, DnsRecordSummary, EdgePlatformDns } from './edge-dns.js';
|
|
44
|
+
export { createEdgeAccess } from './edge-access.js';
|
|
45
|
+
export type { AccessAppSummary, AccessDecision, AccessPolicyRule, AccessPort, EdgePlatformAccess, } from './edge-access.js';
|
|
46
|
+
export { createEdgeWorkers } from './edge-workers.js';
|
|
47
|
+
export type { EdgePlatformWorkers, EdgeWorkersOptions } from './edge-workers.js';
|
|
48
|
+
export { createEdgeRateLimits } from './edge-ratelimits.js';
|
|
49
|
+
export type { EdgePlatformRateLimits, EdgeRateLimitsOptions, RateLimitPort, RateLimitRuleInput, } from './edge-ratelimits.js';
|
|
50
|
+
export { createEdgeZones } from './edge-zones.js';
|
|
51
|
+
export type { EdgePlatformZones, EdgeZonesOptions, ZonePort } from './edge-zones.js';
|
|
52
|
+
export { createEdgeTunnels } from './edge-tunnels.js';
|
|
53
|
+
export type { EdgePlatformTunnels, TunnelIngress, TunnelPort, TunnelSummary, } from './edge-tunnels.js';
|
|
42
54
|
export type { ZitadelListAppsRawApp } from './executor.js';
|
|
43
55
|
export { adaptZitadelListApps } from './executor.js';
|
|
44
56
|
export { HttpJobsSchedulerClient } from './jobs-scheduler-http.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, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_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_INTERNAL_URL_SCOPE_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';
|
|
@@ -14,11 +14,17 @@ export { createZitadelAdminMinter } from './zitadel-machine-user.js';
|
|
|
14
14
|
export { createZitadelProjectRoles } from './zitadel-project-roles.js';
|
|
15
15
|
export { MissingCallsSfuConnectionError, applyCallsSfuPlatformStep } from './platform-sfu.js';
|
|
16
16
|
export { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, MissingMailCopyAccountsError, MissingMailCopySourceTokenError, MissingMailCopyStalwartTokenError, parseMailCopyCompleted, runMailCopyStep, } from './mail-copy.js';
|
|
17
|
-
export { ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
|
|
17
|
+
export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeScopeResolver, storeRecordedIds, } from './providers-readers.js';
|
|
18
18
|
export { PlatformBrokerPort } from './broker-entries.js';
|
|
19
19
|
export { loginHostnameFor } from './login-host.js';
|
|
20
20
|
export { checkMailIdentityAudience } from './mail-identity.js';
|
|
21
21
|
export { checkMailAppsAllowlist } from './mail-identity-allowlist.js';
|
|
22
22
|
export { FoundryStepExecutor, NotBuiltError, healthUrls } from './executor.js';
|
|
23
|
+
export { createEdgeDns, EDGE_DNS_WRITE_CONCURRENCY } from './edge-dns.js';
|
|
24
|
+
export { createEdgeAccess } from './edge-access.js';
|
|
25
|
+
export { createEdgeWorkers } from './edge-workers.js';
|
|
26
|
+
export { createEdgeRateLimits } from './edge-ratelimits.js';
|
|
27
|
+
export { createEdgeZones } from './edge-zones.js';
|
|
28
|
+
export { createEdgeTunnels } from './edge-tunnels.js';
|
|
23
29
|
export { adaptZitadelListApps } from './executor.js';
|
|
24
30
|
export { HttpJobsSchedulerClient } from './jobs-scheduler-http.js';
|