@wtfalch/foundry 0.42.1 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/bootstrap/versions.d.ts +2 -0
  2. package/dist/index.d.ts +8 -4
  3. package/dist/index.js +4 -2
  4. package/dist/migrations/0055_release_requests.sql +20 -0
  5. package/dist/migrations/0056_platform_archon_blue_green.sql +7 -0
  6. package/dist/migrations/0057_org_customers.sql +29 -0
  7. package/dist/org.d.ts +55 -0
  8. package/dist/org.js +90 -27
  9. package/dist/plan/archon-blue-green.d.ts +54 -0
  10. package/dist/plan/archon-blue-green.js +72 -0
  11. package/dist/plan/blue-green-apply.d.ts +82 -0
  12. package/dist/plan/blue-green-apply.js +360 -0
  13. package/dist/plan/blue-green.d.ts +223 -0
  14. package/dist/plan/blue-green.js +444 -0
  15. package/dist/plan/boule.d.ts +43 -2
  16. package/dist/plan/boule.js +48 -1
  17. package/dist/plan/broker-entries.d.ts +28 -0
  18. package/dist/plan/broker-entries.js +46 -1
  19. package/dist/plan/cloudflared-connector.d.ts +26 -0
  20. package/dist/plan/cloudflared-connector.js +37 -0
  21. package/dist/plan/diff.d.ts +20 -0
  22. package/dist/plan/diff.js +207 -20
  23. package/dist/plan/executor.d.ts +134 -2
  24. package/dist/plan/executor.js +517 -25
  25. package/dist/plan/hostnames.d.ts +7 -0
  26. package/dist/plan/hostnames.js +17 -2
  27. package/dist/plan/locked-hostnames.d.ts +11 -0
  28. package/dist/plan/locked-hostnames.js +13 -0
  29. package/dist/plan/mcp.d.ts +57 -0
  30. package/dist/plan/mcp.js +131 -0
  31. package/dist/plan/org-names.d.ts +2 -2
  32. package/dist/plan/org-names.js +16 -1
  33. package/dist/plan/plan-apply.d.ts +77 -0
  34. package/dist/plan/plan-apply.js +267 -5
  35. package/dist/plan/platform-diff.d.ts +9 -0
  36. package/dist/plan/platform-diff.js +24 -1
  37. package/dist/plan/platform-providers.d.ts +15 -1
  38. package/dist/plan/platform-providers.js +90 -5
  39. package/dist/plan/proxy-labels.d.ts +2 -2
  40. package/dist/plan/proxy-labels.js +14 -4
  41. package/dist/plan/release-host.d.ts +59 -0
  42. package/dist/plan/release-host.js +239 -0
  43. package/dist/plan/remote-database.d.ts +124 -0
  44. package/dist/plan/remote-database.js +373 -0
  45. package/dist/plan/worker-app-database.d.ts +148 -0
  46. package/dist/plan/worker-app-database.js +424 -0
  47. package/dist/plan/worker-app.d.ts +320 -0
  48. package/dist/plan/worker-app.js +792 -0
  49. package/dist/plan/zitadel-machine-user.d.ts +13 -1
  50. package/dist/plan/zitadel-machine-user.js +80 -0
  51. package/dist/platform.d.ts +11 -1
  52. package/dist/platform.js +4 -1
  53. package/dist/release/decide.d.ts +95 -0
  54. package/dist/release/decide.js +242 -0
  55. package/dist/release/health.d.ts +48 -0
  56. package/dist/release/health.js +61 -0
  57. package/dist/release/run.d.ts +40 -0
  58. package/dist/release/run.js +56 -0
  59. package/dist/release/state.d.ts +64 -0
  60. package/dist/release/state.js +126 -0
  61. package/dist/release/types.d.ts +54 -0
  62. package/dist/release/types.js +4 -0
  63. package/dist/requested-changes.d.ts +12 -3
  64. package/dist/requested-changes.js +22 -0
  65. package/dist/store/postgres.d.ts +8 -0
  66. package/dist/store/postgres.js +67 -8
  67. package/dist/store/schema.d.ts +188 -0
  68. package/dist/store/schema.js +15 -0
  69. package/dist/worker-app-config.d.ts +151 -0
  70. package/dist/worker-app-config.js +438 -0
  71. package/package.json +1 -1
  72. package/versions.digests.json +2 -1
  73. package/versions.json +2 -1
@@ -1,7 +1,8 @@
1
1
  import type { OrgMailOptions, OrgMailOrg, OrgMailResult } from '@wtfalch/email/postmaster';
2
2
  import { type CallsSfuUrls } from '../calls-sfu.js';
3
- import type { DeclaredOrg, HostingBuildPack } from '../org.js';
3
+ import type { DeclaredOrg, HostingBuildPack, OrgId } from '../org.js';
4
4
  import type { DeclaredOrgStore } from '../store.js';
5
+ import { type BlueGreenServer, type BlueGreenSubject } from './blue-green.js';
5
6
  import { type BrokerPort } from './broker-entries.js';
6
7
  import { type DeployHealthRuntime } from './deploy-health.js';
7
8
  import { type StepAction } from './diff.js';
@@ -16,6 +17,9 @@ import { type OwnServicesPort } from './own-services.js';
16
17
  import type { DestroyTarget, StepDestroyer, StepExecutionContext, StepExecutor } from './plan-apply.js';
17
18
  import type { EdgePortName } from './platform-org.js';
18
19
  import type { RequestHeaderPort } from './platform-providers.js';
20
+ import { type ReleaseContext, type ReleaseHost } from './release-host.js';
21
+ import type { WorkerAppDatabasePort } from './worker-app-database.js';
22
+ import { type KeysCustodyPort, type WorkerAppPort } from './worker-app.js';
19
23
  export declare function coolifyProjectMark(org: Pick<DeclaredOrg, 'id'>): string;
20
24
  /**
21
25
  * The Coolify calls this executor makes, shaped to match
@@ -103,6 +107,8 @@ export interface CoolifyPort {
103
107
  */
104
108
  getApplication(uuid: string): Promise<{
105
109
  uuid: string;
110
+ /** Coolify's name for the app; a blue/green entry's recorded app is its blue copy only when this is the blue name. */
111
+ name?: string;
106
112
  status?: string;
107
113
  custom_labels?: string | null;
108
114
  settings?: {
@@ -831,6 +837,8 @@ export interface ZitadelPort {
831
837
  appType: 'web' | 'native';
832
838
  redirectUris: string[];
833
839
  postLogoutRedirectUris: string[];
840
+ /** `jwt` for an entry with `uses.auth.mcp` (providers 0.11.0). Omitted: opaque. */
841
+ accessTokenType?: 'jwt' | 'opaque';
834
842
  }): Promise<{
835
843
  appId: string;
836
844
  clientId: string;
@@ -1116,6 +1124,25 @@ export interface ZitadelPort {
1116
1124
  * reports no longer live is never attempted at all.
1117
1125
  */
1118
1126
  listLoginClientPats(orgId: string, userId: string): Promise<string[]>;
1127
+ /**
1128
+ * C3: mints a read-only directory credential for one ZITADEL organisation (the company's
1129
+ * customers): a machine user with only the read-only organisation role, and a fresh PAT.
1130
+ * Idempotent on the user by `userName`; every PAT the user already holds is revoked before the
1131
+ * new one is minted, so a rerun leaves exactly one live token (like
1132
+ * {@link mintLoginClientMachineUser}). The caller holds the token and writes it to Boule's env;
1133
+ * it never reaches an error, a log or a recorded id. Optional: a host without it gets the
1134
+ * customers directory reported blocked. `zitadel-machine-user.ts` is the real implementation.
1135
+ */
1136
+ mintOrgDirectoryReader?(orgId: string, input: {
1137
+ userName: string;
1138
+ name: string;
1139
+ }): Promise<{
1140
+ userId: string;
1141
+ token: string;
1142
+ patId: string;
1143
+ }>;
1144
+ /** C3: revokes every PAT of the directory reader, so a company that turns customers off leaves no live credential. A PAT already gone is not an error. */
1145
+ revokeOrgDirectoryReader?(orgId: string, userId: string): Promise<void>;
1119
1146
  /**
1120
1147
  * #267: adds the org's own company login host to the ZITADEL instance's
1121
1148
  * trusted domains (`@wtfalch/providers`' `ZitadelClient.ensureTrustedDomain`)
@@ -1173,6 +1200,21 @@ export interface ZitadelPort {
1173
1200
  * `createOidcApp` and `createOidcAppWithSecret` send it on themselves.
1174
1201
  */
1175
1202
  updateOidcAppIdTokenUserinfoAssertion?(orgId: string, projectId: string, appId: string): Promise<boolean>;
1203
+ /**
1204
+ * Auth ADR 0002: an existing sign-in app issues JWT access tokens and holds
1205
+ * every `redirectUris` entry (`@wtfalch/providers` 0.11.0). Resolves `true`
1206
+ * when it wrote, `false` when nothing differed. Optional, with
1207
+ * {@link ZitadelPort.ensureAccessTokenLifetime}: a host whose client lacks
1208
+ * either gets every `uses.auth.mcp` reported blocked
1209
+ * (`FoundryPlanApplyOptions.zitadelMcp`).
1210
+ */
1211
+ ensureOidcAppMcpAccess?(orgId: string, projectId: string, appId: string, want: {
1212
+ redirectUris: string[];
1213
+ }): Promise<boolean>;
1214
+ /** The instance-wide access-token lifetime, in seconds. Resolves `{ changed }`. */
1215
+ ensureAccessTokenLifetime?(seconds: number): Promise<{
1216
+ changed: boolean;
1217
+ }>;
1176
1218
  }
1177
1219
  /**
1178
1220
  * #274 fix round 4: bridges `@wtfalch/providers`' real `ZitadelClient.
@@ -1536,6 +1578,11 @@ export interface PlatformOrgPortSource {
1536
1578
  /** The platform's shared sign-in host, or `null` when none is declared. */
1537
1579
  authHostname?(): Promise<string | null>;
1538
1580
  }
1581
+ /**
1582
+ * C3: mints the Archon credential for one org's Boule and returns the secret. Every call rotates the
1583
+ * org's credential (the old secret stays valid one more hour) or issues the first.
1584
+ */
1585
+ export type BouleArchonCredentialMinter = (orgId: OrgId) => Promise<string>;
1539
1586
  export interface FoundryStepExecutorPorts {
1540
1587
  coolify: CoolifyPort;
1541
1588
  /**
@@ -1662,6 +1709,29 @@ export interface FoundryStepExecutorPorts {
1662
1709
  * sets `FoundryPlanApplyOptions.edgeWorkers`).
1663
1710
  */
1664
1711
  workers?: WorkersPort;
1712
+ /**
1713
+ * ADR 0002: deploys and routes an entry's `workerApp` -- see
1714
+ * {@link WorkerAppPort}. Optional the same way `workers` is: without it the
1715
+ * plan reports each Worker app blocked (the host also sets
1716
+ * `FoundryPlanApplyOptions.workerApps`).
1717
+ */
1718
+ workerApps?: WorkerAppPort;
1719
+ /** ADR 0002: the keys custody Worker, for a `keysCustody` secret of a Worker app. */
1720
+ workerAppKeys?: KeysCustodyPort;
1721
+ /**
1722
+ * C3: mints the credential an org's Boule reaches Archon with (org-scoped; `orgs:request` and
1723
+ * `orgs:read` for that org). Archon implements it. Every call rotates the credential (the old secret
1724
+ * stays valid one more hour) and credentials expire after 365 days, so foundry calls it only when none
1725
+ * was pushed or the recorded push time is 335 days old. It reaches Boule's env as `ARCHON_CREDENTIAL` and nowhere else. Without it the host
1726
+ * writes no Archon credential and the plan says so.
1727
+ */
1728
+ mintBouleArchonCredential?: BouleArchonCredentialMinter;
1729
+ /**
1730
+ * C2: the Hyperdrive config, the release's `migrations.json` and the SQL runner behind the tunnel,
1731
+ * for a Worker app with `workerApp.database`. Without it the plan reports that entry blocked
1732
+ * (the host also sets `FoundryPlanApplyOptions.workerAppDatabase`).
1733
+ */
1734
+ workerAppDatabase?: WorkerAppDatabasePort;
1665
1735
  /**
1666
1736
  * Hands a just-minted, one-time secret (today: an API app's
1667
1737
  * `clientSecret`) to the host, immediately after the provider returns it
@@ -1843,6 +1913,10 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
1843
1913
  private executeScoped;
1844
1914
  /** Whether `ensureOwnAuthMailToken` can mint: the same two inputs it blocks on. */
1845
1915
  ownMailKeyMintable(): boolean;
1916
+ /** C3: whether the ZITADEL port can mint and revoke a customers directory credential. */
1917
+ customersDirectoryAvailable(): boolean;
1918
+ /** C3: whether the host supplied `mintBouleArchonCredential`. */
1919
+ bouleArchonCredentialAvailable(): boolean;
1846
1920
  /** Whether the held keys can store a new company's sign-in database CA and certificate (#746). */
1847
1921
  ownSigninDbKeysPuttable(): boolean;
1848
1922
  /** Whether the host supplies the backup bucket the sign-in database dumps into (#746). */
@@ -1944,6 +2018,7 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
1944
2018
  * "Declared changes apply" nothing is marked done until the
1945
2019
  * live state actually matches it.
1946
2020
  */
2021
+ private workerAppDeps;
1947
2022
  private cloudflareWorker;
1948
2023
  /**
1949
2024
  * Makes the no-Worker routes recorded for an entry match `patterns`: deletes
@@ -2003,6 +2078,12 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2003
2078
  * nothing: the next apply finds the account by username and mints a new code.
2004
2079
  */
2005
2080
  private zitadelOwner;
2081
+ /** `plan/mcp.ts`: the entry's MCP audience project. */
2082
+ private zitadelMcpProject;
2083
+ /** `plan/mcp.ts`: JWT access tokens and the broker's callback on the entry's app. */
2084
+ private zitadelMcpAccess;
2085
+ /** `plan/mcp.ts`: the instance access-token lifetime. */
2086
+ private zitadelAccessTokenLifetime;
2006
2087
  /**
2007
2088
  * Switches on "user info inside the ID token" for each live sign-in app
2008
2089
  * the planner named. An app's marker (its ZITADEL app id) is recorded and
@@ -2096,7 +2177,7 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2096
2177
  /**
2097
2178
  * `plan/diff.ts`'s `planCustomers` (plan gap A9): the org's customers sign
2098
2179
  * in through a second ZITADEL org, `<slug> customers`, with
2099
- * self-registration on and a grant to the org's app project. Staff stay in
2180
+ * self-registration as `customers.signUp` says (off by default) and a grant to the org's app project. Staff stay in
2100
2181
  * the org's own invite-only ZITADEL org. The space keeps the name apart from
2101
2182
  * every issuer org, which is named by a slug and a slug has no spaces. Every call here is idempotent, so
2102
2183
  * a re-run after a crash finishes the job.
@@ -2141,6 +2222,34 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2141
2222
  * actually be created on.
2142
2223
  */
2143
2224
  private resolveServer;
2225
+ /**
2226
+ * Contract C3: every usable, reachable Coolify server, the org's primary first. The primary is
2227
+ * {@link resolveServer}'s (databases and single-copy services stay on it, unchanged). A server that is
2228
+ * down is named in `down` and left out of `servers`; it is not an error.
2229
+ */
2230
+ resolveServers(org: DeclaredOrg, context: StepExecutionContext): Promise<{
2231
+ primary: {
2232
+ uuid: string;
2233
+ };
2234
+ servers: Array<{
2235
+ uuid: string;
2236
+ }>;
2237
+ down: string[];
2238
+ }>;
2239
+ /**
2240
+ * The release runner's outside calls for one blue/green entry (`plan/release-host.ts`), or `null` while its
2241
+ * blue copy is not recorded. The health token is only read here, never minted.
2242
+ */
2243
+ releaseHost(org: DeclaredOrg, subject: BlueGreenSubject, context: ReleaseContext, renew: () => Promise<void>): ReleaseHost | null;
2244
+ /** Every server Coolify lists with whether it is usable and reachable, for the planner. */
2245
+ blueGreenServers(): Promise<BlueGreenServer[]>;
2246
+ /**
2247
+ * The blue/green steps (`plan/blue-green.ts`): `bluegreen` makes the apps and the balancer on one server,
2248
+ * `bluegreen-lb` only writes the balancer's weights, `bluegreen-connector` runs the tunnel's connector on a
2249
+ * server. A server that went down since the plan is skipped by `bluegreen-connector`'s caller, not here:
2250
+ * a step for it fails, which the next plan reports instead.
2251
+ */
2252
+ private coolifyBlueGreen;
2144
2253
  private coolifyApp;
2145
2254
  /**
2146
2255
  * #75: mounts a persistent volume at {@link LOG_DIR} on the app, unless one
@@ -2891,6 +3000,27 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2891
3000
  * shape {@link coolifyAgora}'s own doc comment explains.
2892
3001
  */
2893
3002
  private coolifyBoule;
3003
+ /**
3004
+ * C3: the Archon credential of an org's Boule. Archon's mint rotates on every call, so it is called only
3005
+ * when none was pushed or the recorded push time (no secret) is 335 days old. Otherwise the held value is
3006
+ * written again unchanged. The secret is held first; the caller records `pushedAt` only after the env write
3007
+ * succeeded, so a failed hold or a failed push is mended by the next apply minting again. Errors name no secret.
3008
+ */
3009
+ private ensureBouleArchonCredential;
3010
+ /**
3011
+ * C3: the read-only credential for the company's customers organisation. Held first. Minting revokes every
3012
+ * earlier token of the machine user, so a hold that failed is repaired by the next apply minting again and
3013
+ * leaves one live token. `null` while the customers organisation is not recorded yet.
3014
+ */
3015
+ private ensureCustomersDirectoryCredential;
3016
+ /** `put`, tried up to {@link BOULE_CREDENTIAL_ATTEMPTS} times; true once one attempt succeeds. The cause is dropped. */
3017
+ private holdWithRetry;
3018
+ /**
3019
+ * C3: customers switched off. Revokes the directory credential's tokens first, then removes the customers
3020
+ * env from Boule, then forgets the held value and the user id. Each step is repeated safely, so a run that
3021
+ * died halfway is finished by the next. Returns true when Boule's env changed.
3022
+ */
3023
+ private dropCustomersCredential;
2894
3024
  /**
2895
3025
  * Gives Boule `LETTERMINT_API_TOKEN`: the platform's held key for an org on
2896
3026
  * the platform's own apex, else the org's `<slug>-app` token through the
@@ -3009,6 +3139,8 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
3009
3139
  */
3010
3140
  private ownSfu;
3011
3141
  private jobsRegistration;
3142
+ /** The blue/green database route (`plan/remote-database.ts`). */
3143
+ private remoteDatabaseRoute;
3012
3144
  /** `deliverSecret`, tried up to {@link JOBS_DELIVERY_ATTEMPTS} times; true once one attempt succeeds. */
3013
3145
  private deliverWithRetry;
3014
3146
  private cloudflareTunnel;