@wtfalch/foundry 0.16.16 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apply.d.ts +22 -1
- package/dist/bootstrap/bootstrap.d.ts +6 -1
- package/dist/bootstrap/bootstrap.js +26 -3
- package/dist/bootstrap/declare.d.ts +11 -1
- package/dist/bootstrap/declare.js +18 -6
- package/dist/index.d.ts +11 -6
- package/dist/index.js +8 -1
- package/dist/migrations/0038_calls_version_pin.sql +8 -0
- package/dist/migrations/0039_destroy_record.sql +7 -0
- package/dist/migrations/0040_company_included_uuid.sql +72 -0
- package/dist/migrations/0041_company_included_uuid_keys.sql +132 -0
- package/dist/migrations/0042_mutation_leases.sql +12 -0
- package/dist/migrations/0043_platform_names.sql +25 -0
- package/dist/migrations/0044_platform_mail_lettermint_project.sql +11 -0
- package/dist/org.d.ts +128 -3
- package/dist/org.js +186 -1
- package/dist/plan/adopt.d.ts +76 -0
- package/dist/plan/adopt.js +184 -0
- package/dist/plan/agora.d.ts +21 -0
- package/dist/plan/agora.js +37 -0
- package/dist/plan/broker-entries.d.ts +9 -1
- package/dist/plan/broker-entries.js +13 -5
- package/dist/plan/chat-matrix.d.ts +1 -2
- package/dist/plan/chat-matrix.js +15 -4
- package/dist/plan/core-files.d.ts +25 -0
- package/dist/plan/core-files.js +25 -0
- package/dist/plan/diff.d.ts +27 -3
- package/dist/plan/diff.js +264 -86
- package/dist/plan/edge-worker.d.ts +114 -0
- package/dist/plan/edge-worker.js +201 -0
- package/dist/plan/entry-ai-key.d.ts +3 -3
- package/dist/plan/entry-files-key.d.ts +3 -3
- package/dist/plan/entry-names-key.d.ts +73 -0
- package/dist/plan/entry-names-key.js +236 -0
- package/dist/plan/executor.d.ts +292 -24
- package/dist/plan/executor.js +745 -77
- package/dist/plan/index.d.ts +6 -2
- package/dist/plan/index.js +3 -1
- package/dist/plan/live.d.ts +9 -2
- package/dist/plan/login-host.d.ts +13 -2
- package/dist/plan/login-host.js +26 -7
- package/dist/plan/plan-apply.d.ts +153 -6
- package/dist/plan/plan-apply.js +639 -78
- package/dist/plan/platform-diff.d.ts +106 -1
- package/dist/plan/platform-diff.js +182 -2
- package/dist/plan/platform-org.d.ts +45 -1
- package/dist/plan/platform-org.js +34 -3
- package/dist/plan/platform-providers.d.ts +45 -1
- package/dist/plan/platform-providers.js +585 -16
- package/dist/plan/providers-readers.d.ts +2 -0
- package/dist/plan/providers-readers.js +4 -0
- package/dist/plan/rollout.d.ts +52 -0
- package/dist/plan/rollout.js +37 -0
- package/dist/plan/step-system.d.ts +22 -0
- package/dist/plan/step-system.js +38 -0
- package/dist/plan.d.ts +3 -0
- package/dist/platform.d.ts +22 -3
- package/dist/platform.js +6 -0
- package/dist/state.d.ts +13 -0
- package/dist/store/audit.js +15 -1
- package/dist/store/postgres.d.ts +1 -1
- package/dist/store/postgres.js +142 -15
- package/dist/store/schema.d.ts +202 -5
- package/dist/store/schema.js +18 -1
- package/dist/store.d.ts +47 -4
- package/dist/store.js +9 -1
- package/package.json +1 -1
package/dist/plan/executor.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { OrgMailOptions, OrgMailOrg, OrgMailResult } from '@wtfalch/email/p
|
|
|
2
2
|
import type { DeclaredOrg, HostingBuildPack } from '../org.js';
|
|
3
3
|
import type { DeclaredOrgStore } from '../store.js';
|
|
4
4
|
import { type BrokerPort } from './broker-entries.js';
|
|
5
|
-
import type
|
|
5
|
+
import { type StepAction } from './diff.js';
|
|
6
6
|
import { type LoginHostBindingsPort } from './login-host.js';
|
|
7
7
|
import { type MailCopyPort } from './mail-copy.js';
|
|
8
8
|
import type { DestroyTarget, StepDestroyer, StepExecutionContext, StepExecutor } from './plan-apply.js';
|
|
@@ -80,11 +80,25 @@ export interface CoolifyPort {
|
|
|
80
80
|
* `findOrCreateCmsApp`'s own check of whether an app it found by name
|
|
81
81
|
* (rather than just created) needs starting again (#116 review round 3,
|
|
82
82
|
* Finding 1).
|
|
83
|
+
*
|
|
84
|
+
* #480: also the rolling-update/health-check settings `updateApplication`
|
|
85
|
+
* patches on `platform:archon` (`is_consistent_container_name_enabled`,
|
|
86
|
+
* `custom_network_aliases`, `health_check_enabled`), so a drift check can
|
|
87
|
+
* tell a setting changed back by hand from one this run already applied.
|
|
88
|
+
* `is_consistent_container_name_enabled` comes back nested under
|
|
89
|
+
* `settings`, not at the top level -- confirmed against live Coolify
|
|
90
|
+
* 4.3.23 (PR #482 review); `custom_network_aliases` and
|
|
91
|
+
* `health_check_enabled` are both top-level there.
|
|
83
92
|
*/
|
|
84
93
|
getApplication(uuid: string): Promise<{
|
|
85
94
|
uuid: string;
|
|
86
95
|
status?: string;
|
|
87
96
|
custom_labels?: string | null;
|
|
97
|
+
settings?: {
|
|
98
|
+
is_consistent_container_name_enabled?: boolean;
|
|
99
|
+
};
|
|
100
|
+
custom_network_aliases?: string | null;
|
|
101
|
+
health_check_enabled?: boolean;
|
|
88
102
|
}>;
|
|
89
103
|
createGithubAppApplication(input: {
|
|
90
104
|
project_uuid: string;
|
|
@@ -183,10 +197,19 @@ export interface CoolifyPort {
|
|
|
183
197
|
* `is_preview === false` the same way a deployed-config reader must.
|
|
184
198
|
*/
|
|
185
199
|
listEnvs(applicationUuid: string): Promise<Array<{
|
|
200
|
+
uuid?: string;
|
|
186
201
|
key: string;
|
|
187
202
|
value: string;
|
|
188
203
|
is_preview: boolean;
|
|
189
204
|
}>>;
|
|
205
|
+
/**
|
|
206
|
+
* #436 round 2: removes one environment variable row by the `uuid`
|
|
207
|
+
* `listEnvs` names it under -- `bulkUpsertEnvs` only adds or updates, so
|
|
208
|
+
* this is the only way to make a var foundry wrote in an earlier release
|
|
209
|
+
* actually go away, rather than leaving it set to a value nothing reads
|
|
210
|
+
* any more (`provisionPlatformArchon`'s stale `FILES_MANAGEMENT_KEY`).
|
|
211
|
+
*/
|
|
212
|
+
deleteEnv(applicationUuid: string, envUuid: string): Promise<unknown>;
|
|
190
213
|
/** Escape hatch (`CoolifyClient.raw`) — used for stop/start, which have no typed method yet. */
|
|
191
214
|
raw(method: string, path: string, body?: unknown): Promise<unknown>;
|
|
192
215
|
/**
|
|
@@ -526,6 +549,72 @@ export interface CloudflarePort {
|
|
|
526
549
|
*/
|
|
527
550
|
request<T>(method: string, path: string, body?: unknown, missingOkay?: boolean): Promise<T>;
|
|
528
551
|
}
|
|
552
|
+
/**
|
|
553
|
+
* One Cloudflare Workers rate limiting binding, Cloudflare's own "simple"
|
|
554
|
+
* shape -- the metadata part {@link WorkersPort.uploadScript} sends beside
|
|
555
|
+
* the module (#318).
|
|
556
|
+
*/
|
|
557
|
+
export interface WorkersRateLimitBinding {
|
|
558
|
+
type: 'ratelimit';
|
|
559
|
+
name: string;
|
|
560
|
+
namespace_id: string;
|
|
561
|
+
simple: {
|
|
562
|
+
limit: number;
|
|
563
|
+
period: number;
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* The Cloudflare Workers calls `cloudflareWorker` makes (#318, F8): deploys
|
|
568
|
+
* a plain Worker from an entry's `edge` declaration and routes it on the
|
|
569
|
+
* org's own zone. Optional on {@link FoundryStepExecutorPorts} -- a host
|
|
570
|
+
* with no Workers rights on its Cloudflare token (the issue's own "Human
|
|
571
|
+
* steps": the token needs them added first) wires none, and the planner
|
|
572
|
+
* reports every `edge` entry blocked instead of planning a step that would
|
|
573
|
+
* throw (`FoundryPlanApplyOptions.edgeWorkers`).
|
|
574
|
+
*
|
|
575
|
+
* `uploadScript` takes the *declared artifact reference*, not bytes:
|
|
576
|
+
* fetching `artifactUrl` and verifying it against `artifactSha256` happens
|
|
577
|
+
* behind this port, never in `cloudflareWorker` or anywhere else in this
|
|
578
|
+
* package (estate DECISIONS.md, 2026-10-04, "offline.tf Worker setup" --
|
|
579
|
+
* "Foundry consumes and deploys the artifact through the provider
|
|
580
|
+
* boundary... No manual build step and no source checkout/bundling in
|
|
581
|
+
* Foundry"). The real adapter refuses a fetched body whose digest does not
|
|
582
|
+
* match. The upload itself is a multipart request Cloudflare's API wants (a
|
|
583
|
+
* `metadata` part naming `main_module` and `bindings`, plus the module) --
|
|
584
|
+
* `CloudflarePort.request` sends JSON only, so this port names the call
|
|
585
|
+
* directly rather than forcing it through that escape hatch.
|
|
586
|
+
*
|
|
587
|
+
* `deleteRoute`/`deleteScript` are idempotent: deleting a route or script
|
|
588
|
+
* that is already gone resolves rather than throwing, the same contract
|
|
589
|
+
* `CloudflarePort.deleteDnsRecord`/`deleteAccessApp` give their own
|
|
590
|
+
* already-gone case via `isNotFoundError` at the call site -- here it is
|
|
591
|
+
* the port's own job, since `cloudflareWorker` deletes a route or script by
|
|
592
|
+
* id/name it already recorded, with nothing live to re-check first.
|
|
593
|
+
*/
|
|
594
|
+
export interface WorkersPort {
|
|
595
|
+
/** Whether a script named `name` already exists -- checked only on an entry's first-ever apply, so `cloudflareWorker` can refuse to overwrite a script it did not create instead of PUTting over it by its deterministic name. */
|
|
596
|
+
scriptExists(name: string): Promise<boolean>;
|
|
597
|
+
uploadScript(input: {
|
|
598
|
+
name: string;
|
|
599
|
+
artifactUrl: string;
|
|
600
|
+
artifactSha256: string;
|
|
601
|
+
compatibilityDate: string;
|
|
602
|
+
bindings?: WorkersRateLimitBinding[];
|
|
603
|
+
}): Promise<void>;
|
|
604
|
+
/** The script name currently bound to `pattern` in `zoneId`, or `undefined` for no route there. */
|
|
605
|
+
findRoute(zoneId: string, pattern: string): Promise<{
|
|
606
|
+
id: string;
|
|
607
|
+
script: string;
|
|
608
|
+
} | undefined>;
|
|
609
|
+
/** Creates the route if absent; `cloudflareWorker` only calls this once `findRoute` has confirmed the pattern is foundry's own or unclaimed. */
|
|
610
|
+
createRoute(zoneId: string, pattern: string, script: string): Promise<{
|
|
611
|
+
id: string;
|
|
612
|
+
}>;
|
|
613
|
+
/** A no-op, not a throw, when `routeId` is already gone. */
|
|
614
|
+
deleteRoute(zoneId: string, routeId: string): Promise<void>;
|
|
615
|
+
/** A no-op, not a throw, when `name` is already gone. */
|
|
616
|
+
deleteScript(name: string): Promise<void>;
|
|
617
|
+
}
|
|
529
618
|
/**
|
|
530
619
|
* One DNS record a Lettermint domain's own response says must be published --
|
|
531
620
|
* `purpose` is Lettermint's own label (`return_path`, `dmarc`, `dkim_legacy`,
|
|
@@ -1049,16 +1138,6 @@ export interface ZitadelListAppsRawApp {
|
|
|
1049
1138
|
* `signManagementRequest`/`AssertionTrust`). A host can construct this without
|
|
1050
1139
|
* any human session: it only needs the Ed25519 private key (`SigningIdentity`)
|
|
1051
1140
|
* an operator's `MANAGEMENT_PUBLIC_KEYS`/`MANAGEMENT_ISSUER` pins.
|
|
1052
|
-
*
|
|
1053
|
-
* #177: `setQuota`/`setBudget` (`files/packages/sdk/src/client.ts:154-166`)
|
|
1054
|
-
* used to live on a separate `FilesOrgLimits` a host built per org through a
|
|
1055
|
-
* `filesLimitsFor(orgId)` factory on `FoundryStepExecutorPorts` — ceremony
|
|
1056
|
-
* left over from nothing but currying an id this port's own callers already
|
|
1057
|
-
* have in hand. The signed claim's `organisation` field selects the org per
|
|
1058
|
-
* call, so one shared keypair signs for every org already, the same signer
|
|
1059
|
-
* `provisionOrganisation` uses; there was never a per-org client to build,
|
|
1060
|
-
* only an org id to pass. Both methods now take it explicitly, like
|
|
1061
|
-
* `provisionOrganisation` always did, and `filesLimitsFor` is gone.
|
|
1062
1141
|
*/
|
|
1063
1142
|
export interface FilesOrganisationPort {
|
|
1064
1143
|
provisionOrganisation(input: {
|
|
@@ -1088,9 +1167,6 @@ export interface FilesOrganisationPort {
|
|
|
1088
1167
|
secret: string;
|
|
1089
1168
|
};
|
|
1090
1169
|
}>;
|
|
1091
|
-
/** #27, #177: the org's own spend limit on Files -- see this interface's own doc comment for why `organisationId` is explicit here. */
|
|
1092
|
-
setQuota(organisationId: string, limitBytes: string | null): Promise<unknown>;
|
|
1093
|
-
setBudget(organisationId: string, limitMicros: string | null): Promise<unknown>;
|
|
1094
1170
|
/**
|
|
1095
1171
|
* F6b: mints the organisation's own key, matching files'
|
|
1096
1172
|
* `POST /v1/manage/organisations/:organisationId/keys` (SDK
|
|
@@ -1251,16 +1327,47 @@ export interface AiOrganisationPort {
|
|
|
1251
1327
|
* `ai/packages/sdk/src/client.ts:94-101`'s `updateBudget` (`UpdateBudget`
|
|
1252
1328
|
* from `ai/packages/sdk/src/types.ts:134-137`). Used to live on a separate
|
|
1253
1329
|
* `AiOrgLimits` a host built per org through an `aiLimitsFor(orgId)`
|
|
1254
|
-
* factory on `FoundryStepExecutorPorts` --
|
|
1255
|
-
*
|
|
1256
|
-
* `
|
|
1257
|
-
*
|
|
1330
|
+
* factory on `FoundryStepExecutorPorts` -- ceremony left over from nothing
|
|
1331
|
+
* but currying an id this port's own callers already have in hand.
|
|
1332
|
+
* `organisationId` is explicit here now, like every other method on this
|
|
1333
|
+
* port.
|
|
1258
1334
|
*/
|
|
1259
1335
|
updateBudget(organisationId: string, input: {
|
|
1260
1336
|
keyId: string | null;
|
|
1261
1337
|
limitMicros: string;
|
|
1262
1338
|
}): Promise<unknown>;
|
|
1263
1339
|
}
|
|
1340
|
+
/**
|
|
1341
|
+
* #317 (F7): the names service's own management API
|
|
1342
|
+
* (https://github.com/wtfalch/names, live on main, offering `mintKey`,
|
|
1343
|
+
* `revokeKey` and `setReservedLabels`) -- this port states the shape this
|
|
1344
|
+
* executor assumes of it. `organisationId` is the org itself: unlike
|
|
1345
|
+
* `AiOrganisationPort.mintNestedKey`, names takes no separate minter key
|
|
1346
|
+
* id, so `namesEntryKey` mints directly below the org. A real adapter
|
|
1347
|
+
* wiring this port into `platform-providers.ts` is tracked separately
|
|
1348
|
+
* (https://github.com/wtfalch/archon/issues/339).
|
|
1349
|
+
*/
|
|
1350
|
+
export interface NamesPort {
|
|
1351
|
+
/**
|
|
1352
|
+
* `domain` is required: names's own `mintKey` refuses a request without a
|
|
1353
|
+
* lowercase hostname and binds the key to it
|
|
1354
|
+
* (`wtfalch/names`'s `packages/service/src/keys.ts`) -- `namesEntryKey`
|
|
1355
|
+
* passes `org.domain`, which `validateEntryNames` already requires before
|
|
1356
|
+
* `uses.names` is accepted.
|
|
1357
|
+
*/
|
|
1358
|
+
mintKey(input: {
|
|
1359
|
+
organisationId: string;
|
|
1360
|
+
domain: string;
|
|
1361
|
+
name: string;
|
|
1362
|
+
expiresDays: number;
|
|
1363
|
+
}): Promise<{
|
|
1364
|
+
id: string;
|
|
1365
|
+
secret: string;
|
|
1366
|
+
}>;
|
|
1367
|
+
revokeKey(organisationId: string, keyId: string): Promise<void>;
|
|
1368
|
+
/** Refuses a label any hosting entry of `organisationId` already holds. Rerun whenever the org's own label set changes. */
|
|
1369
|
+
setReservedLabels(organisationId: string, labels: string[]): Promise<void>;
|
|
1370
|
+
}
|
|
1264
1371
|
/**
|
|
1265
1372
|
* #173: the jobs scheduler's own admin API
|
|
1266
1373
|
* (https://github.com/wtfalch/jobs, "The admin API", jobs#62), authenticated
|
|
@@ -1329,6 +1436,15 @@ export interface FoundryStepExecutorPorts {
|
|
|
1329
1436
|
/** Flat, not a factory: `organisationId` is an explicit argument on every method of both ports (see each port's own doc comment; #177 dropped the `filesLimitsFor`/`aiLimitsFor` factories this used to need). */
|
|
1330
1437
|
filesOrganisation?: FilesOrganisationPort;
|
|
1331
1438
|
aiOrganisation?: AiOrganisationPort;
|
|
1439
|
+
/**
|
|
1440
|
+
* #317 (F7): mints an entry's names key -- see {@link NamesPort}'s own
|
|
1441
|
+
* doc comment. Optional the same way `jobsScheduler` is: a host with no
|
|
1442
|
+
* names service deployed builds the executor without one, and tells the
|
|
1443
|
+
* planner so (`FoundryPlanApplyOptions.names`), which then reports each
|
|
1444
|
+
* entry's `uses.names` step as blocked instead of planning one that would
|
|
1445
|
+
* throw.
|
|
1446
|
+
*/
|
|
1447
|
+
names?: NamesPort;
|
|
1332
1448
|
/**
|
|
1333
1449
|
* #177: builds the files, AI and jobs ports from the platform's addresses
|
|
1334
1450
|
* and held keys. A port set above overrides it (tests); with neither, the
|
|
@@ -1351,6 +1467,13 @@ export interface FoundryStepExecutorPorts {
|
|
|
1351
1467
|
* `FoundryPlanApplyOptions.broker`).
|
|
1352
1468
|
*/
|
|
1353
1469
|
broker?: BrokerPort;
|
|
1470
|
+
/**
|
|
1471
|
+
* #318: deploys and routes an entry's `edge` Worker -- see
|
|
1472
|
+
* {@link WorkersPort}'s own doc comment. Optional the same way `broker` is:
|
|
1473
|
+
* without it the plan reports each `edge` entry blocked (the host also
|
|
1474
|
+
* sets `FoundryPlanApplyOptions.edgeWorkers`).
|
|
1475
|
+
*/
|
|
1476
|
+
workers?: WorkersPort;
|
|
1354
1477
|
/**
|
|
1355
1478
|
* Hands a just-minted, one-time secret (today: an API app's
|
|
1356
1479
|
* `clientSecret`) to the host, immediately after the provider returns it
|
|
@@ -1494,6 +1617,49 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1494
1617
|
* holds the hostname.
|
|
1495
1618
|
*/
|
|
1496
1619
|
private cloudflareRateLimit;
|
|
1620
|
+
/**
|
|
1621
|
+
* `plan/diff.ts`'s `planEdgeWorkers` (#318, F8): deploys one entry's
|
|
1622
|
+
* `edge` Worker and routes it on the org's own zone. Throws by name
|
|
1623
|
+
* without a {@link WorkersPort}, the same `jobsRegistration` shape --
|
|
1624
|
+
* `planEdgeWorkers` reports the step blocked instead while the host has
|
|
1625
|
+
* none, so this only fires against a stale plan. The route is checked
|
|
1626
|
+
* before upload: a foreign route already on the pattern is refused, never
|
|
1627
|
+
* adopted or replaced (`cloudflareAccess`'s own precedent for a foreign
|
|
1628
|
+
* resource on the same name), so a Worker is never deployed only to find
|
|
1629
|
+
* nothing can route to it.
|
|
1630
|
+
*
|
|
1631
|
+
* Order on apply: record (and persist) the uploaded name and clear the
|
|
1632
|
+
* retired marker, upload, record the pending route, create the route only
|
|
1633
|
+
* if needed, delete any *previous* route this entry recorded on its own
|
|
1634
|
+
* zone, then record the new route id/zone/pattern and the applied
|
|
1635
|
+
* declaration last -- every crash point leaves a recorded trace that
|
|
1636
|
+
* retire and destroy can clean up (#318 re-review), and per CLAUDE.md's
|
|
1637
|
+
* "Declared changes apply" nothing is marked done until the
|
|
1638
|
+
* live state actually matches it.
|
|
1639
|
+
*/
|
|
1640
|
+
private cloudflareWorker;
|
|
1641
|
+
/**
|
|
1642
|
+
* The zone id a recorded route lives in, or `undefined` only when that
|
|
1643
|
+
* zone is *positively* gone -- its apex resolves to no zone, or to a
|
|
1644
|
+
* different zone than the one recorded (#318 re-review). Reads the zone
|
|
1645
|
+
* recorded beside the route, never `org.domain`: the route id and its
|
|
1646
|
+
* zone are always recorded together (the apply branch below writes both
|
|
1647
|
+
* in the same batch, and the pending-route marker carries its own zone
|
|
1648
|
+
* too), so a route id with no zone recorded cannot happen within this
|
|
1649
|
+
* feature -- and re-resolving from `org.domain` as a fallback would risk
|
|
1650
|
+
* looking up the *new* zone after a domain change and leaving the route
|
|
1651
|
+
* live on the old one, the exact bug a recorded-per-route zone exists to
|
|
1652
|
+
* avoid (#318 re-review).
|
|
1653
|
+
*/
|
|
1654
|
+
private liveEdgeZone;
|
|
1655
|
+
/** Deletes the route an interrupted apply was creating, if `findRoute` shows it exists, points at that apply's script, and is not `keepRouteId`. */
|
|
1656
|
+
private deletePendingEdgeRoute;
|
|
1657
|
+
/**
|
|
1658
|
+
* Retire and destroy share this: delete the recorded route on its own
|
|
1659
|
+
* zone, then any route an interrupted apply created but never recorded,
|
|
1660
|
+
* then the script. Records nothing -- the caller does.
|
|
1661
|
+
*/
|
|
1662
|
+
private teardownEdgeWorker;
|
|
1497
1663
|
private zitadelIssuer;
|
|
1498
1664
|
/**
|
|
1499
1665
|
* `plan/diff.ts`'s `planOwner`: invites the org's owner into its ZITADEL
|
|
@@ -1594,6 +1760,20 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1594
1760
|
private findRecreatedApp;
|
|
1595
1761
|
private ensureZitadelProject;
|
|
1596
1762
|
private ensureCoolifyProject;
|
|
1763
|
+
/**
|
|
1764
|
+
* #445: one company lands on one Coolify server. The first call for an
|
|
1765
|
+
* org picks a usable, reachable server and records it
|
|
1766
|
+
* ({@link COOLIFY_SERVER_SCOPE_KEY}); every later call -- this apply, or a
|
|
1767
|
+
* later one -- reads that recording back and returns the same server,
|
|
1768
|
+
* regardless of what the pool's order or health looks like by then. A
|
|
1769
|
+
* recorded server that has gone unusable or unreachable is reported
|
|
1770
|
+
* blocked (never silently replaced with a second host, which would split
|
|
1771
|
+
* the org's resources across servers and break their internal container
|
|
1772
|
+
* URLs); a recorded server no longer registered at all is the same.
|
|
1773
|
+
* Deliberately no fallback to `servers[0]` when nothing is usable: an
|
|
1774
|
+
* unusable server picked blind is not a server anyone's resources could
|
|
1775
|
+
* actually be created on.
|
|
1776
|
+
*/
|
|
1597
1777
|
private resolveServer;
|
|
1598
1778
|
private coolifyApp;
|
|
1599
1779
|
/**
|
|
@@ -1751,6 +1931,22 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1751
1931
|
* comment), never logged, never interpolated into a thrown error -- the
|
|
1752
1932
|
* same discipline `pushDatabaseUrl`'s doc comment states for a database's
|
|
1753
1933
|
* own connection string.
|
|
1934
|
+
*
|
|
1935
|
+
* #491: none of the above applies to the org whose mail domain is the
|
|
1936
|
+
* platform's own apex -- it shares the platform's existing Lettermint
|
|
1937
|
+
* project (`platformLettermintProjectId`, from `DeclaredPlatform.mail`)
|
|
1938
|
+
* instead of minting a `<slug>-app` one, and this method never creates or
|
|
1939
|
+
* deletes a project for it. `platformApex`/`platformLettermintProjectId`
|
|
1940
|
+
* come straight off this step's own `desired`, the same "the executor
|
|
1941
|
+
* writes exactly what the plan showed" rule `coolifyMail` already follows
|
|
1942
|
+
* for its own `apex`/`mailHost` -- the caller (`coolifyApp`) refuses a
|
|
1943
|
+
* `desired` missing the `platformApex` key outright as a stale plan --
|
|
1944
|
+
* checked here, after the no-op-until-a-mail-domain-exists return just
|
|
1945
|
+
* below, rather than unconditionally at the call site: every other
|
|
1946
|
+
* `coolify:app` concern (hostnames, ports, settings, the sign-in env...)
|
|
1947
|
+
* has nothing to do with Lettermint and must keep working the moment a
|
|
1948
|
+
* mail domain was never even recorded for this org, the same as before
|
|
1949
|
+
* this field existed (review round 2, finding 3).
|
|
1754
1950
|
*/
|
|
1755
1951
|
private ensureAppLettermintMail;
|
|
1756
1952
|
/**
|
|
@@ -1786,6 +1982,12 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1786
1982
|
* it the same way the app step does, rather than assuming it already
|
|
1787
1983
|
* exists -- the domain must never end up unrestricted, whichever step gets
|
|
1788
1984
|
* there first.
|
|
1985
|
+
*
|
|
1986
|
+
* #491: this org's own `<slug>-relay` project is unaffected by whether it
|
|
1987
|
+
* is the platform's own org -- only the domain restriction
|
|
1988
|
+
* (`platformApex`/`platformLettermintProjectId`, forwarded to
|
|
1989
|
+
* {@link ensureLettermintDomainRestriction}) and the delete loop below
|
|
1990
|
+
* (which must never delete the platform's own project) change.
|
|
1789
1991
|
*/
|
|
1790
1992
|
private ensureOrgLettermintRelay;
|
|
1791
1993
|
/**
|
|
@@ -1808,6 +2010,21 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1808
2010
|
* unrestricted domain cannot verify and cannot send, whichever project
|
|
1809
2011
|
* owns it (post-#140 review finding, restated here rather than only at
|
|
1810
2012
|
* the one call site it used to have).
|
|
2013
|
+
*
|
|
2014
|
+
* #491: `platformApex`/`platformLettermintProjectId` cover both callers at
|
|
2015
|
+
* once -- when `mailDomain` is the platform's own apex, the platform's
|
|
2016
|
+
* project id is always folded into the restriction, so a plan that shares
|
|
2017
|
+
* the platform's project (rather than minting a `<slug>-app` one) never
|
|
2018
|
+
* ends up restricting that org's domain to nothing. A no-op for every
|
|
2019
|
+
* other org's domain, since `mailDomain` never matches `platformApex` there.
|
|
2020
|
+
* Review round 2, finding 4: a declared id that is not among `allProjects`
|
|
2021
|
+
* (a mistype, or the project was deleted in Lettermint) is refused rather
|
|
2022
|
+
* than folded in anyway -- that would restrict the apex to whatever *is*
|
|
2023
|
+
* live (the relay project, say) plus an id naming nothing, which excludes
|
|
2024
|
+
* the real platform project from its own domain exactly as completely as
|
|
2025
|
+
* never restricting it at all. Centralised here rather than duplicated at
|
|
2026
|
+
* each caller, since both {@link ensureAppLettermintMail} and
|
|
2027
|
+
* {@link ensureOrgLettermintRelay} reach the apex through this one method.
|
|
1811
2028
|
*/
|
|
1812
2029
|
private ensureLettermintDomainRestriction;
|
|
1813
2030
|
/**
|
|
@@ -1938,6 +2155,10 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
1938
2155
|
* `coolifyApp`'s own analogous gap (it never restarted a found-but-stopped
|
|
1939
2156
|
* app either) has since closed the same way, inline in that method (#130);
|
|
1940
2157
|
* this method is `cmsSite`'s only caller.
|
|
2158
|
+
*
|
|
2159
|
+
* `repo` (#245) lets the site app build from an org's own skin repo
|
|
2160
|
+
* (`org.site.repo`) instead of the shared `CMS_REPO`; `cmsSite` always
|
|
2161
|
+
* passes `CMS_REPO` for the cms admin app, which is never org-specific.
|
|
1941
2162
|
*/
|
|
1942
2163
|
private findOrCreateCmsApp;
|
|
1943
2164
|
/**
|
|
@@ -2091,6 +2312,19 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2091
2312
|
* pushed once calls are on and this org's calls service app carries its
|
|
2092
2313
|
* internal name (`desiredAgoraEnv` reads that marker from the recorded ids,
|
|
2093
2314
|
* not the host values).
|
|
2315
|
+
*
|
|
2316
|
+
* #442: a found-but-already-running app's container binds env only at
|
|
2317
|
+
* start, so writing it alone leaves that process on its old config --
|
|
2318
|
+
* restarted (never redeployed: no new image, so `restart` is the same
|
|
2319
|
+
* primitive `ensureLogVolume`'s own restart uses) whenever the live env
|
|
2320
|
+
* actually differs, or whenever the last restart this step confirmed
|
|
2321
|
+
* ({@link AGORA_ACTIVATED_ENV_SCOPE_KEY}) is not this exact env (an org
|
|
2322
|
+
* applied before this existed, whose live env already matched desired
|
|
2323
|
+
* with nothing ever confirmed running it). A brand new app has nothing
|
|
2324
|
+
* running yet to restart. The activation marker is recorded last, after
|
|
2325
|
+
* the restart (if any) returns without throwing, so a failed restart
|
|
2326
|
+
* leaves it stale and the next plan replans this step even though
|
|
2327
|
+
* Coolify's own env store already matches desired.
|
|
2094
2328
|
*/
|
|
2095
2329
|
private coolifyAgora;
|
|
2096
2330
|
/**
|
|
@@ -2329,6 +2563,13 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2329
2563
|
* domain recorded) and on a same-domain re-apply (compared
|
|
2330
2564
|
* case-insensitively, the same rule {@link findExactLettermintDomain}
|
|
2331
2565
|
* applies to the lookup itself).
|
|
2566
|
+
*
|
|
2567
|
+
* #491 review: also a no-op when the previous domain is the platform's own
|
|
2568
|
+
* apex -- an org that moves its mail *off* that domain (`usesDomain` flips,
|
|
2569
|
+
* or `org.domain` changes) must never delete it here, the same protection
|
|
2570
|
+
* {@link parkLettermintMail} already gives the apex on a move to `off`.
|
|
2571
|
+
* `planMail` only blocks a move *onto* the apex (no project id yet); a move
|
|
2572
|
+
* away from it reaches this method with nothing to block it otherwise.
|
|
2332
2573
|
*/
|
|
2333
2574
|
private deleteStaleLettermintDomain;
|
|
2334
2575
|
/**
|
|
@@ -2371,6 +2612,10 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2371
2612
|
* A no-op until a previous apply recorded {@link MAIL_DOMAIN_SCOPE_KEY}:
|
|
2372
2613
|
* nothing was ever provisioned for an org whose mail went straight from
|
|
2373
2614
|
* never-applied to off.
|
|
2615
|
+
*
|
|
2616
|
+
* #491: when `mailDomain` is the platform's own apex, this must never
|
|
2617
|
+
* delete the platform's own project (the delete loop skips it by id) or
|
|
2618
|
+
* the platform's own domain (returns before the domain lookup/delete).
|
|
2374
2619
|
*/
|
|
2375
2620
|
private parkLettermintMail;
|
|
2376
2621
|
/**
|
|
@@ -2395,11 +2640,11 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2395
2640
|
*/
|
|
2396
2641
|
private ensureLettermintDmarcRecord;
|
|
2397
2642
|
/**
|
|
2398
|
-
* #80: provisions the org's own organisation on Files
|
|
2399
|
-
*
|
|
2400
|
-
*
|
|
2401
|
-
*
|
|
2402
|
-
*
|
|
2643
|
+
* #80: provisions the org's own organisation on Files.
|
|
2644
|
+
* `provisionOrganisation` is safely repeatable on its own
|
|
2645
|
+
* (`FilesOrganisationPort`'s doc comment), so this is called every time
|
|
2646
|
+
* regardless of whether it already ran -- idempotent by find-or-create,
|
|
2647
|
+
* not by checking `recordedIds` first.
|
|
2403
2648
|
*
|
|
2404
2649
|
* F6b: `action.desired.shareHost` (`diff.ts`'s `planFilesOrganisation`) is
|
|
2405
2650
|
* passed through every time, even though `entry-files-key.ts` reports the
|
|
@@ -2412,6 +2657,14 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2412
2657
|
* repeat-is-a-no-op shape as `provisionOrganisation` itself -- every org
|
|
2413
2658
|
* gets an Agora (#164), so `diff.ts`'s `planFilesOrganisation` always sets
|
|
2414
2659
|
* `desired.agoraOrigin`.
|
|
2660
|
+
*
|
|
2661
|
+
* #437: the owner member/name, the core ZITADEL files project and the core
|
|
2662
|
+
* collection (`core-files.ts`) are no longer conditioned on a hosting entry
|
|
2663
|
+
* declaring `uses.files` -- the core bundle (Agora's own uploads) needs
|
|
2664
|
+
* all three just as much as a bespoke entry does, and withholding them
|
|
2665
|
+
* until one existed is why a plain Agora/Boule org never got a usable
|
|
2666
|
+
* Files setup at all. Both the project and the collection are idempotent
|
|
2667
|
+
* by name, so a re-run that already created either finds it instead.
|
|
2415
2668
|
*/
|
|
2416
2669
|
private filesOrganisation;
|
|
2417
2670
|
/**
|
|
@@ -2422,7 +2675,6 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2422
2675
|
* that fails revokes the key again instead of leaving a live key nobody holds.
|
|
2423
2676
|
*/
|
|
2424
2677
|
private deliverFilesAgoraKey;
|
|
2425
|
-
private filesStorage;
|
|
2426
2678
|
/**
|
|
2427
2679
|
* #80: provisions the org's own organisation on AI and switches its AI use
|
|
2428
2680
|
* on, before {@link aiBudget} can set its own spend limit. Both
|
|
@@ -2463,6 +2715,22 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2463
2715
|
* new key.
|
|
2464
2716
|
*/
|
|
2465
2717
|
private revokeEntryKey;
|
|
2718
|
+
/**
|
|
2719
|
+
* F7 (#317): mints one entry's names key directly below the org (names
|
|
2720
|
+
* takes no separate minter key id, unlike ai/files), writes `NAMES_URL`
|
|
2721
|
+
* and `NAMES_API_KEY` to the entry's app env, then restarts it. Retires
|
|
2722
|
+
* the key when `uses.names` is removed. Follows the "one mint" rule
|
|
2723
|
+
* (CLAUDE.md "Platform steps"): the id is recorded as undelivered and
|
|
2724
|
+
* persisted before delivery is attempted, so a crash or a failed write
|
|
2725
|
+
* never leads to a second mint -- `planEntryNamesKeys` blocks instead.
|
|
2726
|
+
*/
|
|
2727
|
+
private namesEntryKey;
|
|
2728
|
+
/**
|
|
2729
|
+
* F7 (#317): `plan/entry-names-key.ts`'s `planNamesReservedLabels` --
|
|
2730
|
+
* tells the names service every label this org's own entries already
|
|
2731
|
+
* hold, so names refuses a customer claiming one of them.
|
|
2732
|
+
*/
|
|
2733
|
+
private namesReservedLabels;
|
|
2466
2734
|
/**
|
|
2467
2735
|
* F6b: mints or renews the org's own files key, or revokes the ones a
|
|
2468
2736
|
* renewal replaced. The key is minted below the files organisation's owner
|