@wtfalch/foundry 0.28.0 → 0.30.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.
@@ -712,6 +712,39 @@ export interface CallsSfuPort {
712
712
  apiSecret: string;
713
713
  } | null>;
714
714
  }
715
+ /**
716
+ * #463: the cross-homeserver copy chat's forward re-key needs (design note,
717
+ * `docs/chat-rekey-design.md`, Option 1) -- copies every room, member and
718
+ * message from the org's platform-named homeserver onto its newly
719
+ * provisioned, domain-named one, inviting members under their new ids, so
720
+ * history reads continuous once the chat service is repointed
721
+ * (`coolifyChatRekey`).
722
+ *
723
+ * Optional on {@link FoundryStepExecutorPorts}, the same shape as
724
+ * `jobsScheduler`/`edge`/`mailCopy`: nothing in the estate has proven this
725
+ * mechanism against a real Synapse and MAS pair yet (issue #463's own Human
726
+ * step), so a host without one builds the executor without it and
727
+ * `coolify:chat`'s forward re-key throws `NotBuiltError` naming the gap,
728
+ * rather than running a copy nobody has watched work.
729
+ *
730
+ * Idempotent by `fromServerName`/`toServerName`: a room or message the
731
+ * implementation already copied for this pair is skipped, so a retried or
732
+ * resumed copy never duplicates history. Never returns or throws a secret,
733
+ * token or credential -- same rule as every other port here.
734
+ */
735
+ export interface ChatRekeyPort {
736
+ copyHomeserver(input: {
737
+ orgSlug: string;
738
+ fromServerName: string;
739
+ fromInternalHost: string;
740
+ toServerName: string;
741
+ toInternalHost: string;
742
+ /** Aborted when foundry stops waiting for the copy. */
743
+ signal: AbortSignal;
744
+ }): Promise<{
745
+ roomsCopied: number;
746
+ }>;
747
+ }
715
748
  /**
716
749
  * The ZITADEL calls this executor makes. `findOrCreateOrg`/`findOrCreateProject`
717
750
  * already exist on `@wtfalch/providers/zitadel`'s `ZitadelClient` (returning
@@ -871,6 +904,16 @@ export interface ZitadelPort {
871
904
  allowSelfRegistration(orgId: string): Promise<{
872
905
  changed: boolean;
873
906
  }>;
907
+ /**
908
+ * Forces the org's login policy to `allowRegister: false`, writing the org
909
+ * its own policy even when it only inherits the instance default.
910
+ * Idempotent: a custom policy that already forbids registration is no
911
+ * write. `@wtfalch/providers` can only turn registration on, so
912
+ * `zitadel-login-policy.ts` is the real implementation.
913
+ */
914
+ closeSelfRegistration(orgId: string): Promise<{
915
+ changed: boolean;
916
+ }>;
874
917
  /** Grants another ZITADEL org access to a project (plan gap A9). Idempotent. */
875
918
  findOrCreateProjectGrant(orgId: string, projectId: string, grantedOrgId: string): Promise<{
876
919
  created: boolean;
@@ -1010,6 +1053,14 @@ export interface ZitadelPort {
1010
1053
  * the binding holds; `zitadelApp` never sets it at creation.
1011
1054
  */
1012
1055
  updateOidcAppLoginBaseUri(orgId: string, projectId: string, appId: string, loginBaseUri: string): Promise<void>;
1056
+ /**
1057
+ * Occupies `userName` in `orgId` with a machine user that has no role and
1058
+ * no token, so nobody can register under that name. Idempotent: a user
1059
+ * already holding the name, however it was made, is returned unchanged.
1060
+ */
1061
+ reserveUserName(orgId: string, userName: string): Promise<{
1062
+ userId: string;
1063
+ }>;
1013
1064
  /**
1014
1065
  * Switches on "user info inside the ID token" for an already-created app,
1015
1066
  * read-modify-write (`@wtfalch/providers`' `ZitadelClient.
@@ -1405,6 +1456,13 @@ export interface FoundryStepExecutorPorts {
1405
1456
  * compilation over one dependency nothing else needs.
1406
1457
  */
1407
1458
  mailCopy?: MailCopyPort;
1459
+ /**
1460
+ * #463: the cross-homeserver copy chat's forward re-key needs -- see
1461
+ * {@link ChatRekeyPort}'s own doc comment. Optional the same way
1462
+ * `mailCopy` is: without it `coolify:chat`'s forward re-key throws
1463
+ * `NotBuiltError` naming the gap instead of running an unproven copy.
1464
+ */
1465
+ chatRekey?: ChatRekeyPort;
1408
1466
  /** 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). */
1409
1467
  filesOrganisation?: FilesOrganisationPort;
1410
1468
  aiOrganisation?: AiOrganisationPort;
@@ -1957,6 +2015,10 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
1957
2015
  * domain restricted to them before any old project is deleted, so the
1958
2016
  * restriction list never becomes empty.
1959
2017
  *
2018
+ * #654: the non-platform-apex part now lives in {@link ensureOrgAppMailToken},
2019
+ * shared with Boule, which holds the same token; the rule below is its rule
2020
+ * with the consumers read from the hosted app and Boule.
2021
+ *
1960
2022
  * Mint rule ("Credential model"): the project is healthy when the app env
1961
2023
  * holds `LETTERMINT_API_TOKEN` and Lettermint lists exactly one project
1962
2024
  * named `<slug>-app` -- unlike the earlier provider's key, a Lettermint project's name
@@ -2003,6 +2065,31 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2003
2065
  * this field existed (review round 2, finding 3).
2004
2066
  */
2005
2067
  private ensureAppLettermintMail;
2068
+ /**
2069
+ * The one mint rule for the org's `<slug>-app` Lettermint token, shared by
2070
+ * the hosted app (`ensureAppLettermintMail`) and Boule
2071
+ * (`ensureBouleMail`), which hold the same token. Lettermint shows a
2072
+ * token once, so the token lives only in those apps' envs: the rule reads
2073
+ * every consumer that is recorded (the hosted app first, Boule second).
2074
+ * The project is healthy when Lettermint lists exactly one `<slug>-app`
2075
+ * project and some consumer holds a token. A healthy rule gives `self` that
2076
+ * token (the app's wins) when it lacks it or holds another; a peer gets it
2077
+ * in its own step. An
2078
+ * unhealthy one mints a fresh project and writes its token to every
2079
+ * consumer, restarting those that run, before any other `<slug>-app`
2080
+ * project is deleted. Either role may run first: an app added after Boule
2081
+ * adopts Boule's token instead of deleting its project.
2082
+ *
2083
+ * The token goes straight to `bulkUpsertEnvs` and nowhere else: never
2084
+ * returned, recorded, logged or interpolated into an error. Returns
2085
+ * whether `self`'s own token changed and whether it was restarted here.
2086
+ *
2087
+ * A peer recorded but gone from Coolify (404) is no consumer. A healthy
2088
+ * company's step writes only its own env; a re-mint writes every live
2089
+ * consumer, restarts the running ones, restricts the domain, then deletes
2090
+ * the old projects.
2091
+ */
2092
+ private ensureOrgAppMailToken;
2006
2093
  /**
2007
2094
  * #149: mints this org's own Lettermint relay credential and writes it to
2008
2095
  * Stalwart via `MailPort.applyOrgRelay`, the same mint rule
@@ -2309,6 +2396,26 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2309
2396
  * No secret reaches an error: errors name the org and the missing input only.
2310
2397
  */
2311
2398
  private coolifyChat;
2399
+ /**
2400
+ * `coolifyChat`'s forward re-key (#463, design note
2401
+ * `docs/chat-rekey-design.md` Option 1): a live chat recorded, and
2402
+ * `org.chat.usesDomain` flipped on. Provisions a second Synapse+MAS pair
2403
+ * named for the org's own domain (`chatRekeyedInternalNames`), copies
2404
+ * every room, member and message across through
2405
+ * {@link FoundryStepExecutorPorts.chatRekey}, repoints the existing chat
2406
+ * service at the new pair, and parks -- never deletes, so a stalled
2407
+ * migration can be rolled back by simply restarting them -- the original
2408
+ * Synapse and MAS apps. The chat service and its own database are never
2409
+ * re-created; only the Matrix half of the stack moves.
2410
+ *
2411
+ * Idempotent on recorded ids: new resources are found by name before they
2412
+ * are created, the new MAS's secrets are read back from its own envs, and
2413
+ * the copy, the repoint and the park each run once
2414
+ * ({@link CHAT_REKEY_COPIED_SCOPE_KEY}, {@link CHAT_REKEY_REPOINTED_SCOPE_KEY}).
2415
+ * The original pair keeps serving until the chat service is healthy on the
2416
+ * new one; a failure before that leaves it running.
2417
+ */
2418
+ private coolifyChatRekey;
2312
2419
  /**
2313
2420
  * `plan/diff.ts`'s `planCallsService`: the org's own calls service --
2314
2421
  * `wtfalch/calls`'s `packages/service`, wired to the calls Matrix stack
@@ -2323,6 +2430,21 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2323
2430
  * 1); its own database is never touched from that branch.
2324
2431
  */
2325
2432
  private coolifyCallsService;
2433
+ /**
2434
+ * `plan/diff.ts`'s `planCallsChat`: the one internal token that lets the
2435
+ * calls service ask chat who is in a room, and the chat address. The token
2436
+ * is `CHAT_INTERNAL_TOKEN` on chat-service and `CALLS_CHAT_TOKEN` on the
2437
+ * calls service, which gets `CALLS_CHAT_URL` in the same write: it refuses
2438
+ * to boot on one without the other. Chat-service's env is the only memory
2439
+ * of the token across a run, the way {@link ensureCallsMatrixSecrets} keeps
2440
+ * the MAS secrets on the MAS app, so it is generated only when that env has
2441
+ * none. Its id is recorded and persisted before either app holds it, and the
2442
+ * guard after both do (platform rule 1). Both apps are redeployed last; the
2443
+ * applied marker is recorded after that, so a run that fails in between is
2444
+ * planned and finished again. `remove` (chat is off) deletes both vars from
2445
+ * the calls service and restarts it.
2446
+ */
2447
+ private coolifyCallsChat;
2326
2448
  /**
2327
2449
  * The seven ZITADEL token env vars the calls service needs to accept
2328
2450
  * Agora's sign-in token (`calls/docs/member-auth.md`). Only called with all
@@ -2443,6 +2565,16 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2443
2565
  * shape {@link coolifyAgora}'s own doc comment explains.
2444
2566
  */
2445
2567
  private coolifyBoule;
2568
+ /**
2569
+ * Gives Boule `LETTERMINT_API_TOKEN`: the platform's held key for an org on
2570
+ * the platform's own apex, else the org's `<slug>-app` token through the
2571
+ * shared mint rule ({@link ensureOrgAppMailToken}), which mints that project
2572
+ * when no hosted app made it yet. `MAIL_FROM` is written with the rest of
2573
+ * Boule's env. Returns whether Boule's token changed, so the caller
2574
+ * restarts a running Boule, unless `restarted`: the shared rule then already
2575
+ * restarted it (`running`), before it deleted the old project.
2576
+ */
2577
+ private ensureBouleMail;
2446
2578
  /**
2447
2579
  * `plan/diff.ts`'s `planJobsRegistration`: registers the org's own app with
2448
2580
  * the platform's jobs scheduler (`JobsSchedulerPort.registerProject`, the