@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
@@ -372,6 +372,40 @@ export declare const declaredOrgs: import("drizzle-orm/pg-core").PgTableWithColu
372
372
  identity: undefined;
373
373
  generated: undefined;
374
374
  }, {}, {}>;
375
+ customersEnabled: import("drizzle-orm/pg-core").PgColumn<{
376
+ name: "customers_enabled";
377
+ tableName: "foundry_declared_orgs";
378
+ dataType: "boolean";
379
+ columnType: "PgBoolean";
380
+ data: boolean;
381
+ driverParam: boolean;
382
+ notNull: true;
383
+ hasDefault: true;
384
+ isPrimaryKey: false;
385
+ isAutoincrement: false;
386
+ hasRuntimeDefault: false;
387
+ enumValues: undefined;
388
+ baseColumn: never;
389
+ identity: undefined;
390
+ generated: undefined;
391
+ }, {}, {}>;
392
+ customersSignUp: import("drizzle-orm/pg-core").PgColumn<{
393
+ name: "customers_sign_up";
394
+ tableName: "foundry_declared_orgs";
395
+ dataType: "boolean";
396
+ columnType: "PgBoolean";
397
+ data: boolean;
398
+ driverParam: boolean;
399
+ notNull: true;
400
+ hasDefault: true;
401
+ isPrimaryKey: false;
402
+ isAutoincrement: false;
403
+ hasRuntimeDefault: false;
404
+ enumValues: undefined;
405
+ baseColumn: never;
406
+ identity: undefined;
407
+ generated: undefined;
408
+ }, {}, {}>;
375
409
  versionPin: import("drizzle-orm/pg-core").PgColumn<{
376
410
  name: "version_pin";
377
411
  tableName: "foundry_declared_orgs";
@@ -2002,6 +2036,31 @@ export declare const foundryDeclaredPlatform: import("drizzle-orm/pg-core").PgTa
2002
2036
  identity: undefined;
2003
2037
  generated: undefined;
2004
2038
  }, {}, {}>;
2039
+ archonBlueGreen: import("drizzle-orm/pg-core").PgColumn<{
2040
+ name: "archon_blue_green";
2041
+ tableName: "foundry_declared_platform";
2042
+ dataType: "json";
2043
+ columnType: "PgJsonb";
2044
+ data: {
2045
+ steps?: number[];
2046
+ stepHoldMinutes?: number;
2047
+ } | null;
2048
+ driverParam: unknown;
2049
+ notNull: false;
2050
+ hasDefault: false;
2051
+ isPrimaryKey: false;
2052
+ isAutoincrement: false;
2053
+ hasRuntimeDefault: false;
2054
+ enumValues: undefined;
2055
+ baseColumn: never;
2056
+ identity: undefined;
2057
+ generated: undefined;
2058
+ }, {}, {
2059
+ $type: {
2060
+ steps?: number[];
2061
+ stepHoldMinutes?: number;
2062
+ } | null;
2063
+ }>;
2005
2064
  edgeEnabled: import("drizzle-orm/pg-core").PgColumn<{
2006
2065
  name: "edge_enabled";
2007
2066
  tableName: "foundry_declared_platform";
@@ -2414,6 +2473,135 @@ export declare const foundryRequestedChanges: import("drizzle-orm/pg-core").PgTa
2414
2473
  };
2415
2474
  dialect: "pg";
2416
2475
  }>;
2476
+ /** Mirrors `migrations/0055_release_requests.sql`: an operator's release action, consumed once by the runner. */
2477
+ export declare const foundryReleaseRequests: import("drizzle-orm/pg-core").PgTableWithColumns<{
2478
+ name: "foundry_release_requests";
2479
+ schema: undefined;
2480
+ columns: {
2481
+ id: import("drizzle-orm/pg-core").PgColumn<{
2482
+ name: "id";
2483
+ tableName: "foundry_release_requests";
2484
+ dataType: "number";
2485
+ columnType: "PgBigSerial53";
2486
+ data: number;
2487
+ driverParam: number;
2488
+ notNull: true;
2489
+ hasDefault: true;
2490
+ isPrimaryKey: true;
2491
+ isAutoincrement: false;
2492
+ hasRuntimeDefault: false;
2493
+ enumValues: undefined;
2494
+ baseColumn: never;
2495
+ identity: undefined;
2496
+ generated: undefined;
2497
+ }, {}, {}>;
2498
+ orgId: import("drizzle-orm/pg-core").PgColumn<{
2499
+ name: "org_id";
2500
+ tableName: "foundry_release_requests";
2501
+ dataType: "string";
2502
+ columnType: "PgText";
2503
+ data: string;
2504
+ driverParam: string;
2505
+ notNull: true;
2506
+ hasDefault: false;
2507
+ isPrimaryKey: false;
2508
+ isAutoincrement: false;
2509
+ hasRuntimeDefault: false;
2510
+ enumValues: [string, ...string[]];
2511
+ baseColumn: never;
2512
+ identity: undefined;
2513
+ generated: undefined;
2514
+ }, {}, {}>;
2515
+ entryKey: import("drizzle-orm/pg-core").PgColumn<{
2516
+ name: "entry_key";
2517
+ tableName: "foundry_release_requests";
2518
+ dataType: "string";
2519
+ columnType: "PgText";
2520
+ data: string;
2521
+ driverParam: string;
2522
+ notNull: true;
2523
+ hasDefault: false;
2524
+ isPrimaryKey: false;
2525
+ isAutoincrement: false;
2526
+ hasRuntimeDefault: false;
2527
+ enumValues: [string, ...string[]];
2528
+ baseColumn: never;
2529
+ identity: undefined;
2530
+ generated: undefined;
2531
+ }, {}, {}>;
2532
+ action: import("drizzle-orm/pg-core").PgColumn<{
2533
+ name: "action";
2534
+ tableName: "foundry_release_requests";
2535
+ dataType: "json";
2536
+ columnType: "PgJsonb";
2537
+ data: Record<string, unknown>;
2538
+ driverParam: unknown;
2539
+ notNull: true;
2540
+ hasDefault: false;
2541
+ isPrimaryKey: false;
2542
+ isAutoincrement: false;
2543
+ hasRuntimeDefault: false;
2544
+ enumValues: undefined;
2545
+ baseColumn: never;
2546
+ identity: undefined;
2547
+ generated: undefined;
2548
+ }, {}, {
2549
+ $type: Record<string, unknown>;
2550
+ }>;
2551
+ actor: import("drizzle-orm/pg-core").PgColumn<{
2552
+ name: "actor";
2553
+ tableName: "foundry_release_requests";
2554
+ dataType: "string";
2555
+ columnType: "PgText";
2556
+ data: string;
2557
+ driverParam: string;
2558
+ notNull: true;
2559
+ hasDefault: false;
2560
+ isPrimaryKey: false;
2561
+ isAutoincrement: false;
2562
+ hasRuntimeDefault: false;
2563
+ enumValues: [string, ...string[]];
2564
+ baseColumn: never;
2565
+ identity: undefined;
2566
+ generated: undefined;
2567
+ }, {}, {}>;
2568
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
2569
+ name: "created_at";
2570
+ tableName: "foundry_release_requests";
2571
+ dataType: "date";
2572
+ columnType: "PgTimestamp";
2573
+ data: Date;
2574
+ driverParam: string;
2575
+ notNull: true;
2576
+ hasDefault: true;
2577
+ isPrimaryKey: false;
2578
+ isAutoincrement: false;
2579
+ hasRuntimeDefault: false;
2580
+ enumValues: undefined;
2581
+ baseColumn: never;
2582
+ identity: undefined;
2583
+ generated: undefined;
2584
+ }, {}, {}>;
2585
+ consumedAt: import("drizzle-orm/pg-core").PgColumn<{
2586
+ name: "consumed_at";
2587
+ tableName: "foundry_release_requests";
2588
+ dataType: "date";
2589
+ columnType: "PgTimestamp";
2590
+ data: Date;
2591
+ driverParam: string;
2592
+ notNull: false;
2593
+ hasDefault: false;
2594
+ isPrimaryKey: false;
2595
+ isAutoincrement: false;
2596
+ hasRuntimeDefault: false;
2597
+ enumValues: undefined;
2598
+ baseColumn: never;
2599
+ identity: undefined;
2600
+ generated: undefined;
2601
+ }, {}, {}>;
2602
+ };
2603
+ dialect: "pg";
2604
+ }>;
2417
2605
  export type DeclaredOrgRow = typeof declaredOrgs.$inferSelect;
2418
2606
  export type NewDeclaredOrgRow = typeof declaredOrgs.$inferInsert;
2419
2607
  export type AppliedStateRow = typeof foundryAppliedState.$inferSelect;
@@ -40,6 +40,9 @@ export const declaredOrgs = pgTable('foundry_declared_orgs', {
40
40
  servicesSfuSlot: integer('services_sfu_slot'),
41
41
  // SignInConfig.inApp (org.ts); migrations/0054.
42
42
  signInInApp: boolean('signin_in_app').notNull().default(false),
43
+ // CustomersConfig (org.ts); migrations/0057.
44
+ customersEnabled: boolean('customers_enabled').notNull().default(false),
45
+ customersSignUp: boolean('customers_sign_up').notNull().default(false),
43
46
  // VersionPin (org.ts): null follows the platform. `migrations/0053_org_version_pin.sql`.
44
47
  versionPin: jsonb('version_pin').$type(),
45
48
  // SiteConfig (org.ts, T10). A brand new org has no cms site.
@@ -191,6 +194,8 @@ export const foundryDeclaredPlatform = pgTable('foundry_declared_platform', {
191
194
  archonEnabled: boolean('archon_enabled').notNull().default(false),
192
195
  archonHostname: text('archon_hostname').notNull(),
193
196
  archonGitRef: text('archon_git_ref').notNull().default(''),
197
+ /** `migrations/0056_platform_archon_blue_green.sql`: `platform.archon.blueGreen`; null keeps one app. */
198
+ archonBlueGreen: jsonb('archon_blue_green').$type(),
194
199
  /** #317 (F7), `migrations/0043_platform_names.sql`, renamed from `names_*` by `0045_names_to_edge.sql`. */
195
200
  edgeEnabled: boolean('edge_enabled').notNull().default(false),
196
201
  edgeHostname: text('edge_hostname').notNull(),
@@ -230,3 +235,13 @@ export const foundryRequestedChanges = pgTable('foundry_requested_changes', {
230
235
  createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
231
236
  state: text('state').notNull().default('needs_approval'),
232
237
  });
238
+ /** Mirrors `migrations/0055_release_requests.sql`: an operator's release action, consumed once by the runner. */
239
+ export const foundryReleaseRequests = pgTable('foundry_release_requests', {
240
+ id: bigserial('id', { mode: 'number' }).primaryKey(),
241
+ orgId: text('org_id').notNull(),
242
+ entryKey: text('entry_key').notNull(),
243
+ action: jsonb('action').notNull().$type(),
244
+ actor: text('actor').notNull(),
245
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
246
+ consumedAt: timestamp('consumed_at', { withTimezone: true }),
247
+ });
@@ -0,0 +1,151 @@
1
+ import type { HostingConfig, HostingEntry } from './org.js';
2
+ /**
3
+ * A hosting entry that runs as a Cloudflare Worker with Durable Objects and,
4
+ * optionally, Containers (foundry ADR 0002, `docs/adr/0002-worker-app-hosting-option.md`).
5
+ * The entry has no Coolify app, no volumes and no gate. The org's repo CI builds
6
+ * the module and the image; foundry fetches the module, checks its sha256 and
7
+ * uploads it, and never builds or checks out source.
8
+ */
9
+ export interface WorkerAppConfig {
10
+ /** Built module (https URL) and its sha256, as in `WorkerConfig`. */
11
+ artifactUrl: string;
12
+ artifactSha256: string;
13
+ /** Defaults to `WORKER_COMPATIBILITY_DATE`. Flags come from an allowlist in foundry. */
14
+ compatibilityDate?: string;
15
+ compatibilityFlags?: 'nodejs_compat'[];
16
+ durableObjects: {
17
+ /** Binding name -> exported class. */
18
+ classes: Record<string, string>;
19
+ /** Append-only. Foundry sends only tags not yet applied. */
20
+ migrations: {
21
+ tag: string;
22
+ newSqliteClasses?: string[];
23
+ deletedClasses?: string[];
24
+ renamedClasses?: {
25
+ from: string;
26
+ to: string;
27
+ }[];
28
+ }[];
29
+ };
30
+ containers?: {
31
+ /** Must be a durable object class above. */
32
+ className: string;
33
+ /** Image pushed by the app's CI to the platform registry, pinned by digest. */
34
+ image: {
35
+ repository: string;
36
+ digest: `sha256:${string}`;
37
+ };
38
+ instanceType: 'lite' | 'basic' | 'standard-1' | 'standard-2' | 'standard-3' | 'standard-4';
39
+ maxInstances: number;
40
+ }[];
41
+ /**
42
+ * Name -> source. Delivered as Worker secrets. `keysCustody` (named
43
+ * `KEYS_CREDENTIAL`, at most one) delivers a keys custody Worker credential
44
+ * for the app's own host id (`workerAppHostId`), with `KEYS_URL` as a var.
45
+ * `keys` (an issued API key) is refused: custody does not accept one.
46
+ */
47
+ secrets?: Record<string, {
48
+ from: 'generated';
49
+ } | {
50
+ from: 'keysCustody';
51
+ }>;
52
+ /** True delivers the entry's sign-in env as Worker vars. Needs `uses.auth`. */
53
+ authEnv?: boolean;
54
+ /**
55
+ * A Postgres database in the org's Coolify database, reached by the Worker
56
+ * through a Hyperdrive config bound as `HYPERDRIVE` (connect as `<db>_rt`).
57
+ * The release may carry `migrations.json` (`[{ id, sql }]`, ordered); foundry
58
+ * applies the unapplied ids before the Worker deploys. `appendOnly` names
59
+ * tables the runtime role may insert into and read but never update, delete
60
+ * or truncate. `migrationsSha256` (64 lowercase hex) pins the release's
61
+ * `migrations.json`: required when the release has one, and foundry refuses
62
+ * to parse bytes whose sha256 differs. Omit it only for a release with no
63
+ * `migrations.json`; one that has the file is then refused.
64
+ */
65
+ database?: {
66
+ appendOnly?: string[];
67
+ migrationsSha256?: string;
68
+ };
69
+ /** Plain vars. Never secrets. */
70
+ vars?: Record<string, string>;
71
+ /** Host label: '@' for the apex, else a label. Exact host, never a wildcard. */
72
+ host: string;
73
+ }
74
+ /** The compatibility flags a Worker app may ask for. */
75
+ export declare const WORKER_APP_COMPATIBILITY_FLAGS: ReadonlySet<string>;
76
+ export declare const WORKER_APP_INSTANCE_TYPES: ReadonlySet<string>;
77
+ /** The most instances one container class may scale to. */
78
+ export declare const WORKER_APP_MAX_INSTANCES = 100;
79
+ /** The secret that carries a `keysCustody` source: the custody Worker credential. */
80
+ export declare const WORKER_APP_KEYS_SECRET = "KEYS_CREDENTIAL";
81
+ export declare const WORKER_APP_KEYS_URL_VAR = "KEYS_URL";
82
+ /**
83
+ * Names foundry writes into a Worker app's environment itself, so a
84
+ * declaration may not use them as a binding, secret or var.
85
+ */
86
+ export declare const WORKER_APP_RESERVED_NAMES: RegExp;
87
+ /** True when the entry needs the org's Postgres database: a Coolify app with `database`, or a Worker app with `workerApp.database`. */
88
+ export declare const entryHasDatabase: (entry: {
89
+ database: boolean;
90
+ workerApp?: {
91
+ database?: unknown;
92
+ } | undefined;
93
+ }) => boolean;
94
+ /** The Hyperdrive binding of a Worker app with `database`. */
95
+ export declare const WORKER_APP_HYPERDRIVE_BINDING = "HYPERDRIVE";
96
+ /** The most tables `database.appendOnly` may name, and the shape of one name (`table` or `schema.table`). */
97
+ export declare const WORKER_APP_APPEND_ONLY_MAX = 100;
98
+ /** Hex characters of the org id's sha256 in a host id. */
99
+ export declare const WORKER_APP_HOST_ID_HASH_LENGTH = 12;
100
+ /**
101
+ * The custody host id of a Worker app: `app-<12 hex of sha256(org id)>-<entry key>`.
102
+ * It derives from the org id, never the slug, so a slug rename keeps it, and
103
+ * two orgs cannot share one (their hashes differ). The entry key is unique
104
+ * within an org. Null when the result breaks the custody pattern
105
+ * `^app-[a-z0-9]+(-[a-z0-9]+)*$`, 64 characters, or ends in `-next`.
106
+ */
107
+ export declare function workerAppHostId(orgId: string, entryKey: string): string | null;
108
+ /** The script name ends in this; the entry key's budget against Cloudflare's 63 characters follows from it. */
109
+ export declare const WORKER_APP_NAME_SUFFIX = "-app";
110
+ /**
111
+ * Checks an artifact reference: an https URL with no userinfo, query or
112
+ * fragment (those end up verbatim in recorded scope ids) and a lowercase hex
113
+ * sha256. Shared by `worker` and `workerApp`.
114
+ */
115
+ export declare function validateArtifact(at: string, artifactUrl: string, sha256: string, repo?: string | null): string | null;
116
+ /**
117
+ * The one exact name a Worker app is served at: `<host>.<domain>` or the apex
118
+ * on the org's own domain, else `<slug>-<key>.oikos.host` (one label under
119
+ * `oikos.host`, so the free Universal certificate covers it), and
120
+ * `<slug>.oikos.host` for the entry keyed `app`.
121
+ */
122
+ export declare function workerAppHostnameOf(org: {
123
+ slug: string;
124
+ domain: string | null;
125
+ usesDomain: boolean;
126
+ }, key: string, host: string): string;
127
+ /**
128
+ * The Durable Object classes the migrations leave behind, folded in order:
129
+ * created, then removed, then renamed. A rename of a class that does not exist
130
+ * at that point is an error.
131
+ */
132
+ export declare function foldMigrationClasses(migrations: WorkerAppConfig['durableObjects']['migrations']): {
133
+ classes: Set<string>;
134
+ } | {
135
+ error: string;
136
+ };
137
+ /**
138
+ * `workerApp`: exclusive with `worker` and a Coolify repo, no Coolify-only
139
+ * setting, a Durable Object and container declaration Cloudflare will accept,
140
+ * names that cannot clash with what foundry delivers, and one exact host.
141
+ * `null` when valid. Whether a migration was already applied is a fact of the
142
+ * live system; the planner checks that (`plan/worker-app.ts`).
143
+ */
144
+ export declare function validateEntryWorkerApp(entry: HostingEntry, hosting: HostingConfig, names?: {
145
+ domain: string | null;
146
+ locked: ReadonlySet<string>;
147
+ /** Without it the domain-less host `<slug>-<key>.oikos.host` cannot be checked. */
148
+ slug?: string;
149
+ /** True when the site is served at the apex of the org's domain. */
150
+ siteOwnsApex?: boolean;
151
+ }): string | null;