@wtfalch/foundry 0.61.0 → 0.63.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/declare.js +4 -1
- package/dist/bootstrap/versions.d.ts +2 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +3 -0
- package/dist/migrations/0065_native_build_runs.sql +32 -0
- package/dist/migrations/0066_org_kosmos.sql +10 -0
- package/dist/migrations/0067_requested_changes_kosmos.sql +6 -0
- package/dist/migrations/0068_platform_registry.sql +17 -0
- package/dist/modules/sign-in.js +1 -1
- package/dist/native-build.d.ts +50 -0
- package/dist/native-build.js +72 -0
- package/dist/org.d.ts +22 -3
- package/dist/org.js +25 -2
- package/dist/plan/agora.d.ts +1 -1
- package/dist/plan/agora.js +4 -0
- package/dist/plan/archon-blue-green.d.ts +5 -0
- package/dist/plan/archon-blue-green.js +2 -0
- package/dist/plan/blue-green-apply.d.ts +5 -0
- package/dist/plan/blue-green-apply.js +197 -22
- package/dist/plan/blue-green.d.ts +80 -9
- package/dist/plan/blue-green.js +115 -14
- package/dist/plan/boule.d.ts +1 -1
- package/dist/plan/boule.js +2 -0
- package/dist/plan/broker-entries.d.ts +9 -0
- package/dist/plan/broker-entries.js +29 -0
- package/dist/plan/deploy-health.js +9 -2
- package/dist/plan/desktop-callback.js +4 -1
- package/dist/plan/diff.d.ts +5 -0
- package/dist/plan/diff.js +184 -63
- package/dist/plan/edge-access.d.ts +2 -3
- package/dist/plan/edge-access.js +8 -0
- package/dist/plan/executor.d.ts +111 -19
- package/dist/plan/executor.js +578 -72
- package/dist/plan/index.d.ts +2 -0
- package/dist/plan/index.js +1 -0
- package/dist/plan/jobs-scheduler-http.d.ts +2 -0
- package/dist/plan/jobs-scheduler-http.js +11 -0
- package/dist/plan/kosmos.d.ts +56 -0
- package/dist/plan/kosmos.js +84 -0
- package/dist/plan/locked-hostnames.js +5 -2
- package/dist/plan/org-names.d.ts +3 -3
- package/dist/plan/org-names.js +3 -1
- package/dist/plan/org-postgres-migrate.js +1 -0
- package/dist/plan/own-limits.d.ts +12 -0
- package/dist/plan/own-limits.js +2 -0
- package/dist/plan/plan-apply.d.ts +51 -1
- package/dist/plan/plan-apply.js +298 -25
- package/dist/plan/platform-diff.d.ts +6 -0
- package/dist/plan/platform-diff.js +74 -0
- package/dist/plan/platform-org.d.ts +2 -0
- package/dist/plan/platform-providers.d.ts +46 -1
- package/dist/plan/platform-providers.js +162 -1
- package/dist/plan/platform-registry-keys.d.ts +58 -0
- package/dist/plan/platform-registry-keys.js +69 -0
- package/dist/plan/platform-registry.d.ts +67 -0
- package/dist/plan/platform-registry.js +298 -0
- package/dist/plan/post-logout-uris.d.ts +1 -1
- package/dist/plan/post-logout-uris.js +5 -2
- package/dist/plan/providers-readers.js +20 -1
- package/dist/plan/release-host.d.ts +31 -2
- package/dist/plan/release-host.js +114 -6
- package/dist/plan/stage-copy-host.d.ts +104 -0
- package/dist/plan/stage-copy-host.js +259 -0
- package/dist/plan/stage-copy.js +44 -0
- package/dist/plan/stage-env.d.ts +9 -0
- package/dist/plan/stage-env.js +38 -0
- package/dist/plan/stage-guards.d.ts +57 -1
- package/dist/plan/stage-guards.js +106 -0
- package/dist/plan/stage-isolation.js +56 -0
- package/dist/plan/stage-services.d.ts +93 -0
- package/dist/plan/stage-services.js +153 -0
- package/dist/plan/stage-signin.d.ts +152 -0
- package/dist/plan/stage-signin.js +133 -0
- package/dist/plan/stage-sink.d.ts +2 -0
- package/dist/plan/stage-sink.js +6 -1
- package/dist/platform.d.ts +7 -1
- package/dist/platform.js +1 -0
- package/dist/release/decide.d.ts +12 -0
- package/dist/release/decide.js +37 -2
- package/dist/release/migration-compatibility.d.ts +66 -0
- package/dist/release/migration-compatibility.js +340 -0
- package/dist/release/run.d.ts +71 -1
- package/dist/release/run.js +92 -8
- package/dist/release/state.d.ts +14 -1
- package/dist/release/state.js +43 -0
- package/dist/release/types.d.ts +20 -0
- package/dist/release/types.js +2 -0
- package/dist/requested-changes.d.ts +11 -2
- package/dist/requested-changes.js +13 -1
- package/dist/slug.d.ts +1 -1
- package/dist/slug.js +11 -2
- package/dist/store/postgres.js +21 -0
- package/dist/store/schema.d.ts +87 -0
- package/dist/store/schema.js +10 -0
- package/package.json +3 -2
- package/versions.digests.json +1 -0
- package/versions.json +1 -0
|
@@ -53,6 +53,7 @@ const SERVICES_ENABLED_BY_DEFAULT = new Set([
|
|
|
53
53
|
'archon',
|
|
54
54
|
'edge',
|
|
55
55
|
'hc',
|
|
56
|
+
'registry',
|
|
56
57
|
]);
|
|
57
58
|
/**
|
|
58
59
|
* The services every company deploys its own copy of. Their platform entries
|
|
@@ -153,7 +154,9 @@ export function declaredPlatformFor(apex, serverIpv4) {
|
|
|
153
154
|
? DEFAULT_MAIL_IMAGE_TAG
|
|
154
155
|
: key === 'hc'
|
|
155
156
|
? DEFAULT_HC_IMAGE_TAG
|
|
156
|
-
:
|
|
157
|
+
: key === 'registry'
|
|
158
|
+
? VERSIONS.registry
|
|
159
|
+
: DEFAULT_GIT_REF,
|
|
157
160
|
};
|
|
158
161
|
}
|
|
159
162
|
return {
|
|
@@ -12,6 +12,8 @@ export interface PinnedVersions {
|
|
|
12
12
|
cloudflared: string;
|
|
13
13
|
stalwart: string;
|
|
14
14
|
hc: string;
|
|
15
|
+
/** The platform's image registry (`registry:3`, Docker's `distribution`). */
|
|
16
|
+
registry: string;
|
|
15
17
|
/** The mail relay's base image (an exact Alpine release); Postfix comes from its package repository. */
|
|
16
18
|
alpine: string;
|
|
17
19
|
/** A static busybox, copied into the MAS image so its health check has an HTTP client. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, HostingEntry, HostingGithub, BlueGreenConfig, OrgBuiltinApp, BouleApp, OrgRefusal, OrgRefusalCode, WorkerAppConfig, EntryGate, EntryVolume, HostingEntryUses, EntryAuth, EntryAuthRoom, EntryFiles, EntryAi, EnvValue, EnvHeldSecret, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, OwnerIdentity, OwnerMailbox, DeclaredOrg, OrgServiceDatabase, OrgServiceSchema, EntryWorkspace, ServicesConfig, SignInConfig, VersionPin, VersionPinPart, } from './org.js';
|
|
1
|
+
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, HostingEntry, HostingGithub, BlueGreenConfig, OrgBuiltinApp, BouleApp, KosmosApp, OrgRefusal, OrgRefusalCode, WorkerAppConfig, EntryGate, EntryVolume, HostingEntryUses, EntryAuth, EntryAuthRoom, EntryFiles, EntryAi, EnvValue, EnvHeldSecret, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, OwnerIdentity, OwnerMailbox, DeclaredOrg, OrgServiceDatabase, OrgServiceSchema, EntryWorkspace, ServicesConfig, SignInConfig, VersionPin, VersionPinPart, } from './org.js';
|
|
2
2
|
export { APP_ENTRY_KEY, APPS_DOMAIN, DEFAULT_GATE_LIMIT, MAX_WORKSPACE_TESTERS, isLiveEntry, normalizeTesters, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, orgServiceDatabase, orgServiceDatabases, SFU_SLOT_MAX, VERSION_PIN_PARTS, isMainEntry, mainEntry, companyGithubFor, heldEnvRef, ownerMailboxes, ownsServices, validateBuyerIdentity, validateHosting, normalizeHostingRepos, validateMail, validateOwners, validateServices, validateCustomers, validateSignIn, validateVersionPin, validateSite, } from './org.js';
|
|
3
3
|
export { refuseOrg } from './refuse-org.js';
|
|
4
4
|
export { workerAppHasBuild } from './worker-app-config.js';
|
|
@@ -15,6 +15,8 @@ export type { SlugRejectionReason, SlugRejection } from './slug.js';
|
|
|
15
15
|
export { validateSlug, slugFromName, RESERVED_SLUGS } from './slug.js';
|
|
16
16
|
export type { Checkpoint, LastApplied, AppliedState, DestroyRecord, DestroyStepRecord, } from './state.js';
|
|
17
17
|
export type { PlanStep, UnmanagedItem, PlanNote, Plan, VersionChangeItem } from './plan.js';
|
|
18
|
+
export type { NativeBuildDispatchDeps } from './native-build.js';
|
|
19
|
+
export { NATIVE_BUILD_REPO, NATIVE_BUILD_WORKFLOW_FILE, NATIVE_BUILD_WORKFLOW_REF, NATIVE_BUILD_DISPATCH_TIMEOUT_MS, NATIVE_BUILD_KEY_ENV, NATIVE_BUILD_KEY_BYTES, NativeBuildDispatchError, dispatchNativeBuild, } from './native-build.js';
|
|
18
20
|
export type { AppliedVersion, AppliedVersionsStore, DeclaredOrgStore, HeldLease, MutationLease, PlatformStore, RequestedChangeStore, StaleUpdate, UpdateOptions, } from './store.js';
|
|
19
21
|
export { StaleLeaseError } from './store.js';
|
|
20
22
|
export type { PlanApply, DestroyProgress } from './apply.js';
|
|
@@ -23,6 +25,8 @@ export { createPostgresAuditLog } from './store/audit.js';
|
|
|
23
25
|
export type { Color, ReleaseAction, ReleaseSignal, ReleaseState, WindowState, } from './release/types.js';
|
|
24
26
|
export type { ReleaseStore } from './release/state.js';
|
|
25
27
|
export { readReleaseState, requestReleaseAction } from './release/state.js';
|
|
28
|
+
export type { DeclaredClassRemoval, MigrationCompatibilityInput, MigrationCompatibilityProblem, MigrationCompatibilityResult, } from './release/migration-compatibility.js';
|
|
29
|
+
export { checkMigrationCompatibility } from './release/migration-compatibility.js';
|
|
26
30
|
export { createPostgresReleaseStore } from './store/postgres.js';
|
|
27
31
|
export type { FoundryStoreDb } from './store/postgres.js';
|
|
28
32
|
export { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryAuditEvents, foundryDeclaredPlatform, foundryReleaseRequests, foundryRequestedChanges, type AuditEventRow, type DeclaredOrgRow, type NewDeclaredOrgRow, type AppliedStateRow, type NewAppliedStateRow, type AppliedVersionRow, type NewAppliedVersionRow, type DeclaredPlatformRow, type NewDeclaredPlatformRow, type RequestedChangeRow, type NewRequestedChangeRow, } from './store/schema.js';
|
|
@@ -36,6 +40,8 @@ export { STAGE_COPY_EXCLUDED_TABLES, StageCopyFailedError, StageCopyRefusedError
|
|
|
36
40
|
export type { StageCopyInput, StageCopyPort, StageCopyProduction, StageCopyRunRecord, UndoStageCopyInput, } from './plan/index.js';
|
|
37
41
|
export { StageRestoreError, StageRestoreRefusedError, createStageCopyPort, execRunner, } from './plan/index.js';
|
|
38
42
|
export type { Runner, StageCopyPortDeps } from './plan/index.js';
|
|
43
|
+
export type { PlatformRegistryDeps, PlatformRegistryRuntime } from './plan/index.js';
|
|
44
|
+
export { PLATFORM_REGISTRY_PULL_PASSWORD_REF, PLATFORM_REGISTRY_PUSH_PASSWORD_REF, PLATFORM_REGISTRY_PUSH_USER, platformRegistryPushCredential, } from './plan/index.js';
|
|
39
45
|
export { InvalidMigrationConfirmationError, ORG_POSTGRES_MIGRATION_CONFIRMED_SCOPE_KEY, OrgPostgresMigrationBackupMissingError, OrgPostgresMigrationNotConfirmedError, } from './plan/index.js';
|
|
40
46
|
export type { OrgPostgresMigrationConfirmation } from './plan/index.js';
|
|
41
47
|
export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './plan/index.js';
|
|
@@ -55,6 +61,8 @@ export type { PlanOwnSfuOptions, SfuAppDeps, SfuAppInput } from './plan/index.js
|
|
|
55
61
|
export { OWN_SFU_BLOCKED_KEY, OWN_SFU_FIREWALL_KEY, planOwnSfu, provisionSfuApp, sfuConfig, sfuCompose, } from './plan/index.js';
|
|
56
62
|
export type { CoolifyPort, CloudflarePort, ZitadelPort, CallsSfuPort, JobsSchedulerPort, FilesOrganisationPort, AiOrganisationPort, FoundryStepExecutorPorts, KeysRegistrarPort, PlatformOrgPortSource, AgoraHostPort, AgoraHostValues, LoginHostBinding, LoginHostBindingsPort, LoginHostDiscoveryPort, } from './plan/index.js';
|
|
57
63
|
export { FoundryStepExecutor, NotBuiltError, healthUrls, loginHostnameFor } from './plan/index.js';
|
|
64
|
+
export type { StageProbePort, ReleasePreviewMembers } from './plan/stage-guards.js';
|
|
65
|
+
export type { StageCopyHostConfig } from './plan/stage-copy-host.js';
|
|
58
66
|
export { NO_DOMAIN_ZONE, entryMachinePaths, ownHostnames, privatePageGateKey, workspaceHost, } from './plan/index.js';
|
|
59
67
|
export type { PrivatePageScope } from './plan/index.js';
|
|
60
68
|
export type { HostedLabelRefusal } from './hosted-label.js';
|
package/dist/index.js
CHANGED
|
@@ -10,10 +10,12 @@ export const PINNED_VERSIONS = VERSIONS;
|
|
|
10
10
|
export { buildJsonSchema, parseBuildJson, BuildJsonError } from './build-json.js';
|
|
11
11
|
export { REQUESTABLE_FIELDS, classifyChange, RequestedChangeError } from './requested-changes.js';
|
|
12
12
|
export { validateSlug, slugFromName, RESERVED_SLUGS } from './slug.js';
|
|
13
|
+
export { NATIVE_BUILD_REPO, NATIVE_BUILD_WORKFLOW_FILE, NATIVE_BUILD_WORKFLOW_REF, NATIVE_BUILD_DISPATCH_TIMEOUT_MS, NATIVE_BUILD_KEY_ENV, NATIVE_BUILD_KEY_BYTES, NativeBuildDispatchError, dispatchNativeBuild, } from './native-build.js';
|
|
13
14
|
export { StaleLeaseError } from './store.js';
|
|
14
15
|
export { createPostgresAppliedVersionsStore, createPostgresDeclaredOrgStore, createPostgresPlatformStore, createPostgresRequestedChangeStore, DeclaredOrgError, PlatformError, } from './store/postgres.js';
|
|
15
16
|
export { createPostgresAuditLog } from './store/audit.js';
|
|
16
17
|
export { readReleaseState, requestReleaseAction } from './release/state.js';
|
|
18
|
+
export { checkMigrationCompatibility } from './release/migration-compatibility.js';
|
|
17
19
|
export { createPostgresReleaseStore } from './store/postgres.js';
|
|
18
20
|
export { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryAuditEvents, foundryDeclaredPlatform, foundryReleaseRequests, foundryRequestedChanges, } from './store/schema.js';
|
|
19
21
|
export { COMPANY_DESTINATION_NAME_PREFIX, COMPANY_NETWORKS, DEFAULT_MAX_DESTINATIONS, DestinationCapacityError, PLATFORM_NETWORK, companyNetworkName, ensureDestination, findDestination, placed, placedOnPlatform, validateNetworkName, } from './plan/index.js';
|
|
@@ -22,6 +24,7 @@ export { DumpError, dumpNow } from './plan/index.js';
|
|
|
22
24
|
export { STAGE_SINK_APPLIED_SCOPE_KEY, STAGE_SINK_IMAGE_REF, stageMailEnv, stageSinkAppName, } from './plan/index.js';
|
|
23
25
|
export { STAGE_COPY_EXCLUDED_TABLES, StageCopyFailedError, StageCopyRefusedError, stageCopy, tableExcluded, undoStageCopy, } from './plan/index.js';
|
|
24
26
|
export { StageRestoreError, StageRestoreRefusedError, createStageCopyPort, execRunner, } from './plan/index.js';
|
|
27
|
+
export { PLATFORM_REGISTRY_PULL_PASSWORD_REF, PLATFORM_REGISTRY_PUSH_PASSWORD_REF, PLATFORM_REGISTRY_PUSH_USER, platformRegistryPushCredential, } from './plan/index.js';
|
|
25
28
|
export { InvalidMigrationConfirmationError, ORG_POSTGRES_MIGRATION_CONFIRMED_SCOPE_KEY, OrgPostgresMigrationBackupMissingError, OrgPostgresMigrationNotConfirmedError, } from './plan/index.js';
|
|
26
29
|
export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './plan/index.js';
|
|
27
30
|
export { createEdgeAccess, createEdgeDns, edgeDnsRecordOwners, createEdgeRateLimits, createEdgeTunnels, createEdgeWorkers, createEdgeZones, } from './plan/index.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
-- @wtfalch/foundry: a company's native app settings and its native build runs (foundry#948, estate
|
|
2
|
+
-- `.plans/2026-10-22-app-shells.md`). Additive, like 0060_hosting_repos.sql and
|
|
3
|
+
-- 0055_release_requests.sql: a column with a default plus a new table, so a row written before this
|
|
4
|
+
-- reads back `[]` and an older reader ignores both. Safe on live rows when the container starts.
|
|
5
|
+
--
|
|
6
|
+
-- foundry#951: Boule owns the native settings, build runs and store keys now (estate#262) -- the
|
|
7
|
+
-- column and the table below are unused by this package's code from here on, but foundry#951 also
|
|
8
|
+
-- establishes that a migration only adds in a release where idle and live share a database, so
|
|
9
|
+
-- neither is dropped here. A later release drops both.
|
|
10
|
+
|
|
11
|
+
alter table foundry_declared_orgs
|
|
12
|
+
add column if not exists hosting_native_apps jsonb not null default '[]'::jsonb;
|
|
13
|
+
|
|
14
|
+
create table if not exists foundry_native_build_runs (
|
|
15
|
+
id bigserial primary key,
|
|
16
|
+
org_id text not null,
|
|
17
|
+
app text not null,
|
|
18
|
+
state text not null default 'queued',
|
|
19
|
+
log text,
|
|
20
|
+
created_at timestamptz not null default now(),
|
|
21
|
+
updated_at timestamptz not null default now()
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
alter table foundry_native_build_runs
|
|
25
|
+
drop constraint if exists foundry_native_build_runs_state_check;
|
|
26
|
+
alter table foundry_native_build_runs
|
|
27
|
+
add constraint foundry_native_build_runs_state_check
|
|
28
|
+
check (state in ('queued', 'building', 'uploaded', 'failed'));
|
|
29
|
+
|
|
30
|
+
-- Listing one app's runs, and (while foundry still checked it, before foundry#951) the `uploaded` look-up.
|
|
31
|
+
create index if not exists foundry_native_build_runs_app_idx
|
|
32
|
+
on foundry_native_build_runs (org_id, app, id desc);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- @wtfalch/foundry: `kosmos_enabled` is `org.kosmos.enabled` (foundry#949): the company's own kosmos is set up
|
|
2
|
+
-- only when it asks. `kosmos_blue_green` is `org.kosmos.blueGreen` ({ steps?, stepHoldMinutes? }, as the other
|
|
3
|
+
-- two built-ins' own columns hold it) -- kept even though kosmos always runs blue/green once enabled, so tuning
|
|
4
|
+
-- its steps needs no new column later. Both default to off. Additive like 0059_org_builtin_blue_green.sql and
|
|
5
|
+
-- 0063_boule_apps.sql: `if not exists`, safe to run at container start on live rows.
|
|
6
|
+
-- Mirrors src/store/schema.ts's `declaredOrgs`.
|
|
7
|
+
|
|
8
|
+
alter table foundry_declared_orgs
|
|
9
|
+
add column if not exists kosmos_enabled boolean not null default false,
|
|
10
|
+
add column if not exists kosmos_blue_green jsonb;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- @wtfalch/foundry: a company's Boule can request `kosmos` (turn `org.kosmos.enabled` on or off).
|
|
2
|
+
alter table foundry_requested_changes
|
|
3
|
+
drop constraint if exists foundry_requested_changes_field_check;
|
|
4
|
+
alter table foundry_requested_changes
|
|
5
|
+
add constraint foundry_requested_changes_field_check
|
|
6
|
+
check (field in ('calls', 'customers', 'storageLimit', 'aiLimit', 'domain', 'mailTier', 'app', 'build', 'bouleApps', 'kosmos'));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- @wtfalch/foundry: the platform's image registry (`registry:3`), declared with the rest of the
|
|
2
|
+
-- platform. Mirrors `src/store/schema.ts`'s `foundryDeclaredPlatform` additions. Off for the
|
|
3
|
+
-- platform that already exists: foundry deploys nothing new until it is declared on. Backfills
|
|
4
|
+
-- `registry_hostname` the way `0047_platform_coolify.sql` backfilled `coolify_hostname`: the
|
|
5
|
+
-- column must not be empty (`src/platform.ts`'s `validatePlatform`).
|
|
6
|
+
|
|
7
|
+
alter table foundry_declared_platform
|
|
8
|
+
add column if not exists registry_enabled boolean not null default false,
|
|
9
|
+
add column if not exists registry_hostname text,
|
|
10
|
+
add column if not exists registry_git_ref text not null default '';
|
|
11
|
+
|
|
12
|
+
update foundry_declared_platform
|
|
13
|
+
set registry_hostname = 'registry.' || apex
|
|
14
|
+
where registry_hostname is null;
|
|
15
|
+
|
|
16
|
+
alter table foundry_declared_platform
|
|
17
|
+
alter column registry_hostname set not null;
|
package/dist/modules/sign-in.js
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `wtfalch/shell`'s `build-app.yml`: the one GitHub Actions workflow that
|
|
3
|
+
* builds and signs a company's native app shell (estate
|
|
4
|
+
* `.plans/2026-10-22-app-shells.md`). Boule now owns the `NativeAppConfig`,
|
|
5
|
+
* the build runs and the company's store keys (estate#262, foundry#951);
|
|
6
|
+
* foundry keeps only two things, both in this file: dispatching the
|
|
7
|
+
* workflow, and the key Boule presents when it asks the host (Archon) to
|
|
8
|
+
* dispatch it.
|
|
9
|
+
*
|
|
10
|
+
* `dispatchNativeBuild` runs inside Archon's own `/api/native/builds`
|
|
11
|
+
* route, not foundry's apply loop. The key itself is minted by
|
|
12
|
+
* `plan/executor.ts`'s `coolifyBoule` (`ensureNativeBuildKey`), reusing the
|
|
13
|
+
* same `mintIfAbsent` path every other per-company Boule secret goes
|
|
14
|
+
* through (foundry#970) -- this file keeps only the env name and the byte
|
|
15
|
+
* length, not a second mint function (foundry#957 review note 2 and
|
|
16
|
+
* thermo note 1: the earlier `mintNativeBuildKey` was a third, buggy copy
|
|
17
|
+
* of that same check).
|
|
18
|
+
*/
|
|
19
|
+
export declare const NATIVE_BUILD_REPO = "wtfalch/shell";
|
|
20
|
+
export declare const NATIVE_BUILD_WORKFLOW_FILE = "build-app.yml";
|
|
21
|
+
export declare const NATIVE_BUILD_WORKFLOW_REF = "main";
|
|
22
|
+
/** Every GitHub call this module makes is cut off after this long. */
|
|
23
|
+
export declare const NATIVE_BUILD_DISPATCH_TIMEOUT_MS = 20000;
|
|
24
|
+
/** A `build-app.yml` dispatch that failed. The text is fixed: it never holds the token or a response body. */
|
|
25
|
+
export declare class NativeBuildDispatchError extends Error {
|
|
26
|
+
readonly status: number | null;
|
|
27
|
+
constructor(status: number | null);
|
|
28
|
+
}
|
|
29
|
+
export interface NativeBuildDispatchDeps {
|
|
30
|
+
/** A GitHub token that may dispatch workflows in {@link NATIVE_BUILD_REPO}. */
|
|
31
|
+
token: string;
|
|
32
|
+
/** Global fetch by default; tests give their own. */
|
|
33
|
+
fetchImpl?: typeof fetch;
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Dispatches `build-app.yml` with exactly `{ runId, origin }` as its inputs:
|
|
38
|
+
* `runId` is the run the caller (Archon, behind its own `/api/native/builds`
|
|
39
|
+
* route) already made, `origin` is the company's Boule address the job
|
|
40
|
+
* reports its status back to and trades its OIDC token for keys at. Foundry
|
|
41
|
+
* keeps no run store of its own any more -- Boule does -- so this is the one
|
|
42
|
+
* thing left for foundry to do with a run: start it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function dispatchNativeBuild(deps: NativeBuildDispatchDeps, run: {
|
|
45
|
+
runId: string;
|
|
46
|
+
origin: string;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
/** The env name Boule presents this key under, to Archon's native-build route. */
|
|
49
|
+
export declare const NATIVE_BUILD_KEY_ENV = "NATIVE_BUILD_KEY";
|
|
50
|
+
export declare const NATIVE_BUILD_KEY_BYTES = 32;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `wtfalch/shell`'s `build-app.yml`: the one GitHub Actions workflow that
|
|
3
|
+
* builds and signs a company's native app shell (estate
|
|
4
|
+
* `.plans/2026-10-22-app-shells.md`). Boule now owns the `NativeAppConfig`,
|
|
5
|
+
* the build runs and the company's store keys (estate#262, foundry#951);
|
|
6
|
+
* foundry keeps only two things, both in this file: dispatching the
|
|
7
|
+
* workflow, and the key Boule presents when it asks the host (Archon) to
|
|
8
|
+
* dispatch it.
|
|
9
|
+
*
|
|
10
|
+
* `dispatchNativeBuild` runs inside Archon's own `/api/native/builds`
|
|
11
|
+
* route, not foundry's apply loop. The key itself is minted by
|
|
12
|
+
* `plan/executor.ts`'s `coolifyBoule` (`ensureNativeBuildKey`), reusing the
|
|
13
|
+
* same `mintIfAbsent` path every other per-company Boule secret goes
|
|
14
|
+
* through (foundry#970) -- this file keeps only the env name and the byte
|
|
15
|
+
* length, not a second mint function (foundry#957 review note 2 and
|
|
16
|
+
* thermo note 1: the earlier `mintNativeBuildKey` was a third, buggy copy
|
|
17
|
+
* of that same check).
|
|
18
|
+
*/
|
|
19
|
+
export const NATIVE_BUILD_REPO = 'wtfalch/shell';
|
|
20
|
+
export const NATIVE_BUILD_WORKFLOW_FILE = 'build-app.yml';
|
|
21
|
+
export const NATIVE_BUILD_WORKFLOW_REF = 'main';
|
|
22
|
+
/** Every GitHub call this module makes is cut off after this long. */
|
|
23
|
+
export const NATIVE_BUILD_DISPATCH_TIMEOUT_MS = 20_000;
|
|
24
|
+
/** A `build-app.yml` dispatch that failed. The text is fixed: it never holds the token or a response body. */
|
|
25
|
+
export class NativeBuildDispatchError extends Error {
|
|
26
|
+
status;
|
|
27
|
+
constructor(status) {
|
|
28
|
+
super(status === null
|
|
29
|
+
? `dispatching ${NATIVE_BUILD_WORKFLOW_FILE} did not answer`
|
|
30
|
+
: `dispatching ${NATIVE_BUILD_WORKFLOW_FILE} failed with status ${status}`);
|
|
31
|
+
this.status = status;
|
|
32
|
+
this.name = 'NativeBuildDispatchError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Dispatches `build-app.yml` with exactly `{ runId, origin }` as its inputs:
|
|
37
|
+
* `runId` is the run the caller (Archon, behind its own `/api/native/builds`
|
|
38
|
+
* route) already made, `origin` is the company's Boule address the job
|
|
39
|
+
* reports its status back to and trades its OIDC token for keys at. Foundry
|
|
40
|
+
* keeps no run store of its own any more -- Boule does -- so this is the one
|
|
41
|
+
* thing left for foundry to do with a run: start it.
|
|
42
|
+
*/
|
|
43
|
+
export async function dispatchNativeBuild(deps, run) {
|
|
44
|
+
const doFetch = deps.fetchImpl ?? fetch;
|
|
45
|
+
const timeoutMs = deps.timeoutMs ?? NATIVE_BUILD_DISPATCH_TIMEOUT_MS;
|
|
46
|
+
let response;
|
|
47
|
+
try {
|
|
48
|
+
response = await doFetch(`https://api.github.com/repos/${NATIVE_BUILD_REPO}/actions/workflows/${NATIVE_BUILD_WORKFLOW_FILE}/dispatches`, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: {
|
|
51
|
+
Authorization: `Bearer ${deps.token}`,
|
|
52
|
+
Accept: 'application/vnd.github+json',
|
|
53
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
ref: NATIVE_BUILD_WORKFLOW_REF,
|
|
58
|
+
inputs: { runId: run.runId, origin: run.origin },
|
|
59
|
+
}),
|
|
60
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error(`native build: dispatch did not answer (${error instanceof Error ? error.name : 'error'})`);
|
|
65
|
+
throw new NativeBuildDispatchError(null);
|
|
66
|
+
}
|
|
67
|
+
if (!response.ok)
|
|
68
|
+
throw new NativeBuildDispatchError(response.status);
|
|
69
|
+
}
|
|
70
|
+
/** The env name Boule presents this key under, to Archon's native-build route. */
|
|
71
|
+
export const NATIVE_BUILD_KEY_ENV = 'NATIVE_BUILD_KEY';
|
|
72
|
+
export const NATIVE_BUILD_KEY_BYTES = 32;
|
package/dist/org.d.ts
CHANGED
|
@@ -301,7 +301,7 @@ export type AccessRule = {
|
|
|
301
301
|
/** The key of the entry that is today's one app: the Coolify app named `<slug>`. */
|
|
302
302
|
export declare const APP_ENTRY_KEY = "app";
|
|
303
303
|
/** Why a validator refused. `code` is stable and never reworded; `message` is for people. */
|
|
304
|
-
export type OrgRefusalCode = 'hosting_tunnel_needs_hosting' | 'issuer_app_name_missing' | 'issuer_app_name_not_unique' | 'issuer_app_kind_unknown' | 'issuer_app_name_reserved_agora' | 'issuer_app_name_reserved_boule' | 'hosting_needs_entry' | 'hosting_main_count' | 'entry_key_not_unique' | 'ai_webhook_path_not_unique' | 'worker_route_not_unique' | 'database_not_unique' | 'access_hostname_not_unique' | 'access_hostname_invalid' | 'access_needs_hosting' | 'access_kind_unknown' | 'access_service_token_name_missing' | 'access_hostname_locked' | 'access_hostname_not_served' | 'gate_limit_invalid' | 'hosting_repo_invalid' | 'hosting_github_invalid' | 'gate_needs_domain' | 'gate_key_invalid' | 'gate_key_not_unique' | 'gate_needs_hostname' | 'gate_hostname_not_entry_hostname' | 'gate_hostname_held_twice' | 'gate_hostname_locked' | 'gate_limit_exceeded' | 'wildcard_needs_domain' | 'wildcard_held_twice' | 'hostnames_need_domain' | 'hostname_needed' | 'hostname_invalid' | 'hostname_not_unique' | 'hostname_locked' | 'hosted_name_invalid' | 'hosted_name_not_unique' | 'hosted_name_worker_app' | 'entry_workspace_invalid' | 'entry_workspace_unsupported' | 'entry_live_needs_workspace' | 'entry_live_commit_invalid' | 'gate_key_taken_by_workspace' | 'gate_key_taken_by_private_page' | 'private_page_invalid' | 'private_page_path_invalid' | 'private_page_duplicate' | 'private_page_limit' | 'private_page_needs_hosting' | 'private_page_roles_invalid' | 'private_page_people_invalid' | 'entry_key_invalid' | 'entry_key_reserved' | 'entry_repo_invalid' | 'entry_folder_invalid' | 'entry_branch_empty' | 'entry_build_pack_unknown' | 'entry_build_pack_unavailable' | 'entry_port_invalid' | 'entry_health_path_invalid' | 'entry_capabilities_invalid' | 'entry_database_needs_hosting' | 'blue_green_needs_hostname' | 'env_name_empty' | 'env_name_reserved' | 'env_source_platform_mail_key' | 'env_source_unknown' | 'env_shared_secret_name_invalid' | 'env_held_secret_name_mismatch' | 'env_held_secret_name_invalid' | 'env_looks_like_secret' | 'builtin_app_invalid' | 'blue_green_not_object' | 'blue_green_steps_length' | 'blue_green_steps_range' | 'blue_green_steps_order' | 'blue_green_steps_end' | 'blue_green_hold_invalid' | 'blue_green_advance_invalid' | 'blue_green_min_requests_invalid' | 'blue_green_error_margin_invalid' | 'blue_green_staff_first_invalid' | 'volumes_not_list' | 'volumes_too_many' | 'volume_name_invalid' | 'volume_name_logs' | 'volume_name_not_unique' | 'volume_mount_path_invalid' | 'volume_mount_path_too_long' | 'volume_mount_path_log_dir' | 'volume_mount_path_forbidden' | 'volume_mount_path_not_unique' | 'auth_needs_hosting' | 'auth_users_invalid' | 'auth_registration_invalid' | 'registration_open_needs_customers' | 'auth_replaces_broker_entry_invalid' | 'auth_replaces_broker_entry_owned' | 'auth_needs_exact_hostname' | 'mcp_needs_open_customers' | 'mcp_path_invalid' | 'mcp_onboarding_path_invalid' | 'auth_sign_in_invalid' | 'email_code_needs_customers' | 'auth_room_invalid' | 'auth_room_needs_customers' | 'auth_room_not_unique' | 'client_name_taken' | 'workspaces_needs_customers' | 'workspaces_needs_server' | 'workspaces_roles_invalid' | 'workspaces_role_reserved' | 'workspaces_invite_path_invalid' | 'workspaces_env_clash' | 'ai_needs_hosting' | 'ai_offerings_invalid' | 'ai_permissions_invalid' | 'ai_webhook_path_invalid' | 'ai_webhook_needs_exact_hostname' | 'ai_env_clash' | 'files_needs_hosting' | 'files_collections_invalid' | 'files_permissions_invalid' | 'files_share_host_invalid' | 'files_share_host_needs_domain' | 'files_env_clash' | 'worker_needs_hosting' | 'worker_needs_repo' | 'worker_needs_own_repo' | 'worker_key_too_long' | 'worker_artifact_invalid' | 'worker_route_invalid' | 'worker_route_off_domain' | 'worker_rate_limit_name_missing' | 'worker_rate_limit_namespace_missing' | 'worker_rate_limit_invalid' | 'worker_rate_limit_period_invalid' | 'worker_app_invalid' | 'edge_not_true' | 'edge_needs_hosting' | 'edge_needs_domain' | 'edge_env_clash' | 'mail_not_true' | 'mail_needs_hosting' | 'mail_env_clash' | 'jobs_not_true' | 'jobs_needs_hosting' | 'jobs_env_clash' | 'customer_grants_not_true' | 'customer_grants_needs_customers' | 'customer_grants_env_clash' | 'repo_use_invalid' | 'repo_use_needs_repo' | 'repo_use_env_clash' | 'audit_not_true' | 'audit_needs_hosting' | 'audit_needs_worker_database' | 'customers_invalid' | 'customers_sign_up_needs_enabled' | 'registration_open_needs_sign_up';
|
|
304
|
+
export type OrgRefusalCode = 'hosting_tunnel_needs_hosting' | 'issuer_app_name_missing' | 'issuer_app_name_not_unique' | 'issuer_app_kind_unknown' | 'issuer_app_name_reserved_agora' | 'issuer_app_name_reserved_boule' | 'issuer_app_name_reserved_kosmos' | 'hosting_needs_entry' | 'hosting_main_count' | 'entry_key_not_unique' | 'ai_webhook_path_not_unique' | 'worker_route_not_unique' | 'database_not_unique' | 'access_hostname_not_unique' | 'access_hostname_invalid' | 'access_needs_hosting' | 'access_kind_unknown' | 'access_service_token_name_missing' | 'access_hostname_locked' | 'access_hostname_not_served' | 'gate_limit_invalid' | 'hosting_repo_invalid' | 'hosting_github_invalid' | 'gate_needs_domain' | 'gate_key_invalid' | 'gate_key_not_unique' | 'gate_needs_hostname' | 'gate_hostname_not_entry_hostname' | 'gate_hostname_held_twice' | 'gate_hostname_locked' | 'gate_limit_exceeded' | 'wildcard_needs_domain' | 'wildcard_held_twice' | 'hostnames_need_domain' | 'hostname_needed' | 'hostname_invalid' | 'hostname_not_unique' | 'hostname_locked' | 'hosted_name_invalid' | 'hosted_name_not_unique' | 'hosted_name_worker_app' | 'entry_workspace_invalid' | 'entry_workspace_unsupported' | 'entry_live_needs_workspace' | 'entry_live_commit_invalid' | 'gate_key_taken_by_workspace' | 'gate_key_taken_by_private_page' | 'private_page_invalid' | 'private_page_path_invalid' | 'private_page_duplicate' | 'private_page_limit' | 'private_page_needs_hosting' | 'private_page_roles_invalid' | 'private_page_people_invalid' | 'entry_key_invalid' | 'entry_key_reserved' | 'entry_repo_invalid' | 'entry_folder_invalid' | 'entry_branch_empty' | 'entry_build_pack_unknown' | 'entry_build_pack_unavailable' | 'entry_port_invalid' | 'entry_health_path_invalid' | 'entry_capabilities_invalid' | 'entry_database_needs_hosting' | 'blue_green_needs_hostname' | 'env_name_empty' | 'env_name_reserved' | 'env_source_platform_mail_key' | 'env_source_unknown' | 'env_shared_secret_name_invalid' | 'env_held_secret_name_mismatch' | 'env_held_secret_name_invalid' | 'env_looks_like_secret' | 'builtin_app_invalid' | 'blue_green_not_object' | 'blue_green_steps_length' | 'blue_green_steps_range' | 'blue_green_steps_order' | 'blue_green_steps_end' | 'blue_green_hold_invalid' | 'blue_green_advance_invalid' | 'blue_green_min_requests_invalid' | 'blue_green_error_margin_invalid' | 'blue_green_staff_first_invalid' | 'volumes_not_list' | 'volumes_too_many' | 'volume_name_invalid' | 'volume_name_logs' | 'volume_name_not_unique' | 'volume_mount_path_invalid' | 'volume_mount_path_too_long' | 'volume_mount_path_log_dir' | 'volume_mount_path_forbidden' | 'volume_mount_path_not_unique' | 'auth_needs_hosting' | 'auth_users_invalid' | 'auth_registration_invalid' | 'registration_open_needs_customers' | 'auth_replaces_broker_entry_invalid' | 'auth_replaces_broker_entry_owned' | 'auth_needs_exact_hostname' | 'mcp_needs_open_customers' | 'mcp_path_invalid' | 'mcp_onboarding_path_invalid' | 'auth_sign_in_invalid' | 'email_code_needs_customers' | 'auth_room_invalid' | 'auth_room_needs_customers' | 'auth_room_not_unique' | 'client_name_taken' | 'workspaces_needs_customers' | 'workspaces_needs_server' | 'workspaces_roles_invalid' | 'workspaces_role_reserved' | 'workspaces_invite_path_invalid' | 'workspaces_env_clash' | 'ai_needs_hosting' | 'ai_offerings_invalid' | 'ai_permissions_invalid' | 'ai_webhook_path_invalid' | 'ai_webhook_needs_exact_hostname' | 'ai_env_clash' | 'files_needs_hosting' | 'files_collections_invalid' | 'files_permissions_invalid' | 'files_share_host_invalid' | 'files_share_host_needs_domain' | 'files_env_clash' | 'worker_needs_hosting' | 'worker_needs_repo' | 'worker_needs_own_repo' | 'worker_key_too_long' | 'worker_artifact_invalid' | 'worker_route_invalid' | 'worker_route_off_domain' | 'worker_rate_limit_name_missing' | 'worker_rate_limit_namespace_missing' | 'worker_rate_limit_invalid' | 'worker_rate_limit_period_invalid' | 'worker_app_invalid' | 'edge_not_true' | 'edge_needs_hosting' | 'edge_needs_domain' | 'edge_env_clash' | 'mail_not_true' | 'mail_needs_hosting' | 'mail_env_clash' | 'jobs_not_true' | 'jobs_needs_hosting' | 'jobs_env_clash' | 'customer_grants_not_true' | 'customer_grants_needs_customers' | 'customer_grants_env_clash' | 'repo_use_invalid' | 'repo_use_needs_repo' | 'repo_use_env_clash' | 'audit_not_true' | 'audit_needs_hosting' | 'audit_needs_worker_database' | 'customers_invalid' | 'customers_sign_up_needs_enabled' | 'registration_open_needs_sign_up';
|
|
305
305
|
/**
|
|
306
306
|
* One refusal: a stable `code`, the dotted `path` into {@link DeclaredOrg} it is about
|
|
307
307
|
* (`hosting.entries.terminal.uses.auth.registration`) and the sentence for people.
|
|
@@ -484,6 +484,18 @@ export interface BouleApp extends OrgBuiltinApp {
|
|
|
484
484
|
}
|
|
485
485
|
/** True while the company's Boule runs apps (`org.boule.apps`). */
|
|
486
486
|
export declare const appsOn: (org: Pick<DeclaredOrg, "boule">) => boolean;
|
|
487
|
+
/**
|
|
488
|
+
* The company's kosmos: its own creative workspace (canvas, libraries, photos, pages) that drives the
|
|
489
|
+
* frontend of its apps. Unlike Boule and Agora, which every company runs, kosmos is set up only when the
|
|
490
|
+
* company asks (William, 2026-10-22) -- absent, or `enabled` absent or `false`, plans nothing for it.
|
|
491
|
+
* `enabled: true` plans its ZITADEL client, its database and its live and idle copies; turning it back off
|
|
492
|
+
* parks the app but keeps the database, the same rule every other switched-off service follows.
|
|
493
|
+
*/
|
|
494
|
+
export interface KosmosApp extends OrgBuiltinApp {
|
|
495
|
+
enabled?: boolean;
|
|
496
|
+
}
|
|
497
|
+
/** True while the company's kosmos is turned on (`org.kosmos.enabled`). */
|
|
498
|
+
export declare const kosmosOn: (org: Pick<DeclaredOrg, "kosmos">) => boolean;
|
|
487
499
|
/** The traffic steps (percent to the new color) and the hold at each step. Both default in `plan/blue-green.ts`. */
|
|
488
500
|
export interface BlueGreenConfig {
|
|
489
501
|
/** Ascending, each 1..100, ending at 100. */
|
|
@@ -798,8 +810,13 @@ export declare function refuseHosting(hosting: HostingConfig, names?: HostingNam
|
|
|
798
810
|
export declare function companyGithubFor(hosting: Pick<HostingConfig, 'github'>, repo: string | null): HostingGithub | undefined;
|
|
799
811
|
/** `blueGreen`: steps ascending, each a whole number 1..100, the last 100; a hold of 1..1440 minutes. */
|
|
800
812
|
export declare function validateBlueGreen(value: unknown, at: string): string | null;
|
|
801
|
-
/**
|
|
802
|
-
|
|
813
|
+
/**
|
|
814
|
+
* {@link refuseBlueGreen} for the `blueGreen` of the company's Boule, Agora and kosmos; the same codes.
|
|
815
|
+
* Also refuses an `issuerApps` entry named `kosmos` while kosmos is on -- the same reservation
|
|
816
|
+
* {@link refuseHosting}'s own loop already gives Agora and Boule, but kosmos is opt-in, so it is checked here
|
|
817
|
+
* instead, the same way {@link validateSite} reserves `cms` only while `site.enabled` is true.
|
|
818
|
+
*/
|
|
819
|
+
export declare function refuseBuiltinBlueGreen(org: Pick<DeclaredOrg, 'boule' | 'agora' | 'kosmos' | 'hosting'>): OrgRefusal | null;
|
|
803
820
|
/** The env names `uses.repo` writes; `env` may not name them. */
|
|
804
821
|
export declare const REPO_ENTRY_ENV_NAMES: readonly ["GIT_REPO_URL", "GIT_SSH_KEY"];
|
|
805
822
|
/** The env name `uses.customerGrants` writes; `env` may not name it. */
|
|
@@ -1226,6 +1243,8 @@ export interface DeclaredOrg {
|
|
|
1226
1243
|
boule?: BouleApp;
|
|
1227
1244
|
/** The company's Agora, a static app: its release moves on hard fails and time only. Absent means no blue/green. */
|
|
1228
1245
|
agora?: OrgBuiltinApp;
|
|
1246
|
+
/** The company's kosmos. Absent or `enabled: false` means it is not set up (2026-10-22). */
|
|
1247
|
+
kosmos?: KosmosApp;
|
|
1229
1248
|
/** Absent means `{ enabled: false, signUp: false }`. Optional so every existing literal and fixture compiles unchanged. */
|
|
1230
1249
|
customers?: CustomersConfig;
|
|
1231
1250
|
/** Parts of this company held on their own version. Absent or null follows the platform. Ignored for the owner company. */
|
package/dist/org.js
CHANGED
|
@@ -33,6 +33,8 @@ export function normalizeTesters(testers) {
|
|
|
33
33
|
}
|
|
34
34
|
/** True while the company's Boule runs apps (`org.boule.apps`). */
|
|
35
35
|
export const appsOn = (org) => org.boule?.apps === true;
|
|
36
|
+
/** True while the company's kosmos is turned on (`org.kosmos.enabled`). */
|
|
37
|
+
export const kosmosOn = (org) => org.kosmos?.enabled === true;
|
|
36
38
|
/** The most volumes one entry may declare. */
|
|
37
39
|
export const MAX_ENTRY_VOLUMES = 8;
|
|
38
40
|
/** The env names `uses.auth.workspaces` writes; `env` may not name them. */
|
|
@@ -763,9 +765,14 @@ function refuseBlueGreen(value, at, path) {
|
|
|
763
765
|
}
|
|
764
766
|
return null;
|
|
765
767
|
}
|
|
766
|
-
/**
|
|
768
|
+
/**
|
|
769
|
+
* {@link refuseBlueGreen} for the `blueGreen` of the company's Boule, Agora and kosmos; the same codes.
|
|
770
|
+
* Also refuses an `issuerApps` entry named `kosmos` while kosmos is on -- the same reservation
|
|
771
|
+
* {@link refuseHosting}'s own loop already gives Agora and Boule, but kosmos is opt-in, so it is checked here
|
|
772
|
+
* instead, the same way {@link validateSite} reserves `cms` only while `site.enabled` is true.
|
|
773
|
+
*/
|
|
767
774
|
export function refuseBuiltinBlueGreen(org) {
|
|
768
|
-
for (const app of ['boule', 'agora']) {
|
|
775
|
+
for (const app of ['boule', 'agora', 'kosmos']) {
|
|
769
776
|
const value = org[app];
|
|
770
777
|
if (value === undefined)
|
|
771
778
|
continue;
|
|
@@ -781,6 +788,15 @@ export function refuseBuiltinBlueGreen(org) {
|
|
|
781
788
|
? 'boule.apps must be true or false'
|
|
782
789
|
: `${app} has no apps switch: only the company's Boule runs apps`);
|
|
783
790
|
}
|
|
791
|
+
const enabled = value.enabled;
|
|
792
|
+
if (enabled !== undefined && (app !== 'kosmos' || typeof enabled !== 'boolean')) {
|
|
793
|
+
return refusal('builtin_app_invalid', `${app}.enabled`, app === 'kosmos'
|
|
794
|
+
? 'kosmos.enabled must be true or false'
|
|
795
|
+
: `${app} has no enabled switch: it is always on`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (kosmosOn(org) && org.hosting.issuerApps.some((app) => app.name === 'kosmos')) {
|
|
799
|
+
return refusal('issuer_app_name_reserved_kosmos', 'hosting.issuerApps.kosmos.name', "kosmos.enabled reserves the issuerApps name 'kosmos'");
|
|
784
800
|
}
|
|
785
801
|
return null;
|
|
786
802
|
}
|
|
@@ -899,6 +915,7 @@ function validateEntryAuth(entry, hosting, domain) {
|
|
|
899
915
|
if (entry.key === 'agora' ||
|
|
900
916
|
entry.key === 'boule' ||
|
|
901
917
|
entry.key === 'cms' ||
|
|
918
|
+
entry.key === 'kosmos' ||
|
|
902
919
|
hosting.issuerApps.some((app) => app.name === entry.key)) {
|
|
903
920
|
return refusal('client_name_taken', `hosting.entries.${entry.key}.key`, `${at} client name '${entry.key}' is already held by another ZITADEL client`);
|
|
904
921
|
}
|
|
@@ -1881,6 +1898,8 @@ function orgServiceSchema(service, schema, legacyKey, split = true) {
|
|
|
1881
1898
|
export const ORG_SERVICE_SCHEMAS = [
|
|
1882
1899
|
orgServiceSchema('Boule', 'boule', () => 'coolify_boule_database'),
|
|
1883
1900
|
orgServiceSchema('CMS', 'cms', () => 'coolify_cms_database'),
|
|
1901
|
+
// kosmos, like CMS, sits behind its own feature switch (`KosmosApp.enabled`); see `orgServiceSchemaEnabled`.
|
|
1902
|
+
orgServiceSchema('kosmos', 'kosmos', () => 'coolify_kosmos_database'),
|
|
1884
1903
|
orgServiceSchema("chat's Synapse homeserver", 'chat_synapse', () => 'coolify_chat_synapse_database', false),
|
|
1885
1904
|
orgServiceSchema("chat's matrix-authentication-service", 'chat_mas', () => 'coolify_chat_mas_database', false),
|
|
1886
1905
|
orgServiceSchema("chat's own service", 'chat_service', () => 'coolify_chat_service_database'),
|
|
@@ -1914,6 +1933,10 @@ export function orgServiceDatabase(org, home) {
|
|
|
1914
1933
|
database = underscored(`${org.slug}-cms-db`);
|
|
1915
1934
|
runtimeRole = 'cms_rt';
|
|
1916
1935
|
break;
|
|
1936
|
+
case 'kosmos':
|
|
1937
|
+
database = underscored(`${org.slug}-kosmos-db`);
|
|
1938
|
+
runtimeRole = 'kosmos_rt';
|
|
1939
|
+
break;
|
|
1917
1940
|
case 'chat_service':
|
|
1918
1941
|
database = 'chat';
|
|
1919
1942
|
break;
|
package/dist/plan/agora.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type DeclaredOrg, type OrgId } from '../org.js';
|
|
2
2
|
/**
|
|
3
3
|
* #164: what both the planner (`diff.ts`) and the executor (`executor.ts`)
|
|
4
4
|
* need to agree on for an org's Agora -- the hostname, the values the host
|
package/dist/plan/agora.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { kosmosOn } from '../org.js';
|
|
1
2
|
import { chatUpstreams } from './chat-matrix.js';
|
|
2
3
|
import { FILES_CORE_COLLECTION_SCOPE_KEY, ZITADEL_FILES_PROJECT_SCOPE_KEY } from './core-files.js';
|
|
4
|
+
import { kosmosHostnameFor } from './kosmos.js';
|
|
3
5
|
import { SIGN_IN_UPSTREAM_ENV, inAppHostApplied, signInUpstream, staleSignInUpstream, } from './sign-in.js';
|
|
4
6
|
/**
|
|
5
7
|
* #164: what both the planner (`diff.ts`) and the executor (`executor.ts`)
|
|
@@ -307,6 +309,8 @@ export function desiredAgoraEnv(org, recorded, rawHost) {
|
|
|
307
309
|
chat: { enabled: org.chat.enabled },
|
|
308
310
|
// Agora draws call buttons only when this is true.
|
|
309
311
|
calls: { enabled: org.calls.enabled },
|
|
312
|
+
// foundry#949: kosmos is opt-in, so Agora is told both whether it is on and where it lives.
|
|
313
|
+
kosmos: { enabled: kosmosOn(org), url: `https://${kosmosHostnameFor(org)}` },
|
|
310
314
|
// In-app sign-in: Agora reads this at runtime (one image serves every company). Set with `SIGNIN_UPSTREAM`
|
|
311
315
|
// only, so Agora never shows the in-app screen before the login can serve it.
|
|
312
316
|
...(inAppHostApplied(org, recorded, agoraHostnameFor(org)) ? { signInInApp: true } : {}),
|
|
@@ -2,6 +2,7 @@ import { type BlueGreenApplyDeps } from './blue-green-apply.js';
|
|
|
2
2
|
import { type BlueGreenSubject } from './blue-green.js';
|
|
3
3
|
import type { DeployHealthRuntime } from './deploy-health.js';
|
|
4
4
|
import type { PlatformTunnelPort } from './platform-providers.js';
|
|
5
|
+
import type { StageGuardDeps } from './stage-guards.js';
|
|
5
6
|
export interface ArchonBlueGreenRun {
|
|
6
7
|
subject: BlueGreenSubject;
|
|
7
8
|
/** The owner company's id: the health token's held ref is `sharedSecretRef(ownerOrgId, ...)` like an entry's. */
|
|
@@ -42,6 +43,10 @@ export interface ArchonBlueGreenRun {
|
|
|
42
43
|
sleep: (ms: number) => Promise<void>;
|
|
43
44
|
logDir: string;
|
|
44
45
|
isRunning: (status: string | undefined) => boolean;
|
|
46
|
+
/** Stage's probe port and Access gate, the same guards an entry's stage gets. Absent: the guards read "not run". */
|
|
47
|
+
stageGuards?: StageGuardDeps;
|
|
48
|
+
/** The guard names the plan counted (`desired.guards`); they go into the applied hash. */
|
|
49
|
+
guardInputs?: readonly string[];
|
|
45
50
|
}
|
|
46
51
|
/**
|
|
47
52
|
* platform-archon on the blue/green path (`platform.archon.blueGreen`): the archon app keeps its name and is
|
|
@@ -70,6 +70,8 @@ export async function applyArchonBlueGreen(run) {
|
|
|
70
70
|
generate: () => randomBytes(32).toString('hex'),
|
|
71
71
|
secret: run.secret,
|
|
72
72
|
}),
|
|
73
|
+
...(run.stageGuards ? { stageGuards: run.stageGuards } : {}),
|
|
74
|
+
...(run.guardInputs ? { guardInputs: run.guardInputs } : {}),
|
|
73
75
|
actionsKey: async () => {
|
|
74
76
|
const key = await mintActionsKey({
|
|
75
77
|
id,
|
|
@@ -54,6 +54,11 @@ export interface BlueGreenApplyDeps {
|
|
|
54
54
|
* guard unrecorded ("not run", never passed), so `stageGuardsReady` stays false and the copy does not start.
|
|
55
55
|
*/
|
|
56
56
|
stageGuards?: StageGuardDeps;
|
|
57
|
+
/**
|
|
58
|
+
* The guard names the plan counted ({@link stageGuardInputs}, from `desired.guards`). They go into the
|
|
59
|
+
* applied structure hash, so the next plan sees the same hash until a guard becomes possible.
|
|
60
|
+
*/
|
|
61
|
+
guardInputs?: readonly string[];
|
|
57
62
|
/**
|
|
58
63
|
* The company the stage mail sink belongs to (its name is `<slug>-stage-sink`). Absent: no sink is made and
|
|
59
64
|
* stage holds no mail env at all, so the copy stays refused.
|