@wtfalch/foundry 0.16.16 → 0.17.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 (59) hide show
  1. package/dist/apply.d.ts +22 -1
  2. package/dist/bootstrap/bootstrap.d.ts +6 -1
  3. package/dist/bootstrap/bootstrap.js +26 -3
  4. package/dist/bootstrap/declare.d.ts +11 -1
  5. package/dist/bootstrap/declare.js +12 -2
  6. package/dist/index.d.ts +9 -4
  7. package/dist/index.js +7 -0
  8. package/dist/migrations/0038_calls_version_pin.sql +8 -0
  9. package/dist/migrations/0039_destroy_record.sql +7 -0
  10. package/dist/migrations/0040_company_included_uuid.sql +72 -0
  11. package/dist/migrations/0041_company_included_uuid_keys.sql +132 -0
  12. package/dist/migrations/0042_mutation_leases.sql +12 -0
  13. package/dist/org.d.ts +54 -0
  14. package/dist/org.js +43 -1
  15. package/dist/plan/adopt.d.ts +76 -0
  16. package/dist/plan/adopt.js +184 -0
  17. package/dist/plan/agora.d.ts +21 -0
  18. package/dist/plan/agora.js +37 -0
  19. package/dist/plan/broker-entries.d.ts +9 -1
  20. package/dist/plan/broker-entries.js +13 -5
  21. package/dist/plan/chat-matrix.d.ts +1 -1
  22. package/dist/plan/chat-matrix.js +11 -3
  23. package/dist/plan/core-files.d.ts +25 -0
  24. package/dist/plan/core-files.js +25 -0
  25. package/dist/plan/diff.d.ts +11 -3
  26. package/dist/plan/diff.js +27 -62
  27. package/dist/plan/entry-ai-key.d.ts +3 -3
  28. package/dist/plan/entry-files-key.d.ts +3 -3
  29. package/dist/plan/executor.d.ts +57 -23
  30. package/dist/plan/executor.js +134 -50
  31. package/dist/plan/index.d.ts +4 -0
  32. package/dist/plan/index.js +2 -0
  33. package/dist/plan/live.d.ts +5 -0
  34. package/dist/plan/login-host.d.ts +1 -1
  35. package/dist/plan/login-host.js +6 -5
  36. package/dist/plan/plan-apply.d.ts +139 -6
  37. package/dist/plan/plan-apply.js +546 -76
  38. package/dist/plan/platform-diff.d.ts +14 -0
  39. package/dist/plan/platform-diff.js +16 -1
  40. package/dist/plan/platform-org.d.ts +16 -0
  41. package/dist/plan/platform-org.js +17 -1
  42. package/dist/plan/platform-providers.d.ts +1 -1
  43. package/dist/plan/platform-providers.js +73 -11
  44. package/dist/plan/providers-readers.d.ts +2 -0
  45. package/dist/plan/providers-readers.js +4 -0
  46. package/dist/plan/rollout.d.ts +52 -0
  47. package/dist/plan/rollout.js +37 -0
  48. package/dist/plan/step-system.d.ts +22 -0
  49. package/dist/plan/step-system.js +37 -0
  50. package/dist/plan.d.ts +3 -0
  51. package/dist/state.d.ts +13 -0
  52. package/dist/store/audit.js +15 -1
  53. package/dist/store/postgres.d.ts +1 -1
  54. package/dist/store/postgres.js +127 -14
  55. package/dist/store/schema.d.ts +134 -5
  56. package/dist/store/schema.js +12 -1
  57. package/dist/store.d.ts +47 -4
  58. package/dist/store.js +9 -1
  59. package/package.json +1 -1
package/dist/plan/diff.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { VERSIONS, floatingTagBlocked } from '../bootstrap/versions.js';
2
2
  import { APP_ENTRY_KEY, ownerMailboxRecorded, ownerMailboxes } from '../org.js';
3
- import { CALLS_INTROSPECTION_SCOPE_KEY, CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY, NO_AGORA_HOST_VALUES, ZITADEL_ORG_SCOPE_KEY, agoraEnvDrift, agoraHostMissing, agoraHostnameFor, callsServiceHasName, callsServiceHasTokenEnv, callsTokenEnvMovedMessage, callsTokenHostFrom, desiredAgoraEnv, withRecordedMessagingProject, } from './agora.js';
3
+ import { AGORA_ACTIVATED_ENV_SCOPE_KEY, CALLS_INTROSPECTION_SCOPE_KEY, CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY, NO_AGORA_HOST_VALUES, ZITADEL_ORG_SCOPE_KEY, agoraEnvDrift, agoraEnvRevision, agoraHostMissing, agoraHostnameFor, callsServiceHasName, callsServiceHasTokenEnv, callsTokenEnvMovedMessage, callsTokenHostFrom, desiredAgoraEnv, withRecordedMessagingProject, } from './agora.js';
4
4
  import { BOULE_BROKER_ENTRY_KEY, bouleEnvDrift, desiredBouleEnv, wantedBouleBrokerEntry, } from './boule.js';
5
5
  import { brokerEntryAppliedScopeKey, brokerEntryWarningsScopeKey, entryAuthEnv, entryAuthEnvAppliedScopeKey, wantedBrokerEntry, } from './broker-entries.js';
6
6
  import { planEntryAiKeys } from './entry-ai-key.js';
@@ -12,6 +12,7 @@ import { CALLS_MAS_APPLIED_IMAGE_TAG_SCOPE_KEY, CALLS_SYNAPSE_APPLIED_IMAGE_TAG_
12
12
  import { DEFAULT_ORG_PLATFORM_READINESS } from './platform-org.js';
13
13
  import { PLATFORM_AUTH_BROKER_MAIL_KEY_REF } from './platform-providers.js';
14
14
  import { appLabelsAppliedScopeKey, entryLabelHash, entryLabelSpec } from './proxy-labels.js';
15
+ import { withStepSystem } from './step-system.js';
15
16
  /** The `Plan` (#19's public contract) a `PlanDetail` carries. */
16
17
  export function toPlan(detail) {
17
18
  return {
@@ -42,9 +43,8 @@ function resourceKey(system, kind, key) {
42
43
  * `hosting.issuerApps[]` entry rather than a single singleton, since an org
43
44
  * can declare more than one ZITADEL app. #80 adds two more singletons,
44
45
  * modeled on {@link planIssuer}: `planFilesOrganisation` and
45
- * `planAiOrganisation`, each planned before its own system's limit step
46
- * ({@link planStorage}, {@link planAiBudget}) so an org has somewhere for a
47
- * limit to land, and AI use is switched on, before either limit is ever set.
46
+ * `planAiOrganisation`, the second planned before {@link planAiBudget} so AI
47
+ * use is switched on before its own limit is ever set.
48
48
  * `planOwners` below adds one more singleton, in a different shape from the
49
49
  * rest: the org's owner, invited by ZITADEL email, idempotent on the
50
50
  * *recorded* resource id (`recordedIds`, sourced from
@@ -142,9 +142,6 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
142
142
  const filesOrganisation = planFilesOrganisation(org, byKey, claimed, platformReadiness.filesReady, platformReadiness.missingKeys.files, platformReadiness.noKeyReader);
143
143
  if (filesOrganisation.step)
144
144
  plannedSteps.push(filesOrganisation.step);
145
- const storage = planStorage(org, byKey, claimed, platformReadiness.filesReady, platformReadiness.missingKeys.files, platformReadiness.noKeyReader);
146
- if (storage.step)
147
- plannedSteps.push(storage.step);
148
145
  const aiOrganisation = planAiOrganisation(org, byKey, claimed, platformReadiness.aiReady, platformReadiness.missingKeys.ai, platformReadiness.noKeyReader);
149
146
  if (aiOrganisation.step)
150
147
  plannedSteps.push(aiOrganisation.step);
@@ -313,8 +310,6 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
313
310
  if (aiOrganisation.blocked) {
314
311
  unmanaged.push({ description: aiOrganisation.blocked, key: 'ai:organisation' });
315
312
  }
316
- if (storage.blocked)
317
- unmanaged.push({ description: storage.blocked, key: 'files:storage' });
318
313
  if (aiBudget.blocked)
319
314
  unmanaged.push({ description: aiBudget.blocked, key: 'ai:budget' });
320
315
  // #238: a pending zone, reported here with its nameservers rather than
@@ -326,7 +321,12 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
326
321
  unmanaged.push(...app.blocked);
327
322
  unmanaged.push(...brokerEntries.blocked);
328
323
  unmanaged.push(...bouleBroker.blocked);
329
- return { orgId: org.id, plannedSteps, unmanaged, versionChanges };
324
+ return {
325
+ orgId: org.id,
326
+ plannedSteps: plannedSteps.map(withStepSystem),
327
+ unmanaged,
328
+ versionChanges,
329
+ };
330
330
  }
331
331
  /**
332
332
  * The org's own ZITADEL org (issuer), a per-org singleton like every other
@@ -990,11 +990,8 @@ function notReadyReason(service, keyName, missingKeyRef, noKeyReader) {
990
990
  : `the platform's ${service} host is off or not yet applied`;
991
991
  }
992
992
  /**
993
- * #80: every org's own organisation on Files, a per-org singleton resource
994
- * planned before {@link planStorage} -- `setQuota`/`setBudget` 404 against an
995
- * org Files has never provisioned (`files/packages/sdk/src/client.ts`'s
996
- * tenant routes need the row `provisionOrganisation` creates). Modeled on
997
- * {@link planIssuer}: nothing to diff once it exists -- Files' own
993
+ * #80: every org's own organisation on Files, a per-org singleton resource.
994
+ * Modeled on {@link planIssuer}: nothing to diff once it exists -- Files' own
998
995
  * `provisionOrganisation` is safely repeatable (`executor.ts`'s
999
996
  * `filesOrganisation` calls it every time regardless), so the only planned
1000
997
  * state is "not provisioned yet". The operator-driven `ceilingBytes`/
@@ -1117,53 +1114,9 @@ function planAiOrganisation(org, byKey, claimed, aiReady, missingKeyRef, noKeyRe
1117
1114
  blocked: null,
1118
1115
  };
1119
1116
  }
1120
- /**
1121
- * #27: the org's own spend limit on Files, a per-org singleton resource in
1122
- * the same shape as issuer/mail/chat above. Setting a limit is a config
1123
- * write, never destructive or migrating (#27 "Notes": "safe, idempotent and
1124
- * reversible"). A missing live resource reads as uncapped (`null`), the same
1125
- * value a freshly-declared org's `storage` starts at, so a brand new org with
1126
- * no limits declared produces no step.
1127
- *
1128
- * #177: silently plans nothing while there is no organisation to size a limit
1129
- * on yet and `filesReady` is false -- {@link planFilesOrganisation} already
1130
- * reports the platform's files host as missing once.
1131
- */
1132
- function planStorage(org, byKey, claimed, filesReady, missingKeyRef, noKeyReader = false) {
1133
- const key = resourceKey('files', 'storage', org.id);
1134
- const live = byKey.get(key);
1135
- if (live)
1136
- claimed.add(key);
1137
- if (!filesReady && !live)
1138
- return { step: null, blocked: null };
1139
- const desired = { limitBytes: org.storage.limitBytes, limitMicros: org.storage.limitMicros };
1140
- const liveLimitBytes = live ? (live.config.limitBytes ?? null) : null;
1141
- const liveLimitMicros = live ? (live.config.limitMicros ?? null) : null;
1142
- if (liveLimitBytes === desired.limitBytes && liveLimitMicros === desired.limitMicros) {
1143
- return { step: null, blocked: null };
1144
- }
1145
- // A live resource with drift needs the platform's key to change; blocked, not planned to throw at apply.
1146
- if (!filesReady) {
1147
- return {
1148
- step: null,
1149
- blocked: `${org.slug}'s storage limit change is blocked, not planned: ${notReadyReason('files', 'files management key', missingKeyRef, noKeyReader)}`,
1150
- };
1151
- }
1152
- return {
1153
- blocked: null,
1154
- step: {
1155
- action: { system: 'files', kind: 'storage', key: org.id, desired },
1156
- step: {
1157
- description: `set ${org.slug}'s storage limit to ${desired.limitBytes ?? 'uncapped'} bytes / ${desired.limitMicros ?? 'uncapped'} micros`,
1158
- destroys: false,
1159
- migrates: false,
1160
- },
1161
- },
1162
- };
1163
- }
1164
1117
  /**
1165
1118
  * #27: the org's own spend limit on AI, a per-org singleton resource in the
1166
- * same shape as {@link planStorage}. Planned after {@link planAiOrganisation}
1119
+ * same singleton shape as issuer/mail/chat above. Planned after {@link planAiOrganisation}
1167
1120
  * (#80), which already seeds a brand new org's first budget row from this
1168
1121
  * same `org.ai.limitMicros` -- this planner only fires again once that row's
1169
1122
  * live value has drifted from what is declared.
@@ -1469,13 +1422,25 @@ function planAgora(org, byKey, claimed, recordedIds, reading) {
1469
1422
  if (liveEnv.AGORA_CONFIG === undefined && !drift.includes('AGORA_CONFIG')) {
1470
1423
  drift.push('AGORA_CONFIG');
1471
1424
  }
1472
- if (drift.length === 0)
1425
+ // #442: `drift` alone, once this step has ever run, is comparing `env`
1426
+ // against Coolify's own env store -- which the executor's `bulkUpsertEnvs`
1427
+ // leaves matching `env` the moment the write succeeds, restarted or not.
1428
+ // So once applied, drift reads empty forever even if the running process
1429
+ // was never restarted onto it (a crash between the write and the restart,
1430
+ // or simply never restarting at all). `AGORA_ACTIVATED_ENV_SCOPE_KEY` is
1431
+ // the executor's own record that a restart confirmed the process is
1432
+ // actually serving this exact `env`; while it disagrees, the step stays
1433
+ // planned no matter what Coolify's env store already shows.
1434
+ const activated = recordedIds[AGORA_ACTIVATED_ENV_SCOPE_KEY] === agoraEnvRevision(env);
1435
+ if (drift.length === 0 && activated)
1473
1436
  return { step: null, blocked: null };
1474
1437
  return {
1475
1438
  step: {
1476
1439
  action,
1477
1440
  step: {
1478
- description: `update ${org.slug}'s Agora config (${drift.join(', ')} missing or different)`,
1441
+ description: drift.length > 0
1442
+ ? `update ${org.slug}'s Agora config (${drift.join(', ')} missing or different)`
1443
+ : `activate ${org.slug}'s Agora config -- written but not yet confirmed restarted`,
1479
1444
  destroys: false,
1480
1445
  migrates: false,
1481
1446
  },
@@ -1,5 +1,5 @@
1
1
  import { type DeclaredOrg, type HostingEntry } from '../org.js';
2
- import type { PlannedStep } from './diff.js';
2
+ import type { DraftStep } from './diff.js';
3
3
  import type { OrgPlatformReadiness } from './platform-org.js';
4
4
  /**
5
5
  * F6a: one ai key per entry with `uses.ai`, minted below the org's own ai key
@@ -49,8 +49,8 @@ export type EntryAiMode = 'mint' | 'renew' | 'settings' | 'finish' | 'retire';
49
49
  * entry's app or the ai address is not recorded, or after a mint whose
50
50
  * delivery failed: a second mint would leave two live keys.
51
51
  */
52
- export declare function planEntryAiKeys(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>, plannedSteps: readonly PlannedStep[], readiness: OrgPlatformReadiness, now: number): {
53
- steps: PlannedStep[];
52
+ export declare function planEntryAiKeys(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>, plannedSteps: readonly DraftStep[], readiness: OrgPlatformReadiness, now: number): {
53
+ steps: DraftStep[];
54
54
  blocked: Array<{
55
55
  description: string;
56
56
  key: string;
@@ -1,5 +1,5 @@
1
1
  import { type DeclaredOrg, type HostingEntry } from '../org.js';
2
- import type { PlannedStep } from './diff.js';
2
+ import type { DraftStep } from './diff.js';
3
3
  import type { OrgPlatformReadiness } from './platform-org.js';
4
4
  /**
5
5
  * F6b: the org's own files key, minted below the files organisation's
@@ -77,8 +77,8 @@ type Blocked = Array<{
77
77
  * it by its own `renew` step, and only then does `revoke-previous` revoke the
78
78
  * old org key, which revokes everything still below it.
79
79
  */
80
- export declare function planFilesKeys(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>, plannedSteps: readonly PlannedStep[], readiness: OrgPlatformReadiness, now: number): {
81
- steps: PlannedStep[];
80
+ export declare function planFilesKeys(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>, plannedSteps: readonly DraftStep[], readiness: OrgPlatformReadiness, now: number): {
81
+ steps: DraftStep[];
82
82
  blocked: Blocked;
83
83
  };
84
84
  export {};
@@ -183,10 +183,19 @@ export interface CoolifyPort {
183
183
  * `is_preview === false` the same way a deployed-config reader must.
184
184
  */
185
185
  listEnvs(applicationUuid: string): Promise<Array<{
186
+ uuid?: string;
186
187
  key: string;
187
188
  value: string;
188
189
  is_preview: boolean;
189
190
  }>>;
191
+ /**
192
+ * #436 round 2: removes one environment variable row by the `uuid`
193
+ * `listEnvs` names it under -- `bulkUpsertEnvs` only adds or updates, so
194
+ * this is the only way to make a var foundry wrote in an earlier release
195
+ * actually go away, rather than leaving it set to a value nothing reads
196
+ * any more (`provisionPlatformArchon`'s stale `FILES_MANAGEMENT_KEY`).
197
+ */
198
+ deleteEnv(applicationUuid: string, envUuid: string): Promise<unknown>;
190
199
  /** Escape hatch (`CoolifyClient.raw`) — used for stop/start, which have no typed method yet. */
191
200
  raw(method: string, path: string, body?: unknown): Promise<unknown>;
192
201
  /**
@@ -1049,16 +1058,6 @@ export interface ZitadelListAppsRawApp {
1049
1058
  * `signManagementRequest`/`AssertionTrust`). A host can construct this without
1050
1059
  * any human session: it only needs the Ed25519 private key (`SigningIdentity`)
1051
1060
  * an operator's `MANAGEMENT_PUBLIC_KEYS`/`MANAGEMENT_ISSUER` pins.
1052
- *
1053
- * #177: `setQuota`/`setBudget` (`files/packages/sdk/src/client.ts:154-166`)
1054
- * used to live on a separate `FilesOrgLimits` a host built per org through a
1055
- * `filesLimitsFor(orgId)` factory on `FoundryStepExecutorPorts` — ceremony
1056
- * left over from nothing but currying an id this port's own callers already
1057
- * have in hand. The signed claim's `organisation` field selects the org per
1058
- * call, so one shared keypair signs for every org already, the same signer
1059
- * `provisionOrganisation` uses; there was never a per-org client to build,
1060
- * only an org id to pass. Both methods now take it explicitly, like
1061
- * `provisionOrganisation` always did, and `filesLimitsFor` is gone.
1062
1061
  */
1063
1062
  export interface FilesOrganisationPort {
1064
1063
  provisionOrganisation(input: {
@@ -1088,9 +1087,6 @@ export interface FilesOrganisationPort {
1088
1087
  secret: string;
1089
1088
  };
1090
1089
  }>;
1091
- /** #27, #177: the org's own spend limit on Files -- see this interface's own doc comment for why `organisationId` is explicit here. */
1092
- setQuota(organisationId: string, limitBytes: string | null): Promise<unknown>;
1093
- setBudget(organisationId: string, limitMicros: string | null): Promise<unknown>;
1094
1090
  /**
1095
1091
  * F6b: mints the organisation's own key, matching files'
1096
1092
  * `POST /v1/manage/organisations/:organisationId/keys` (SDK
@@ -1251,10 +1247,10 @@ export interface AiOrganisationPort {
1251
1247
  * `ai/packages/sdk/src/client.ts:94-101`'s `updateBudget` (`UpdateBudget`
1252
1248
  * from `ai/packages/sdk/src/types.ts:134-137`). Used to live on a separate
1253
1249
  * `AiOrgLimits` a host built per org through an `aiLimitsFor(orgId)`
1254
- * factory on `FoundryStepExecutorPorts` -- the same dead currying
1255
- * {@link FilesOrganisationPort}'s own doc comment explains for
1256
- * `setQuota`/`setBudget`. `organisationId` is explicit here now, like
1257
- * every other method on this port.
1250
+ * factory on `FoundryStepExecutorPorts` -- ceremony left over from nothing
1251
+ * but currying an id this port's own callers already have in hand.
1252
+ * `organisationId` is explicit here now, like every other method on this
1253
+ * port.
1258
1254
  */
1259
1255
  updateBudget(organisationId: string, input: {
1260
1256
  keyId: string | null;
@@ -1594,6 +1590,20 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
1594
1590
  private findRecreatedApp;
1595
1591
  private ensureZitadelProject;
1596
1592
  private ensureCoolifyProject;
1593
+ /**
1594
+ * #445: one company lands on one Coolify server. The first call for an
1595
+ * org picks a usable, reachable server and records it
1596
+ * ({@link COOLIFY_SERVER_SCOPE_KEY}); every later call -- this apply, or a
1597
+ * later one -- reads that recording back and returns the same server,
1598
+ * regardless of what the pool's order or health looks like by then. A
1599
+ * recorded server that has gone unusable or unreachable is reported
1600
+ * blocked (never silently replaced with a second host, which would split
1601
+ * the org's resources across servers and break their internal container
1602
+ * URLs); a recorded server no longer registered at all is the same.
1603
+ * Deliberately no fallback to `servers[0]` when nothing is usable: an
1604
+ * unusable server picked blind is not a server anyone's resources could
1605
+ * actually be created on.
1606
+ */
1597
1607
  private resolveServer;
1598
1608
  private coolifyApp;
1599
1609
  /**
@@ -1938,6 +1948,10 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
1938
1948
  * `coolifyApp`'s own analogous gap (it never restarted a found-but-stopped
1939
1949
  * app either) has since closed the same way, inline in that method (#130);
1940
1950
  * this method is `cmsSite`'s only caller.
1951
+ *
1952
+ * `repo` (#245) lets the site app build from an org's own skin repo
1953
+ * (`org.site.repo`) instead of the shared `CMS_REPO`; `cmsSite` always
1954
+ * passes `CMS_REPO` for the cms admin app, which is never org-specific.
1941
1955
  */
1942
1956
  private findOrCreateCmsApp;
1943
1957
  /**
@@ -2091,6 +2105,19 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2091
2105
  * pushed once calls are on and this org's calls service app carries its
2092
2106
  * internal name (`desiredAgoraEnv` reads that marker from the recorded ids,
2093
2107
  * not the host values).
2108
+ *
2109
+ * #442: a found-but-already-running app's container binds env only at
2110
+ * start, so writing it alone leaves that process on its old config --
2111
+ * restarted (never redeployed: no new image, so `restart` is the same
2112
+ * primitive `ensureLogVolume`'s own restart uses) whenever the live env
2113
+ * actually differs, or whenever the last restart this step confirmed
2114
+ * ({@link AGORA_ACTIVATED_ENV_SCOPE_KEY}) is not this exact env (an org
2115
+ * applied before this existed, whose live env already matched desired
2116
+ * with nothing ever confirmed running it). A brand new app has nothing
2117
+ * running yet to restart. The activation marker is recorded last, after
2118
+ * the restart (if any) returns without throwing, so a failed restart
2119
+ * leaves it stale and the next plan replans this step even though
2120
+ * Coolify's own env store already matches desired.
2094
2121
  */
2095
2122
  private coolifyAgora;
2096
2123
  /**
@@ -2395,11 +2422,11 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2395
2422
  */
2396
2423
  private ensureLettermintDmarcRecord;
2397
2424
  /**
2398
- * #80: provisions the org's own organisation on Files, before
2399
- * {@link filesStorage} can set its spend limit. `provisionOrganisation` is
2400
- * safely repeatable on its own (`FilesOrganisationPort`'s doc comment), so
2401
- * this is called every time regardless of whether it already ran --
2402
- * idempotent by find-or-create, not by checking `recordedIds` first.
2425
+ * #80: provisions the org's own organisation on Files.
2426
+ * `provisionOrganisation` is safely repeatable on its own
2427
+ * (`FilesOrganisationPort`'s doc comment), so this is called every time
2428
+ * regardless of whether it already ran -- idempotent by find-or-create,
2429
+ * not by checking `recordedIds` first.
2403
2430
  *
2404
2431
  * F6b: `action.desired.shareHost` (`diff.ts`'s `planFilesOrganisation`) is
2405
2432
  * passed through every time, even though `entry-files-key.ts` reports the
@@ -2412,6 +2439,14 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2412
2439
  * repeat-is-a-no-op shape as `provisionOrganisation` itself -- every org
2413
2440
  * gets an Agora (#164), so `diff.ts`'s `planFilesOrganisation` always sets
2414
2441
  * `desired.agoraOrigin`.
2442
+ *
2443
+ * #437: the owner member/name, the core ZITADEL files project and the core
2444
+ * collection (`core-files.ts`) are no longer conditioned on a hosting entry
2445
+ * declaring `uses.files` -- the core bundle (Agora's own uploads) needs
2446
+ * all three just as much as a bespoke entry does, and withholding them
2447
+ * until one existed is why a plain Agora/Boule org never got a usable
2448
+ * Files setup at all. Both the project and the collection are idempotent
2449
+ * by name, so a re-run that already created either finds it instead.
2415
2450
  */
2416
2451
  private filesOrganisation;
2417
2452
  /**
@@ -2422,7 +2457,6 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2422
2457
  * that fails revokes the key again instead of leaving a live key nobody holds.
2423
2458
  */
2424
2459
  private deliverFilesAgoraKey;
2425
- private filesStorage;
2426
2460
  /**
2427
2461
  * #80: provisions the org's own organisation on AI and switches its AI use
2428
2462
  * on, before {@link aiBudget} can set its own spend limit. Both