@wtfalch/foundry 0.33.1 → 0.35.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.
Files changed (106) hide show
  1. package/dist/bootstrap/bootstrap.d.ts +1 -1
  2. package/dist/bootstrap/bootstrap.js +49 -11
  3. package/dist/bootstrap/declare.d.ts +12 -5
  4. package/dist/bootstrap/declare.js +33 -18
  5. package/dist/bootstrap/versions.d.ts +24 -0
  6. package/dist/bootstrap/versions.js +46 -0
  7. package/dist/calls-sfu.d.ts +10 -0
  8. package/dist/calls-sfu.js +44 -3
  9. package/dist/index.d.ts +21 -8
  10. package/dist/index.js +11 -4
  11. package/dist/migrations/0051_org_services.sql +13 -0
  12. package/dist/migrations/0052_platform_mail_relay.sql +8 -0
  13. package/dist/migrations/0053_heartbeats_provider.sql +9 -0
  14. package/dist/migrations/0053_org_version_pin.sql +6 -0
  15. package/dist/org.d.ts +76 -12
  16. package/dist/org.js +136 -20
  17. package/dist/plan/backup-gate.d.ts +93 -0
  18. package/dist/plan/backup-gate.js +261 -0
  19. package/dist/plan/broker-entries.d.ts +9 -1
  20. package/dist/plan/broker-entries.js +20 -3
  21. package/dist/plan/build-record.d.ts +56 -0
  22. package/dist/plan/build-record.js +109 -0
  23. package/dist/plan/calls-matrix.d.ts +9 -0
  24. package/dist/plan/calls-matrix.js +18 -1
  25. package/dist/plan/cloudflared-connector.d.ts +7 -1
  26. package/dist/plan/cloudflared-connector.js +15 -2
  27. package/dist/plan/company-backups.d.ts +31 -0
  28. package/dist/plan/company-backups.js +101 -0
  29. package/dist/plan/deploy-health.d.ts +12 -2
  30. package/dist/plan/deploy-health.js +3 -1
  31. package/dist/plan/destinations.d.ts +73 -0
  32. package/dist/plan/destinations.js +217 -0
  33. package/dist/plan/diff.d.ts +46 -8
  34. package/dist/plan/diff.js +367 -126
  35. package/dist/plan/edge-access.d.ts +15 -0
  36. package/dist/plan/edge-access.js +21 -2
  37. package/dist/plan/executor.d.ts +276 -5
  38. package/dist/plan/executor.js +1111 -109
  39. package/dist/plan/health-check-settings.d.ts +28 -0
  40. package/dist/plan/health-check-settings.js +36 -0
  41. package/dist/plan/health-gaps.d.ts +33 -0
  42. package/dist/plan/health-gaps.js +89 -0
  43. package/dist/plan/health-targets.d.ts +1 -3
  44. package/dist/plan/health-targets.js +36 -36
  45. package/dist/plan/held-key-refs.d.ts +15 -0
  46. package/dist/plan/held-key-refs.js +15 -0
  47. package/dist/plan/hostnames.d.ts +10 -0
  48. package/dist/plan/hostnames.js +14 -0
  49. package/dist/plan/index.d.ts +19 -6
  50. package/dist/plan/index.js +10 -3
  51. package/dist/plan/live.d.ts +11 -2
  52. package/dist/plan/live.js +16 -2
  53. package/dist/plan/locked-hostnames.d.ts +7 -3
  54. package/dist/plan/locked-hostnames.js +25 -4
  55. package/dist/plan/login-host.d.ts +61 -12
  56. package/dist/plan/login-host.js +99 -11
  57. package/dist/plan/mail-relay.d.ts +139 -0
  58. package/dist/plan/mail-relay.js +400 -0
  59. package/dist/plan/network-move.d.ts +20 -0
  60. package/dist/plan/network-move.js +41 -0
  61. package/dist/plan/oikos-ingress.d.ts +111 -0
  62. package/dist/plan/oikos-ingress.js +231 -0
  63. package/dist/plan/org-names.d.ts +30 -0
  64. package/dist/plan/org-names.js +108 -0
  65. package/dist/plan/own-diff.d.ts +170 -0
  66. package/dist/plan/own-diff.js +440 -0
  67. package/dist/plan/own-dns.d.ts +6 -0
  68. package/dist/plan/own-dns.js +15 -0
  69. package/dist/plan/own-hostnames.d.ts +27 -0
  70. package/dist/plan/own-hostnames.js +24 -0
  71. package/dist/plan/own-limits.d.ts +213 -0
  72. package/dist/plan/own-limits.js +232 -0
  73. package/dist/plan/own-services.d.ts +193 -0
  74. package/dist/plan/own-services.js +169 -0
  75. package/dist/plan/own-sfu.d.ts +69 -0
  76. package/dist/plan/own-sfu.js +256 -0
  77. package/dist/plan/plan-apply.d.ts +121 -10
  78. package/dist/plan/plan-apply.js +357 -18
  79. package/dist/plan/platform-diff.d.ts +155 -11
  80. package/dist/plan/platform-diff.js +271 -66
  81. package/dist/plan/platform-org.d.ts +107 -4
  82. package/dist/plan/platform-org.js +175 -3
  83. package/dist/plan/platform-providers.d.ts +474 -81
  84. package/dist/plan/platform-providers.js +1407 -560
  85. package/dist/plan/proxy-labels.d.ts +57 -11
  86. package/dist/plan/proxy-labels.js +94 -14
  87. package/dist/plan/run-options.d.ts +19 -0
  88. package/dist/plan/run-options.js +46 -0
  89. package/dist/plan/sign-in-org.d.ts +1 -1
  90. package/dist/plan/sign-in-org.js +20 -2
  91. package/dist/plan/zitadel-login-policy.d.ts +1 -1
  92. package/dist/plan/zitadel-login-policy.js +77 -9
  93. package/dist/plan/zitadel-system-api.d.ts +22 -1
  94. package/dist/plan/zitadel-system-api.js +49 -8
  95. package/dist/platform.d.ts +40 -0
  96. package/dist/platform.js +16 -0
  97. package/dist/provision.d.ts +2 -0
  98. package/dist/provision.js +3 -1
  99. package/dist/slug.d.ts +3 -1
  100. package/dist/slug.js +26 -3
  101. package/dist/store/postgres.js +93 -2
  102. package/dist/store/schema.d.ts +87 -0
  103. package/dist/store/schema.js +9 -0
  104. package/package.json +3 -3
  105. package/versions.digests.json +11 -0
  106. package/versions.json +3 -1
@@ -7,10 +7,12 @@ import type { AppliedState } from '../state.js';
7
7
  import { type AppliedVersionsStore, type DeclaredOrgStore, type PlatformStore } from '../store.js';
8
8
  import { type AdoptCandidate, type AdoptInput, type AdoptPlan } from './adopt.js';
9
9
  import { type AgoraHostPort } from './agora.js';
10
+ import { type MoveGatePorts } from './backup-gate.js';
10
11
  import { type UnconfirmedUpgrade } from './confirm-upgrade.js';
11
12
  import { type StepAction } from './diff.js';
12
13
  import { type LiveReaders, type LiveResource, type PlatformLiveReaders } from './live.js';
13
- import { type EdgePortName, type HeldKeyResolver } from './platform-org.js';
14
+ import { type LoginHostDiscoveryPort } from './login-host.js';
15
+ import { type EdgePortName, type HeldKeyResolver, type PlatformOwnerInput } from './platform-org.js';
14
16
  import type { CallsSfuConnectionPort } from './platform-sfu.js';
15
17
  /** Who did it, for `@wtfalch/audit`'s "who, when, the plan, the result" (#9, #19). */
16
18
  export interface AuditActor {
@@ -123,6 +125,11 @@ export interface AuthzGate {
123
125
  */
124
126
  export interface StepExecutionContext {
125
127
  readonly org: DeclaredOrg;
128
+ /**
129
+ * True when `org` is the platform's own company (`DeclaredPlatform.ownerOrgId`). It stays on the
130
+ * platform's Docker network, not on `co-<slug>`, so Archon reaches its database. Absent: false.
131
+ */
132
+ readonly ownerCompany?: boolean;
126
133
  /**
127
134
  * Every id foundry has recorded for this org so far — both `system:kind:key`
128
135
  * resource ids (`runSteps` records these itself from `execute`'s own return
@@ -172,6 +179,39 @@ export interface StepExecutor {
172
179
  * an executor without it supplies none.
173
180
  */
174
181
  ownEdgePorts?(): Partial<Record<EdgePortName, boolean>>;
182
+ /**
183
+ * What the executor's own files dependencies supply (`ownServices.provision.files`):
184
+ * valid keys worker settings, and a bucket port. The planner blocks an own
185
+ * org's files host until both are true. Optional: an executor without it supplies neither.
186
+ */
187
+ ownFilesDeps?(): {
188
+ keys: boolean;
189
+ bucket: boolean;
190
+ };
191
+ /**
192
+ * Which ports an own org's files storage key step needs this executor has: the R2 key port, and
193
+ * `storeR2Credentials` on the org's files-organisation port. Optional: an executor without it
194
+ * leaves the step unplanned.
195
+ */
196
+ ownFilesR2Ports?(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<{
197
+ r2Keys: boolean;
198
+ storeR2Credentials: boolean;
199
+ }>;
200
+ /** Whether the executor can mint an own org's Lettermint key: a Lettermint port and a reader for the held ref. */
201
+ ownMailKeyMintable?(): boolean;
202
+ /**
203
+ * Whether the host's scoped ports for this own org include a files-organisation
204
+ * port. The planner plans no `files:organisation` step for an own org while
205
+ * this says false. Optional: an executor without it is taken to have the port.
206
+ */
207
+ ownFilesOrganisationPort?(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<boolean>;
208
+ /**
209
+ * Whether the host's scoped ports for this own org include a broker port, the company's own
210
+ * broker. An own org's broker entries are planned on this answer, not on
211
+ * {@link FoundryPlanApplyOptions.broker}, which names the shared broker. Optional: an executor
212
+ * without it leaves the option as the answer.
213
+ */
214
+ ownBrokerPort?(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>): Promise<boolean>;
175
215
  /**
176
216
  * Every secret this executor minted or read so far (peer password, bridge
177
217
  * secret, invite code and link). The host's run adds them to its redactor
@@ -192,6 +232,8 @@ export interface StepExecutor {
192
232
  */
193
233
  export interface PlatformStepExecutionContext {
194
234
  readonly platform: DeclaredPlatform;
235
+ /** The owner company's own sign-in and files host, when applied. Archon and the Access provider read them. */
236
+ readonly owner?: PlatformOwnerInput;
195
237
  /** {@link StepExecutionContext.recordedIds}, for the platform's own run. */
196
238
  recordedIds(): Readonly<Record<string, string>>;
197
239
  /** {@link StepExecutionContext.recordScopeId}, for the platform's own run. */
@@ -227,7 +269,7 @@ export interface PlatformStepExecutor {
227
269
  * unmanaged item and plans no step for it, the way a blocked Agora is
228
270
  * reported, instead of planning a step that throws on every apply.
229
271
  */
230
- blockedSteps?(platform: DeclaredPlatform, recordedIds: Readonly<Record<string, string>>): Promise<{
272
+ blockedSteps?(platform: DeclaredPlatform, recordedIds: Readonly<Record<string, string>>, owner?: PlatformOwnerInput): Promise<{
231
273
  auth?: string;
232
274
  /** #211 round 6: see `PlanPlatformOptions.blocked.authExecutorMissing`. */
233
275
  authExecutorMissing?: boolean;
@@ -238,8 +280,12 @@ export interface PlatformStepExecutor {
238
280
  ai?: string;
239
281
  archon?: string;
240
282
  mail?: string;
283
+ /** See `PlanPlatformOptions.blocked.relay` (`platform-diff.ts`). */
284
+ relay?: string;
241
285
  /** #604: see `PlanPlatformOptions.blocked.accessProvider` (`platform-diff.ts`). */
242
286
  accessProvider?: string;
287
+ /** See `PlanPlatformOptions.blocked.oikosIngress` (`platform-diff.ts`). */
288
+ oikosIngress?: string;
243
289
  /** #257: see `PlanPlatformOptions.blocked.backupExecutorMissing` (`platform-diff.ts`). */
244
290
  backupExecutorMissing?: boolean;
245
291
  /** #540: see `PlanPlatformOptions.blocked.hc` (`platform-diff.ts`). */
@@ -262,7 +308,7 @@ export interface PlatformStepExecutor {
262
308
  * the token and the edge step does not clear the flag, so the host must stop
263
309
  * reporting it once edge holds the new token and the old one is gone.
264
310
  */
265
- driftedSteps?(platform: DeclaredPlatform, recordedIds: Readonly<Record<string, string>>): Promise<PlatformDriftedSteps>;
311
+ driftedSteps?(platform: DeclaredPlatform, recordedIds: Readonly<Record<string, string>>, owner?: PlatformOwnerInput): Promise<PlatformDriftedSteps>;
266
312
  /**
267
313
  * Gives the platform files runner a changed share-domain set and touches
268
314
  * nothing else. Returns the scope ids to record (only the share-domains
@@ -382,6 +428,14 @@ export interface FoundryPlanApplyOptions {
382
428
  * before zitadel-login's binding can be written.
383
429
  */
384
430
  loginHostBindings?: boolean;
431
+ /**
432
+ * Reads `GET https://<login host>/.well-known/openid-configuration` for a
433
+ * company's login host (5 s limit). Optional, and without it every company's
434
+ * Agora keeps the platform issuer and the plan carries a blocked line for the
435
+ * switch. The host must route the company's login host to a login app that
436
+ * serves discovery (https://github.com/wtfalch/auth/pull/233).
437
+ */
438
+ loginHostDiscovery?: LoginHostDiscoveryPort;
385
439
  /**
386
440
  * True when the host's ZITADEL client has `updateOidcAppIdTokenUserinfoAssertion`
387
441
  * (`@wtfalch/providers` at the version that has it). Optional, and false
@@ -420,6 +474,23 @@ export interface FoundryPlanApplyOptions {
420
474
  * reported blocked instead of planned to fail.
421
475
  */
422
476
  edge?: boolean;
477
+ /**
478
+ * The executor was built with an `ownServices` port. Without it, an org
479
+ * that runs its own services (`services.own`) is reported blocked and none of
480
+ * its steps run.
481
+ */
482
+ ownServices?: boolean;
483
+ /**
484
+ * Says whether an own org's media server slot has its ports open on the
485
+ * Coolify server. Foundry opens none, so without this the plan keeps a
486
+ * fixed line naming them. A call that fails or takes over five seconds
487
+ * counts as not open.
488
+ */
489
+ sfuPortsOpen?: (range: {
490
+ udpStart: number;
491
+ udpEnd: number;
492
+ tcp: number;
493
+ }) => Promise<boolean>;
423
494
  /**
424
495
  * Narrows every org plan to the steps `step` accepts, and its blocked items
425
496
  * to the keys `blocked` accepts, so a plan, its stale check and its apply
@@ -459,6 +530,15 @@ export interface FoundryPlanApplyOptions {
459
530
  * every existing caller of this constructor is unaffected.
460
531
  */
461
532
  appliedVersions?: AppliedVersionsStore;
533
+ /**
534
+ * F6: the Coolify slice that reads a company database's backups. With it, an
535
+ * org's databases get backup steps and a confirmed stateful upgrade is
536
+ * refused without a fresh finished dump (`backup-gate.ts`). Without it
537
+ * neither happens, so an existing caller is unaffected.
538
+ */
539
+ backupGate?: {
540
+ coolify: MoveGatePorts['coolify'];
541
+ };
462
542
  /**
463
543
  * #167: dependencies for planning/applying the shared platform (#166)
464
544
  * alongside an org. Optional so an org-only caller — every #19-era caller,
@@ -487,12 +567,6 @@ export interface FoundryPlanApplyOptions {
487
567
  heldKeys?: HeldKeyResolver;
488
568
  };
489
569
  }
490
- /**
491
- * The #19 implementation of {@link PlanApply}: computes a plan against live
492
- * state, and refuses to apply one that has gone stale, one that touches a
493
- * destructive or migrating step without the org's name typed, or one the
494
- * actor is not authorized to run.
495
- */
496
570
  export declare class FoundryPlanApply implements PlanApply {
497
571
  private readonly store;
498
572
  private readonly readers;
@@ -504,6 +578,7 @@ export declare class FoundryPlanApply implements PlanApply {
504
578
  private readonly agora;
505
579
  private readonly jobsScheduler;
506
580
  private readonly loginHostBindings;
581
+ private readonly loginHostDiscovery;
507
582
  private readonly zitadelIdTokenUserinfo;
508
583
  private readonly broker;
509
584
  private readonly edgeWorkers;
@@ -511,10 +586,13 @@ export declare class FoundryPlanApply implements PlanApply {
511
586
  private readonly hostFiles;
512
587
  private readonly hostAi;
513
588
  private readonly hostEdge;
589
+ private readonly hostOwnServices;
590
+ private readonly sfuPortsOpen;
514
591
  private readonly onlySteps;
515
592
  private readonly now;
516
593
  private readonly leaseTtlMs;
517
594
  private readonly appliedVersions;
595
+ private readonly backupGate;
518
596
  private readonly platformDeps;
519
597
  private readonly runner;
520
598
  constructor(options: FoundryPlanApplyOptions);
@@ -653,6 +731,8 @@ export declare class FoundryPlanApply implements PlanApply {
653
731
  private refusePlatform;
654
732
  private refuseDestroy;
655
733
  private runSteps;
734
+ /** An own org with calls on: the host's answer about its slot's ports. A failure or a slow answer is "not open". */
735
+ private readSfuPortsOpen;
656
736
  private computeFreshPlan;
657
737
  /**
658
738
  * An entry may not take a name foundry owns (`locked-hostnames.ts`). The
@@ -669,12 +749,31 @@ export declare class FoundryPlanApply implements PlanApply {
669
749
  * org step that needs one is blocked.
670
750
  */
671
751
  private orgPlatformReadiness;
752
+ /** An own org's Lettermint key is held. A resolver that throws counts as not held. */
753
+ private ownMailKeyHeld;
754
+ /** Which of an own org's undelivered-secret refs hold a value now, so a rerun resumes instead of blocking. */
755
+ private ownResumableRefs;
756
+ /**
757
+ * The host port an own org's Stalwart publishes for the relay: the one it already has recorded, else
758
+ * the lowest port in the range no other org has recorded. Two plans made at the same moment could pick
759
+ * the same port; the second Stalwart then fails to bind it and the step fails, with nothing recorded.
760
+ */
761
+ private ownMailSmtpPort;
762
+ /** The relay is declared and has recorded the routes it last applied: port 25 reaches the mail servers. */
763
+ private mailRelayApplied;
764
+ /**
765
+ * The relay's last applied routes (a fingerprint, recorded when it deploys) are the routes the
766
+ * orgs' recorded SMTP addresses give now, and one of them is this org's mail domain.
767
+ */
768
+ private mailRelayHoldsRoute;
769
+ private sharedPlatformReadiness;
672
770
  /**
673
771
  * #177: `AgoraHostValues` from the applied platform, the same
674
772
  * `platformDeps` {@link orgPlatformReadiness} reads -- but only when the
675
773
  * host supplies no `agora` port of its own. An explicit `agora` (#164)
676
774
  * always wins, unchanged, so a host that already has its own values
677
- * (perhaps not yet wired to this platform) keeps them.
775
+ * (perhaps not yet wired to this platform) keeps them. An org that owns its
776
+ * services never takes the host's values: they belong to the shared services.
678
777
  */
679
778
  private orgAgoraHost;
680
779
  /**
@@ -690,6 +789,18 @@ export declare class FoundryPlanApply implements PlanApply {
690
789
  /** #167: `FoundryPlanApplyOptions.platform`, or a plain `Error` naming what's missing. */
691
790
  private requirePlatformDeps;
692
791
  private computeFreshPlatformPlan;
792
+ /** The owner company's own sign-in and files host, read from its applied ids; `null` members until applied. */
793
+ private platformOwnerInput;
794
+ /**
795
+ * What the shared mail relay needs from the orgs. A route exists for an org
796
+ * that owns its services, serves mail on its own domain and has recorded its
797
+ * mail server's SMTP address (`mail_smtp_internal`, written by the company
798
+ * mail step), on its mail domain: its own domain, or `<slug>.oikos.host`.
799
+ * There is no platform mail server: a domain with no route is refused. An org
800
+ * that owns its services but has no mail server yet has no route, so the
801
+ * relay refuses its mail until the server exists.
802
+ */
803
+ private mailRelayInputs;
693
804
  private sfuBlockedReason;
694
805
  /**
695
806
  * {@link runSteps}, for the platform. The ids the steps return are recorded