@wtfalch/foundry 0.5.0 → 0.6.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.
- package/dist/bootstrap/bootstrap.d.ts +16 -1
- package/dist/bootstrap/bootstrap.js +40 -2
- package/dist/bootstrap/declare.js +6 -1
- package/dist/calls-sfu-deploy-support.d.ts +8 -0
- package/dist/calls-sfu-deploy-support.js +10 -1
- package/dist/calls-sfu.d.ts +31 -8
- package/dist/calls-sfu.js +36 -5
- package/dist/index.d.ts +8 -2
- package/dist/index.js +5 -2
- package/dist/migrations/0019_mail_owner_mailbox.sql +15 -0
- package/dist/migrations/0020_mail_apps.sql +7 -0
- package/dist/migrations/0021_mail_role_mailboxes.sql +6 -0
- package/dist/org.d.ts +81 -0
- package/dist/org.js +88 -0
- package/dist/plan/diff.js +93 -10
- package/dist/plan/executor.d.ts +236 -35
- package/dist/plan/executor.js +274 -76
- package/dist/plan/index.d.ts +7 -1
- package/dist/plan/index.js +4 -1
- package/dist/plan/mail-copy.d.ts +125 -0
- package/dist/plan/mail-copy.js +166 -0
- package/dist/plan/mail-identity-allowlist.d.ts +50 -0
- package/dist/plan/mail-identity-allowlist.js +11 -0
- package/dist/plan/mail-identity.d.ts +68 -0
- package/dist/plan/mail-identity.js +17 -0
- package/dist/plan/plan-apply.d.ts +5 -0
- package/dist/plan/platform-diff.d.ts +184 -9
- package/dist/plan/platform-diff.js +368 -15
- package/dist/plan/platform-org.d.ts +3 -2
- package/dist/plan/platform-providers.d.ts +138 -24
- package/dist/plan/platform-providers.js +1460 -79
- package/dist/plan/platform-sfu.d.ts +54 -19
- package/dist/plan/platform-sfu.js +64 -22
- package/dist/plan/zitadel-machine-user.d.ts +60 -11
- package/dist/plan/zitadel-machine-user.js +182 -39
- package/dist/requested-changes.d.ts +17 -7
- package/dist/requested-changes.js +37 -7
- package/dist/store/postgres.js +31 -2
- package/dist/store/schema.d.ts +72 -0
- package/dist/store/schema.js +13 -0
- package/dist/store.d.ts +7 -0
- package/package.json +1 -1
|
@@ -18,7 +18,7 @@ export interface PlatformAuthDeps {
|
|
|
18
18
|
/** #197: fetches the tunnel's connector token for {@link ensureCloudflaredConnector}. */
|
|
19
19
|
| 'getTunnelToken'>;
|
|
20
20
|
/** Must target the platform's own new instance (its issuer), or `zitadelForInstance` must be set. */
|
|
21
|
-
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'mintAdminMachineUser'>;
|
|
21
|
+
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'mintAdminMachineUser' | 'mintLoginClientMachineUser' | 'listLoginClientPats'>;
|
|
22
22
|
/**
|
|
23
23
|
* Builds the ZITADEL port for the new instance from its issuer URL
|
|
24
24
|
* (`https://<auth.hostname>`). When set it is used instead of
|
|
@@ -134,6 +134,9 @@ export interface FoundryPlatformStepExecutorOptions {
|
|
|
134
134
|
* optional: an existing host that constructs this executor for the zone step
|
|
135
135
|
* alone keeps compiling, and a plan that includes an `sfu:livekit` step
|
|
136
136
|
* without it configured refuses clearly rather than silently doing nothing.
|
|
137
|
+
* #152: the step mints one LiveKit key per org with calls enabled rather
|
|
138
|
+
* than one shared pair, so it also needs `declaredOrgStore.list()` -- the
|
|
139
|
+
* same store this class already takes for `findOrgIdForZone`.
|
|
137
140
|
*
|
|
138
141
|
* #168 adds `coolify:app` keyed {@link PLATFORM_AUTH_KEY}, the platform's own
|
|
139
142
|
* sign-in ({@link provisionPlatformAuth}), once a host passes
|
|
@@ -169,23 +172,41 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
169
172
|
private readonly files?;
|
|
170
173
|
private readonly ai?;
|
|
171
174
|
private readonly archon?;
|
|
172
|
-
constructor(cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'createZone'>, declaredOrgStore: Pick<DeclaredOrgStore, 'findOrgIdForZone'>, options?: FoundryPlatformStepExecutorOptions);
|
|
175
|
+
constructor(cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'createZone'>, declaredOrgStore: Pick<DeclaredOrgStore, 'findOrgIdForZone' | 'list'>, options?: FoundryPlatformStepExecutorOptions);
|
|
173
176
|
blockedSteps(platform: DeclaredPlatform, recordedIds?: Readonly<Record<string, string>>): Promise<{
|
|
174
177
|
auth?: string;
|
|
178
|
+
authExecutorMissing?: boolean;
|
|
179
|
+
authOtherInputsMissing?: string[];
|
|
175
180
|
files?: string;
|
|
176
181
|
integrations?: string;
|
|
177
182
|
ai?: string;
|
|
178
183
|
archon?: string;
|
|
179
184
|
mail?: string;
|
|
180
185
|
}>;
|
|
181
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* Whether Archon's live Access policy lets through anyone but the declared
|
|
188
|
+
* emails, or (#211 round 4) the broker it last registered with no longer
|
|
189
|
+
* carries its entry; or (#216 round 4) the platform's recorded
|
|
190
|
+
* login-client token is no longer the sole live PAT on its ZITADEL machine
|
|
191
|
+
* user.
|
|
192
|
+
*/
|
|
182
193
|
driftedSteps(platform: DeclaredPlatform, recordedIds?: Readonly<Record<string, string>>): Promise<{
|
|
183
194
|
archon?: boolean;
|
|
195
|
+
auth?: boolean;
|
|
184
196
|
}>;
|
|
185
197
|
execute(action: StepAction, context: PlatformStepExecutionContext): Promise<{
|
|
186
198
|
resourceId?: string;
|
|
187
199
|
} | undefined>;
|
|
188
200
|
private cloudflareZone;
|
|
201
|
+
/**
|
|
202
|
+
* #152: the platform `sfu` step owns the shared LiveKit config, so it
|
|
203
|
+
* lists every org with calls enabled itself -- `DeclaredOrgStore.list()`,
|
|
204
|
+
* filtered the same way an org's own `calls.enabled` flag gates its
|
|
205
|
+
* `coolify:calls-service` step (`executor.ts`'s `coolifyCallsService`) --
|
|
206
|
+
* rather than taking the list as an argument nothing else would supply.
|
|
207
|
+
* Sorted by slug so a re-run with the same set of enabled orgs is a no-op
|
|
208
|
+
* even before `applyCallsSfuPlatformStep`'s own sort (its doc comment).
|
|
209
|
+
*/
|
|
189
210
|
private executeSfu;
|
|
190
211
|
private coolifyJobs;
|
|
191
212
|
}
|
|
@@ -290,9 +311,31 @@ export interface PlatformArchonDeps {
|
|
|
290
311
|
* value it returns goes straight to `bulkUpsertEnvs` and nowhere else.
|
|
291
312
|
* Optional so a host without it still compiles; while a service is enabled
|
|
292
313
|
* without it (or without a key it holds) Archon is blocked, not skipped.
|
|
314
|
+
*
|
|
315
|
+
* `put` (#213) writes a value under `ref`, once -- see
|
|
316
|
+
* {@link putArchonMasterKeys}. Also optional, and also blocking: while
|
|
317
|
+
* {@link masterKeys} is given but this cannot put, Archon is blocked, not
|
|
318
|
+
* skipped, the same rule.
|
|
293
319
|
*/
|
|
294
320
|
heldKeys?: {
|
|
295
321
|
resolve(ref: string): Promise<string | null>;
|
|
322
|
+
put?(ref: string, value: string): Promise<void>;
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
325
|
+
* #213: the credentials Archon's own onboarding reads back as held keys
|
|
326
|
+
* before it can start (archon#101 round 3, archon#104) -- the Coolify URL
|
|
327
|
+
* and token this package already deploys with, and a Cloudflare parent
|
|
328
|
+
* token with "API Tokens Write" a human creates by hand (#213 "Human
|
|
329
|
+
* steps"), so Archon can mint its own scoped child token from it. Put
|
|
330
|
+
* once, under tenant `platform`, before Archon's app deploys
|
|
331
|
+
* ({@link putArchonMasterKeys}); optional so a host without it still
|
|
332
|
+
* compiles, and Archon is blocked, not skipped, while it (or
|
|
333
|
+
* {@link heldKeys}'s `put`) is missing.
|
|
334
|
+
*/
|
|
335
|
+
masterKeys?: {
|
|
336
|
+
coolifyUrl: string;
|
|
337
|
+
coolifyToken: string;
|
|
338
|
+
cloudflareParentToken: string;
|
|
296
339
|
};
|
|
297
340
|
}
|
|
298
341
|
/**
|
|
@@ -306,24 +349,28 @@ export declare const PLATFORM_ZITADEL_ORG_SCOPE_KEY = "zitadel_platform_org";
|
|
|
306
349
|
/** Archon's OIDC client id: public, not a secret, so a plain scope id. */
|
|
307
350
|
export declare const PLATFORM_ARCHON_CLIENT_ID_SCOPE_KEY = "zitadel_platform_archon_client_id";
|
|
308
351
|
/**
|
|
309
|
-
* #172: mints
|
|
352
|
+
* #172/#201: mints Archon's own AI management key -- the plain bearer key
|
|
310
353
|
* (`wtfk_...`) `AiOrganisationPort` (`executor.ts`) authenticates with,
|
|
311
354
|
* holding {@link PLATFORM_AI_MANAGEMENT_PERMISSIONS} in ai's platform
|
|
312
|
-
* (operator) tenant
|
|
313
|
-
*
|
|
355
|
+
* (operator) tenant, named `archon` (the key is Archon's; foundry only mints
|
|
356
|
+
* it while provisioning -- see {@link provisionPlatformAi}). The secret comes
|
|
357
|
+
* back once; {@link provisionPlatformAi} hands it to `deliverSecret` and
|
|
358
|
+
* records only the credential id.
|
|
314
359
|
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
360
|
+
* {@link createAiManagementKeyMinter} is the real implementation
|
|
361
|
+
* {@link provisionPlatformAi} builds by default, signing with the Ed25519
|
|
362
|
+
* pair it mints and pins on the runner and calling ai's own
|
|
363
|
+
* `POST /v1/platform/keys` (github.com/wtfalch/ai/issues/141). That route is
|
|
364
|
+
* idempotent by name: a repeat call with the same permissions and expiry
|
|
365
|
+
* returns the live key's id with no secret rather than a second key, so
|
|
366
|
+
* `mintManagementKey` throws {@link AiManagementKeyExistsError} for that
|
|
367
|
+
* case -- {@link provisionPlatformAi} only ever calls it once there is no
|
|
368
|
+
* delivered record, so a 200 here means an earlier mint's secret was lost,
|
|
369
|
+
* never a fresh one to hand back.
|
|
320
370
|
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
* before the caller records the credential id leaves a live key nobody knows
|
|
325
|
-
* about; only the minter can find it again, so without this lookup the next
|
|
326
|
-
* apply mints a second key.
|
|
371
|
+
* A test may still inject a different {@link AiManagementKeyMinter} in
|
|
372
|
+
* {@link PlatformAiDeps.managementKeyMinter}, which skips building the
|
|
373
|
+
* default one and its signing-key machinery entirely.
|
|
327
374
|
*/
|
|
328
375
|
export interface AiManagementKeyMinter {
|
|
329
376
|
mintManagementKey(input: {
|
|
@@ -338,12 +385,55 @@ export interface AiManagementKeyMinter {
|
|
|
338
385
|
secret: string;
|
|
339
386
|
}>;
|
|
340
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* Thrown by {@link createAiManagementKeyMinter} (never by a test's own
|
|
390
|
+
* injected minter, unless it chooses to) when ai's `POST /v1/platform/keys`
|
|
391
|
+
* answers 200: a live `archon` key already exists and no secret comes back.
|
|
392
|
+
* See {@link PLATFORM_AI_MANAGEMENT_KEY_CONFLICT_SCOPE_KEY}.
|
|
393
|
+
*/
|
|
394
|
+
export declare class AiManagementKeyExistsError extends Error {
|
|
395
|
+
readonly credentialId: string;
|
|
396
|
+
readonly organisationId: string;
|
|
397
|
+
constructor(credentialId: string, organisationId: string);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Thrown by {@link createAiManagementKeyMinter} on a 409: a live `archon` key
|
|
401
|
+
* already exists with different permissions or expiry than this request
|
|
402
|
+
* asked for. ai never overwrites it, so this is never retried under another
|
|
403
|
+
* name -- an operator revokes the live key in ai first.
|
|
404
|
+
*/
|
|
405
|
+
export declare class AiManagementKeyConflictError extends Error {
|
|
406
|
+
constructor();
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Thrown by {@link createAiManagementKeyMinter} on a 401 or 403: ai does not
|
|
410
|
+
* trust foundry's signing key yet for this request. Most likely the runner
|
|
411
|
+
* has not redeployed since {@link AI_SIGNING_KEY_REF}'s public half was
|
|
412
|
+
* pinned as `MANAGEMENT_PUBLIC_KEYS`/`MANAGEMENT_ISSUER` -- a Coolify env
|
|
413
|
+
* change reaches the container only on its next deploy.
|
|
414
|
+
*/
|
|
415
|
+
export declare class AiManagementKeyUntrustedError extends Error {
|
|
416
|
+
readonly status: number;
|
|
417
|
+
constructor(status: number);
|
|
418
|
+
}
|
|
341
419
|
/** #172: everything {@link provisionPlatformAi} needs beyond {@link PlatformStepExecutionContext}. */
|
|
342
420
|
export interface PlatformAiDeps {
|
|
343
421
|
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'raw'>;
|
|
344
422
|
deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
|
|
345
|
-
/**
|
|
423
|
+
/**
|
|
424
|
+
* Reads back a secret this step delivered on an earlier apply -- needed
|
|
425
|
+
* only to re-sign a mint request when the signing pair was pinned on a
|
|
426
|
+
* run that crashed before the management key itself was minted and
|
|
427
|
+
* delivered (#201). Not needed on the common path, where both happen in
|
|
428
|
+
* the same run.
|
|
429
|
+
*/
|
|
430
|
+
heldKeys?: {
|
|
431
|
+
resolve(ref: string): Promise<string | null>;
|
|
432
|
+
};
|
|
433
|
+
/** Injected for tests. Absent, {@link provisionPlatformAi} builds the real {@link createAiManagementKeyMinter} from the signing key it mints. */
|
|
346
434
|
managementKeyMinter?: AiManagementKeyMinter;
|
|
435
|
+
/** Injected for tests, e.g. against a fake ai host. Defaults to the global `fetch`. */
|
|
436
|
+
fetchImpl?: typeof fetch;
|
|
347
437
|
runtime?: PlatformAiRuntime;
|
|
348
438
|
}
|
|
349
439
|
export interface PlatformAiRuntime {
|
|
@@ -353,19 +443,43 @@ export interface PlatformAiRuntime {
|
|
|
353
443
|
migrateTimeoutMs?: number;
|
|
354
444
|
/** Pause between migrate-job status polls (default 5 seconds). */
|
|
355
445
|
migrateIntervalMs?: number;
|
|
356
|
-
/** Tries at handing
|
|
446
|
+
/** Tries at handing a secret to the host (default 3): the management key and the signing key both use this. */
|
|
357
447
|
deliverAttempts?: number;
|
|
358
448
|
}
|
|
359
449
|
/** The ref the AI management key is delivered under; the host's `AiOrganisationPort` adapter reads it back. */
|
|
360
450
|
export declare const PLATFORM_AI_MANAGEMENT_KEY_REF = "ai:platform:managementKey";
|
|
361
451
|
/**
|
|
362
|
-
* What
|
|
363
|
-
* (
|
|
364
|
-
*
|
|
452
|
+
* What Archon's management key may do in ai: exactly `ai`'s own
|
|
453
|
+
* `ARCHON_AI_PERMISSIONS` (`apps/host/scripts/authz/bootstrap-archon.ts`) --
|
|
454
|
+
* configuration only (offerings, provisioning, offers, budgets, usage, cost)
|
|
455
|
+
* plus deleting (archiving) an organisation, never connections, runs or
|
|
456
|
+
* webhooks. ai's `/v1/platform/keys` route refuses any permission outside
|
|
457
|
+
* this same list, so a request for more than this is a 403, not a wider key.
|
|
365
458
|
*/
|
|
366
459
|
export declare const PLATFORM_AI_MANAGEMENT_PERMISSIONS: readonly string[];
|
|
367
|
-
/** The
|
|
368
|
-
export declare const
|
|
460
|
+
/** The ref the AI signing key's private half (PKCS#8 PEM) is delivered under -- the {@link INTEGRATIONS_MANAGEMENT_KEY_REF} pattern, for ai instead of integrations. */
|
|
461
|
+
export declare const AI_SIGNING_KEY_REF = "ai:platform:signingPrivateKey";
|
|
462
|
+
/** `MANAGEMENT_ISSUER` on the ai runner: whoever signs with the delivered key signs as this issuer. */
|
|
463
|
+
export declare const AI_MANAGEMENT_ISSUER = "foundry";
|
|
464
|
+
/** The key id the public half is pinned under in `MANAGEMENT_PUBLIC_KEYS` (ai's keyring allows `^[A-Za-z0-9_-]{1,100}$`). Fixed, since there is one key until rotation is built. */
|
|
465
|
+
export declare const AI_MANAGEMENT_KEY_ID = "foundry-1";
|
|
466
|
+
/**
|
|
467
|
+
* The real {@link AiManagementKeyMinter}: signs a management assertion with
|
|
468
|
+
* `privateKeyPem` (the PKCS#8 PEM {@link provisionPlatformAi} delivers under
|
|
469
|
+
* {@link AI_SIGNING_KEY_REF} and pins on the runner under `keyId`) and calls
|
|
470
|
+
* ai's `POST /v1/platform/keys`. `audience` must be the exact origin ai's own
|
|
471
|
+
* `APP_URL` resolves to (`https://<platform.ai.hostname>`), since ai checks
|
|
472
|
+
* the claim against that env var, not against the internal address the
|
|
473
|
+
* request itself is sent to.
|
|
474
|
+
*/
|
|
475
|
+
export declare function createAiManagementKeyMinter(options: {
|
|
476
|
+
privateKeyPem: string;
|
|
477
|
+
keyId: string;
|
|
478
|
+
issuer: string;
|
|
479
|
+
audience: string;
|
|
480
|
+
fetchImpl?: typeof fetch;
|
|
481
|
+
now?: () => number;
|
|
482
|
+
}): AiManagementKeyMinter;
|
|
369
483
|
/**
|
|
370
484
|
* #171: everything {@link provisionPlatformFiles} needs beyond
|
|
371
485
|
* {@link PlatformStepExecutionContext}, bundled for the same reason as
|