@wtfalch/foundry 0.60.0 → 0.61.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/apps/cloudflare.d.ts +173 -0
- package/dist/apps/cloudflare.js +17 -0
- package/dist/apps/deploy.d.ts +116 -0
- package/dist/apps/deploy.js +791 -0
- package/dist/apps/github.d.ts +47 -0
- package/dist/apps/github.js +201 -0
- package/dist/apps/index.d.ts +9 -0
- package/dist/apps/index.js +9 -0
- package/dist/apps/migrations.d.ts +110 -0
- package/dist/apps/migrations.js +458 -0
- package/dist/audit/connector.d.ts +43 -0
- package/dist/audit/connector.js +22 -0
- package/dist/audit/evidence.d.ts +49 -0
- package/dist/audit/evidence.js +56 -0
- package/dist/audit/index.d.ts +9 -0
- package/dist/audit/index.js +9 -0
- package/dist/audit/migrations.d.ts +5 -0
- package/dist/audit/migrations.js +5 -0
- package/dist/audit/registry.d.ts +49 -0
- package/dist/audit/registry.js +44 -0
- package/dist/audit/seal.d.ts +49 -0
- package/dist/audit/seal.js +131 -0
- package/dist/audit/sealer-role.d.ts +33 -0
- package/dist/audit/sealer-role.js +56 -0
- package/dist/audit/signer.d.ts +40 -0
- package/dist/audit/signer.js +78 -0
- package/dist/audit/tick.d.ts +36 -0
- package/dist/audit/tick.js +87 -0
- package/dist/catalogue/copy-en.d.ts +1 -0
- package/dist/catalogue/copy-en.js +129 -0
- package/dist/catalogue/index.d.ts +9 -0
- package/dist/catalogue/index.js +21 -0
- package/dist/catalogue/integrations.d.ts +4 -0
- package/dist/catalogue/integrations.js +104 -0
- package/dist/catalogue/systems.d.ts +7 -0
- package/dist/catalogue/systems.js +31 -0
- package/dist/catalogue/types.d.ts +110 -0
- package/dist/catalogue/types.js +1 -0
- package/dist/entry-rules.d.ts +53 -0
- package/dist/entry-rules.js +71 -0
- package/dist/hosted-label.d.ts +13 -0
- package/dist/hosted-label.js +81 -0
- package/dist/index.d.ts +10 -5
- package/dist/index.js +5 -3
- package/dist/migrations/0062_hosting_github.sql +6 -0
- package/dist/migrations/0063_boule_apps.sql +7 -0
- package/dist/migrations/0064_requested_changes_boule_apps.sql +6 -0
- package/dist/modules/ai.d.ts +3 -0
- package/dist/modules/ai.js +64 -0
- package/dist/modules/assistants.d.ts +3 -0
- package/dist/modules/assistants.js +72 -0
- package/dist/modules/audit-ledger.d.ts +29 -0
- package/dist/modules/audit-ledger.js +49 -0
- package/dist/modules/audit.d.ts +7 -0
- package/dist/modules/audit.js +44 -0
- package/dist/modules/database.d.ts +23 -0
- package/dist/modules/database.js +189 -0
- package/dist/modules/email.d.ts +3 -0
- package/dist/modules/email.js +18 -0
- package/dist/modules/entry-parts.d.ts +23 -0
- package/dist/modules/entry-parts.js +63 -0
- package/dist/modules/files.d.ts +3 -0
- package/dist/modules/files.js +64 -0
- package/dist/modules/index.d.ts +74 -0
- package/dist/modules/index.js +774 -0
- package/dist/modules/jobs.d.ts +3 -0
- package/dist/modules/jobs.js +23 -0
- package/dist/modules/organisations.d.ts +3 -0
- package/dist/modules/organisations.js +20 -0
- package/dist/modules/outputs.d.ts +9 -0
- package/dist/modules/outputs.js +38 -0
- package/dist/modules/sign-in.d.ts +3 -0
- package/dist/modules/sign-in.js +197 -0
- package/dist/modules/signed-records.d.ts +3 -0
- package/dist/modules/signed-records.js +32 -0
- package/dist/modules/types.d.ts +194 -0
- package/dist/modules/types.js +1 -0
- package/dist/modules/workspaces.d.ts +3 -0
- package/dist/modules/workspaces.js +75 -0
- package/dist/org.d.ts +163 -22
- package/dist/org.js +419 -47
- package/dist/plan/apps-db-route.d.ts +90 -0
- package/dist/plan/apps-db-route.js +168 -0
- package/dist/plan/apps-zones.d.ts +56 -0
- package/dist/plan/apps-zones.js +118 -0
- package/dist/plan/archon-blue-green.js +11 -1
- package/dist/plan/blue-green-apply.d.ts +37 -1
- package/dist/plan/blue-green-apply.js +55 -15
- package/dist/plan/blue-green.d.ts +41 -1
- package/dist/plan/blue-green.js +80 -2
- package/dist/plan/boule.d.ts +23 -1
- package/dist/plan/boule.js +35 -2
- package/dist/plan/broker-entries.d.ts +28 -3
- package/dist/plan/broker-entries.js +74 -3
- package/dist/plan/broker-registrar.d.ts +168 -0
- package/dist/plan/broker-registrar.js +440 -0
- package/dist/plan/broker-workspaces-database.d.ts +48 -0
- package/dist/plan/broker-workspaces-database.js +87 -0
- package/dist/plan/desktop-callback.js +12 -7
- package/dist/plan/diff.d.ts +8 -2
- package/dist/plan/diff.js +373 -42
- package/dist/plan/edge-access.d.ts +6 -0
- package/dist/plan/edge-dns.d.ts +6 -0
- package/dist/plan/edge-dns.js +9 -7
- package/dist/plan/entry-ai-key.d.ts +1 -1
- package/dist/plan/entry-ai-key.js +2 -2
- package/dist/plan/entry-audit.d.ts +58 -0
- package/dist/plan/entry-audit.js +122 -0
- package/dist/plan/entry-customer-grants.d.ts +75 -0
- package/dist/plan/entry-customer-grants.js +154 -0
- package/dist/plan/entry-edge-key.d.ts +26 -1
- package/dist/plan/entry-edge-key.js +42 -3
- package/dist/plan/entry-jobs.d.ts +64 -0
- package/dist/plan/entry-jobs.js +92 -0
- package/dist/plan/entry-repo-key-apply.d.ts +55 -0
- package/dist/plan/entry-repo-key-apply.js +185 -0
- package/dist/plan/entry-repo-key.d.ts +92 -0
- package/dist/plan/entry-repo-key.js +235 -0
- package/dist/plan/entry-workspaces.d.ts +55 -0
- package/dist/plan/entry-workspaces.js +90 -0
- package/dist/plan/executor.d.ts +191 -1
- package/dist/plan/executor.js +1505 -90
- package/dist/plan/gates.d.ts +1 -1
- package/dist/plan/gates.js +62 -9
- package/dist/plan/github-deploy-keys.d.ts +78 -0
- package/dist/plan/github-deploy-keys.js +305 -0
- package/dist/plan/github-source.d.ts +53 -0
- package/dist/plan/github-source.js +140 -0
- package/dist/plan/held-key-refs.d.ts +12 -0
- package/dist/plan/held-key-refs.js +12 -0
- package/dist/plan/hostnames.d.ts +9 -0
- package/dist/plan/hostnames.js +28 -1
- package/dist/plan/index.d.ts +9 -0
- package/dist/plan/index.js +6 -0
- package/dist/plan/jobs-scheduler-http.js +3 -0
- package/dist/plan/keys-registrar-http.d.ts +32 -0
- package/dist/plan/keys-registrar-http.js +105 -0
- package/dist/plan/keys-registrar.d.ts +67 -0
- package/dist/plan/keys-registrar.js +103 -0
- package/dist/plan/live.d.ts +1 -1
- package/dist/plan/locked-hostnames.d.ts +2 -2
- package/dist/plan/locked-hostnames.js +2 -2
- package/dist/plan/login-host.js +11 -5
- package/dist/plan/machine-paths.d.ts +36 -0
- package/dist/plan/machine-paths.js +137 -0
- package/dist/plan/mcp.d.ts +1 -1
- package/dist/plan/mcp.js +49 -17
- package/dist/plan/org-apps-admin.d.ts +108 -0
- package/dist/plan/org-apps-admin.js +434 -0
- package/dist/plan/org-names.js +12 -1
- package/dist/plan/own-diff.d.ts +23 -0
- package/dist/plan/own-diff.js +53 -0
- package/dist/plan/own-hostnames.d.ts +2 -0
- package/dist/plan/own-hostnames.js +1 -0
- package/dist/plan/own-jobs-route.d.ts +47 -0
- package/dist/plan/own-jobs-route.js +91 -0
- package/dist/plan/own-services.d.ts +4 -0
- package/dist/plan/own-services.js +4 -0
- package/dist/plan/plan-apply.d.ts +39 -0
- package/dist/plan/plan-apply.js +163 -9
- package/dist/plan/platform-diff.d.ts +2 -2
- package/dist/plan/platform-diff.js +7 -2
- package/dist/plan/platform-org.d.ts +30 -0
- package/dist/plan/platform-org.js +12 -1
- package/dist/plan/platform-providers.d.ts +2 -0
- package/dist/plan/platform-providers.js +7 -2
- package/dist/plan/post-logout-uris.js +7 -1
- package/dist/plan/private-pages.d.ts +26 -0
- package/dist/plan/private-pages.js +39 -0
- package/dist/plan/proxy-labels.d.ts +12 -1
- package/dist/plan/proxy-labels.js +32 -2
- package/dist/plan/release-host.d.ts +6 -2
- package/dist/plan/release-host.js +36 -8
- package/dist/plan/stage-env.d.ts +19 -1
- package/dist/plan/stage-env.js +25 -1
- package/dist/plan/stage-guards.d.ts +20 -0
- package/dist/plan/stage-guards.js +24 -0
- package/dist/plan/stage-isolation.d.ts +24 -0
- package/dist/plan/stage-isolation.js +29 -1
- package/dist/plan/worker-app-database.d.ts +46 -42
- package/dist/plan/worker-app-database.js +150 -153
- package/dist/plan/worker-app.d.ts +5 -65
- package/dist/plan/worker-app.js +28 -105
- package/dist/plan/worker.d.ts +2 -2
- package/dist/plan/worker.js +2 -1
- package/dist/plan/workspace-client.d.ts +25 -0
- package/dist/plan/workspace-client.js +48 -0
- package/dist/plan/workspace-sign-in.d.ts +55 -0
- package/dist/plan/workspace-sign-in.js +273 -0
- package/dist/plan/workspace-worker.d.ts +4 -0
- package/dist/plan/workspace-worker.js +13 -0
- package/dist/plan/workspace.d.ts +101 -0
- package/dist/plan/workspace.js +305 -0
- package/dist/release/decide.d.ts +30 -1
- package/dist/release/decide.js +73 -11
- package/dist/release/health.d.ts +8 -3
- package/dist/release/health.js +58 -5
- package/dist/release/run.d.ts +2 -5
- package/dist/release/run.js +4 -0
- package/dist/release/state.d.ts +3 -1
- package/dist/release/state.js +6 -0
- package/dist/release/types.d.ts +20 -0
- package/dist/requested-changes.d.ts +11 -2
- package/dist/requested-changes.js +11 -0
- package/dist/service-labels.d.ts +4 -0
- package/dist/service-labels.js +11 -0
- package/dist/slug.d.ts +2 -0
- package/dist/slug.js +10 -6
- package/dist/store/postgres.js +12 -2
- package/dist/store/schema.d.ts +37 -1
- package/dist/store/schema.js +4 -0
- package/dist/worker-app-config.d.ts +1 -11
- package/dist/worker-app-config.js +4 -17
- package/dist/worker-app-migrations.d.ts +105 -0
- package/dist/worker-app-migrations.js +166 -0
- package/dist/worker-app-upload.d.ts +103 -0
- package/dist/worker-app-upload.js +128 -0
- package/package.json +18 -1
package/dist/plan/diff.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { VERSIONS, floatingTagBlocked } from '../bootstrap/versions.js';
|
|
2
|
-
import { APP_ENTRY_KEY, ORG_POSTGRES_SERVER_SCOPE_KEY, ORG_SERVICE_SCHEMAS, heldEnvRef, mainEntry, ownerMailboxRecorded, ownerMailboxes, ownerNameProblem, ownsServices, } from '../org.js';
|
|
2
|
+
import { APP_ENTRY_KEY, ORG_POSTGRES_SERVER_SCOPE_KEY, ORG_SERVICE_SCHEMAS, appsOn, companyGithubFor, heldEnvRef, mainEntry, ownerMailboxRecorded, ownerMailboxes, ownerNameProblem, ownsServices, } from '../org.js';
|
|
3
3
|
import { entryHasDatabase } from '../worker-app-config.js';
|
|
4
|
-
import { AGORA_ACTIVATED_ENV_SCOPE_KEY, AGORA_APP_PORTS_EXPOSES, CALLS_INTROSPECTION_SCOPE_KEY, CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY, CALLS_SERVICE_DEPLOYED_SCOPE_KEY, CHAT_CALLS_APPLIED_SCOPE_KEY, CHAT_CALLS_TOKEN_SCOPE_KEY, CHAT_CALLS_TOKEN_UNDELIVERED_SCOPE_KEY, CHAT_INTROSPECTION_SCOPE_KEY, NO_AGORA_HOST_VALUES, ZITADEL_ORG_SCOPE_KEY, agoraEnvDrift, agoraEnvRevision, agoraHostMissing, agoraHostnameFor, callsServiceHasName, callsServiceHasTokenEnv, callsTokenEnvMovedMessage, callsTokenHostFrom, chatCallsApplied, chatHasUpstreams, desiredAgoraEnv, withRecordedMessagingProject, } from './agora.js';
|
|
5
|
-
import {
|
|
4
|
+
import { AGORA_ACTIVATED_ENV_SCOPE_KEY, AGORA_APP_PORTS_EXPOSES, CALLS_INTROSPECTION_SCOPE_KEY, CALLS_INTROSPECTION_UNDELIVERED_SCOPE_KEY, CALLS_SERVICE_DEPLOYED_SCOPE_KEY, CHAT_CALLS_APPLIED_SCOPE_KEY, CHAT_CALLS_TOKEN_SCOPE_KEY, CHAT_CALLS_TOKEN_UNDELIVERED_SCOPE_KEY, CHAT_INTROSPECTION_SCOPE_KEY, NO_AGORA_HOST_VALUES, ZITADEL_ORG_SCOPE_KEY, agoraEnvDrift, agoraEnvRevision, agoraHostMissing, agoraHostnameFor, callsServiceHasName, callsServiceHasTokenEnv, callsTokenEnvMovedMessage, callsTokenHostFrom, chatCallsApplied, chatHasUpstreams, desiredAgoraEnv, entryClientIdScopeKey, withRecordedMessagingProject, } from './agora.js';
|
|
5
|
+
import { planAppsDbRoute } from './apps-db-route.js';
|
|
6
|
+
import { planAppsZones } from './apps-zones.js';
|
|
7
|
+
import { bgKey, planBlueGreen, } from './blue-green.js';
|
|
6
8
|
import { BOULE_ARCHON_CREDENTIAL_ENV, BOULE_BRIDGE_SECRET_ENV, BOULE_BROKER_ENTRY_KEY, BOULE_CUSTOMERS_CREDENTIAL_ENV, BOULE_MAIL_TOKEN_ENV, BOULE_OWNER_SEED_SECRET_ENV, BOULE_PEER_PASSWORD_ENV, BOULE_SECRETS_RESTARTED_SCOPE_KEY, CUSTOMERS_ROOM_SCOPE_KEY, CUSTOMERS_SIGN_IN_SCOPE_KEY, CUSTOMERS_SIGN_UP_SCOPE_KEY, archonCredentialRenewDue, bouleEnvDrift, bouleMailTokenDrift, bouleMailWanted, customersOrgIdOf, customersRoomValue, desiredBouleEnv, planBouleOwners, staleCustomersEnv, wantedBouleBrokerEntry, } from './boule.js';
|
|
7
|
-
import { brokerEntryAppliedScopeKey, brokerEntryWarningsScopeKey, entryAuthEnv, entryAuthEnvAppliedScopeKey, wantedBrokerEntry, } from './broker-entries.js';
|
|
9
|
+
import { brokerEntryAppliedScopeKey, brokerEntryWarningsScopeKey, entryAuthEnv, entryAuthEnvAppliedScopeKey, entryMcpProjectScopeKey, wantedBrokerEntry, } from './broker-entries.js';
|
|
10
|
+
import { bouleRegistrarDrift, planBrokerRegistrar } from './broker-registrar.js';
|
|
8
11
|
import { chatInternalNames, chatUpstreams } from './chat-matrix.js';
|
|
9
12
|
import { planCompanyBackups } from './company-backups.js';
|
|
10
13
|
import { COOKIE_DOMAIN_WAITING_REASONS, LOGIN_COOKIE_DOMAIN_SCOPE_KEY, MIN_AUTH_FOR_CLIENT_ORGS, authAtLeast, cookieDomainAllowed, cookieDomainWaitingLine, } from './cookie-domain.js';
|
|
@@ -12,34 +15,48 @@ import { DESKTOP_AUTH_HOME, planDesktopAuth } from './desktop-auth.js';
|
|
|
12
15
|
import { planDesktopCallbacks } from './desktop-callback.js';
|
|
13
16
|
import { onPlatformNetwork } from './destinations.js';
|
|
14
17
|
import { planEntryAiKeys } from './entry-ai-key.js';
|
|
15
|
-
import {
|
|
18
|
+
import { planEntryAudit } from './entry-audit.js';
|
|
19
|
+
import { CUSTOMER_GRANTS_ENV, CUSTOMER_GRANTS_WRITTEN_SCOPE_KEY, customerGrantsEntries, customerGrantsShapeMatches, customerGrantsToDrop, wantedCustomerGrants, } from './entry-customer-grants.js';
|
|
20
|
+
import { planEdgeReservedLabels, planEdgeSlug, planEntryEdgeKeys } from './entry-edge-key.js';
|
|
16
21
|
import { planFilesKeys } from './entry-files-key.js';
|
|
17
|
-
import {
|
|
22
|
+
import { planEntryJobs } from './entry-jobs.js';
|
|
23
|
+
import { planEntryRepoKeys } from './entry-repo-key.js';
|
|
24
|
+
import { BROKER_WORKSPACES_DATABASE_ENV, workspacesDatabaseRef } from './entry-workspaces.js';
|
|
25
|
+
import { gateIdRecordKey, hasWildcardGate, planGates } from './gates.js';
|
|
26
|
+
import { githubSourceResourceKey, planGithubSource } from './github-source.js';
|
|
18
27
|
import { appHostname, tunnelDnsHostnames } from './hostnames.js';
|
|
28
|
+
import { planKeysRegistrar } from './keys-registrar.js';
|
|
19
29
|
import { APP_GONE_KIND } from './live.js';
|
|
20
30
|
import { lockedHostnames } from './locked-hostnames.js';
|
|
21
31
|
import { LOGIN_BINDING_SCOPE_KEY, LOGIN_CLIENT_ORGS_SCOPE_KEY, OWN_CUSTOMERS_BASE_URI_SCOPE_KEY, agoraIssuerHostFor, clientOrganizationsState, companyLoginTunnelHost, customersClientOrganizations, inAppApps, loginBindingState, loginHostClientIds, loginHostServesAgora, loginHostnameFor, ownAuthApplied, ownCustomersAppState, pendingRemovalHosts, } from './login-host.js';
|
|
32
|
+
import { planMachineBypasses } from './machine-paths.js';
|
|
22
33
|
import { MAIL_COPY_COMPLETED_SCOPE_KEY, MAIL_COPY_DONE_SCOPE_KEY, parseMailCopyCompleted, } from './mail-copy.js';
|
|
23
34
|
import { mailStoreSwitchChange } from './mail-store.js';
|
|
24
35
|
import { planMcp } from './mcp.js';
|
|
25
36
|
import { hasAdoptedResources, networkMoveNeeded, planNetworkMove } from './network-move.js';
|
|
37
|
+
import { planAppsAdmin } from './org-apps-admin.js';
|
|
26
38
|
import { planOrgPostgresMigration } from './org-postgres-migrate.js';
|
|
27
39
|
import { legacyUnmigrated, orgPostgresUndeliveredScopeKey, planOrgPostgresSteps, serviceOnServer, } from './org-postgres.js';
|
|
28
|
-
import { OWN_AUTH_BLOCKED_KEY, OWN_FILES_BLOCKED_KEY, OWN_FILES_R2_BLOCKED_KEY, OWN_JOBS_BLOCKED_KEY, OWN_MAIL_BLOCKED_KEY, OWN_SFU_BLOCKED_KEY, OWN_SFU_FIREWALL_KEY, OWN_SIGNIN_DB_MOVE_BLOCKED_KEY, planOwnAuth, planOwnFiles, planOwnFilesR2, planOwnJobs, planOwnMail, planOwnSfu, planSigninDbMove, } from './own-diff.js';
|
|
40
|
+
import { OWN_AUTH_BLOCKED_KEY, OWN_FILES_BLOCKED_KEY, OWN_FILES_R2_BLOCKED_KEY, OWN_JOBS_BLOCKED_KEY, OWN_JOBS_ROUTE_BLOCKED_KEY, OWN_MAIL_BLOCKED_KEY, OWN_SFU_BLOCKED_KEY, OWN_SFU_FIREWALL_KEY, OWN_SIGNIN_DB_MOVE_BLOCKED_KEY, planOwnAuth, planOwnFiles, planOwnFilesR2, planOwnJobs, planOwnJobsRoute, planOwnMail, planOwnSfu, planSigninDbMove, } from './own-diff.js';
|
|
29
41
|
import { ownDnsHostnames } from './own-dns.js';
|
|
30
42
|
import { ownHostnames } from './own-hostnames.js';
|
|
43
|
+
import { platformMailMxHost } from './platform-diff.js';
|
|
31
44
|
import { CALLS_MAS_APPLIED_IMAGE_TAG_SCOPE_KEY, CALLS_SYNAPSE_APPLIED_IMAGE_TAG_SCOPE_KEY, ORG_MAIL_APPLIED_APEX_SCOPE_KEY, ORG_MAIL_APPLIED_HOST_SCOPE_KEY, ORG_MAIL_APPLIED_MODE_SCOPE_KEY, ORG_MAIL_APPLIED_USES_DOMAIN_SCOPE_KEY, PLATFORM_AUTH_MCP_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, appEnvAppliedScopeKey, appFolderDiffers, appSecretAvailability, appSettingsOf, appStepKey, appliedAppDomains, appliedAppSettings, dbEnvAppliedScopeKey, resolveAppEnv, serviceDbEnvAppliedScopeKey, versionNotAppliedLine, zitadelAppIdTokenUserinfoAppliedScopeKey, } from './platform-diff.js';
|
|
32
45
|
import { DEFAULT_ORG_PLATFORM_READINESS, } from './platform-org.js';
|
|
33
46
|
import { PLATFORM_AUTH_BROKER_MAIL_KEY_REF } from './platform-providers.js';
|
|
34
47
|
import { companyAppPostLogoutUris, postLogoutUpdateOwed, zitadelAppPostLogoutAppliedScopeKey, } from './post-logout-uris.js';
|
|
35
|
-
import { CLOUDFLARE_TUNNEL_ENTRY_ROUTES_SCOPE_KEY, CLOUDFLARE_TUNNEL_OIKOS_HOSTNAMES_SCOPE_KEY, appLabelsAppliedScopeKey, entryLabelHash, entryLabelSpec, entryOikosHostnames, entryRoutes, entryRoutesMarker, } from './proxy-labels.js';
|
|
48
|
+
import { CLOUDFLARE_TUNNEL_APPS_HOSTNAMES_SCOPE_KEY, CLOUDFLARE_TUNNEL_ENTRY_ROUTES_SCOPE_KEY, CLOUDFLARE_TUNNEL_OIKOS_HOSTNAMES_SCOPE_KEY, appLabelsAppliedScopeKey, entryAppsHostnames, entryLabelHash, entryLabelSpec, entryOikosHostnames, entryRoutes, entryRoutesMarker, } from './proxy-labels.js';
|
|
36
49
|
import { planRemoteDatabase } from './remote-database.js';
|
|
37
50
|
import { ZITADEL_ORG_DOMAIN_BLOCKED_SCOPE_KEY, ZITADEL_ORG_DOMAIN_SCOPE_KEY, ZITADEL_ORG_REGISTRATION_CLOSED_SCOPE_KEY, signInDomainBlockedText, signInOrgDomain, } from './sign-in-org.js';
|
|
38
|
-
import { SIGN_IN_BASE_PATH, SIGN_IN_IMAGE_TAG_SUFFIX, SIGN_IN_IN_APP_SCOPE_KEY, SIGN_IN_LOGIN_APP_SCOPE_KEY, SIGN_IN_LOGIN_INTERNAL_NAME, SIGN_IN_RATE_LIMIT_SCOPE_KEY, SIGN_IN_TRUSTED_SCOPE_KEY, inAppSignIn, inAppStatePairs, inAppTargets, } from './sign-in.js';
|
|
51
|
+
import { SIGN_IN_BASE_PATH, SIGN_IN_IMAGE_TAG_SUFFIX, SIGN_IN_IN_APP_SCOPE_KEY, SIGN_IN_LOGIN_APP_SCOPE_KEY, SIGN_IN_LOGIN_INTERNAL_NAME, SIGN_IN_RATE_LIMIT_SCOPE_KEY, SIGN_IN_TRUSTED_SCOPE_KEY, inAppSignIn, inAppStatePairs, inAppTargets, signInLoginNames, } from './sign-in.js';
|
|
39
52
|
import { withStepSystem } from './step-system.js';
|
|
40
53
|
import { WORKER_APP_DB_BLOCKED_KEY, WORKER_APP_DB_LEGACY_NOTE_KEY, WORKER_APP_DB_SCOPE_KEYS, planWorkerAppDb, } from './worker-app-db.js';
|
|
41
54
|
import { brokerKeyDelivered, planWorkerApps } from './worker-app.js';
|
|
42
55
|
import { planWorkers } from './worker.js';
|
|
56
|
+
import { testCustomersOrgKey, withDarkWorkspaceSignIn, workspaceClientKey, } from './workspace-client.js';
|
|
57
|
+
import { planTestCustomers, planWorkspaceBrokerEntries, planWorkspaceClients, workspaceSignInMarker, } from './workspace-sign-in.js';
|
|
58
|
+
import { workspaceWorkerParent } from './workspace-worker.js';
|
|
59
|
+
import { planWorkspaces, withWorkspaceWorkers, withoutDarkEntries, workspaceGateKey, wsKey, } from './workspace.js';
|
|
43
60
|
/** The `Plan` (#19's public contract) a `PlanDetail` carries. */
|
|
44
61
|
export function toPlan(detail) {
|
|
45
62
|
return {
|
|
@@ -84,7 +101,17 @@ function madeByFoundry(org, r, recordedIds, recordedValues, platformNames) {
|
|
|
84
101
|
if (r.system === 'coolify') {
|
|
85
102
|
if (r.kind === 'environment')
|
|
86
103
|
return true;
|
|
87
|
-
|
|
104
|
+
if (r.kind !== 'application')
|
|
105
|
+
return false;
|
|
106
|
+
// The in-app login app of the org's own sign-in stack; its id is not recorded, so its name says it.
|
|
107
|
+
const login = `${org.slug}-auth-login`;
|
|
108
|
+
const inAppLogin = signInLoginNames({
|
|
109
|
+
names: { authLogin: login },
|
|
110
|
+
internal: { authLogin: login },
|
|
111
|
+
});
|
|
112
|
+
if (r.config.name === inAppLogin.name)
|
|
113
|
+
return Boolean(recordedIds[`coolify:own-auth:${org.id}`]);
|
|
114
|
+
return r.config.name === chatInternalNames(org.slug).mas;
|
|
88
115
|
}
|
|
89
116
|
if (r.system !== 'cloudflare' || r.kind !== 'dns_record')
|
|
90
117
|
return false;
|
|
@@ -122,6 +149,7 @@ const OWN_MARKER_KINDS = new Set([
|
|
|
122
149
|
'own-files',
|
|
123
150
|
'own-files-r2',
|
|
124
151
|
'own-jobs',
|
|
152
|
+
'own-jobs-route',
|
|
125
153
|
'own-sfu',
|
|
126
154
|
]);
|
|
127
155
|
/** The kinds a provider lists a resource under before a recorded `system:kind:key` maps it to foundry's own. */
|
|
@@ -197,7 +225,10 @@ function liveResourceName(r) {
|
|
|
197
225
|
* Company Factory end to end", which wires real readers and executors
|
|
198
226
|
* against this seam.
|
|
199
227
|
*/
|
|
200
|
-
export function computePlanDetail(
|
|
228
|
+
export function computePlanDetail(declaredOrg, live, recordedIds = {}, agoraHost = NO_AGORA_HOST_VALUES, jobsAvailable = false, platformReadiness = DEFAULT_ORG_PLATFORM_READINESS, options = {}) {
|
|
229
|
+
// An entry that is not live has no app, address or Worker of its own: only its workspace exists (the
|
|
230
|
+
// workspace steps, the gate and the tunnel read `declaredOrg`).
|
|
231
|
+
const org = withoutDarkEntries(declaredOrg);
|
|
201
232
|
const byKey = new Map(live.map((r) => [resourceKey(r.system, r.kind, r.key), r]));
|
|
202
233
|
const claimed = new Set();
|
|
203
234
|
const plannedSteps = [];
|
|
@@ -243,7 +274,7 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
243
274
|
// #267: after the ZITADEL org step, before any app's own issuer
|
|
244
275
|
// application. Apps created later in this run are not in the binding yet;
|
|
245
276
|
// the next run replans this step and repoints them.
|
|
246
|
-
const loginHost = planLoginHost(org, recordedIds, {
|
|
277
|
+
const loginHost = planLoginHost(withDarkWorkspaceSignIn(org, declaredOrg), recordedIds, {
|
|
247
278
|
authReady: platformReadiness.authReady,
|
|
248
279
|
issuerPlanned: issuer.step !== null,
|
|
249
280
|
bindingsWired: options.loginHostBindings ?? false,
|
|
@@ -423,9 +454,14 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
423
454
|
const customers = planCustomers(org, live, claimed, recordedIds, options.zitadelOrgMetadata ?? false);
|
|
424
455
|
if (customers.step)
|
|
425
456
|
plannedSteps.push(customers.step);
|
|
457
|
+
// The workspaces' own customers, before the clients and the workspace steps that read the organisation's id.
|
|
458
|
+
const testCustomers = planTestCustomers(declaredOrg, live, claimed, recordedIds, options.zitadelOrgMetadata ?? false);
|
|
459
|
+
if (testCustomers.step)
|
|
460
|
+
plannedSteps.push(testCustomers.step);
|
|
426
461
|
for (const step of [
|
|
427
462
|
...planIssuerApps(org, byKey, claimed),
|
|
428
463
|
...planEntryClients(org, byKey, claimed),
|
|
464
|
+
...planWorkspaceClients(declaredOrg, byKey, claimed),
|
|
429
465
|
]) {
|
|
430
466
|
const guarded = guardApp(step);
|
|
431
467
|
if (guarded)
|
|
@@ -433,14 +469,16 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
433
469
|
}
|
|
434
470
|
// Auth ADR 0002: the MCP project and the JWT app, after the app they change
|
|
435
471
|
// and before the broker entry and the Worker that read the project id.
|
|
436
|
-
const mcp = planMcp(org, recordedIds, plannedSteps, options.zitadelMcp ?? false);
|
|
472
|
+
const mcp = planMcp(org, recordedIds, plannedSteps, options.zitadelMcp ?? false, declaredOrg);
|
|
437
473
|
plannedSteps.push(...mcp.steps);
|
|
438
474
|
// The Access gate before the database, app, tunnel and zone (#84): on a
|
|
439
475
|
// first apply the hostname is gated before anything answers on it.
|
|
440
476
|
// An Access gate that cannot be planned must not leave the hostname it
|
|
441
477
|
// guards routed: the tunnel (and its DNS) that exposes it waits too.
|
|
442
478
|
let accessGateBlocked = false;
|
|
443
|
-
const access = planAccess(org, recordedIds, platformReadiness.accessProviderId ?? null, options.platformHostnames ?? []);
|
|
479
|
+
const access = planAccess(org, recordedIds, platformReadiness.accessProviderId ?? null, options.platformHostnames ?? [], declaredOrg);
|
|
480
|
+
for (const note of access.notes)
|
|
481
|
+
dnsBlocked.push({ description: note, key: 'cloudflare:access' });
|
|
444
482
|
if (access.blocked) {
|
|
445
483
|
accessGateBlocked = true;
|
|
446
484
|
dnsBlocked.push({
|
|
@@ -477,11 +515,32 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
477
515
|
});
|
|
478
516
|
if (workerAppDb.step)
|
|
479
517
|
plannedSteps.push(workerAppDb.step);
|
|
518
|
+
// Before the apps: an app built from the company's own account is created through this source.
|
|
519
|
+
const githubSource = planGithubSource(org, recordedIds, platformReadiness.githubAppKeys);
|
|
520
|
+
if (githubSource.step)
|
|
521
|
+
plannedSteps.push(githubSource.step);
|
|
480
522
|
const authIssuer = 'error' in agoraHost ? null : agoraHost.issuer;
|
|
481
523
|
const app = planApps(org, byKey, claimed, recordedIds, plannedSteps, authIssuer, platformReadiness);
|
|
482
524
|
plannedSteps.push(...app.steps);
|
|
525
|
+
// The broker's workspaces database, before any broker entry that needs it: made once, when none is held or set.
|
|
526
|
+
const workspacesDatabasePlanned = declaredOrg.hosting.enabled &&
|
|
527
|
+
declaredOrg.hosting.entries.some((e) => e.uses?.auth?.workspaces) &&
|
|
528
|
+
(options.broker ?? false) &&
|
|
529
|
+
!(options.brokerWorkspacesDatabase ?? false) &&
|
|
530
|
+
(options.provisionWorkspacesDatabase ?? false);
|
|
531
|
+
if (workspacesDatabasePlanned) {
|
|
532
|
+
plannedSteps.push({
|
|
533
|
+
action: { system: 'coolify', kind: 'workspaces-database', key: org.id, desired: {} },
|
|
534
|
+
step: {
|
|
535
|
+
description: `make the database of ${org.slug}'s broker for customer workspaces and hold its URL at ${workspacesDatabaseRef(org)}`,
|
|
536
|
+
destroys: false,
|
|
537
|
+
migrates: false,
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
const brokerWorkspacesDatabase = (options.brokerWorkspacesDatabase ?? false) || workspacesDatabasePlanned;
|
|
483
542
|
// After the apps: the broker entry's key is minted into each app's env.
|
|
484
|
-
const brokerEntries = planBrokerEntries(org, recordedIds, plannedSteps, options.broker ?? false);
|
|
543
|
+
const brokerEntries = planBrokerEntries(org, recordedIds, plannedSteps, options.broker ?? false, brokerWorkspacesDatabase);
|
|
485
544
|
plannedSteps.push(...brokerEntries.steps);
|
|
486
545
|
// F6a: after the apps and the org's own ai key, which it mints below.
|
|
487
546
|
const entryAiKeys = planEntryAiKeys(org, recordedIds, plannedSteps, platformReadiness, (options.now ?? Date.now)());
|
|
@@ -495,6 +554,10 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
495
554
|
const edgeReservedLabels = planEdgeReservedLabels(org, recordedIds, platformReadiness, options.platformHostnames ?? []);
|
|
496
555
|
if (edgeReservedLabels.step)
|
|
497
556
|
plannedSteps.push(edgeReservedLabels.step);
|
|
557
|
+
// F3: the slug edge keys a company's platform hostnames on, next to the company's own edge key.
|
|
558
|
+
const edgeSlugStep = planEdgeSlug(org, recordedIds, platformReadiness);
|
|
559
|
+
if (edgeSlugStep)
|
|
560
|
+
plannedSteps.push(edgeSlugStep);
|
|
498
561
|
// Blue/green (`blue-green.ts`): the apps and balancers before the tunnel routes to them, a second
|
|
499
562
|
// server's connector after the tunnel exists.
|
|
500
563
|
const blueGreen = planBlueGreen({
|
|
@@ -509,7 +572,49 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
509
572
|
p.action.key === appStepKey(org, entryKey)),
|
|
510
573
|
});
|
|
511
574
|
plannedSteps.push(...blueGreen.steps.filter((x) => x.phase === 'before').map(blueGreenDraft()));
|
|
512
|
-
|
|
575
|
+
// A workspace is made only behind its gate: recorded already, or planned earlier in this run.
|
|
576
|
+
const workspaceGateReady = (gateKey) => Boolean(recordedIds[gateIdRecordKey(org.id, gateKey)]) ||
|
|
577
|
+
plannedSteps.some((p) => p.action.system === 'cloudflare' &&
|
|
578
|
+
p.action.kind === 'gate' &&
|
|
579
|
+
p.action.key === appStepKey(org, gateKey));
|
|
580
|
+
const stepPlanned = (system, kind, key) => plannedSteps.some((p) => p.action.system === system &&
|
|
581
|
+
p.action.kind === kind &&
|
|
582
|
+
(key === undefined || p.action.key === key));
|
|
583
|
+
// A workspace's sign-in is ready when its values are recorded, or the steps that record them are planned.
|
|
584
|
+
const workspaceSignInWanted = (entry) => {
|
|
585
|
+
const marker = workspaceSignInMarker(declaredOrg, entry, recordedIds, authIssuer);
|
|
586
|
+
if (marker)
|
|
587
|
+
return marker;
|
|
588
|
+
const customers = entry.uses?.auth?.users === 'customers';
|
|
589
|
+
const orgKnown = customers
|
|
590
|
+
? Boolean(recordedIds[testCustomersOrgKey(org.id)]) ||
|
|
591
|
+
stepPlanned('zitadel', 'customers-test')
|
|
592
|
+
: Boolean(recordedIds.zitadel_org);
|
|
593
|
+
const clientKnown = Boolean(recordedIds[entryClientIdScopeKey(workspaceClientKey(entry.key))]) ||
|
|
594
|
+
stepPlanned('zitadel', 'app', workspaceClientKey(entry.key));
|
|
595
|
+
const mcpKnown = !entry.uses?.auth?.mcp ||
|
|
596
|
+
Boolean(recordedIds[entryMcpProjectScopeKey(workspaceClientKey(entry.key))]) ||
|
|
597
|
+
stepPlanned('zitadel', 'mcp-project', `${org.id}:${workspaceClientKey(entry.key)}`);
|
|
598
|
+
return orgKnown && clientKnown && mcpKnown && authIssuer ? 'pending' : null;
|
|
599
|
+
};
|
|
600
|
+
// Blue/green's cousin: each server workspace is a stage with a host of its own, behind its gate.
|
|
601
|
+
const workspaces = planWorkspaces({
|
|
602
|
+
org: declaredOrg,
|
|
603
|
+
recordedIds,
|
|
604
|
+
servers: options.blueGreenServers,
|
|
605
|
+
gateReady: workspaceGateReady,
|
|
606
|
+
signInWanted: workspaceSignInWanted,
|
|
607
|
+
authIssuer,
|
|
608
|
+
...(options.stageSinkDown ? { stageSinkDown: true } : {}),
|
|
609
|
+
});
|
|
610
|
+
plannedSteps.push(...workspaces.steps);
|
|
611
|
+
// After the workspace step has put the stage's own key in its env.
|
|
612
|
+
const workspaceBrokerEntries = planWorkspaceBrokerEntries(declaredOrg, recordedIds, plannedSteps, options.broker ?? false, brokerWorkspacesDatabase, (entry) => recordedIds[bgKey.stageState(entry.key)] === 'ok' ||
|
|
613
|
+
plannedSteps.some((p) => p.action.system === 'coolify' &&
|
|
614
|
+
p.action.kind === 'workspace' &&
|
|
615
|
+
p.action.desired.entry === entry.key));
|
|
616
|
+
plannedSteps.push(...workspaceBrokerEntries.steps);
|
|
617
|
+
const tunnel = planTunnel(declaredOrg, byKey, claimed, recordedIds, platformReadiness.platformApex);
|
|
513
618
|
// #238: a tunnel that is being provisioned waits for an active zone; parking
|
|
514
619
|
// one never does.
|
|
515
620
|
// #555: provisioning writes the tunnel config, the zone lookup and DNS; a
|
|
@@ -577,7 +682,33 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
577
682
|
archon: options.bouleArchonCredential === true,
|
|
578
683
|
directory: options.customersDirectory === true,
|
|
579
684
|
}, platformReadiness.canHoldSecrets === true, (options.now ?? Date.now)());
|
|
580
|
-
|
|
685
|
+
// F13: Boule's `CUSTOMER_GRANTS`, from the entries that declare `uses.customerGrants`. Every reason it
|
|
686
|
+
// says nothing yet is a plan line, never silence.
|
|
687
|
+
const customerGrants = wantedCustomerGrants(org, recordedIds, {
|
|
688
|
+
live: byKey.has(resourceKey('coolify', 'boule', org.id)),
|
|
689
|
+
});
|
|
690
|
+
const customerGrantsNotes = [...customerGrants.notes];
|
|
691
|
+
let customerGrantsShape = null;
|
|
692
|
+
if (customerGrants.shape) {
|
|
693
|
+
if (bouleCredentials.wants.customersDirectory) {
|
|
694
|
+
customerGrantsShape = customerGrants.shape;
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
customerGrantsNotes.push(`${org.slug}'s customer grants are not planned: they need the customers directory credential, which is blocked above`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
// T3: a company with customers gets a registrar key at the sign-in broker; Boule is given it.
|
|
701
|
+
const registrar = planBrokerRegistrar(org, recordedIds, plannedSteps, {
|
|
702
|
+
brokerWired: options.broker ?? false,
|
|
703
|
+
canHoldSecrets: platformReadiness.canHoldSecrets === true,
|
|
704
|
+
issuerKnown: Boolean(authIssuer),
|
|
705
|
+
});
|
|
706
|
+
const boule = planBoule(org, byKey, claimed, recordedIds, authIssuer, options.otherSlugs ?? [], options.archonUrl ?? null, platformReadiness, {
|
|
707
|
+
...bouleCredentials.wants,
|
|
708
|
+
...(customerGrantsShape ? { customerGrants: true } : {}),
|
|
709
|
+
...(registrar.deliver ? { registrar: true } : {}),
|
|
710
|
+
...(registrar.keepBouleEnv ? { registrarKept: true } : {}),
|
|
711
|
+
}, customerGrantsShape);
|
|
581
712
|
if (boule.step)
|
|
582
713
|
plannedSteps.push(boule.step);
|
|
583
714
|
// Boule's and Agora's blue/green: after their own steps, which make the app it copies.
|
|
@@ -598,11 +729,26 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
598
729
|
// the same dependency `planEntryAiKeys`/`planFilesKeys` have.
|
|
599
730
|
const entryEdgeKeys = planEntryEdgeKeys(org, recordedIds, plannedSteps, platformReadiness, (options.now ?? Date.now)());
|
|
600
731
|
plannedSteps.push(...entryEdgeKeys.steps);
|
|
732
|
+
// F7: the signed-records registrar key, after Boule like the edge key.
|
|
733
|
+
const keysRegistrar = planKeysRegistrar(org, recordedIds, plannedSteps, { ...(options.keysRegistrarUrl !== undefined ? { url: options.keysRegistrarUrl } : {}) }, (options.now ?? Date.now)());
|
|
734
|
+
plannedSteps.push(...keysRegistrar.steps);
|
|
735
|
+
// A company on shared services registers on the scheduler the host supplies (the owner company's); a company
|
|
736
|
+
// that runs its own services never does, only on its own.
|
|
737
|
+
const schedulerUsable = (jobsAvailable && !ownsServices(org)) || platformReadiness.jobsReady;
|
|
738
|
+
// A `uses.jobs` entry registers with the scheduler after its app, whose env the step writes.
|
|
739
|
+
const entryJobs = planEntryJobs(org, recordedIds, plannedSteps, platformReadiness, schedulerUsable);
|
|
740
|
+
plannedSteps.push(...entryJobs.steps);
|
|
741
|
+
// F12: a deploy key for an entry's own repo, after its app (whose env the step writes).
|
|
742
|
+
const entryRepoKeys = planEntryRepoKeys(org, recordedIds, plannedSteps, platformReadiness);
|
|
743
|
+
plannedSteps.push(...entryRepoKeys.steps);
|
|
601
744
|
// Boule's own broker entry, after its app: the entry needs the AUTH_APP_KEY
|
|
602
745
|
// that step puts in the app's env.
|
|
603
746
|
const bouleBroker = planBouleBrokerEntry(org, recordedIds, plannedSteps, options.broker ?? false);
|
|
604
747
|
if (bouleBroker.step)
|
|
605
748
|
plannedSteps.push(bouleBroker.step);
|
|
749
|
+
// The company's registrar record in the broker, after Boule's own broker entry.
|
|
750
|
+
if (registrar.step)
|
|
751
|
+
plannedSteps.push(registrar.step);
|
|
606
752
|
// #440: Agora's desktop sign-in backend, after Agora and Boule: its readiness probe goes through Agora's proxy, and
|
|
607
753
|
// Agora writes the upstream only once the service ran healthy (so a first apply needs a second to turn it on).
|
|
608
754
|
const desktopAuth = planDesktopAuth(org, recordedIds, {
|
|
@@ -622,12 +768,20 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
622
768
|
});
|
|
623
769
|
if (ownJobs.step)
|
|
624
770
|
plannedSteps.push(ownJobs.step);
|
|
771
|
+
const ownJobsRoute = planOwnJobsRoute({
|
|
772
|
+
org,
|
|
773
|
+
recordedIds,
|
|
774
|
+
readiness: platformReadiness,
|
|
775
|
+
ownServices: options.ownServices ?? false,
|
|
776
|
+
zoneActive: ownZoneActive,
|
|
777
|
+
schedulerPlanned: ownJobs.step !== null,
|
|
778
|
+
});
|
|
779
|
+
if (ownJobsRoute.step)
|
|
780
|
+
plannedSteps.push(ownJobsRoute.step);
|
|
625
781
|
// #173: register the org's own app with the platform's jobs scheduler.
|
|
626
782
|
// After Agora, so a failing registration can never stop Agora from
|
|
627
783
|
// provisioning (`runSteps` stops at the first failing step).
|
|
628
|
-
const jobsRegistration = planJobsRegistration(org, recordedIds,
|
|
629
|
-
// An own org never registers with the shared scheduler the host supplies.
|
|
630
|
-
(jobsAvailable && !ownsServices(org)) || platformReadiness.jobsReady, platformReadiness.missingKeys.jobs, platformReadiness.noKeyReader, platformReadiness.jobsUrlMissing ?? false);
|
|
784
|
+
const jobsRegistration = planJobsRegistration(org, recordedIds, schedulerUsable, platformReadiness.missingKeys.jobs, platformReadiness.noKeyReader, platformReadiness.jobsUrlMissing ?? false);
|
|
631
785
|
if (jobsRegistration.step)
|
|
632
786
|
plannedSteps.push(jobsRegistration.step);
|
|
633
787
|
const remoteDatabase = planRemoteDatabase(org, options.remoteDatabase, recordedIds);
|
|
@@ -660,7 +814,8 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
660
814
|
}
|
|
661
815
|
// ADR 0002: Worker apps, after the Workers for the same reason: a route, record
|
|
662
816
|
// or script name that is not ours throws at apply and `runSteps` stops there.
|
|
663
|
-
const
|
|
817
|
+
const workerOrg = withWorkspaceWorkers(org, declaredOrg);
|
|
818
|
+
const workerApps = planWorkerApps(workerOrg, recordedIds, {
|
|
664
819
|
available: options.workerApps ?? false,
|
|
665
820
|
...(options.workerAppKeysUrl !== undefined ? { keysUrl: options.workerAppKeysUrl } : {}),
|
|
666
821
|
...(options.workerAppRegistryAccountId !== undefined
|
|
@@ -683,7 +838,10 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
683
838
|
});
|
|
684
839
|
for (const step of workerApps.steps) {
|
|
685
840
|
const stepEntry = step.action.key.slice(org.id.length + 1);
|
|
686
|
-
|
|
841
|
+
// A workspace Worker lives in the platform's apps zone, not the org's own.
|
|
842
|
+
const wsEntry = workerOrg.hosting.entries.find((e) => e.key === stepEntry);
|
|
843
|
+
const wsParent = wsEntry ? workspaceWorkerParent(wsEntry) : null;
|
|
844
|
+
const ownZone = wsParent === null && org.hosting.usesDomain && Boolean(org.domain);
|
|
687
845
|
const apply = step.action.desired.mode === 'apply';
|
|
688
846
|
// Retire never looks a zone up; a deploy needs the org's zone, or the shared oikos.host zone.
|
|
689
847
|
const hasDatabase = Boolean(org.hosting.entries.find((e) => e.key === stepEntry)?.workerApp?.database);
|
|
@@ -691,10 +849,16 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
691
849
|
? null
|
|
692
850
|
: hasDatabase
|
|
693
851
|
? edgeNeeded(platformReadiness, ['zones', 'dns', 'access', 'tunnels'])
|
|
694
|
-
: ownZone
|
|
852
|
+
: ownZone || wsParent !== null
|
|
695
853
|
? edgeNeeded(platformReadiness, ['zones', 'dns'])
|
|
696
854
|
: edgeNeeded(platformReadiness, ['zones']);
|
|
697
|
-
if (
|
|
855
|
+
if (apply && wsParent !== null && !workspaceGateReady(workspaceGateKey(wsParent))) {
|
|
856
|
+
appBlocked.push({
|
|
857
|
+
description: `${org.slug}'s ${wsParent} workspace Worker is blocked, not planned: its Access gate is not recorded or planned yet`,
|
|
858
|
+
key: step.action.key,
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
else if (reason) {
|
|
698
862
|
dnsBlocked.push(edgeBlockedItem(org, `${stepEntry} Worker app`, step.action.key, reason));
|
|
699
863
|
}
|
|
700
864
|
else if (apply && ownZone && zoneWaiting) {
|
|
@@ -703,7 +867,7 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
703
867
|
key: step.action.key,
|
|
704
868
|
});
|
|
705
869
|
}
|
|
706
|
-
else if (apply && !ownZone && !(options.oikosZoneReady ?? false)) {
|
|
870
|
+
else if (apply && wsParent === null && !ownZone && !(options.oikosZoneReady ?? false)) {
|
|
707
871
|
zoneBlocked.push({
|
|
708
872
|
description: `${org.slug}'s ${stepEntry} Worker app is blocked, not planned: the shared oikos.host zone is not ready yet`,
|
|
709
873
|
key: step.action.key,
|
|
@@ -713,11 +877,14 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
713
877
|
plannedSteps.push(step);
|
|
714
878
|
}
|
|
715
879
|
}
|
|
880
|
+
// `uses.audit`: registers the entry's ledger for platform sealing, after its Worker app.
|
|
881
|
+
const entryAudit = planEntryAudit(org, recordedIds, plannedSteps, platformReadiness);
|
|
882
|
+
plannedSteps.push(...entryAudit.steps);
|
|
716
883
|
// Last: a repair that fails must not stop any step after it (`runSteps` stops at the first failure).
|
|
717
884
|
const idTokenUserinfo = planIdTokenUserinfo(org, live, recordedIds, plannedSteps, platformReadiness.authReady, options.zitadelIdTokenUserinfo ?? false);
|
|
718
885
|
if (idTokenUserinfo)
|
|
719
886
|
plannedSteps.push(idTokenUserinfo);
|
|
720
|
-
const postLogoutUris = planPostLogoutUris(
|
|
887
|
+
const postLogoutUris = planPostLogoutUris(declaredOrg, live, recordedIds, plannedSteps, platformReadiness.authReady, options.zitadelPostLogoutUris ?? false);
|
|
721
888
|
if (postLogoutUris)
|
|
722
889
|
plannedSteps.push(postLogoutUris);
|
|
723
890
|
const desktopCallbacks = planDesktopCallbacks(org, live, recordedIds, plannedSteps, platformReadiness.authReady, options.zitadelRedirectUris ?? false);
|
|
@@ -725,6 +892,24 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
725
892
|
plannedSteps.push(desktopCallbacks);
|
|
726
893
|
// #444, last: a Postgres step that fails must not stop the zone, mail, app, Agora and Boule steps of a live company.
|
|
727
894
|
plannedSteps.push(...orgPostgresSteps);
|
|
895
|
+
// F4 (#925): with apps on, the apps admin role on that server and the route Hyperdrive reaches it by.
|
|
896
|
+
const appsAdmin = planAppsAdmin(org, recordedIds, plannedSteps, {
|
|
897
|
+
canHoldSecrets: platformReadiness.canHoldSecrets === true,
|
|
898
|
+
postgresPlanned: orgPostgresSteps.some((p) => p.action.system === 'coolify' && p.action.kind === 'org-postgres'),
|
|
899
|
+
});
|
|
900
|
+
plannedSteps.push(...appsAdmin.steps);
|
|
901
|
+
const appsDbRoute = planAppsDbRoute(org, recordedIds, plannedSteps, {
|
|
902
|
+
accessReady: edgeNeeded(platformReadiness, ['access', 'tunnels', 'dns', 'zones']) === null,
|
|
903
|
+
canHoldSecrets: platformReadiness.canHoldSecrets === true,
|
|
904
|
+
tunnelReady: Boolean(recordedIds[`cloudflare:tunnel:${org.id}`]) ||
|
|
905
|
+
plannedSteps.some((s) => s.action.system === 'cloudflare' && s.action.kind === 'tunnel'),
|
|
906
|
+
});
|
|
907
|
+
plannedSteps.push(...appsDbRoute.steps);
|
|
908
|
+
// FZ: the company slug and the zone ids, in the same Boule env.
|
|
909
|
+
const appsZones = planAppsZones(org, recordedIds, plannedSteps, {
|
|
910
|
+
zonesReady: edgeNeeded(platformReadiness, ['zones']) === null,
|
|
911
|
+
});
|
|
912
|
+
plannedSteps.push(...appsZones.steps);
|
|
728
913
|
// After the org-postgres steps: the desktop sign-in service lives on a home they may create in this same plan.
|
|
729
914
|
if (desktopAuth.step)
|
|
730
915
|
plannedSteps.push(desktopAuth.step);
|
|
@@ -742,7 +927,12 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
742
927
|
for (const id of platformReadiness.platformZitadelIds ?? [])
|
|
743
928
|
recordedValues.add(id);
|
|
744
929
|
}
|
|
745
|
-
const platformNames = new Set([
|
|
930
|
+
const platformNames = new Set([
|
|
931
|
+
...(options.platformHostnames ?? []),
|
|
932
|
+
platformReadiness.mailHost?.mailHost ?? '',
|
|
933
|
+
// The shared mail relay's MX host, which the platform's relay step writes.
|
|
934
|
+
platformReadiness.platformApex ? platformMailMxHost(platformReadiness.platformApex) : '',
|
|
935
|
+
].map((h) => h.toLowerCase()));
|
|
746
936
|
platformNames.delete('');
|
|
747
937
|
const unmanaged = live
|
|
748
938
|
.filter((r) => !claimed.has(resourceKey(r.system, r.kind, r.key)))
|
|
@@ -778,6 +968,12 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
778
968
|
for (const description of bouleCredentials.blocked) {
|
|
779
969
|
unmanaged.push({ description, key: 'coolify:boule:credentials' });
|
|
780
970
|
}
|
|
971
|
+
customerGrantsNotes.forEach((description, index) => {
|
|
972
|
+
unmanaged.push({ description, key: `coolify:boule:customer-grants:${index}` });
|
|
973
|
+
});
|
|
974
|
+
registrar.blocked.forEach((description, index) => {
|
|
975
|
+
unmanaged.push({ description, key: `coolify:broker-registrar:${index}` });
|
|
976
|
+
});
|
|
781
977
|
if (boule.mailBlocked) {
|
|
782
978
|
unmanaged.push({ description: boule.mailBlocked, key: 'coolify:boule:mail' });
|
|
783
979
|
}
|
|
@@ -786,6 +982,7 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
786
982
|
if (aiKey.blocked)
|
|
787
983
|
unmanaged.push({ description: aiKey.blocked, key: 'ai:key' });
|
|
788
984
|
unmanaged.push(...blueGreen.unmanaged);
|
|
985
|
+
unmanaged.push(...workspaces.unmanaged);
|
|
789
986
|
unmanaged.push(...filesKeys.blocked);
|
|
790
987
|
unmanaged.push(...entryAiKeys.blocked);
|
|
791
988
|
unmanaged.push(...edgeWorkers.blocked);
|
|
@@ -798,7 +995,15 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
798
995
|
}
|
|
799
996
|
unmanaged.push(...mcp.blocked);
|
|
800
997
|
unmanaged.push(...customers.blocked);
|
|
998
|
+
unmanaged.push(...testCustomers.blocked);
|
|
801
999
|
unmanaged.push(...entryEdgeKeys.blocked);
|
|
1000
|
+
unmanaged.push(...keysRegistrar.blocked);
|
|
1001
|
+
unmanaged.push(...appsAdmin.blocked);
|
|
1002
|
+
unmanaged.push(...appsDbRoute.blocked);
|
|
1003
|
+
unmanaged.push(...appsZones.blocked);
|
|
1004
|
+
unmanaged.push(...entryJobs.blocked);
|
|
1005
|
+
unmanaged.push(...entryAudit.blocked);
|
|
1006
|
+
unmanaged.push(...entryRepoKeys.blocked);
|
|
802
1007
|
unmanaged.push(...edgeReservedLabels.blocked);
|
|
803
1008
|
if (ownAuth.blocked)
|
|
804
1009
|
unmanaged.push({ description: ownAuth.blocked, key: OWN_AUTH_BLOCKED_KEY });
|
|
@@ -807,6 +1012,9 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
807
1012
|
}
|
|
808
1013
|
if (ownJobs.blocked)
|
|
809
1014
|
unmanaged.push({ description: ownJobs.blocked, key: OWN_JOBS_BLOCKED_KEY });
|
|
1015
|
+
if (ownJobsRoute.blocked) {
|
|
1016
|
+
unmanaged.push({ description: ownJobsRoute.blocked, key: OWN_JOBS_ROUTE_BLOCKED_KEY });
|
|
1017
|
+
}
|
|
810
1018
|
if (ownMail.blocked)
|
|
811
1019
|
unmanaged.push({ description: ownMail.blocked, key: OWN_MAIL_BLOCKED_KEY });
|
|
812
1020
|
if (ownSfu.blocked)
|
|
@@ -895,8 +1103,10 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
895
1103
|
});
|
|
896
1104
|
}
|
|
897
1105
|
// #243: an appEnv source nothing will record is reported, not silently skipped.
|
|
1106
|
+
unmanaged.push(...githubSource.blocked);
|
|
898
1107
|
unmanaged.push(...app.blocked);
|
|
899
1108
|
unmanaged.push(...brokerEntries.blocked);
|
|
1109
|
+
unmanaged.push(...workspaceBrokerEntries.blocked);
|
|
900
1110
|
unmanaged.push(...bouleBroker.blocked);
|
|
901
1111
|
// F6: last, so every database step above runs before the backup of its database.
|
|
902
1112
|
const backups = planCompanyBackups(org, recordedIds, options.backupStorageUuid);
|
|
@@ -940,6 +1150,7 @@ const NEEDS_OWN_ZITADEL = new Set([
|
|
|
940
1150
|
'zitadel:owner',
|
|
941
1151
|
'zitadel:app',
|
|
942
1152
|
'zitadel:customers',
|
|
1153
|
+
'zitadel:customers-test',
|
|
943
1154
|
'zitadel:id-token-userinfo',
|
|
944
1155
|
'zitadel:desktop-callback',
|
|
945
1156
|
'zitadel:login-host',
|
|
@@ -947,7 +1158,10 @@ const NEEDS_OWN_ZITADEL = new Set([
|
|
|
947
1158
|
'coolify:calls-service',
|
|
948
1159
|
'coolify:calls-chat',
|
|
949
1160
|
'coolify:broker-entry',
|
|
1161
|
+
'coolify:workspace-broker-entry',
|
|
1162
|
+
'coolify:workspaces-database',
|
|
950
1163
|
'coolify:boule-broker-entry',
|
|
1164
|
+
'coolify:broker-registrar',
|
|
951
1165
|
'files:organisation',
|
|
952
1166
|
]);
|
|
953
1167
|
/**
|
|
@@ -2919,7 +3133,7 @@ function planBouleCredentials(org, recordedIds, host, canHoldSecrets, nowMs) {
|
|
|
2919
3133
|
* address (the one Agora signs in at); without it the Boule is blocked, not
|
|
2920
3134
|
* planned, so every other step still applies.
|
|
2921
3135
|
*/
|
|
2922
|
-
function planBoule(org, byKey, claimed, recordedIds, issuer, otherSlugs, archonUrl, readiness, credentials) {
|
|
3136
|
+
function planBoule(org, byKey, claimed, recordedIds, issuer, otherSlugs, archonUrl, readiness, credentials, customerGrantsShape = null) {
|
|
2923
3137
|
const key = resourceKey('coolify', 'boule', org.id);
|
|
2924
3138
|
const live = byKey.get(key);
|
|
2925
3139
|
if (live)
|
|
@@ -2991,6 +3205,9 @@ function planBoule(org, byKey, claimed, recordedIds, issuer, otherSlugs, archonU
|
|
|
2991
3205
|
platformOwner,
|
|
2992
3206
|
archonCredential: credentials.archonCredential === true,
|
|
2993
3207
|
customersDirectory: credentials.customersDirectory === true,
|
|
3208
|
+
// Only an org that declares the flag carries the key, so every other plan is unchanged.
|
|
3209
|
+
...(credentials.customerGrants === true ? { customerGrants: true } : {}),
|
|
3210
|
+
...(credentials.registrar === true ? { registrar: true } : {}),
|
|
2994
3211
|
},
|
|
2995
3212
|
};
|
|
2996
3213
|
if (!live) {
|
|
@@ -3020,6 +3237,26 @@ function planBoule(org, byKey, claimed, recordedIds, issuer, otherSlugs, archonU
|
|
|
3020
3237
|
if (!drift.includes(key))
|
|
3021
3238
|
drift.push(key);
|
|
3022
3239
|
}
|
|
3240
|
+
// F13: the grants entry says what the flagged entries say (credentials by presence only). Taken away only
|
|
3241
|
+
// once no entry declares the flag and foundry wrote it; held credentials of dropped entries are forgotten.
|
|
3242
|
+
if (customerGrantsShape) {
|
|
3243
|
+
if (!customerGrantsShapeMatches(liveEnv[CUSTOMER_GRANTS_ENV], customerGrantsShape)) {
|
|
3244
|
+
drift.push(CUSTOMER_GRANTS_ENV);
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
else if (customerGrantsEntries(org).length === 0 &&
|
|
3248
|
+
recordedIds[CUSTOMER_GRANTS_WRITTEN_SCOPE_KEY] &&
|
|
3249
|
+
liveEnv[CUSTOMER_GRANTS_ENV] !== undefined) {
|
|
3250
|
+
drift.push(CUSTOMER_GRANTS_ENV);
|
|
3251
|
+
}
|
|
3252
|
+
if (customerGrantsToDrop(org, recordedIds).length > 0 && !drift.includes(CUSTOMER_GRANTS_ENV)) {
|
|
3253
|
+
drift.push(CUSTOMER_GRANTS_ENV);
|
|
3254
|
+
}
|
|
3255
|
+
// T3: the registrar key by presence and by which mint Boule got; taken away once customers are off.
|
|
3256
|
+
for (const key of bouleRegistrarDrift(org, recordedIds, liveEnv, credentials.registrar === true, credentials.registrarKept === true)) {
|
|
3257
|
+
if (!drift.includes(key))
|
|
3258
|
+
drift.push(key);
|
|
3259
|
+
}
|
|
3023
3260
|
// The token is compared, never planned with its value.
|
|
3024
3261
|
if (mailOn &&
|
|
3025
3262
|
bouleMailTokenDrift(liveEnv[BOULE_MAIL_TOKEN_ENV], live.config.appMailToken)) {
|
|
@@ -3191,7 +3428,7 @@ function planApp(org, entryKey, byKey, claimed, recordedIds, plannedSteps, authI
|
|
|
3191
3428
|
// right now, and gating this on `mailHost` let a platform-apex org with an
|
|
3192
3429
|
// already-recorded domain take the unprotected path the moment platform
|
|
3193
3430
|
// mail went back to off.
|
|
3194
|
-
if (entryKey === mainEntry(org.hosting)?.key) {
|
|
3431
|
+
if (entryKey === mainEntry(org.hosting)?.key || entry?.uses?.mail) {
|
|
3195
3432
|
const mailDomain = recordedIds[MAIL_DOMAIN_SCOPE_KEY];
|
|
3196
3433
|
const platformApex = readiness.platformApex;
|
|
3197
3434
|
const platformLettermintProjectId = readiness.platformLettermintProjectId;
|
|
@@ -3519,11 +3756,53 @@ function planApps(org, byKey, claimed, recordedIds, plannedSteps, authIssuer, re
|
|
|
3519
3756
|
if (org.hosting.entries.some((e) => e.key === key && e.workerApp))
|
|
3520
3757
|
continue;
|
|
3521
3758
|
const app = planApp(org, key, byKey, claimed, recordedIds, plannedSteps, authIssuer, readiness);
|
|
3759
|
+
// An app built from the company's own account is created through its GitHub source: with no source
|
|
3760
|
+
// recorded or planned, a new app waits (blocked, not planned) instead of failing at create.
|
|
3761
|
+
const sourceEntry = org.hosting.entries.find((e) => e.key === key);
|
|
3762
|
+
if (app.step &&
|
|
3763
|
+
app.step.action.desired.enabled !== false &&
|
|
3764
|
+
sourceEntry &&
|
|
3765
|
+
companyGithubFor(org.hosting, sourceEntry.repo) &&
|
|
3766
|
+
!recordedIds[githubSourceResourceKey(org.id)] &&
|
|
3767
|
+
!recordedIds[`coolify:app:${appStepKey(org, key)}`] &&
|
|
3768
|
+
!plannedSteps.some((p) => p.action.system === 'coolify' && p.action.kind === 'github-source')) {
|
|
3769
|
+
blocked.push({
|
|
3770
|
+
description: `${org.slug}'s app for entry '${key}' is blocked, not planned: it is built from the company's GitHub account and its GitHub source is not made yet`,
|
|
3771
|
+
key: 'github-source',
|
|
3772
|
+
});
|
|
3773
|
+
blocked.push(...app.blocked);
|
|
3774
|
+
continue;
|
|
3775
|
+
}
|
|
3776
|
+
// The entry's workspaces key is made and held by this step; a store that cannot put leaves the step failing every run.
|
|
3777
|
+
if (app.step &&
|
|
3778
|
+
app.step.action.desired.enabled !== false &&
|
|
3779
|
+
org.hosting.entries.some((e) => e.key === key && e.uses?.auth?.workspaces) &&
|
|
3780
|
+
readiness.canHoldSecrets !== true) {
|
|
3781
|
+
blocked.push({
|
|
3782
|
+
description: `${org.slug}'s app for entry '${key}' is blocked, not planned: its workspaces key needs a held-key store that can put, and the host has none`,
|
|
3783
|
+
key: `coolify:app:${key}:workspaces-key`,
|
|
3784
|
+
});
|
|
3785
|
+
blocked.push(...app.blocked);
|
|
3786
|
+
continue;
|
|
3787
|
+
}
|
|
3788
|
+
// The entry's customer-grants credential is made and held by this step; a store that cannot put leaves it failing every run.
|
|
3789
|
+
if (app.step &&
|
|
3790
|
+
app.step.action.desired.enabled !== false &&
|
|
3791
|
+
org.hosting.entries.some((e) => e.key === key && e.uses?.customerGrants) &&
|
|
3792
|
+
readiness.canHoldSecrets !== true) {
|
|
3793
|
+
blocked.push({
|
|
3794
|
+
description: `${org.slug}'s app for entry '${key}' is blocked, not planned: its customer grants credential needs a held-key store that can put, and the host has none`,
|
|
3795
|
+
key: `coolify:app:${key}:customer-grants`,
|
|
3796
|
+
});
|
|
3797
|
+
blocked.push(...app.blocked);
|
|
3798
|
+
continue;
|
|
3799
|
+
}
|
|
3522
3800
|
// The executor pushes the issuer the plan read, the way Agora gets its host values.
|
|
3523
3801
|
// The same availability the plan's marker used: the executor records its
|
|
3524
3802
|
// marker from this, never from its own port, so the two cannot disagree.
|
|
3525
3803
|
const usesSecrets = org.hosting.entries.some((e) => e.key === key &&
|
|
3526
|
-
|
|
3804
|
+
(e.uses?.customerGrants === true ||
|
|
3805
|
+
Object.values(e.env).some((v) => typeof v !== 'string' && (v.from === 'sharedSecret' || v.from === 'heldSecret'))));
|
|
3527
3806
|
if (usesSecrets && app.step && app.step.action.desired.enabled !== false) {
|
|
3528
3807
|
const entry = org.hosting.entries.find((e) => e.key === key);
|
|
3529
3808
|
app.step.action.desired = {
|
|
@@ -3538,15 +3817,16 @@ function planApps(org, byKey, claimed, recordedIds, plannedSteps, authIssuer, re
|
|
|
3538
3817
|
if (signsIn)
|
|
3539
3818
|
app.step.action.desired = { ...app.step.action.desired, authIssuer };
|
|
3540
3819
|
}
|
|
3541
|
-
// #491: only the `app` entry's
|
|
3542
|
-
// (`executor.ts`'s own
|
|
3820
|
+
// #491: only the `app` entry's step and an entry with `uses.mail` run
|
|
3821
|
+
// `ensureAppLettermintMail` (`executor.ts`'s own gate). Unlike
|
|
3543
3822
|
// `secretSources`/`authIssuer` above, these two are set unconditionally
|
|
3544
3823
|
// -- `null` when no platform is declared, never omitted -- because
|
|
3545
3824
|
// `executor.ts` treats the key being *absent* as a stale plan predating
|
|
3546
3825
|
// this field and refuses to run (review round 2, finding 3): an omitted
|
|
3547
3826
|
// key here would read identically to a stale plan and silently fall
|
|
3548
3827
|
// back to the unprotected pre-#491 path instead.
|
|
3549
|
-
|
|
3828
|
+
const sendsMail = key === mainKey || org.hosting.entries.some((e) => e.key === key && e.uses?.mail);
|
|
3829
|
+
if (sendsMail && app.step && app.step.action.desired.enabled !== false) {
|
|
3550
3830
|
app.step.action.desired = {
|
|
3551
3831
|
...app.step.action.desired,
|
|
3552
3832
|
platformApex: readiness.platformApex,
|
|
@@ -3558,17 +3838,53 @@ function planApps(org, byKey, claimed, recordedIds, plannedSteps, authIssuer, re
|
|
|
3558
3838
|
// edge: blocked, not planned, while edge is not applied.
|
|
3559
3839
|
const appReason = edgeNeeded(readiness, ['zones', 'dns']);
|
|
3560
3840
|
if (appReason &&
|
|
3561
|
-
|
|
3841
|
+
sendsMail &&
|
|
3562
3842
|
app.step &&
|
|
3563
3843
|
app.step.action.desired.enabled !== false &&
|
|
3564
3844
|
recordedIds[MAIL_DOMAIN_SCOPE_KEY]) {
|
|
3565
|
-
blocked.push(edgeBlockedItem(org, 'app'
|
|
3845
|
+
blocked.push(edgeBlockedItem(org, key === mainKey ? 'app' : `${key} app`, key === mainKey ? 'coolify:app' : `coolify:app:${key}`, appReason));
|
|
3566
3846
|
blocked.push(...app.blocked);
|
|
3567
3847
|
continue;
|
|
3568
3848
|
}
|
|
3849
|
+
// A workspace entry's live app is made from the commit its workspace runs: none is made before one is known.
|
|
3850
|
+
const goLive = org.hosting.entries.find((e) => e.key === key && e.workspace !== undefined && e.workerApp === undefined);
|
|
3851
|
+
if (goLive &&
|
|
3852
|
+
app.step &&
|
|
3853
|
+
app.step.action.desired.enabled !== false &&
|
|
3854
|
+
!recordedIds[`coolify:app:${appStepKey(org, key)}`]) {
|
|
3855
|
+
const commit = goLive.liveCommit ?? recordedIds[wsKey.commit(key)];
|
|
3856
|
+
if (!commit) {
|
|
3857
|
+
blocked.push({
|
|
3858
|
+
description: `${org.slug}'s app for entry '${key}' is blocked, not planned: its workspace has not deployed a commit yet, and the live app is made from that commit`,
|
|
3859
|
+
key: `workspace:${key}:golive`,
|
|
3860
|
+
});
|
|
3861
|
+
blocked.push(...app.blocked);
|
|
3862
|
+
continue;
|
|
3863
|
+
}
|
|
3864
|
+
app.step.action.desired = { ...app.step.action.desired, workspaceCommit: commit };
|
|
3865
|
+
app.step.step.description = `${app.step.step.description} from the commit its workspace runs (${commit.slice(0, 12)})`;
|
|
3866
|
+
}
|
|
3569
3867
|
if (app.step)
|
|
3570
3868
|
steps.push(app.step);
|
|
3571
3869
|
blocked.push(...app.blocked);
|
|
3870
|
+
// A promote: the live app exists and runs another commit than the entry's `liveCommit`.
|
|
3871
|
+
if (goLive?.liveCommit !== undefined &&
|
|
3872
|
+
recordedIds[`coolify:app:${appStepKey(org, key)}`] &&
|
|
3873
|
+
recordedIds[wsKey.liveCommit(key)] !== goLive.liveCommit) {
|
|
3874
|
+
steps.push({
|
|
3875
|
+
action: {
|
|
3876
|
+
system: 'coolify',
|
|
3877
|
+
kind: 'promote',
|
|
3878
|
+
key: appStepKey(org, key),
|
|
3879
|
+
desired: { entry: key, commit: goLive.liveCommit },
|
|
3880
|
+
},
|
|
3881
|
+
step: {
|
|
3882
|
+
description: `put ${org.slug}'s live app for entry '${key}' on commit ${goLive.liveCommit.slice(0, 12)} and deploy it`,
|
|
3883
|
+
destroys: false,
|
|
3884
|
+
migrates: false,
|
|
3885
|
+
},
|
|
3886
|
+
});
|
|
3887
|
+
}
|
|
3572
3888
|
}
|
|
3573
3889
|
return { steps, blocked };
|
|
3574
3890
|
}
|
|
@@ -3713,6 +4029,8 @@ function planTunnel(org, byKey, claimed, recordedIds, platformApex) {
|
|
|
3713
4029
|
(org.site.enabled && Boolean(org.domain)) ||
|
|
3714
4030
|
(org.calls.enabled && org.calls.usesDomain && Boolean(org.domain)) ||
|
|
3715
4031
|
Boolean(org.domain) ||
|
|
4032
|
+
// F4 (#925): apps on, the tunnel carries the route to the company's Postgres server for Hyperdrive.
|
|
4033
|
+
appsOn(org) ||
|
|
3716
4034
|
// A company without a domain still gets a tunnel of its own for an entry at `<slug>.oikos.host`.
|
|
3717
4035
|
entryRoutes(org, recordedIds).length > 0;
|
|
3718
4036
|
const toggled = planHostingToggle('cloudflare', 'tunnel', desired, org, byKey, claimed, () => `provision a Cloudflare tunnel for ${org.slug}'s app`, () => `park ${org.slug}'s Cloudflare tunnel (configuration is retained, not deleted -- #7 corrections 1)`, org.id,
|
|
@@ -3764,10 +4082,13 @@ function planTunnel(org, byKey, claimed, recordedIds, platformApex) {
|
|
|
3764
4082
|
sortedNames(tunnelDnsHostnames(org, org.domain, loginHost));
|
|
3765
4083
|
// The entry names in the platform's oikos.host zone are recorded apart from the company's own zone.
|
|
3766
4084
|
const oikosStale = (recordedIds[CLOUDFLARE_TUNNEL_OIKOS_HOSTNAMES_SCOPE_KEY] ?? '') !==
|
|
3767
|
-
entryOikosHostnames(org).join(',');
|
|
4085
|
+
entryOikosHostnames(org, recordedIds).join(',');
|
|
4086
|
+
const appsStale = (recordedIds[CLOUDFLARE_TUNNEL_APPS_HOSTNAMES_SCOPE_KEY] ?? '') !==
|
|
4087
|
+
entryAppsHostnames(org, recordedIds).join(',');
|
|
3768
4088
|
const dnsStale = (org.domain !== null && recordedIds[CLOUDFLARE_TUNNEL_DNS_DOMAIN_SCOPE_KEY] !== org.domain) ||
|
|
3769
4089
|
edgeChanged ||
|
|
3770
|
-
oikosStale
|
|
4090
|
+
oikosStale ||
|
|
4091
|
+
appsStale;
|
|
3771
4092
|
// An entry's hostname goes straight to its container, so a changed port, key or name, or an ingress
|
|
3772
4093
|
// written before entries were routed that way, needs the ingress written again. Nothing recorded
|
|
3773
4094
|
// equals no routes.
|
|
@@ -3962,7 +4283,7 @@ function planEntryClients(org, byKey, claimed) {
|
|
|
3962
4283
|
* record blocks it (rule 2). A foreign broker entry the last write found
|
|
3963
4284
|
* invalid is listed as a warning: the broker drops it, foundry keeps it.
|
|
3964
4285
|
*/
|
|
3965
|
-
export function planBrokerEntries(org, recordedIds, plannedSteps, brokerWired) {
|
|
4286
|
+
export function planBrokerEntries(org, recordedIds, plannedSteps, brokerWired, brokerWorkspacesDatabase) {
|
|
3966
4287
|
const steps = [];
|
|
3967
4288
|
const blocked = [];
|
|
3968
4289
|
for (const entry of org.hosting.entries) {
|
|
@@ -3985,6 +4306,14 @@ export function planBrokerEntries(org, recordedIds, plannedSteps, brokerWired) {
|
|
|
3985
4306
|
});
|
|
3986
4307
|
continue;
|
|
3987
4308
|
}
|
|
4309
|
+
// The broker does not start once an entry declares workspaces without its database, and then no one signs in.
|
|
4310
|
+
if (auth.workspaces && !brokerWorkspacesDatabase) {
|
|
4311
|
+
blocked.push({
|
|
4312
|
+
description: `${at} is blocked, not planned: the broker has no ${BROKER_WORKSPACES_DATABASE_ENV}; hold the connection string of its workspaces database at ${workspacesDatabaseRef(org)}, then apply again`,
|
|
4313
|
+
key,
|
|
4314
|
+
});
|
|
4315
|
+
continue;
|
|
4316
|
+
}
|
|
3988
4317
|
// Auth ADR 0002: a broker given an `mcp` entry before it holds its database and secret exits at boot.
|
|
3989
4318
|
// A company's own sign-in step sets them and records it; a shared org's broker is the platform's, whose
|
|
3990
4319
|
// marker this plan cannot see, so `writeBrokerEntry` refuses there until the platform step has run.
|
|
@@ -4205,10 +4534,12 @@ const signInMetadataBlocked = (org) => ({
|
|
|
4205
4534
|
* gates (`planGates`) come first. Under a `'*'` gate, a bypass on each name
|
|
4206
4535
|
* foundry owns comes before them, so no owned name is ever gated.
|
|
4207
4536
|
*/
|
|
4208
|
-
function planAccess(org, recordedIds, providerId, platformHostnames) {
|
|
4209
|
-
const { steps: gates, blocked } = planGates(org, recordedIds, providerId);
|
|
4537
|
+
function planAccess(org, recordedIds, providerId, platformHostnames, declaredOrg = org) {
|
|
4538
|
+
const { steps: gates, blocked } = planGates(org, recordedIds, providerId, declaredOrg);
|
|
4539
|
+
// F11: a machine path of a gated entry is let through its gate, and a removed one is closed again.
|
|
4540
|
+
const machine = planMachineBypasses(org, recordedIds, declaredOrg);
|
|
4210
4541
|
if (!org.hosting.enabled)
|
|
4211
|
-
return { steps: gates, blocked };
|
|
4542
|
+
return { steps: [...gates, ...machine.steps], blocked, notes: machine.notes };
|
|
4212
4543
|
const owned = (hasWildcardGate(org) ? [...lockedHostnames(org, platformHostnames)].sort() : [])
|
|
4213
4544
|
.filter((hostname) => !recordedIds[resourceKey('cloudflare', 'access', appStepKey(org, hostname))])
|
|
4214
4545
|
.map((hostname) => ({
|
|
@@ -4239,7 +4570,7 @@ function planAccess(org, recordedIds, providerId, platformHostnames) {
|
|
|
4239
4570
|
migrates: false,
|
|
4240
4571
|
},
|
|
4241
4572
|
}));
|
|
4242
|
-
return { steps: [...owned, ...gates, ...rules], blocked };
|
|
4573
|
+
return { steps: [...owned, ...gates, ...machine.steps, ...rules], blocked, notes: machine.notes };
|
|
4243
4574
|
}
|
|
4244
4575
|
/** The plan description for one `hosting.access` rule, by kind (#246). */
|
|
4245
4576
|
function accessRuleDescription(org, rule) {
|