@wtfalch/foundry 0.45.1 → 0.46.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 (60) hide show
  1. package/dist/index.d.ts +4 -2
  2. package/dist/index.js +2 -1
  3. package/dist/org.d.ts +37 -0
  4. package/dist/org.js +135 -10
  5. package/dist/plan/agora.d.ts +14 -0
  6. package/dist/plan/agora.js +21 -0
  7. package/dist/plan/backup-gate.d.ts +15 -0
  8. package/dist/plan/backup-gate.js +38 -0
  9. package/dist/plan/boule-owners-http.d.ts +12 -0
  10. package/dist/plan/boule-owners-http.js +59 -0
  11. package/dist/plan/boule.d.ts +38 -2
  12. package/dist/plan/boule.js +72 -1
  13. package/dist/plan/company-backups.d.ts +7 -1
  14. package/dist/plan/company-backups.js +42 -2
  15. package/dist/plan/cookie-domain.d.ts +41 -0
  16. package/dist/plan/cookie-domain.js +71 -0
  17. package/dist/plan/desktop-auth.d.ts +58 -0
  18. package/dist/plan/desktop-auth.js +144 -0
  19. package/dist/plan/desktop-callback.d.ts +26 -0
  20. package/dist/plan/desktop-callback.js +59 -0
  21. package/dist/plan/diff.d.ts +13 -4
  22. package/dist/plan/diff.js +272 -84
  23. package/dist/plan/executor.d.ts +136 -6
  24. package/dist/plan/executor.js +725 -98
  25. package/dist/plan/health-gaps.js +5 -0
  26. package/dist/plan/index.d.ts +3 -0
  27. package/dist/plan/index.js +2 -0
  28. package/dist/plan/job-app.d.ts +23 -0
  29. package/dist/plan/job-app.js +41 -0
  30. package/dist/plan/live.d.ts +6 -0
  31. package/dist/plan/live.js +16 -0
  32. package/dist/plan/login-host.d.ts +28 -0
  33. package/dist/plan/login-host.js +36 -0
  34. package/dist/plan/org-names.js +9 -1
  35. package/dist/plan/org-postgres-job.d.ts +22 -0
  36. package/dist/plan/org-postgres-job.js +91 -0
  37. package/dist/plan/org-postgres-migrate.d.ts +115 -0
  38. package/dist/plan/org-postgres-migrate.js +466 -0
  39. package/dist/plan/org-postgres.d.ts +90 -0
  40. package/dist/plan/org-postgres.js +293 -0
  41. package/dist/plan/plan-apply.d.ts +30 -2
  42. package/dist/plan/plan-apply.js +104 -7
  43. package/dist/plan/platform-diff.d.ts +45 -0
  44. package/dist/plan/platform-diff.js +159 -4
  45. package/dist/plan/platform-org.d.ts +2 -0
  46. package/dist/plan/platform-org.js +4 -0
  47. package/dist/plan/platform-providers.d.ts +12 -2
  48. package/dist/plan/platform-providers.js +279 -9
  49. package/dist/plan/post-logout-uris.d.ts +22 -0
  50. package/dist/plan/post-logout-uris.js +43 -0
  51. package/dist/plan/providers-readers.d.ts +10 -1
  52. package/dist/plan/providers-readers.js +41 -1
  53. package/dist/plan/sign-in-bindings.d.ts +4 -0
  54. package/dist/plan/sign-in-bindings.js +10 -0
  55. package/dist/plan/sign-in-org.d.ts +19 -6
  56. package/dist/plan/sign-in-org.js +35 -10
  57. package/dist/plan/sign-in.d.ts +12 -5
  58. package/dist/plan/sign-in.js +12 -5
  59. package/dist/plan/signin-db-move.js +10 -34
  60. package/package.json +5 -5
@@ -12,6 +12,7 @@ import { PLATFORM_MAIL_RELAY_KEY, planMailRelay } from './mail-relay.js';
12
12
  import { MAIL_DATABASE_SCOPE_KEY, mailStoreIsLegacy, mailStoreSwitchChange, mailStoreSwitchDescription, } from './mail-store.js';
13
13
  import { planOikosIngress } from './oikos-ingress.js';
14
14
  import { callsSfuAppliedFingerprint } from './platform-sfu.js';
15
+ import { postLogoutUpdateOwed, postLogoutUrisFor } from './post-logout-uris.js';
15
16
  import { archonInAppHost } from './sign-in-archon.js';
16
17
  import { signInDomainBlockedText } from './sign-in-org.js';
17
18
  import { withStepSystem } from './step-system.js';
@@ -64,6 +65,12 @@ export const PLATFORM_AUTH_ZITADEL_APPLIED_IMAGE_TAG_SCOPE_KEY = 'auth_zitadel_a
64
65
  export const AUTH_ZITADEL_RUN_OPTIONS = '--entrypoint "/app/zitadel start-from-init --masterkeyFromEnv --tlsMode external"';
65
66
  /** The run options last applied to the ZITADEL app (value: the options string), so a changed value forces one redeploy. */
66
67
  export const PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY = 'zitadel_platform_auth_run_options_applied';
68
+ /**
69
+ * The `gitRef` the platform's login, in-app login and broker apps were last built from (value: the ref). A
70
+ * different declared ref plans the auth step again: it patches `git_branch` on those three apps and
71
+ * redeploys them, so a release tag named in the declaration reaches the running login.
72
+ */
73
+ export const PLATFORM_AUTH_APPLIED_GIT_REF_SCOPE_KEY = 'platform_auth_applied_git_ref';
67
74
  /**
68
75
  * #251: the Synapse/MAS image tags an org's calls Matrix stack last
69
76
  * successful apply actually set live. `coolifyCalls` (`executor.ts`)
@@ -694,6 +701,31 @@ export const PLATFORM_ACCESS_ZITADEL_APP_SCOPE_KEY = 'zitadel_platform_access_ap
694
701
  * repair while this differs from the recorded client app id.
695
702
  */
696
703
  export const PLATFORM_ARCHON_ID_TOKEN_USERINFO_APPLIED_SCOPE_KEY = 'archon_id_token_userinfo_applied';
704
+ /**
705
+ * The marker for Archon's sign-in client once its post-logout addresses were
706
+ * set: the client's ZITADEL app id and the set it holds
707
+ * (`postLogoutAppliedValue`). `planArchonPostLogoutUris` plans the update
708
+ * while this differs from what the declared hostname wants.
709
+ */
710
+ export const PLATFORM_ARCHON_POST_LOGOUT_APPLIED_SCOPE_KEY = 'archon_post_logout_applied';
711
+ /**
712
+ * #394 (decision D2, release one): the role key on the platform's `mail`
713
+ * ZITADEL project that every mailbox user holds once the project's role check
714
+ * is on. This release only makes the role and grants it; the check stays off.
715
+ */
716
+ export const PLATFORM_MAIL_USER_ROLE = 'mail:use';
717
+ /** The mail project id once {@link PLATFORM_MAIL_USER_ROLE} exists on it. */
718
+ export const PLATFORM_MAIL_ROLE_ENSURED_SCOPE_KEY = 'zitadel_platform_mail_role';
719
+ /** `<mail project id>|<owner user id, or none>` once the owner holds the role. */
720
+ export const PLATFORM_MAIL_ROLE_GRANTED_SCOPE_KEY = 'zitadel_platform_mail_role_granted';
721
+ /** The mail project id once its role check and has-project check are on (release two, behind an option). */
722
+ export const PLATFORM_MAIL_ROLE_CHECK_SCOPE_KEY = 'zitadel_platform_mail_role_check';
723
+ /**
724
+ * The ZITADEL app id of Archon's sign-in client once `/auth/desktop/callback`
725
+ * was registered on it (#339). `planArchonDesktopCallback` plans the step
726
+ * while this differs from the recorded client app id.
727
+ */
728
+ export const PLATFORM_ARCHON_DESKTOP_CALLBACK_APPLIED_SCOPE_KEY = 'archon_desktop_callback_applied';
697
729
  export function appSettingsOf(entry) {
698
730
  return {
699
731
  folder: entry.folder,
@@ -1324,7 +1356,7 @@ export function planPlatform(platform, live, options = {}) {
1324
1356
  // reconcile below can be planned in the *same* round hc first comes up,
1325
1357
  // the same way files/mail/archon are planned in auth's own first round.
1326
1358
  const hcReady = hc.step !== null || Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_HC_KEY)]);
1327
- const auth = planAuth(platform, recordedIds, blocked.auth ?? dnsBlocked?.('sign-in'), blocked.authExecutorMissing ?? false, drifted.auth ?? false, blocked.authOtherInputsMissing ?? [], blocked.authOwnerInvite);
1359
+ const auth = planAuth(platform, recordedIds, blocked.auth ?? dnsBlocked?.('sign-in'), blocked.authExecutorMissing ?? false, drifted.auth ?? false, blocked.authOtherInputsMissing ?? [], blocked.authOwnerInvite, drifted.authApps ?? false);
1328
1360
  if (auth.step)
1329
1361
  plannedSteps.push(auth.step);
1330
1362
  // #174: the integrations host. Planned after auth; neither step reads the
@@ -1414,6 +1446,20 @@ export function planPlatform(platform, live, options = {}) {
1414
1446
  const idTokenUserinfo = planArchonIdTokenUserinfo(platform, recordedIds, blocked.archonIdTokenUserinfo ?? false);
1415
1447
  if (idTokenUserinfo)
1416
1448
  plannedSteps.push(idTokenUserinfo);
1449
+ const postLogoutUris = planArchonPostLogoutUris(platform, recordedIds, blocked.archonPostLogoutUris ?? false);
1450
+ if (postLogoutUris)
1451
+ plannedSteps.push(postLogoutUris);
1452
+ // #339: Archon's sign-in client takes the desktop callback too.
1453
+ const desktopCallback = planArchonDesktopCallback(platform, recordedIds, blocked.archonDesktopCallback ?? false);
1454
+ if (desktopCallback)
1455
+ plannedSteps.push(desktopCallback);
1456
+ // #394: the mail role and its grants. After the auth and mail steps, which record what it reads.
1457
+ const mailRole = planMailRole(platform, recordedIds, {
1458
+ role: blocked.mailRole ?? false,
1459
+ check: blocked.mailRoleCheck ?? false,
1460
+ });
1461
+ if (mailRole.step)
1462
+ plannedSteps.push(mailRole.step);
1417
1463
  // #669: after the ID token repair too, which is proven; a policy call that fails every time holds up nothing.
1418
1464
  const signInPolicy = planAuthSignInPolicy(platform, recordedIds, blocked.authSignInPolicy ?? false);
1419
1465
  if (signInPolicy)
@@ -1481,6 +1527,8 @@ export function planPlatform(platform, live, options = {}) {
1481
1527
  unmanaged.push({ description: auth.blocked, key: 'auth' });
1482
1528
  if (mail.blocked)
1483
1529
  unmanaged.push({ description: mail.blocked, key: 'mail' });
1530
+ if (mailRole.blocked)
1531
+ unmanaged.push({ description: mailRole.blocked, key: 'mail-role' });
1484
1532
  if (relay.blocked)
1485
1533
  unmanaged.push({ description: relay.blocked, key: 'mail-relay' });
1486
1534
  if (platform.mail.relay === true) {
@@ -1946,7 +1994,7 @@ function planSfu(platform, callsOrgSlugs, recordedIds) {
1946
1994
  * promising "this apply re-mints" while one of those is still missing would
1947
1995
  * be wrong the moment it is read.
1948
1996
  */
1949
- function planAuth(platform, recordedIds, executorBlocked, executorMissing, authDrifted, otherInputsMissing = [], ownerInviteBlocked) {
1997
+ function planAuth(platform, recordedIds, executorBlocked, executorMissing, authDrifted, otherInputsMissing = [], ownerInviteBlocked, appsDown = false) {
1950
1998
  if (!platform.auth.enabled)
1951
1999
  return { step: null, blocked: null };
1952
2000
  // C3/S2: standing notes. The invite and the domain wait on a person; they hold nothing else back.
@@ -1977,7 +2025,14 @@ function planAuth(platform, recordedIds, executorBlocked, executorMissing, authD
1977
2025
  // comment for why this never holds the step back the way the admin key's
1978
2026
  // own `undelivered` check below does.
1979
2027
  const loginClientUndelivered = recordedIds[PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY];
1980
- if (recordedIds[key] &&
2028
+ if (
2029
+ // #643: the apps recorded, but one is gone or stopped. The step finds each by name, deploys what is not
2030
+ // running and mints nothing while the records stand.
2031
+ !appsDown &&
2032
+ // #741: a refused owner-org domain (blocked key recorded, domain not) is tried again on the next apply.
2033
+ // While it stays refused the step is planned on every run, each spending about five seconds; known, no code.
2034
+ ownerDomainBlockedText(recordedIds) === null &&
2035
+ recordedIds[key] &&
1981
2036
  connectorApplied &&
1982
2037
  brokerApplied &&
1983
2038
  brokerDeployed &&
@@ -1995,7 +2050,8 @@ function planAuth(platform, recordedIds, executorBlocked, executorMissing, authD
1995
2050
  recordedIds[PLATFORM_OWNER_ORG_SCOPE_KEY]) &&
1996
2051
  // The ZITADEL run options reach the container only through the auth
1997
2052
  // step's redeploy, so a platform recorded before them still plans it.
1998
- recordedIds[PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY] === AUTH_ZITADEL_RUN_OPTIONS) {
2053
+ recordedIds[PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY] === AUTH_ZITADEL_RUN_OPTIONS &&
2054
+ recordedIds[PLATFORM_AUTH_APPLIED_GIT_REF_SCOPE_KEY] === platform.auth.gitRef) {
1999
2055
  return { step: null, blocked: standingNote };
2000
2056
  }
2001
2057
  // The admin key was minted but never reached the host. ZITADEL shows a
@@ -2542,6 +2598,97 @@ function planArchonIdTokenUserinfo(platform, recordedIds, wired) {
2542
2598
  },
2543
2599
  };
2544
2600
  }
2601
+ /**
2602
+ * #394, decision D2 (release one): one `zitadel:mail-role` step keyed
2603
+ * {@link PLATFORM_MAIL_KEY}. It makes the role {@link PLATFORM_MAIL_USER_ROLE}
2604
+ * on the mail project, grants it to the platform owner, and (only when the host
2605
+ * asks, `wired.check`) turns on the project's role check on a later run than the
2606
+ * grant. It waits until the mail project, the platform's ZITADEL org and the
2607
+ * owner invite are recorded.
2608
+ *
2609
+ * The owner's user lives in the owner company's sign-in org, and the mail project
2610
+ * in the platform's own org. A role grant on a project of another org needs a
2611
+ * project grant with role keys, which the ports do not have; the owner's grant
2612
+ * is reported blocked, not attempted, and the role is still made.
2613
+ */
2614
+ function planMailRole(platform, recordedIds, wired) {
2615
+ if (!wired.role || !platform.mail.enabled || !platform.auth.enabled) {
2616
+ return { step: null, blocked: null };
2617
+ }
2618
+ const projectId = recordedIds[PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY];
2619
+ const orgId = recordedIds[PLATFORM_ZITADEL_ORG_SCOPE_KEY];
2620
+ const invite = recordedIds[PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY];
2621
+ if (!projectId || !orgId || !invite)
2622
+ return { step: null, blocked: null };
2623
+ const sameOrg = invite === 'none' || recordedIds[PLATFORM_AUTH_OWNER_INVITE_ORG_SCOPE_KEY] === orgId;
2624
+ const blocked = sameOrg
2625
+ ? null
2626
+ : "the platform owner's mail role is blocked, not planned: the owner is in another ZITADEL org than the mail project, and a role grant across orgs needs a project grant with role keys, which is not built";
2627
+ const needsRole = recordedIds[PLATFORM_MAIL_ROLE_ENSURED_SCOPE_KEY] !== projectId;
2628
+ const granted = recordedIds[PLATFORM_MAIL_ROLE_GRANTED_SCOPE_KEY] === `${projectId}|${invite}`;
2629
+ // The check is planned only after a grant an earlier apply made, never with the first grant,
2630
+ // and never while the owner holds no role (it would lock the owner out).
2631
+ const needsCheck = wired.check &&
2632
+ sameOrg &&
2633
+ granted &&
2634
+ recordedIds[PLATFORM_MAIL_ROLE_CHECK_SCOPE_KEY] !== projectId;
2635
+ if (!needsRole && !(sameOrg && !granted) && !needsCheck)
2636
+ return { step: null, blocked };
2637
+ return {
2638
+ step: {
2639
+ action: { system: 'zitadel', kind: 'mail-role', key: PLATFORM_MAIL_KEY, desired: {} },
2640
+ step: {
2641
+ description: `make the mail role '${PLATFORM_MAIL_USER_ROLE}' on the mail project and grant it to the platform owner`,
2642
+ destroys: false,
2643
+ migrates: false,
2644
+ },
2645
+ },
2646
+ blocked,
2647
+ };
2648
+ }
2649
+ function planArchonDesktopCallback(platform, recordedIds, wired) {
2650
+ if (!wired || !platform.archon.enabled)
2651
+ return null;
2652
+ const appId = recordedIds[PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY];
2653
+ if (!appId || recordedIds[PLATFORM_ARCHON_DESKTOP_CALLBACK_APPLIED_SCOPE_KEY] === appId) {
2654
+ return null;
2655
+ }
2656
+ return {
2657
+ action: { system: 'zitadel', kind: 'desktop-callback', key: PLATFORM_ARCHON_KEY, desired: {} },
2658
+ step: {
2659
+ description: "register the desktop sign-in callback on Archon's sign-in client",
2660
+ destroys: false,
2661
+ migrates: false,
2662
+ },
2663
+ };
2664
+ }
2665
+ /**
2666
+ * Archon's sign-in client holds its root and `/auth/signed-out` as post-logout
2667
+ * addresses. One `zitadel:post-logout-uris` step keyed {@link PLATFORM_ARCHON_KEY},
2668
+ * planned while {@link PLATFORM_ARCHON_POST_LOGOUT_APPLIED_SCOPE_KEY} differs
2669
+ * from the recorded client app id and the declared hostname's set (a client
2670
+ * made again records it at creation). Not planned when the host's ZITADEL
2671
+ * client lacks the method, and not blocked either.
2672
+ */
2673
+ function planArchonPostLogoutUris(platform, recordedIds, wired) {
2674
+ if (!wired || !platform.archon.enabled)
2675
+ return null;
2676
+ const appId = recordedIds[PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY];
2677
+ if (!appId)
2678
+ return null;
2679
+ const uris = postLogoutUrisFor([platform.archon.hostname]);
2680
+ if (!postLogoutUpdateOwed(recordedIds[PLATFORM_ARCHON_POST_LOGOUT_APPLIED_SCOPE_KEY], appId, uris)) {
2681
+ return null;
2682
+ }
2683
+ return {
2684
+ action: { system: 'zitadel', kind: 'post-logout-uris', key: PLATFORM_ARCHON_KEY, desired: {} },
2685
+ step: {
2686
+ description: "update the post-logout addresses of Archon's sign-in client",
2687
+ destroys: false,
2688
+ migrates: false,
2689
+ },
2690
+ };
2691
+ }
2545
2692
  /**
2546
2693
  * #394, #669: the platform's sign-in org reserves the username `admin` and
2547
2694
  * switches self-registration off. One `zitadel:sign-in-policy` step keyed
@@ -3061,6 +3208,14 @@ function planArchon(platform, recordedIds, executorBlocked, ownerSignIn, ownerFi
3061
3208
  // A deploy the step triggered is still running: planning it again would deploy twice.
3062
3209
  if (deployRunning)
3063
3210
  return { step: null, blocked: deployRunning };
3211
+ // Giving Archon's address up edits the in-app login app's host table: without that app's id the binding
3212
+ // cannot be removed, so nothing is planned (and the address is not recorded as given up).
3213
+ if (ownerSignIn && !ownerSignIn.signInLoginAppId && archonInAppHost(recordedIds) !== '') {
3214
+ return {
3215
+ step: null,
3216
+ blocked: "Archon's own sign-in address cannot be given up: the owner company's in-app login app is not recorded",
3217
+ };
3218
+ }
3064
3219
  const key = resourceKey('coolify', 'app', PLATFORM_ARCHON_KEY);
3065
3220
  const archonOrgId = recordedIds[PLATFORM_ARCHON_ORG_SCOPE_KEY];
3066
3221
  const applied = Boolean(recordedIds[key]) &&
@@ -104,6 +104,8 @@ export interface OrgPlatformReadiness {
104
104
  platformZitadelIds?: readonly string[];
105
105
  /** The second login app that serves `/sign-in` for companies with `signIn.inApp` is applied. Absent reads as false. */
106
106
  signInLoginReady?: boolean;
107
+ /** `LOGIN_SESSION_BIND_KEY` is live on the in-app login app (`sign_in_bind_key_ready`). Absent reads as false. */
108
+ signInBindKeyReady?: boolean;
107
109
  /** The platform's own sign-in (#168), which every org's own ZITADEL org lives inside. */
108
110
  authReady: boolean;
109
111
  /**
@@ -1,5 +1,6 @@
1
1
  import { ownsServices } from '../org.js';
2
2
  import { ZITADEL_ORG_SCOPE_KEY, agoraHostnameFor } from './agora.js';
3
+ import { SIGN_IN_BIND_KEY_SCOPE_KEY } from './cookie-domain.js';
3
4
  import { ACCESS_PROVIDER_RECORD_KEY } from './gates.js';
4
5
  import { FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_KEY_REF, } from './held-key-refs.js';
5
6
  import { OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY, ownAuthApplied } from './login-host.js';
@@ -161,6 +162,9 @@ edgePortPresent = false) {
161
162
  ? { platformAuthRecordedHost: platform.auth.hostname }
162
163
  : {}),
163
164
  ...(platformRecordedIds[SIGN_IN_LOGIN_APP_SCOPE_KEY] ? { signInLoginReady: true } : {}),
165
+ ...(platformRecordedIds[SIGN_IN_BIND_KEY_SCOPE_KEY] === '1'
166
+ ? { signInBindKeyReady: true }
167
+ : {}),
164
168
  authReady,
165
169
  authIssuer: authReady ? `https://${platform.auth.hostname}` : null,
166
170
  jobsReady: jobsApplied && jobsUrl !== undefined && missingKeys.jobs === null,
@@ -391,6 +391,10 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
391
391
  archonRunsInsideApp?: boolean;
392
392
  archonDeployRunning?: string;
393
393
  archonIdTokenUserinfo?: boolean;
394
+ archonPostLogoutUris?: boolean;
395
+ archonDesktopCallback?: boolean;
396
+ mailRole?: boolean;
397
+ mailRoleCheck?: boolean;
394
398
  authSignInPolicy?: boolean;
395
399
  archonOrg?: string;
396
400
  archonOrgInvite?: string;
@@ -716,7 +720,13 @@ export interface PlatformMailDeps {
716
720
  * checks an access token's `aud` claim against -- #346, no host has to
717
721
  * register a resource API for this by hand any more.
718
722
  */
719
- zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'findOrCreateProject'>;
723
+ zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'findOrCreateProject'> & Partial<Pick<ZitadelPort, 'ensureProjectRole' | 'grantProjectRoles' | 'setProjectRoleChecks'>>;
724
+ /**
725
+ * #394 (decision D2): turn on the mail project's role check and has-project check. Default off:
726
+ * release one only makes the role and grants it, and the check follows in release two, on a
727
+ * run after the grant. Needs `setProjectRoleChecks` on the client.
728
+ */
729
+ requireMailRole?: boolean;
720
730
  /** {@link PlatformArchonDeps.zitadelForInstance}, for Stalwart's OIDC directory. */
721
731
  zitadelForInstance?: (issuerUrl: string) => PlatformMailDeps['zitadel'];
722
732
  /**
@@ -917,7 +927,7 @@ export interface PlatformArchonDeps {
917
927
  access: PlatformAccessFactory;
918
928
  cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
919
929
  /** Must target the platform's own ZITADEL instance, or `zitadelForInstance` must be set. */
920
- zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'findOrCreateProject' | 'createOidcApp' | 'listApps' | 'deleteApp' | 'updateOidcAppIdTokenUserinfoAssertion'> & Partial<Pick<ZitadelPort, 'ensureTrustedDomain' | 'deleteTrustedDomain' | 'updateOidcAppLoginBaseUri' | 'allowPerAppLoginBaseUri'>>;
930
+ zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'findOrCreateProject' | 'createOidcApp' | 'listApps' | 'deleteApp' | 'updateOidcAppIdTokenUserinfoAssertion'> & Partial<Pick<ZitadelPort, 'ensureTrustedDomain' | 'deleteTrustedDomain' | 'updateOidcAppLoginBaseUri' | 'allowPerAppLoginBaseUri' | 'updateOidcAppPostLogoutRedirectUris' | 'getOidcAppRedirectUris' | 'updateOidcAppRedirectUris'>>;
921
931
  /**
922
932
  * {@link PlatformAuthDeps.zitadelForInstance}, for Archon's sign-in client:
923
933
  * the owner company's own ZITADEL, by its issuer and the held-key reference of