@wtfalch/foundry 0.61.0 → 0.63.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 (97) hide show
  1. package/dist/bootstrap/declare.js +4 -1
  2. package/dist/bootstrap/versions.d.ts +2 -0
  3. package/dist/index.d.ts +9 -1
  4. package/dist/index.js +3 -0
  5. package/dist/migrations/0065_native_build_runs.sql +32 -0
  6. package/dist/migrations/0066_org_kosmos.sql +10 -0
  7. package/dist/migrations/0067_requested_changes_kosmos.sql +6 -0
  8. package/dist/migrations/0068_platform_registry.sql +17 -0
  9. package/dist/modules/sign-in.js +1 -1
  10. package/dist/native-build.d.ts +50 -0
  11. package/dist/native-build.js +72 -0
  12. package/dist/org.d.ts +22 -3
  13. package/dist/org.js +25 -2
  14. package/dist/plan/agora.d.ts +1 -1
  15. package/dist/plan/agora.js +4 -0
  16. package/dist/plan/archon-blue-green.d.ts +5 -0
  17. package/dist/plan/archon-blue-green.js +2 -0
  18. package/dist/plan/blue-green-apply.d.ts +5 -0
  19. package/dist/plan/blue-green-apply.js +197 -22
  20. package/dist/plan/blue-green.d.ts +80 -9
  21. package/dist/plan/blue-green.js +115 -14
  22. package/dist/plan/boule.d.ts +1 -1
  23. package/dist/plan/boule.js +2 -0
  24. package/dist/plan/broker-entries.d.ts +9 -0
  25. package/dist/plan/broker-entries.js +29 -0
  26. package/dist/plan/deploy-health.js +9 -2
  27. package/dist/plan/desktop-callback.js +4 -1
  28. package/dist/plan/diff.d.ts +5 -0
  29. package/dist/plan/diff.js +184 -63
  30. package/dist/plan/edge-access.d.ts +2 -3
  31. package/dist/plan/edge-access.js +8 -0
  32. package/dist/plan/executor.d.ts +111 -19
  33. package/dist/plan/executor.js +578 -72
  34. package/dist/plan/index.d.ts +2 -0
  35. package/dist/plan/index.js +1 -0
  36. package/dist/plan/jobs-scheduler-http.d.ts +2 -0
  37. package/dist/plan/jobs-scheduler-http.js +11 -0
  38. package/dist/plan/kosmos.d.ts +56 -0
  39. package/dist/plan/kosmos.js +84 -0
  40. package/dist/plan/locked-hostnames.js +5 -2
  41. package/dist/plan/org-names.d.ts +3 -3
  42. package/dist/plan/org-names.js +3 -1
  43. package/dist/plan/org-postgres-migrate.js +1 -0
  44. package/dist/plan/own-limits.d.ts +12 -0
  45. package/dist/plan/own-limits.js +2 -0
  46. package/dist/plan/plan-apply.d.ts +51 -1
  47. package/dist/plan/plan-apply.js +298 -25
  48. package/dist/plan/platform-diff.d.ts +6 -0
  49. package/dist/plan/platform-diff.js +74 -0
  50. package/dist/plan/platform-org.d.ts +2 -0
  51. package/dist/plan/platform-providers.d.ts +46 -1
  52. package/dist/plan/platform-providers.js +162 -1
  53. package/dist/plan/platform-registry-keys.d.ts +58 -0
  54. package/dist/plan/platform-registry-keys.js +69 -0
  55. package/dist/plan/platform-registry.d.ts +67 -0
  56. package/dist/plan/platform-registry.js +298 -0
  57. package/dist/plan/post-logout-uris.d.ts +1 -1
  58. package/dist/plan/post-logout-uris.js +5 -2
  59. package/dist/plan/providers-readers.js +20 -1
  60. package/dist/plan/release-host.d.ts +31 -2
  61. package/dist/plan/release-host.js +114 -6
  62. package/dist/plan/stage-copy-host.d.ts +104 -0
  63. package/dist/plan/stage-copy-host.js +259 -0
  64. package/dist/plan/stage-copy.js +44 -0
  65. package/dist/plan/stage-env.d.ts +9 -0
  66. package/dist/plan/stage-env.js +38 -0
  67. package/dist/plan/stage-guards.d.ts +57 -1
  68. package/dist/plan/stage-guards.js +106 -0
  69. package/dist/plan/stage-isolation.js +56 -0
  70. package/dist/plan/stage-services.d.ts +93 -0
  71. package/dist/plan/stage-services.js +153 -0
  72. package/dist/plan/stage-signin.d.ts +152 -0
  73. package/dist/plan/stage-signin.js +133 -0
  74. package/dist/plan/stage-sink.d.ts +2 -0
  75. package/dist/plan/stage-sink.js +6 -1
  76. package/dist/platform.d.ts +7 -1
  77. package/dist/platform.js +1 -0
  78. package/dist/release/decide.d.ts +12 -0
  79. package/dist/release/decide.js +37 -2
  80. package/dist/release/migration-compatibility.d.ts +66 -0
  81. package/dist/release/migration-compatibility.js +340 -0
  82. package/dist/release/run.d.ts +71 -1
  83. package/dist/release/run.js +92 -8
  84. package/dist/release/state.d.ts +14 -1
  85. package/dist/release/state.js +43 -0
  86. package/dist/release/types.d.ts +20 -0
  87. package/dist/release/types.js +2 -0
  88. package/dist/requested-changes.d.ts +11 -2
  89. package/dist/requested-changes.js +13 -1
  90. package/dist/slug.d.ts +1 -1
  91. package/dist/slug.js +11 -2
  92. package/dist/store/postgres.js +21 -0
  93. package/dist/store/schema.d.ts +87 -0
  94. package/dist/store/schema.js +10 -0
  95. package/package.json +3 -2
  96. package/versions.digests.json +1 -0
  97. package/versions.json +1 -0
@@ -51,6 +51,20 @@ export interface ReleaseState {
51
51
  signal: ReleaseSignal | null;
52
52
  held: string[];
53
53
  config: ReleaseConfig;
54
+ /** The last stage copy request's outcome (`bg.<id>.stagecopy`); absent before the first. */
55
+ stageCopy?: StageCopyState;
56
+ /** The last `deploy_stage`: when, whether it worked, and the commit (ok) or the step that failed. */
57
+ stageDeploy?: {
58
+ at: string;
59
+ outcome: 'ok' | 'failed';
60
+ detail: string;
61
+ };
62
+ }
63
+ /** C3: what the last `copy_prod_to_stage` or `undo_stage_copy` request did. `detail` is fixed text or an id. */
64
+ export interface StageCopyState {
65
+ at: string;
66
+ outcome: 'ok' | 'refused' | 'failed';
67
+ detail: string;
54
68
  }
55
69
  export type ReleaseAction = {
56
70
  kind: 'promote';
@@ -69,6 +83,12 @@ export type ReleaseAction = {
69
83
  } | {
70
84
  kind: 'deploy_stage';
71
85
  ref: string;
86
+ } | {
87
+ kind: 'copy_prod_to_stage';
88
+ } | {
89
+ kind: 'undo_stage_copy';
72
90
  };
91
+ /** The requests that run a stage copy. They are handled by the tick's second phase, never by an apply. */
92
+ export declare const isStageCopyAction: (action: ReleaseAction) => boolean;
73
93
  export declare const COLORS: readonly Color[];
74
94
  export declare function otherColor(color: Color): Color;
@@ -1,3 +1,5 @@
1
+ /** The requests that run a stage copy. They are handled by the tick's second phase, never by an apply. */
2
+ export const isStageCopyAction = (action) => action.kind === 'copy_prod_to_stage' || action.kind === 'undo_stage_copy';
1
3
  export const COLORS = ['blue', 'green'];
2
4
  export function otherColor(color) {
3
5
  return color === 'blue' ? 'green' : 'blue';
@@ -16,7 +16,7 @@ import type { PlanDetail } from './plan/diff.js';
16
16
  * lowering or leaving a limit unchanged -- raising either one needs
17
17
  * approval too, until a billing field exists to say what the raise costs.
18
18
  */
19
- export type RequestableField = 'calls' | 'customers' | 'storageLimit' | 'aiLimit' | 'domain' | 'mailTier' | 'app' | 'build' | 'bouleApps';
19
+ export type RequestableField = 'calls' | 'customers' | 'storageLimit' | 'aiLimit' | 'domain' | 'mailTier' | 'app' | 'build' | 'bouleApps' | 'kosmos';
20
20
  export declare const REQUESTABLE_FIELDS: readonly RequestableField[];
21
21
  /** Turn the org's own calls Matrix stack (`org.ts`'s `CallsConfig.enabled`) on or off. */
22
22
  export interface CallsChange {
@@ -84,12 +84,21 @@ export interface BouleAppsChange {
84
84
  field: 'bouleApps';
85
85
  enabled: boolean;
86
86
  }
87
+ /**
88
+ * Turn `org.kosmos.enabled` on or off (`org.ts`'s `KosmosApp.enabled`). On provisions only what kosmos needs
89
+ * (its ZITADEL client, its database, its live and idle copies) -- auto. Off retires its app, database route and
90
+ * ZITADEL client (steps marked `destroys: true`), so it always needs a person, same shape as `bouleApps`.
91
+ */
92
+ export interface KosmosChange {
93
+ field: 'kosmos';
94
+ enabled: boolean;
95
+ }
87
96
  /**
88
97
  * One company-requested change to its own declared org, keyed by
89
98
  * {@link RequestableField}. `classifyChange` and the store both switch on
90
99
  * `.field`.
91
100
  */
92
- export type RequestedChangeValue = CallsChange | CustomersChange | StorageLimitChange | AiLimitChange | DomainChange | MailTierChange | AppChange | BuildChange | BouleAppsChange;
101
+ export type RequestedChangeValue = CallsChange | CustomersChange | StorageLimitChange | AiLimitChange | DomainChange | MailTierChange | AppChange | BuildChange | BouleAppsChange | KosmosChange;
93
102
  /**
94
103
  * `auto`: nothing further is required -- archon may carry the request
95
104
  * straight into a plan and apply it. `approval`: a human must approve the
@@ -9,6 +9,7 @@ export const REQUESTABLE_FIELDS = [
9
9
  'app',
10
10
  'build',
11
11
  'bouleApps',
12
+ 'kosmos',
12
13
  ];
13
14
  /**
14
15
  * Thrown by {@link classifyChange} (and by the requested-change store's
@@ -47,6 +48,8 @@ function applyRequestedChange(org, change) {
47
48
  return { ...org, mail: { ...org.mail, mode: change.mode } };
48
49
  case 'bouleApps':
49
50
  return { ...org, boule: { ...org.boule, apps: change.enabled } };
51
+ case 'kosmos':
52
+ return { ...org, kosmos: { ...org.kosmos, enabled: change.enabled } };
50
53
  case 'app':
51
54
  case 'build':
52
55
  return org;
@@ -124,8 +127,9 @@ export function classifyChange(org, change, plan) {
124
127
  throw new RequestedChangeError(`'${change.field}' is not a requestable field`);
125
128
  }
126
129
  // Archon validates the whole company for these; nothing waits for approval.
127
- if (change.field === 'app' || change.field === 'build')
130
+ if (change.field === 'app' || change.field === 'build') {
128
131
  return 'auto';
132
+ }
129
133
  if (change.field === 'bouleApps') {
130
134
  if (typeof change.enabled !== 'boolean') {
131
135
  throw new RequestedChangeError('bouleApps needs enabled, true or false');
@@ -134,6 +138,14 @@ export function classifyChange(org, change, plan) {
134
138
  // (`destroys: true` steps), so it needs a person whether or not a plan was given.
135
139
  return change.enabled ? 'auto' : 'approval';
136
140
  }
141
+ if (change.field === 'kosmos') {
142
+ if (typeof change.enabled !== 'boolean') {
143
+ throw new RequestedChangeError('kosmos needs enabled, true or false');
144
+ }
145
+ // Turning on provisions only what kosmos needs. Turning off retires its app, database route and
146
+ // ZITADEL client (`destroys: true` steps), so it needs a person whether or not a plan was given.
147
+ return change.enabled ? 'auto' : 'approval';
148
+ }
137
149
  if (change.field === 'customers') {
138
150
  validateCustomersChange(change);
139
151
  // The org after the change must still pass the rule apply checks, for example open registration
package/dist/slug.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  export declare const RESERVED_SLUGS: ReadonlySet<string>;
3
3
  /** Every platform app, tunnel, project and database name starts `platform-`, so no company slug may. */
4
4
  export declare const RESERVED_SLUG_PREFIX = "platform-";
5
- /** Whether `label` ends in a suffix reserved for another org's own hostname (`-stage`, `-pr-<n>`, `-calls`, `-agora`, `-boule`, `-auth`, `-mail`, `-files`, `-sfu`, `-cms`). */
5
+ /** Whether `label` ends in a suffix reserved for another org's own hostname (`-stage`, `-pr-<n>`, `-calls`, `-agora`, `-boule`, `-kosmos`, `-auth`, `-mail`, `-files`, `-sfu`, `-cms`). */
6
6
  export declare function hasBarredSlugSuffix(label: string): boolean;
7
7
  /**
8
8
  * Why {@link validateSlug} refused a candidate slug, by name so a caller —
package/dist/slug.js CHANGED
@@ -138,6 +138,14 @@ const AGORA_SUFFIX = /-agora$/;
138
138
  * one that opts in.
139
139
  */
140
140
  const BOULE_SUFFIX = /-boule$/;
141
+ /**
142
+ * `<something>-kosmos` — a level-1 org's own kosmos hostname
143
+ * (`<slug>-kosmos.oikos.host`, foundry#949). Kosmos is opt-in, the same way
144
+ * {@link CALLS_SUFFIX} guards a feature an org can decline: a slug ending
145
+ * `-kosmos` is only barred because it collides with that one org's own
146
+ * hostname once kosmos is on, not because every org gets one.
147
+ */
148
+ const KOSMOS_SUFFIX = /-kosmos$/;
141
149
  /** `<something>-auth|-mail|-files|-sfu|-cms` — an own company's service hostnames on oikos.host (`plan/own-hostnames.ts`). */
142
150
  const OWN_SERVICE_SUFFIX = /-(auth|mail|files|sfu|cms)$/;
143
151
  /**
@@ -151,13 +159,14 @@ const OWN_SERVICE_SUFFIX = /-(auth|mail|files|sfu|cms)$/;
151
159
  * `names-`.
152
160
  */
153
161
  const NAMES_PREFIX = /^names(-|$)/;
154
- /** Whether `label` ends in a suffix reserved for another org's own hostname (`-stage`, `-pr-<n>`, `-calls`, `-agora`, `-boule`, `-auth`, `-mail`, `-files`, `-sfu`, `-cms`). */
162
+ /** Whether `label` ends in a suffix reserved for another org's own hostname (`-stage`, `-pr-<n>`, `-calls`, `-agora`, `-boule`, `-kosmos`, `-auth`, `-mail`, `-files`, `-sfu`, `-cms`). */
155
163
  export function hasBarredSlugSuffix(label) {
156
164
  return (STAGE_SUFFIX.test(label) ||
157
165
  PR_SUFFIX.test(label) ||
158
166
  CALLS_SUFFIX.test(label) ||
159
167
  AGORA_SUFFIX.test(label) ||
160
168
  BOULE_SUFFIX.test(label) ||
169
+ KOSMOS_SUFFIX.test(label) ||
161
170
  OWN_SERVICE_SUFFIX.test(label));
162
171
  }
163
172
  /**
@@ -189,7 +198,7 @@ export function validateSlug(slug) {
189
198
  if (hasBarredSlugSuffix(slug)) {
190
199
  return {
191
200
  reason: 'environment-suffix',
192
- message: `slug '${slug}' ends with an environment suffix ('-stage', '-pr-<digits>', '-calls', '-agora', '-boule', '-auth', '-mail', '-files', '-sfu' or '-cms') reserved for another org's own hostname (#28, #145, #164, #161) — 'acme-calls' is only legal as acme's calls hostname, 'acme-agora' only as acme's Agora hostname, 'acme-boule' only as acme's Boule hostname, never as an org slug in its own right`,
201
+ message: `slug '${slug}' ends with an environment suffix ('-stage', '-pr-<digits>', '-calls', '-agora', '-boule', '-kosmos', '-auth', '-mail', '-files', '-sfu' or '-cms') reserved for another org's own hostname (#28, #145, #164, #161, #949) — 'acme-calls' is only legal as acme's calls hostname, 'acme-agora' only as acme's Agora hostname, 'acme-boule' only as acme's Boule hostname, 'acme-kosmos' only as acme's kosmos hostname, never as an org slug in its own right`,
193
202
  };
194
203
  }
195
204
  return null;
@@ -174,6 +174,17 @@ function rowToOrg(row) {
174
174
  ...(row.agoraBlueGreen
175
175
  ? { agora: { blueGreen: readBlueGreen(row.agoraBlueGreen, 'agora') } }
176
176
  : {}),
177
+ // Omitted while unset, as `boule`/`agora` are. Validated on read, the same way.
178
+ ...(row.kosmosBlueGreen || row.kosmosEnabled
179
+ ? {
180
+ kosmos: {
181
+ ...(row.kosmosBlueGreen
182
+ ? { blueGreen: readBlueGreen(row.kosmosBlueGreen, 'kosmos') }
183
+ : {}),
184
+ ...(row.kosmosEnabled ? { enabled: true } : {}),
185
+ },
186
+ }
187
+ : {}),
177
188
  ...(row.versionPin ? { versionPin: readVersionPin(row) } : {}),
178
189
  site: { enabled: row.siteEnabled, usesDomain: row.siteUsesDomain },
179
190
  hosting: {
@@ -240,6 +251,8 @@ function orgToRow(org) {
240
251
  bouleBlueGreen: org.boule?.blueGreen ?? null,
241
252
  bouleApps: org.boule?.apps ?? false,
242
253
  agoraBlueGreen: org.agora?.blueGreen ?? null,
254
+ kosmosEnabled: org.kosmos?.enabled ?? false,
255
+ kosmosBlueGreen: org.kosmos?.blueGreen ?? null,
243
256
  servicesOwn: org.services?.own ?? false,
244
257
  servicesSfuSlot: org.services?.sfuSlot ?? null,
245
258
  versionPin: org.versionPin && Object.keys(org.versionPin).length > 0 ? org.versionPin : null,
@@ -788,6 +801,11 @@ function rowToPlatform(row) {
788
801
  hostname: row.hcHostname,
789
802
  gitRef: row.hcGitRef,
790
803
  },
804
+ registry: {
805
+ enabled: row.registryEnabled,
806
+ hostname: row.registryHostname,
807
+ gitRef: row.registryGitRef,
808
+ },
791
809
  coolify: {
792
810
  enabled: row.coolifyEnabled,
793
811
  hostname: row.coolifyHostname,
@@ -837,6 +855,9 @@ function platformToRow(platform) {
837
855
  hcEnabled: platform.hc.enabled,
838
856
  hcHostname: platform.hc.hostname,
839
857
  hcGitRef: platform.hc.gitRef,
858
+ registryEnabled: platform.registry.enabled,
859
+ registryHostname: platform.registry.hostname,
860
+ registryGitRef: platform.registry.gitRef,
840
861
  coolifyEnabled: platform.coolify.enabled,
841
862
  coolifyHostname: platform.coolify.hostname,
842
863
  coolifyGitRef: platform.coolify.gitRef,
@@ -461,6 +461,42 @@ export declare const declaredOrgs: import("drizzle-orm/pg-core").PgTableWithColu
461
461
  identity: undefined;
462
462
  generated: undefined;
463
463
  }, {}, {}>;
464
+ kosmosEnabled: import("drizzle-orm/pg-core").PgColumn<{
465
+ name: "kosmos_enabled";
466
+ tableName: "foundry_declared_orgs";
467
+ dataType: "boolean";
468
+ columnType: "PgBoolean";
469
+ data: boolean;
470
+ driverParam: boolean;
471
+ notNull: true;
472
+ hasDefault: true;
473
+ isPrimaryKey: false;
474
+ isAutoincrement: false;
475
+ hasRuntimeDefault: false;
476
+ enumValues: undefined;
477
+ baseColumn: never;
478
+ identity: undefined;
479
+ generated: undefined;
480
+ }, {}, {}>;
481
+ kosmosBlueGreen: import("drizzle-orm/pg-core").PgColumn<{
482
+ name: "kosmos_blue_green";
483
+ tableName: "foundry_declared_orgs";
484
+ dataType: "json";
485
+ columnType: "PgJsonb";
486
+ data: BlueGreenConfig | null;
487
+ driverParam: unknown;
488
+ notNull: false;
489
+ hasDefault: false;
490
+ isPrimaryKey: false;
491
+ isAutoincrement: false;
492
+ hasRuntimeDefault: false;
493
+ enumValues: undefined;
494
+ baseColumn: never;
495
+ identity: undefined;
496
+ generated: undefined;
497
+ }, {}, {
498
+ $type: BlueGreenConfig | null;
499
+ }>;
464
500
  versionPin: import("drizzle-orm/pg-core").PgColumn<{
465
501
  name: "version_pin";
466
502
  tableName: "foundry_declared_orgs";
@@ -2256,6 +2292,57 @@ export declare const foundryDeclaredPlatform: import("drizzle-orm/pg-core").PgTa
2256
2292
  identity: undefined;
2257
2293
  generated: undefined;
2258
2294
  }, {}, {}>;
2295
+ registryEnabled: import("drizzle-orm/pg-core").PgColumn<{
2296
+ name: "registry_enabled";
2297
+ tableName: "foundry_declared_platform";
2298
+ dataType: "boolean";
2299
+ columnType: "PgBoolean";
2300
+ data: boolean;
2301
+ driverParam: boolean;
2302
+ notNull: true;
2303
+ hasDefault: true;
2304
+ isPrimaryKey: false;
2305
+ isAutoincrement: false;
2306
+ hasRuntimeDefault: false;
2307
+ enumValues: undefined;
2308
+ baseColumn: never;
2309
+ identity: undefined;
2310
+ generated: undefined;
2311
+ }, {}, {}>;
2312
+ registryHostname: import("drizzle-orm/pg-core").PgColumn<{
2313
+ name: "registry_hostname";
2314
+ tableName: "foundry_declared_platform";
2315
+ dataType: "string";
2316
+ columnType: "PgText";
2317
+ data: string;
2318
+ driverParam: string;
2319
+ notNull: true;
2320
+ hasDefault: false;
2321
+ isPrimaryKey: false;
2322
+ isAutoincrement: false;
2323
+ hasRuntimeDefault: false;
2324
+ enumValues: [string, ...string[]];
2325
+ baseColumn: never;
2326
+ identity: undefined;
2327
+ generated: undefined;
2328
+ }, {}, {}>;
2329
+ registryGitRef: import("drizzle-orm/pg-core").PgColumn<{
2330
+ name: "registry_git_ref";
2331
+ tableName: "foundry_declared_platform";
2332
+ dataType: "string";
2333
+ columnType: "PgText";
2334
+ data: string;
2335
+ driverParam: string;
2336
+ notNull: true;
2337
+ hasDefault: true;
2338
+ isPrimaryKey: false;
2339
+ isAutoincrement: false;
2340
+ hasRuntimeDefault: false;
2341
+ enumValues: [string, ...string[]];
2342
+ baseColumn: never;
2343
+ identity: undefined;
2344
+ generated: undefined;
2345
+ }, {}, {}>;
2259
2346
  coolifyEnabled: import("drizzle-orm/pg-core").PgColumn<{
2260
2347
  name: "coolify_enabled";
2261
2348
  tableName: "foundry_declared_platform";
@@ -48,6 +48,9 @@ export const declaredOrgs = pgTable('foundry_declared_orgs', {
48
48
  agoraBlueGreen: jsonb('agora_blue_green').$type(),
49
49
  // BouleApp.apps (org.ts): Boule runs apps for the company; `migrations/0063_boule_apps.sql`.
50
50
  bouleApps: boolean('boule_apps').notNull().default(false),
51
+ // KosmosApp (org.ts, foundry#949): the company's own kosmos, set up only when asked; `migrations/0066_org_kosmos.sql`.
52
+ kosmosEnabled: boolean('kosmos_enabled').notNull().default(false),
53
+ kosmosBlueGreen: jsonb('kosmos_blue_green').$type(),
51
54
  // VersionPin (org.ts): null follows the platform. `migrations/0053_org_version_pin.sql`.
52
55
  versionPin: jsonb('version_pin').$type(),
53
56
  // SiteConfig (org.ts, T10). A brand new org has no cms site.
@@ -80,6 +83,9 @@ export const declaredOrgs = pgTable('foundry_declared_orgs', {
80
83
  hostingRepos: jsonb('hosting_repos').notNull().default([]).$type(),
81
84
  // HostingConfig.github (org.ts); migrations/0062. Null: the company has no GitHub App installation.
82
85
  hostingGithub: jsonb('hosting_github').$type(),
86
+ // migrations/0065 added `hosting_native_apps` for foundry#948; foundry#951 dropped the column from
87
+ // use (Boule owns native settings now, estate#262) but not from the database -- a migration only
88
+ // adds in this release, since idle and live share one database. The column stays, unread.
83
89
  ssoEnabled: boolean('sso_enabled').notNull().default(false),
84
90
  guestsEnabled: boolean('guests_enabled').notNull().default(false),
85
91
  soloEnabled: boolean('solo_enabled').notNull().default(false),
@@ -213,6 +219,10 @@ export const foundryDeclaredPlatform = pgTable('foundry_declared_platform', {
213
219
  hcEnabled: boolean('hc_enabled').notNull().default(false),
214
220
  hcHostname: text('hc_hostname').notNull(),
215
221
  hcGitRef: text('hc_git_ref').notNull().default(''),
222
+ /** `migrations/0067_platform_registry.sql`: the platform's image registry. */
223
+ registryEnabled: boolean('registry_enabled').notNull().default(false),
224
+ registryHostname: text('registry_hostname').notNull(),
225
+ registryGitRef: text('registry_git_ref').notNull().default(''),
216
226
  /** `migrations/0047_platform_coolify.sql`: the Coolify panel's declared address; foundry never deploys it. */
217
227
  coolifyEnabled: boolean('coolify_enabled').notNull().default(false),
218
228
  coolifyHostname: text('coolify_hostname').notNull(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/foundry",
3
- "version": "0.61.0",
3
+ "version": "0.63.0",
4
4
  "description": "Foundry's SDK: the declared org's types, the plan and apply contracts, and the state store's interface.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -61,9 +61,10 @@
61
61
  "@wtfalch/audit": "0.6.0",
62
62
  "@wtfalch/audit-sealing": "npm:@wtfalch/audit@0.14.0",
63
63
  "@wtfalch/auth": "^0.26.0",
64
- "@wtfalch/edge": "0.15.1",
64
+ "@wtfalch/edge": "0.23.0",
65
65
  "@wtfalch/email": "^0.20.0",
66
66
  "@wtfalch/providers": "0.12.0",
67
+ "bcryptjs": "3.0.3",
67
68
  "ssh2": "1.17.0",
68
69
  "zod": "4.6.5"
69
70
  },
@@ -6,6 +6,7 @@
6
6
  "cloudflared": "sha256:072c067d25ccbe61d46e18f0d0723255f2bb5304f7317caa95b27031520ff92c",
7
7
  "stalwart": "sha256:ec011be228596e37e65f41aab17deed573859614430472f7eeb42178c50d87b7",
8
8
  "hc": "sha256:aa08a61b0dcfd83f1332eab195d5cec1b595ae076eb8388cadf8a3d597114f9b",
9
+ "registry": "sha256:01cd312e67026f06f5d89c3e133a4a9adc7cb9eeb9b1194e0b2954dfd770efd2",
9
10
  "alpine": "sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1",
10
11
  "busybox": "sha256:5cec3fc171c87218698e85a52af7087de727372aae264a787b8112901a5b0092",
11
12
  "postgres": "sha256:786dab398303b8ce7cb76b407bb21ef2e4dfbbbd4c6abcf3d29b3130467ffdbc",
package/versions.json CHANGED
@@ -6,6 +6,7 @@
6
6
  "cloudflared": "2026.9.3",
7
7
  "stalwart": "v0.16.24",
8
8
  "hc": "v4.4",
9
+ "registry": "3.1.2",
9
10
  "alpine": "3.22.1",
10
11
  "busybox": "1.37.0-musl",
11
12
  "postgres": "16.14-alpine3.22",