@wtfalch/foundry 0.4.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 +111 -0
- package/dist/bootstrap/bootstrap.js +323 -0
- package/dist/bootstrap/declare.d.ts +36 -0
- package/dist/bootstrap/declare.js +99 -0
- package/dist/bootstrap/drive.d.ts +40 -0
- package/dist/bootstrap/drive.js +51 -0
- package/dist/bootstrap/index.d.ts +3 -0
- package/dist/bootstrap/index.js +1 -0
- package/dist/calls-sfu-deploy-support.d.ts +17 -6
- package/dist/calls-sfu-deploy-support.js +19 -7
- package/dist/calls-sfu-keys.d.ts +21 -0
- package/dist/calls-sfu-keys.js +27 -0
- package/dist/calls-sfu-ssh-adapter.d.ts +55 -0
- package/dist/calls-sfu-ssh-adapter.js +78 -0
- package/dist/calls-sfu-ssh-client.d.ts +49 -0
- package/dist/calls-sfu-ssh-client.js +82 -0
- package/dist/calls-sfu.d.ts +36 -12
- package/dist/calls-sfu.js +41 -9
- package/dist/index.d.ts +38 -7
- package/dist/index.js +20 -4
- package/dist/migrations/0016_platform.sql +61 -0
- package/dist/migrations/0017_requested_changes.sql +42 -0
- package/dist/migrations/0018_platform_apply.sql +19 -0
- 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 +96 -0
- package/dist/plan/agora.d.ts +77 -0
- package/dist/plan/agora.js +127 -0
- package/dist/plan/cloudflared-connector.d.ts +70 -0
- package/dist/plan/cloudflared-connector.js +106 -0
- package/dist/plan/diff.d.ts +20 -3
- package/dist/plan/diff.js +713 -115
- package/dist/plan/executor.d.ts +680 -172
- package/dist/plan/executor.js +755 -189
- package/dist/plan/index.d.ts +28 -5
- package/dist/plan/index.js +15 -2
- package/dist/plan/jobs-scheduler-http.d.ts +29 -0
- package/dist/plan/jobs-scheduler-http.js +50 -0
- package/dist/plan/live.d.ts +51 -2
- package/dist/plan/live.js +8 -0
- 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 +197 -3
- package/dist/plan/plan-apply.js +278 -9
- package/dist/plan/platform-diff.d.ts +487 -0
- package/dist/plan/platform-diff.js +1220 -0
- package/dist/plan/platform-org-ports.d.ts +50 -0
- package/dist/plan/platform-org-ports.js +70 -0
- package/dist/plan/platform-org.d.ts +121 -0
- package/dist/plan/platform-org.js +115 -0
- package/dist/plan/platform-providers.d.ts +615 -0
- package/dist/plan/platform-providers.js +4410 -0
- package/dist/plan/platform-sfu.d.ts +128 -0
- package/dist/plan/platform-sfu.js +142 -0
- package/dist/plan/providers-readers.js +22 -1
- package/dist/plan/zitadel-machine-user.d.ts +83 -0
- package/dist/plan/zitadel-machine-user.js +238 -0
- package/dist/plan.d.ts +6 -0
- package/dist/platform.d.ts +55 -0
- package/dist/platform.js +46 -0
- package/dist/requested-changes.d.ts +118 -0
- package/dist/requested-changes.js +125 -0
- package/dist/slug.js +15 -2
- package/dist/store/audit.js +4 -2
- package/dist/store/postgres.d.ts +29 -1
- package/dist/store/postgres.js +203 -4
- package/dist/store/schema.d.ts +755 -0
- package/dist/store/schema.js +70 -0
- package/dist/store.d.ts +53 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,21 +1,52 @@
|
|
|
1
1
|
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, OwnerIdentity, DeclaredOrg, } from './org.js';
|
|
2
|
-
export { validateBuyerIdentity, validateHosting, validateOwner, validateSite } from './org.js';
|
|
2
|
+
export { DEFAULT_MAIL_APPS, validateBuyerIdentity, validateHosting, validateMail, validateOwner, validateSite, } from './org.js';
|
|
3
|
+
export type { DeclaredPlatform, PlatformService, PlatformServiceKey } from './platform.js';
|
|
4
|
+
export { PLATFORM_SERVICE_KEYS, defaultPlatformHostname, validatePlatform, } from './platform.js';
|
|
5
|
+
export type { RequestableField, CallsChange, StorageLimitChange, AiLimitChange, DomainChange, MailTierChange, RequestedChangeValue, ChangeClassification, RequestedChangeState, RequestedChange, } from './requested-changes.js';
|
|
6
|
+
export { REQUESTABLE_FIELDS, classifyChange, RequestedChangeError } from './requested-changes.js';
|
|
3
7
|
export type { SlugRejectionReason, SlugRejection } from './slug.js';
|
|
4
8
|
export { validateSlug, slugFromName, RESERVED_SLUGS } from './slug.js';
|
|
5
9
|
export type { Checkpoint, LastApplied, AppliedState } from './state.js';
|
|
6
10
|
export type { PlanStep, UnmanagedItem, Plan } from './plan.js';
|
|
7
|
-
export type { DeclaredOrgStore } from './store.js';
|
|
11
|
+
export type { DeclaredOrgStore, PlatformStore, RequestedChangeStore } from './store.js';
|
|
8
12
|
export type { PlanApply } from './apply.js';
|
|
9
|
-
export { createPostgresDeclaredOrgStore, DeclaredOrgError } from './store/postgres.js';
|
|
13
|
+
export { createPostgresDeclaredOrgStore, createPostgresPlatformStore, createPostgresRequestedChangeStore, DeclaredOrgError, PlatformError, } from './store/postgres.js';
|
|
10
14
|
export { createPostgresAuditLog } from './store/audit.js';
|
|
11
15
|
export type { FoundryStoreDb } from './store/postgres.js';
|
|
12
|
-
export { declaredOrgs, foundryAppliedState, foundryAuditEvents, type AuditEventRow, type DeclaredOrgRow, type NewDeclaredOrgRow, type AppliedStateRow, type NewAppliedStateRow, } from './store/schema.js';
|
|
13
|
-
export type { LiveResource, ZitadelReader, CoolifyReader, CloudflareReader, FilesReader, AiReader, CmsReader, LiveReaders, StepAction, PlannedStep, PlanDetail, AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, FoundryPlanApplyOptions, } from './plan/index.js';
|
|
14
|
-
export { readLiveResources, computePlanDetail, toPlan, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, } from './plan/index.js';
|
|
15
|
-
export type {
|
|
16
|
+
export { declaredOrgs, foundryAppliedState, foundryAuditEvents, foundryDeclaredPlatform, foundryRequestedChanges, type AuditEventRow, type DeclaredOrgRow, type NewDeclaredOrgRow, type AppliedStateRow, type NewAppliedStateRow, type DeclaredPlatformRow, type NewDeclaredPlatformRow, type RequestedChangeRow, type NewRequestedChangeRow, } from './store/schema.js';
|
|
17
|
+
export type { LiveResource, ZitadelReader, CoolifyReader, CloudflareReader, FilesReader, AiReader, CmsReader, LiveReaders, PlatformCloudflareReader, PlatformCoolifyReader, PlatformLiveReaders, StepAction, PlannedStep, PlanDetail, PlatformPlanDetail, AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, } from './plan/index.js';
|
|
18
|
+
export { readLiveResources, readLivePlatformResources, computePlanDetail, toPlan, planPlatform, toPlatformPlan, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, planNeedsConfirmation, } from './plan/index.js';
|
|
19
|
+
export type { HeldKeyResolver, OrgPlatformReadiness } from './plan/index.js';
|
|
20
|
+
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './plan/index.js';
|
|
21
|
+
export type { PlatformOrgClientFactories } from './plan/index.js';
|
|
22
|
+
export { PlatformOrgBlockedError, PlatformOrgPorts } from './plan/index.js';
|
|
23
|
+
export type { CoolifyPort, CloudflarePort, ZitadelPort, CallsSfuPort, JobsSchedulerPort, FilesOrganisationPort, AiOrganisationPort, FoundryStepExecutorPorts, PlatformOrgPortSource, AgoraHostPort, AgoraHostValues, } from './plan/index.js';
|
|
16
24
|
export { FoundryStepExecutor, NotBuiltError, healthUrls } from './plan/index.js';
|
|
25
|
+
export { HttpJobsSchedulerClient } from './plan/index.js';
|
|
26
|
+
export type { MailIdentityApp, MailIdentityAudienceCheck } from './plan/index.js';
|
|
27
|
+
export { checkMailIdentityAudience } from './plan/index.js';
|
|
28
|
+
export type { MailAllowlistApp } from './plan/index.js';
|
|
29
|
+
export { checkMailAppsAllowlist } from './plan/index.js';
|
|
30
|
+
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, createZitadelAdminMinter, PLATFORM_AUTH_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, } from './plan/index.js';
|
|
31
|
+
export type { PlatformAuthDeps, PlatformAuthRuntime, PlatformFilesDeps, PlatformFilesRuntime, } from './plan/index.js';
|
|
32
|
+
export type { PlatformMailDeps, PlatformMailAdminPort, PlatformMailRuntime, } from './plan/index.js';
|
|
33
|
+
export { FILES_INTROSPECTION_SECRET_REF, FILES_MANAGEMENT_ISSUER, FILES_MANAGEMENT_KEY_ID, FILES_MANAGEMENT_KEY_REF, } from './plan/index.js';
|
|
34
|
+
export { AI_MANAGEMENT_ISSUER, AI_MANAGEMENT_KEY_ID, AI_SIGNING_KEY_REF, AiManagementKeyExistsError, createAiManagementKeyMinter, PLATFORM_AI_MANAGEMENT_KEY_REF, PLATFORM_AI_MANAGEMENT_PERMISSIONS, } from './plan/index.js';
|
|
35
|
+
export type { AiManagementKeyMinter, PlatformAiDeps, PlatformAiRuntime, } from './plan/index.js';
|
|
36
|
+
export type { CallsSfuConnection, CallsSfuConnectionPort, CallsSfuKeysPort, CallsSfuSshConnector, CallsSfuPlatformDeps, } from './plan/index.js';
|
|
37
|
+
export { MissingCallsSfuConnectionError, applyCallsSfuPlatformStep } from './plan/index.js';
|
|
38
|
+
export type { CallsSfuSshSession } from './calls-sfu-ssh-adapter.js';
|
|
39
|
+
export { connectCallsSfuSsh } from './calls-sfu-ssh-client.js';
|
|
40
|
+
export type { CallsSfuSshConnectOptions } from './calls-sfu-ssh-client.js';
|
|
41
|
+
export { UnknownCallsSfuHostKeyError, toCallsSfuHostPort, applyCallsSfuOverSession, } from './calls-sfu-ssh-adapter.js';
|
|
42
|
+
export type { LivekitCredentials } from './calls-sfu-keys.js';
|
|
43
|
+
export { mintLivekitCredentials } from './calls-sfu-keys.js';
|
|
44
|
+
export type { MailCopyJob, MailCopyPort, MailCopyStepResult } from './plan/index.js';
|
|
45
|
+
export { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, MissingMailCopyAccountsError, MissingMailCopySourceTokenError, MissingMailCopyStalwartTokenError, parseMailCopyCompleted, runMailCopyStep, } from './plan/index.js';
|
|
17
46
|
export type { MigrateSql, MigrationResult } from './migrate.js';
|
|
18
47
|
export { MIGRATIONS_DIR, migrate } from './migrate.js';
|
|
48
|
+
export type { BootstrapCompany, BootstrapExecutorDeps, BootstrapOwner, BootstrapPlatformDeps, BootstrapPlatformInput, BootstrapProgress, BootstrapResult, RootCredentials, } from './bootstrap/index.js';
|
|
49
|
+
export { bootstrapPlatform } from './bootstrap/index.js';
|
|
19
50
|
export type { RunnerDb } from './runner/db.js';
|
|
20
51
|
export { AlreadyRunningError, RunNotFoundError, StepFailedError, StepMismatchError, } from './runner/errors.js';
|
|
21
52
|
export { createJobRunner } from './runner/runner.js';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
|
-
export { validateBuyerIdentity, validateHosting, validateOwner, validateSite } from './org.js';
|
|
1
|
+
export { DEFAULT_MAIL_APPS, validateBuyerIdentity, validateHosting, validateMail, validateOwner, validateSite, } from './org.js';
|
|
2
|
+
export { PLATFORM_SERVICE_KEYS, defaultPlatformHostname, validatePlatform, } from './platform.js';
|
|
3
|
+
export { REQUESTABLE_FIELDS, classifyChange, RequestedChangeError } from './requested-changes.js';
|
|
2
4
|
export { validateSlug, slugFromName, RESERVED_SLUGS } from './slug.js';
|
|
3
|
-
export { createPostgresDeclaredOrgStore, DeclaredOrgError } from './store/postgres.js';
|
|
5
|
+
export { createPostgresDeclaredOrgStore, createPostgresPlatformStore, createPostgresRequestedChangeStore, DeclaredOrgError, PlatformError, } from './store/postgres.js';
|
|
4
6
|
export { createPostgresAuditLog } from './store/audit.js';
|
|
5
|
-
export { declaredOrgs, foundryAppliedState, foundryAuditEvents, } from './store/schema.js';
|
|
6
|
-
export { readLiveResources, computePlanDetail, toPlan, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, } from './plan/index.js';
|
|
7
|
+
export { declaredOrgs, foundryAppliedState, foundryAuditEvents, foundryDeclaredPlatform, foundryRequestedChanges, } from './store/schema.js';
|
|
8
|
+
export { readLiveResources, readLivePlatformResources, computePlanDetail, toPlan, planPlatform, toPlatformPlan, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, planNeedsConfirmation, } from './plan/index.js';
|
|
9
|
+
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesFromPlatform, heldPlatformKeyRefs, orgPlatformReadinessFrom, } from './plan/index.js';
|
|
10
|
+
export { PlatformOrgBlockedError, PlatformOrgPorts } from './plan/index.js';
|
|
7
11
|
export { FoundryStepExecutor, NotBuiltError, healthUrls } from './plan/index.js';
|
|
12
|
+
export { HttpJobsSchedulerClient } from './plan/index.js';
|
|
13
|
+
export { checkMailIdentityAudience } from './plan/index.js';
|
|
14
|
+
export { checkMailAppsAllowlist } from './plan/index.js';
|
|
15
|
+
export { FoundryPlatformStepExecutor, ProvidersPlatformCloudflareReader, ProvidersPlatformCoolifyReader, createZitadelAdminMinter, PLATFORM_AUTH_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ADMIN_KEY_DESCRIPTION, } from './plan/index.js';
|
|
16
|
+
export { FILES_INTROSPECTION_SECRET_REF, FILES_MANAGEMENT_ISSUER, FILES_MANAGEMENT_KEY_ID, FILES_MANAGEMENT_KEY_REF, } from './plan/index.js';
|
|
17
|
+
export { AI_MANAGEMENT_ISSUER, AI_MANAGEMENT_KEY_ID, AI_SIGNING_KEY_REF, AiManagementKeyExistsError, createAiManagementKeyMinter, PLATFORM_AI_MANAGEMENT_KEY_REF, PLATFORM_AI_MANAGEMENT_PERMISSIONS, } from './plan/index.js';
|
|
18
|
+
export { MissingCallsSfuConnectionError, applyCallsSfuPlatformStep } from './plan/index.js';
|
|
19
|
+
export { connectCallsSfuSsh } from './calls-sfu-ssh-client.js';
|
|
20
|
+
export { UnknownCallsSfuHostKeyError, toCallsSfuHostPort, applyCallsSfuOverSession, } from './calls-sfu-ssh-adapter.js';
|
|
21
|
+
export { mintLivekitCredentials } from './calls-sfu-keys.js';
|
|
22
|
+
export { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, MissingMailCopyAccountsError, MissingMailCopySourceTokenError, MissingMailCopyStalwartTokenError, parseMailCopyCompleted, runMailCopyStep, } from './plan/index.js';
|
|
8
23
|
export { MIGRATIONS_DIR, migrate } from './migrate.js';
|
|
24
|
+
export { bootstrapPlatform } from './bootstrap/index.js';
|
|
9
25
|
export { AlreadyRunningError, RunNotFoundError, StepFailedError, StepMismatchError, } from './runner/errors.js';
|
|
10
26
|
export { createJobRunner } from './runner/runner.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
-- @wtfalch/foundry: the declared platform -- the one shared set of services
|
|
2
|
+
-- (auth, mail, sfu, files, ai, jobs, integrations, archon) decided in #165,
|
|
3
|
+
-- owned by Company Included, with its own plan and apply. Mirrors
|
|
4
|
+
-- src/store/schema.ts's `foundryDeclaredPlatform` table.
|
|
5
|
+
--
|
|
6
|
+
-- Singleton by construction: `id` is a boolean that can only ever be `true`
|
|
7
|
+
-- (the check constraint below refuses `false`), and it is the primary key,
|
|
8
|
+
-- so a second row can never exist. This is the same "one declared platform"
|
|
9
|
+
-- decision `foundry_declared_orgs` enforces per-org uniqueness for (#28,
|
|
10
|
+
-- #131), applied here to the whole table instead of a column.
|
|
11
|
+
|
|
12
|
+
create table if not exists foundry_declared_platform (
|
|
13
|
+
id boolean not null default true,
|
|
14
|
+
|
|
15
|
+
-- Company Included's own OrgId (org.ts). Not a level-1 org itself, but
|
|
16
|
+
-- the platform's resources are billed and owned by one.
|
|
17
|
+
owner_org_id text not null,
|
|
18
|
+
|
|
19
|
+
-- The shared apex every service's hostname defaults under, e.g.
|
|
20
|
+
-- companyincluded.com. Validated by src/platform.ts's `validatePlatform`,
|
|
21
|
+
-- not here, the same two-file split declared_orgs uses (#10).
|
|
22
|
+
apex text not null,
|
|
23
|
+
|
|
24
|
+
auth_enabled boolean not null default false,
|
|
25
|
+
auth_hostname text not null,
|
|
26
|
+
auth_git_ref text not null default '',
|
|
27
|
+
|
|
28
|
+
mail_enabled boolean not null default false,
|
|
29
|
+
mail_hostname text not null,
|
|
30
|
+
mail_git_ref text not null default '',
|
|
31
|
+
|
|
32
|
+
sfu_enabled boolean not null default false,
|
|
33
|
+
sfu_hostname text not null,
|
|
34
|
+
sfu_git_ref text not null default '',
|
|
35
|
+
|
|
36
|
+
files_enabled boolean not null default false,
|
|
37
|
+
files_hostname text not null,
|
|
38
|
+
files_git_ref text not null default '',
|
|
39
|
+
|
|
40
|
+
ai_enabled boolean not null default false,
|
|
41
|
+
ai_hostname text not null,
|
|
42
|
+
ai_git_ref text not null default '',
|
|
43
|
+
|
|
44
|
+
jobs_enabled boolean not null default false,
|
|
45
|
+
jobs_hostname text not null,
|
|
46
|
+
jobs_git_ref text not null default '',
|
|
47
|
+
|
|
48
|
+
integrations_enabled boolean not null default false,
|
|
49
|
+
integrations_hostname text not null,
|
|
50
|
+
integrations_git_ref text not null default '',
|
|
51
|
+
|
|
52
|
+
archon_enabled boolean not null default false,
|
|
53
|
+
archon_hostname text not null,
|
|
54
|
+
archon_git_ref text not null default '',
|
|
55
|
+
|
|
56
|
+
created_at timestamptz not null default now(),
|
|
57
|
+
updated_at timestamptz not null default now(),
|
|
58
|
+
|
|
59
|
+
constraint foundry_declared_platform_pkey primary key (id),
|
|
60
|
+
constraint foundry_declared_platform_singleton check (id)
|
|
61
|
+
);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
-- @wtfalch/foundry: a company's own requested setting changes on its
|
|
2
|
+
-- declared org (#178, map #165: "a company's Boule can request setting
|
|
3
|
+
-- changes on its own declared org, and Archon applies them"). Mirrors
|
|
4
|
+
-- src/store/schema.ts's `foundryRequestedChanges` table.
|
|
5
|
+
--
|
|
6
|
+
-- `field` is the whitelist itself, checked here the same way
|
|
7
|
+
-- `foundry_declared_orgs_mail_mode_check` checks `mail_mode`'s closed set --
|
|
8
|
+
-- src/requested-changes.ts's `REQUESTABLE_FIELDS` is the source of truth for
|
|
9
|
+
-- what the list means, this constraint is the database-level copy of it.
|
|
10
|
+
-- `value` carries whatever that field's own shape needs (`enabled` for
|
|
11
|
+
-- calls, `limitBytes`/`limitMicros` for storageLimit, and so on) -- one
|
|
12
|
+
-- jsonb column rather than a column per possible field, since only one
|
|
13
|
+
-- field is ever set per request.
|
|
14
|
+
--
|
|
15
|
+
-- `state` starts at whatever `src/requested-changes.ts`'s `classifyChange`
|
|
16
|
+
-- decided for this request at create time (`auto` or `needs_approval`) --
|
|
17
|
+
-- foundry only stores and classifies (#178 "Why"); archon's own workflow is
|
|
18
|
+
-- what carries a request on to `approved`/`rejected`/`applied`.
|
|
19
|
+
|
|
20
|
+
create table if not exists foundry_requested_changes (
|
|
21
|
+
id bigserial not null,
|
|
22
|
+
org_id text not null,
|
|
23
|
+
|
|
24
|
+
field text not null,
|
|
25
|
+
value jsonb not null,
|
|
26
|
+
|
|
27
|
+
requested_by text not null,
|
|
28
|
+
created_at timestamptz not null default now(),
|
|
29
|
+
|
|
30
|
+
state text not null default 'needs_approval',
|
|
31
|
+
|
|
32
|
+
constraint foundry_requested_changes_pkey primary key (id),
|
|
33
|
+
constraint foundry_requested_changes_org_fkey
|
|
34
|
+
foreign key (org_id) references foundry_declared_orgs (id),
|
|
35
|
+
constraint foundry_requested_changes_field_check
|
|
36
|
+
check (field in ('calls', 'storageLimit', 'aiLimit', 'domain', 'mailTier')),
|
|
37
|
+
constraint foundry_requested_changes_state_check
|
|
38
|
+
check (state in ('auto', 'needs_approval', 'approved', 'rejected', 'applied'))
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
create index if not exists foundry_requested_changes_org_id_idx
|
|
42
|
+
on foundry_requested_changes (org_id);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- @wtfalch/foundry: what applying the declared platform (#167) needs to
|
|
2
|
+
-- remember, and how its audit rows are told apart from an org's.
|
|
3
|
+
--
|
|
4
|
+
-- `service_ids` is the platform's own applied state: the provider ids its
|
|
5
|
+
-- apply made or adopted, keyed `system:kind:key` like
|
|
6
|
+
-- `foundry_applied_state.service_ids`. Without it a Cloudflare zone created
|
|
7
|
+
-- by one apply is forgotten if the next plan's read lags behind the create.
|
|
8
|
+
-- It lives on the singleton row because there is one platform, and
|
|
9
|
+
-- `put`'s upsert never writes it, so re-declaring the platform keeps it.
|
|
10
|
+
--
|
|
11
|
+
-- `target` marks an audit row as the platform's. It is null for an org row.
|
|
12
|
+
-- A platform plan is stamped with the owner org's id, so `org_id` alone
|
|
13
|
+
-- cannot say which of the two a row is.
|
|
14
|
+
|
|
15
|
+
alter table foundry_declared_platform
|
|
16
|
+
add column if not exists service_ids jsonb not null default '{}'::jsonb;
|
|
17
|
+
|
|
18
|
+
alter table foundry_audit_events
|
|
19
|
+
add column if not exists target text check (target in ('platform'));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- @wtfalch/foundry: #162 (decided 2026-09-29) -- the owner's mailbox
|
|
2
|
+
-- address is now declared, not guessed (replacing the hardcoded
|
|
3
|
+
-- `owner@<domain>` PR 163 shipped first), and the org's send-only no-reply
|
|
4
|
+
-- address gets its own configurable local part. Mirrors
|
|
5
|
+
-- `src/store/schema.ts`'s `declaredOrgs` table additions.
|
|
6
|
+
--
|
|
7
|
+
-- owner_mailbox_address is nullable: an org can have mail on with no
|
|
8
|
+
-- address declared yet, and `plan/diff.ts`'s `planMail` reports the
|
|
9
|
+
-- mailbox alone as blocked in that case rather than this column refusing
|
|
10
|
+
-- the row. no_reply_local_part defaults to 'no-reply' so every existing
|
|
11
|
+
-- row keeps today's behaviour unchanged.
|
|
12
|
+
|
|
13
|
+
alter table foundry_declared_orgs
|
|
14
|
+
add column if not exists owner_mailbox_address text,
|
|
15
|
+
add column if not exists no_reply_local_part text not null default 'no-reply';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- @wtfalch/foundry: which ZITADEL apps in the org's audience project may
|
|
2
|
+
-- read a person's mail (#34). Mirrors `src/store/schema.ts`'s `mailApps`.
|
|
3
|
+
-- Defaults to 'agora' and 'webmail' so every existing org keeps its current
|
|
4
|
+
-- behaviour.
|
|
5
|
+
|
|
6
|
+
alter table foundry_declared_orgs
|
|
7
|
+
add column if not exists mail_apps jsonb not null default '["agora","webmail"]'::jsonb;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- @wtfalch/foundry: the role mailboxes an org wants pre-created on its mail
|
|
2
|
+
-- domain (#230). Mirrors `src/store/schema.ts`'s `roleMailboxes`. Defaults to
|
|
3
|
+
-- an empty list so every existing org keeps its current behaviour.
|
|
4
|
+
|
|
5
|
+
alter table foundry_declared_orgs
|
|
6
|
+
add column if not exists role_mailboxes jsonb not null default '[]'::jsonb;
|
package/dist/org.d.ts
CHANGED
|
@@ -32,11 +32,61 @@ export type MailMode = 'off' | 'received' | 'hosted';
|
|
|
32
32
|
* Decided in #7 amendment 1: setting `domain` alone never starts a mail
|
|
33
33
|
* change. `usesDomain` is mail's own field for that, independent of
|
|
34
34
|
* `mode` — a domain can be recorded before mail is ever pointed at it.
|
|
35
|
+
*
|
|
36
|
+
* `mailApps`: which ZITADEL apps in the org's audience project may read a
|
|
37
|
+
* person's mail. Decided by William (2026-09-30, #34): only listed apps
|
|
38
|
+
* may read a person's mail, and the org's own declared config holds the
|
|
39
|
+
* list -- ZITADEL puts a project's own id into the default audience of
|
|
40
|
+
* every app signed in to that project, so a matching `requireAudience`
|
|
41
|
+
* value alone says nothing about *who* carries it (`manage` had exactly
|
|
42
|
+
* this hole until it moved to its own project, wtfalch/agora#100;
|
|
43
|
+
* `operator` has it today, wtfalch/operator#11). `undefined` means the org
|
|
44
|
+
* declares none of its own and {@link DEFAULT_MAIL_APPS} applies --
|
|
45
|
+
* optional so every existing declared org, and every test fixture that
|
|
46
|
+
* builds a `MailConfig` literal, keeps working unchanged; a caller that
|
|
47
|
+
* cares reads `mail.mailApps ?? DEFAULT_MAIL_APPS`, the same fallback
|
|
48
|
+
* `plan/mail-identity-allowlist.ts`'s own doc comment names.
|
|
35
49
|
*/
|
|
36
50
|
export interface MailConfig {
|
|
37
51
|
mode: MailMode;
|
|
38
52
|
usesDomain: boolean;
|
|
53
|
+
mailApps?: string[];
|
|
54
|
+
/**
|
|
55
|
+
* The owner's mailbox address on this org's mail domain. Decided
|
|
56
|
+
* 2026-09-29 on #162: nothing is guessed any more, replacing the
|
|
57
|
+
* hardcoded `owner@<domain>` PR 163 shipped first. `null` (the default)
|
|
58
|
+
* until an operator names one -- `plan/diff.ts`'s `planMail` then reports
|
|
59
|
+
* the owner's mailbox alone as blocked, naming this field, while every
|
|
60
|
+
* other part of the org's mail still applies. Optional so a caller that
|
|
61
|
+
* builds a `MailConfig` for anything other than the owner's mailbox (most
|
|
62
|
+
* existing call sites, before this field existed) is unaffected; absent
|
|
63
|
+
* is treated the same as `null` everywhere this is read.
|
|
64
|
+
*/
|
|
65
|
+
ownerMailboxAddress?: string | null;
|
|
66
|
+
/**
|
|
67
|
+
* The local part of the org's send-only no-reply address (no inbox),
|
|
68
|
+
* read by `plan/executor.ts`'s `ensureAppLettermintMail` for `MAIL_FROM`.
|
|
69
|
+
* Defaults to `'no-reply'` when absent -- #162: a localised default via
|
|
70
|
+
* `@wtfalch/i18n` (a Norwegian company's own word for it, say) is
|
|
71
|
+
* deferred, since that package has none yet. An operator's own value
|
|
72
|
+
* always wins once set.
|
|
73
|
+
*/
|
|
74
|
+
noReplyLocalPart?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Local parts of the role mailboxes this org wants pre-created on its mail
|
|
77
|
+
* domain (#230) -- `postmaster`, `abuse`, `security` and the like. Role
|
|
78
|
+
* mailboxes have no person behind them, so unlike {@link ownerMailboxAddress}
|
|
79
|
+
* they are not tied to `owner` and are never invited through the broker.
|
|
80
|
+
* Foundry holds no default set: every org declares its own. `undefined` or
|
|
81
|
+
* empty means none. Each name is a local part, not an address, so the
|
|
82
|
+
* address is always on the org's own resolved mail domain.
|
|
83
|
+
*/
|
|
84
|
+
roleMailboxes?: string[];
|
|
39
85
|
}
|
|
86
|
+
/** {@link MailConfig.mailApps} when the org declares none of its own (#34, decided 2026-09-30). */
|
|
87
|
+
export declare const DEFAULT_MAIL_APPS: readonly string[];
|
|
88
|
+
/** {@link MailConfig.noReplyLocalPart}'s own default, restated wherever it is read rather than imported (this file's `LOG_DIR`-style convention, named in `plan/executor.ts`'s own doc comments). */
|
|
89
|
+
export declare const DEFAULT_NO_REPLY_LOCAL_PART = "no-reply";
|
|
40
90
|
/**
|
|
41
91
|
* Chat's per-service opt-in into the org's `domain` fact.
|
|
42
92
|
*
|
|
@@ -329,6 +379,37 @@ export interface OwnerIdentity {
|
|
|
329
379
|
* null` (no owner named yet) is always valid.
|
|
330
380
|
*/
|
|
331
381
|
export declare function validateOwner(owner: OwnerIdentity | null): string | null;
|
|
382
|
+
/**
|
|
383
|
+
* `null` when `mail` is valid; otherwise the reason it is not (#162,
|
|
384
|
+
* decided 2026-09-29).
|
|
385
|
+
*
|
|
386
|
+
* `ownerMailboxAddress`, when set, must be a valid email address; when
|
|
387
|
+
* `usesDomain` is also true and `domain` is set, it must be on that exact
|
|
388
|
+
* domain ("validate it is on the org's own domain", #162's "How"). A
|
|
389
|
+
* level-1 org's mail domain is `<slug>.<platform apex>` -- the platform's
|
|
390
|
+
* own fact, not this file's -- so that case is left for `plan/diff.ts`'s
|
|
391
|
+
* `planMail` to resolve and block on, the same way it already resolves
|
|
392
|
+
* `mailHost` itself.
|
|
393
|
+
*
|
|
394
|
+
* Deliberately NOT required just because `mode` is not `'off'`: an org can
|
|
395
|
+
* have mail on with no address declared yet, same as it can have an owner
|
|
396
|
+
* named with no ZITADEL invite sent yet -- `planMail` reports the mailbox
|
|
397
|
+
* alone as blocked in that case rather than this function refusing to let
|
|
398
|
+
* the org be saved at all (#162 "Done when": "mail on with no owner address
|
|
399
|
+
* plans a blocked item and still applies the other steps").
|
|
400
|
+
*
|
|
401
|
+
* `noReplyLocalPart`, when set, must be non-empty and contain neither `@`
|
|
402
|
+
* nor whitespace.
|
|
403
|
+
*
|
|
404
|
+
* `roleMailboxes`, when set (#230), has only plain local parts (no `@`, `,` or
|
|
405
|
+
* whitespace) and no duplicate, ignoring case.
|
|
406
|
+
*
|
|
407
|
+
* `mailApps`, when set (#34), has no empty entry and no duplicate -- same
|
|
408
|
+
* two rules `validateHosting` already applies to `issuerApps`' names, for
|
|
409
|
+
* the same reason: a duplicate or blank entry can never mean anything
|
|
410
|
+
* different from leaving it out.
|
|
411
|
+
*/
|
|
412
|
+
export declare function validateMail(org: Pick<DeclaredOrg, 'mail' | 'domain'>): string | null;
|
|
332
413
|
/**
|
|
333
414
|
* The form an app foundry does not own takes. Decided in #7's `#15`
|
|
334
415
|
* refinement comment:
|
package/dist/org.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/** {@link MailConfig.mailApps} when the org declares none of its own (#34, decided 2026-09-30). */
|
|
2
|
+
export const DEFAULT_MAIL_APPS = ['agora', 'webmail'];
|
|
3
|
+
/** {@link MailConfig.noReplyLocalPart}'s own default, restated wherever it is read rather than imported (this file's `LOG_DIR`-style convention, named in `plan/executor.ts`'s own doc comments). */
|
|
4
|
+
export const DEFAULT_NO_REPLY_LOCAL_PART = 'no-reply';
|
|
1
5
|
const ISSUER_APP_KINDS = new Set(['web', 'native', 'api']);
|
|
2
6
|
const HOSTING_BUILD_PACKS = new Set([
|
|
3
7
|
'dockerfile',
|
|
@@ -49,6 +53,14 @@ export function validateHosting(hosting) {
|
|
|
49
53
|
if (!ISSUER_APP_KINDS.has(app.kind)) {
|
|
50
54
|
return `issuerApps '${app.name}' has an unknown kind '${app.kind}'`;
|
|
51
55
|
}
|
|
56
|
+
// The fixed 'agora' ZITADEL client (#164, `plan/diff.ts`'s
|
|
57
|
+
// `planAgoraClient`) already owns the `zitadel:app:agora` key, for
|
|
58
|
+
// every org -- Agora is always on, unlike the cms client's own 'cms'
|
|
59
|
+
// reservation (`validateSite` below), which only applies once
|
|
60
|
+
// `site.enabled` is true.
|
|
61
|
+
if (app.name === 'agora') {
|
|
62
|
+
return `issuerApps '${app.name}' is reserved for the always-on Agora client every org gets`;
|
|
63
|
+
}
|
|
52
64
|
}
|
|
53
65
|
return null;
|
|
54
66
|
}
|
|
@@ -177,3 +189,87 @@ export function validateOwner(owner) {
|
|
|
177
189
|
return 'owner.familyName must not be empty';
|
|
178
190
|
return null;
|
|
179
191
|
}
|
|
192
|
+
/** A local part has no `@` and no whitespace -- loose on purpose, the same restraint {@link OWNER_EMAIL_PATTERN}'s own doc comment states. */
|
|
193
|
+
const LOCAL_PART_PATTERN = /^[^\s@]+$/;
|
|
194
|
+
/**
|
|
195
|
+
* `null` when `mail` is valid; otherwise the reason it is not (#162,
|
|
196
|
+
* decided 2026-09-29).
|
|
197
|
+
*
|
|
198
|
+
* `ownerMailboxAddress`, when set, must be a valid email address; when
|
|
199
|
+
* `usesDomain` is also true and `domain` is set, it must be on that exact
|
|
200
|
+
* domain ("validate it is on the org's own domain", #162's "How"). A
|
|
201
|
+
* level-1 org's mail domain is `<slug>.<platform apex>` -- the platform's
|
|
202
|
+
* own fact, not this file's -- so that case is left for `plan/diff.ts`'s
|
|
203
|
+
* `planMail` to resolve and block on, the same way it already resolves
|
|
204
|
+
* `mailHost` itself.
|
|
205
|
+
*
|
|
206
|
+
* Deliberately NOT required just because `mode` is not `'off'`: an org can
|
|
207
|
+
* have mail on with no address declared yet, same as it can have an owner
|
|
208
|
+
* named with no ZITADEL invite sent yet -- `planMail` reports the mailbox
|
|
209
|
+
* alone as blocked in that case rather than this function refusing to let
|
|
210
|
+
* the org be saved at all (#162 "Done when": "mail on with no owner address
|
|
211
|
+
* plans a blocked item and still applies the other steps").
|
|
212
|
+
*
|
|
213
|
+
* `noReplyLocalPart`, when set, must be non-empty and contain neither `@`
|
|
214
|
+
* nor whitespace.
|
|
215
|
+
*
|
|
216
|
+
* `roleMailboxes`, when set (#230), has only plain local parts (no `@`, `,` or
|
|
217
|
+
* whitespace) and no duplicate, ignoring case.
|
|
218
|
+
*
|
|
219
|
+
* `mailApps`, when set (#34), has no empty entry and no duplicate -- same
|
|
220
|
+
* two rules `validateHosting` already applies to `issuerApps`' names, for
|
|
221
|
+
* the same reason: a duplicate or blank entry can never mean anything
|
|
222
|
+
* different from leaving it out.
|
|
223
|
+
*/
|
|
224
|
+
export function validateMail(org) {
|
|
225
|
+
const { mail } = org;
|
|
226
|
+
const address = mail.ownerMailboxAddress ?? null;
|
|
227
|
+
if (address !== null) {
|
|
228
|
+
if (!OWNER_EMAIL_PATTERN.test(address)) {
|
|
229
|
+
return `mail.ownerMailboxAddress '${address}' is not a valid email address`;
|
|
230
|
+
}
|
|
231
|
+
if (mail.usesDomain && org.domain) {
|
|
232
|
+
const addressDomain = address.slice(address.indexOf('@') + 1).toLowerCase();
|
|
233
|
+
if (addressDomain !== org.domain.toLowerCase()) {
|
|
234
|
+
return `mail.ownerMailboxAddress '${address}' must be on ${org.domain}`;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (mail.noReplyLocalPart !== undefined && !LOCAL_PART_PATTERN.test(mail.noReplyLocalPart)) {
|
|
239
|
+
return `mail.noReplyLocalPart '${mail.noReplyLocalPart}' must not be empty or contain '@' or whitespace`;
|
|
240
|
+
}
|
|
241
|
+
if (mail.roleMailboxes) {
|
|
242
|
+
const seen = new Set();
|
|
243
|
+
const reserved = new Map([
|
|
244
|
+
[(mail.noReplyLocalPart ?? DEFAULT_NO_REPLY_LOCAL_PART).toLowerCase(), 'no-reply'],
|
|
245
|
+
]);
|
|
246
|
+
if (address !== null)
|
|
247
|
+
reserved.set(address.slice(0, address.indexOf('@')).toLowerCase(), 'owner');
|
|
248
|
+
for (const name of mail.roleMailboxes) {
|
|
249
|
+
if (!LOCAL_PART_PATTERN.test(name) || name.includes(',')) {
|
|
250
|
+
return `mail.roleMailboxes entry '${name}' must be a non-empty local part without '@', ',' or whitespace`;
|
|
251
|
+
}
|
|
252
|
+
if (seen.has(name.toLowerCase())) {
|
|
253
|
+
return `mail.roleMailboxes name '${name}' is not unique`;
|
|
254
|
+
}
|
|
255
|
+
const clash = reserved.get(name.toLowerCase());
|
|
256
|
+
if (clash) {
|
|
257
|
+
return `mail.roleMailboxes name '${name}' clashes with the ${clash} mailbox`;
|
|
258
|
+
}
|
|
259
|
+
seen.add(name.toLowerCase());
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (mail.mailApps) {
|
|
263
|
+
const seen = new Set();
|
|
264
|
+
for (const name of mail.mailApps) {
|
|
265
|
+
if (!name) {
|
|
266
|
+
return 'mail.mailApps entry needs a non-empty name';
|
|
267
|
+
}
|
|
268
|
+
if (seen.has(name)) {
|
|
269
|
+
return `mail.mailApps name '${name}' is not unique`;
|
|
270
|
+
}
|
|
271
|
+
seen.add(name);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { DeclaredOrg, OrgId } from '../org.js';
|
|
2
|
+
/**
|
|
3
|
+
* #164: what both the planner (`diff.ts`) and the executor (`executor.ts`)
|
|
4
|
+
* need to agree on for an org's Agora -- the hostname, the values the host
|
|
5
|
+
* supplies, and the env vars those produce -- so the plan can compare the
|
|
6
|
+
* live app with exactly what the executor would write.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* `AppliedState.serviceIds` scope keys `desiredAgoraEnv` reads:
|
|
10
|
+
* `zitadel_org` is `executor.ts`'s `ensureZitadelProject`/`zitadelIssuer`
|
|
11
|
+
* scope id, `agora_client_id` the fixed `agora` ZITADEL client's `clientId`
|
|
12
|
+
* (recorded by `executor.ts`'s `zitadelApp`).
|
|
13
|
+
*/
|
|
14
|
+
export declare const ZITADEL_ORG_SCOPE_KEY = "zitadel_org";
|
|
15
|
+
export declare const AGORA_CLIENT_ID_SCOPE_KEY = "agora_client_id";
|
|
16
|
+
/**
|
|
17
|
+
* The accent every org's Agora wears until orgs choose their own: the house
|
|
18
|
+
* theme's `--accent` (`@wtfalch/house`, `HOUSE_THEMES['good-company']` in
|
|
19
|
+
* `design/packages/house/src/themes.ts`, the default theme
|
|
20
|
+
* `HOUSE_DEFAULT_THEME`). Agora's config parser requires `theme.accent` as a
|
|
21
|
+
* hex colour (`agora/apps/agora/src/agoras.ts`). Restated here rather than
|
|
22
|
+
* imported: `packages/foundry` carries no `@wtfalch/house` dependency.
|
|
23
|
+
*/
|
|
24
|
+
export declare const AGORA_THEME_ACCENT = "#B23A0C";
|
|
25
|
+
/**
|
|
26
|
+
* Provider-side values only the host can supply, like every other provider
|
|
27
|
+
* value `FoundryStepExecutorPorts` carries. As of #177, `plan-apply.ts`'s
|
|
28
|
+
* `FoundryPlanApply` derives these itself from the applied platform
|
|
29
|
+
* (`platform-org.ts`'s `agoraHostValuesFromPlatform`) whenever a host wires
|
|
30
|
+
* one in and supplies no `AgoraHostPort` of its own -- an explicit `agora`
|
|
31
|
+
* port still overrides that, unchanged. `null` means the value is not
|
|
32
|
+
* available yet. The first three are required by Agora's config parser
|
|
33
|
+
* (`agoras.ts`: `issuer`, `mailProjectId`, `messagingProjectId`);
|
|
34
|
+
* `mailUpstream`/`filesUpstream` are the mail/files servers' internal
|
|
35
|
+
* addresses, optional -- unset, `AGORA_MAIL_UPSTREAM`/`AGORA_FILES_UPSTREAM`
|
|
36
|
+
* are not pushed and Agora answers 502 on those routes rather than serving
|
|
37
|
+
* anything wrong (`executor.ts`'s `coolifyAgora` doc comment).
|
|
38
|
+
*/
|
|
39
|
+
export interface AgoraHostValues {
|
|
40
|
+
issuer: string | null;
|
|
41
|
+
mailProjectId: string | null;
|
|
42
|
+
messagingProjectId: string | null;
|
|
43
|
+
mailUpstream: string | null;
|
|
44
|
+
filesUpstream: string | null;
|
|
45
|
+
}
|
|
46
|
+
export interface AgoraHostPort {
|
|
47
|
+
valuesFor(orgId: OrgId): Promise<AgoraHostValues>;
|
|
48
|
+
}
|
|
49
|
+
/** No host values at all: what an absent port means. */
|
|
50
|
+
export declare const NO_AGORA_HOST_VALUES: AgoraHostValues;
|
|
51
|
+
/** What the plan reads from the host: its values, or why it could not get them. */
|
|
52
|
+
export type AgoraHostReading = AgoraHostValues | {
|
|
53
|
+
error: string;
|
|
54
|
+
};
|
|
55
|
+
/** Reads the port once for a plan. An absent port, or one that throws, is a blocked Agora, never a failed plan. */
|
|
56
|
+
export declare function readAgoraHost(port: AgoraHostPort | undefined, orgId: OrgId): Promise<AgoraHostReading>;
|
|
57
|
+
/** The host values Agora cannot be configured without, by name -- the ones `host` lacks. */
|
|
58
|
+
export declare function agoraHostMissing(host: AgoraHostValues): string[];
|
|
59
|
+
/**
|
|
60
|
+
* `agora.<domain>` when the org has a domain, `<slug>-agora.oikos.host`
|
|
61
|
+
* otherwise (the 2026-09-24 layout, foundry#14; `../slug.ts`'s
|
|
62
|
+
* `AGORA_SUFFIX` reserves the collision).
|
|
63
|
+
*/
|
|
64
|
+
export declare function agoraHostnameFor(org: DeclaredOrg): string;
|
|
65
|
+
/**
|
|
66
|
+
* The env vars an org's Agora app should carry, and every input still
|
|
67
|
+
* missing to build them. `AGORA_CONFIG` is present only when `missing` is
|
|
68
|
+
* empty; callers that write must refuse on a non-empty `missing`.
|
|
69
|
+
* `AGORA_MAIL_UPSTREAM` is present only when mail is on and the host supplies
|
|
70
|
+
* an address.
|
|
71
|
+
*/
|
|
72
|
+
export declare function desiredAgoraEnv(org: DeclaredOrg, recorded: Readonly<Record<string, string>>, host: AgoraHostValues): {
|
|
73
|
+
env: Record<string, string>;
|
|
74
|
+
missing: string[];
|
|
75
|
+
};
|
|
76
|
+
/** The desired env keys the live app lacks or holds a different value for. Extra live keys are ignored. */
|
|
77
|
+
export declare function agoraEnvDrift(desired: Readonly<Record<string, string>>, live: Readonly<Record<string, string>>): string[];
|