@wtfalch/foundry 0.73.0 → 0.75.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/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/migrations/0071_release_request_schedule.sql +12 -0
- package/dist/plan/broker-mcp.d.ts +7 -0
- package/dist/plan/broker-mcp.js +16 -0
- package/dist/plan/diff.js +4 -2
- package/dist/plan/own-diff.js +2 -2
- package/dist/plan/plan-apply.js +3 -0
- package/dist/plan/platform-diff.d.ts +6 -1
- package/dist/plan/platform-diff.js +6 -1
- package/dist/plan/platform-providers.js +4 -3
- package/dist/plan/release-host.d.ts +5 -1
- package/dist/plan/release-host.js +99 -50
- package/dist/release/decide.d.ts +9 -0
- package/dist/release/decide.js +1 -1
- package/dist/release/golive.d.ts +77 -0
- package/dist/release/golive.js +151 -0
- package/dist/release/run.d.ts +7 -1
- package/dist/release/run.js +205 -14
- package/dist/release/state.d.ts +82 -4
- package/dist/release/state.js +114 -2
- package/dist/release/types.d.ts +48 -2
- package/dist/store/postgres.js +97 -6
- package/dist/store/schema.d.ts +34 -0
- package/dist/store/schema.js +11 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -22,9 +22,9 @@ export { StaleLeaseError } from './store.js';
|
|
|
22
22
|
export type { PlanApply, DestroyProgress } from './apply.js';
|
|
23
23
|
export { createPostgresAppliedVersionsStore, createPostgresDeclaredOrgStore, createPostgresPlatformStore, createPostgresRequestedChangeStore, DeclaredOrgError, PlatformError, } from './store/postgres.js';
|
|
24
24
|
export { createPostgresAuditLog } from './store/audit.js';
|
|
25
|
-
export type { Color, ReleaseAction, ReleaseEvent, ReleaseEventKind, ReleaseSeries, ReleaseSeriesColor, ReleaseSignal, ReleaseState, WindowState, } from './release/types.js';
|
|
25
|
+
export type { Color, ReleaseAction, ReleaseEvent, ReleaseEventKind, ReleaseSeries, ReleaseSeriesColor, ReleaseRequirement, ReleaseSignal, ReleaseState, ReleasedVersion, WindowState, } from './release/types.js';
|
|
26
26
|
export type { ReleaseStore } from './release/state.js';
|
|
27
|
-
export { readReleaseHistory, readReleaseState, requestReleaseAction } from './release/state.js';
|
|
27
|
+
export { readReleaseHistory, readReleasedVersions, readReleaseState, requestReleaseAction, rescheduleRelease, } from './release/state.js';
|
|
28
28
|
export type { BuiltinFanOut, FanOutApp, FanOutOrgState } from './release/fanout.js';
|
|
29
29
|
export { readBuiltinFanOut } from './release/fanout.js';
|
|
30
30
|
export type { ReleaseSeriesHost, ReleaseSeriesQuery } from './release/series.js';
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ export { NATIVE_BUILD_REPO, NATIVE_BUILD_WORKFLOW_FILE, NATIVE_BUILD_WORKFLOW_RE
|
|
|
14
14
|
export { StaleLeaseError } from './store.js';
|
|
15
15
|
export { createPostgresAppliedVersionsStore, createPostgresDeclaredOrgStore, createPostgresPlatformStore, createPostgresRequestedChangeStore, DeclaredOrgError, PlatformError, } from './store/postgres.js';
|
|
16
16
|
export { createPostgresAuditLog } from './store/audit.js';
|
|
17
|
-
export { readReleaseHistory, readReleaseState, requestReleaseAction } from './release/state.js';
|
|
17
|
+
export { readReleaseHistory, readReleasedVersions, readReleaseState, requestReleaseAction, rescheduleRelease, } from './release/state.js';
|
|
18
18
|
export { readBuiltinFanOut } from './release/fanout.js';
|
|
19
19
|
export { readReleaseSeries } from './release/series.js';
|
|
20
20
|
export { readEntryReleaseSeries } from './plan/release-series-reader.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- @wtfalch/foundry: a release request can wait for a time. `not_before` is null for every request made
|
|
2
|
+
-- until now, so each of them stays due at once. The release runner reads only the open rows whose time has come.
|
|
3
|
+
-- `claimed_at` is set when the runner starts a due go-live: from then on it can no longer be cancelled or moved.
|
|
4
|
+
-- Additive: two nullable columns and one partial index; no existing row is touched.
|
|
5
|
+
-- Mirrors src/store/schema.ts's `foundryReleaseRequests`.
|
|
6
|
+
|
|
7
|
+
alter table foundry_release_requests add column if not exists not_before timestamptz;
|
|
8
|
+
alter table foundry_release_requests add column if not exists claimed_at timestamptz;
|
|
9
|
+
|
|
10
|
+
create index if not exists foundry_release_requests_scheduled_idx
|
|
11
|
+
on foundry_release_requests (org_id, entry_key, not_before)
|
|
12
|
+
where consumed_at is null and not_before is not null;
|
|
@@ -8,6 +8,13 @@ import { type JobRuntime } from './job-app.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare const MCP_DATABASE_URL_ENV = "MCP_DATABASE_URL";
|
|
10
10
|
export declare const MCP_OAUTH_SECRET_ENV = "MCP_OAUTH_SECRET";
|
|
11
|
+
/**
|
|
12
|
+
* The key that seals the broker's shared auth state (`wtfalch/auth` broker/src/auth/state-config.ts). The
|
|
13
|
+
* broker runs in shared mode whenever `MCP_DATABASE_URL` is set and exits at boot without this key: at
|
|
14
|
+
* least 32 bytes, the same on every replica, never regenerated. `AUTH_STATE_SECRET_PREVIOUS` (rotation)
|
|
15
|
+
* is the operator's and is never touched here.
|
|
16
|
+
*/
|
|
17
|
+
export declare const AUTH_STATE_SECRET_ENV = "AUTH_STATE_SECRET";
|
|
11
18
|
/** The broker's own role and database on the sign-in Postgres. */
|
|
12
19
|
export declare const BROKER_DB_NAME = "broker";
|
|
13
20
|
export declare const BROKER_DB_USER = "broker";
|
package/dist/plan/broker-mcp.js
CHANGED
|
@@ -11,6 +11,13 @@ import { SIGNIN_DB_CA_MOUNT, boundedCall } from './signin-db.js';
|
|
|
11
11
|
*/
|
|
12
12
|
export const MCP_DATABASE_URL_ENV = 'MCP_DATABASE_URL';
|
|
13
13
|
export const MCP_OAUTH_SECRET_ENV = 'MCP_OAUTH_SECRET';
|
|
14
|
+
/**
|
|
15
|
+
* The key that seals the broker's shared auth state (`wtfalch/auth` broker/src/auth/state-config.ts). The
|
|
16
|
+
* broker runs in shared mode whenever `MCP_DATABASE_URL` is set and exits at boot without this key: at
|
|
17
|
+
* least 32 bytes, the same on every replica, never regenerated. `AUTH_STATE_SECRET_PREVIOUS` (rotation)
|
|
18
|
+
* is the operator's and is never touched here.
|
|
19
|
+
*/
|
|
20
|
+
export const AUTH_STATE_SECRET_ENV = 'AUTH_STATE_SECRET';
|
|
14
21
|
/** The broker's own role and database on the sign-in Postgres. */
|
|
15
22
|
export const BROKER_DB_NAME = 'broker';
|
|
16
23
|
export const BROKER_DB_USER = 'broker';
|
|
@@ -84,9 +91,18 @@ export async function ensureBrokerMcpEnv(input) {
|
|
|
84
91
|
throw new Error(`${input.label}: the broker lost its ${MCP_OAUTH_SECRET_ENV} between two reads`);
|
|
85
92
|
}
|
|
86
93
|
runtime.onSecret?.(secret);
|
|
94
|
+
// The same rule as the OAuth secret: minted once, a live value (hand-set too) is kept, and it is written
|
|
95
|
+
// wherever `MCP_DATABASE_URL` is, so a broker never has the one without the other.
|
|
96
|
+
const mintedState = await ensureGeneratedEnv(coolify, brokerAppId, AUTH_STATE_SECRET_ENV, () => randomBytes(32).toString('hex'), MCP_OAUTH_SECRET_MIN_LENGTH);
|
|
97
|
+
const stateSecret = mintedState ?? (await currentEnvValue(coolify, brokerAppId, AUTH_STATE_SECRET_ENV));
|
|
98
|
+
if (!stateSecret) {
|
|
99
|
+
throw new Error(`${input.label}: the broker lost its ${AUTH_STATE_SECRET_ENV} between two reads`);
|
|
100
|
+
}
|
|
101
|
+
runtime.onSecret?.(stateSecret);
|
|
87
102
|
return [
|
|
88
103
|
{ key: MCP_DATABASE_URL_ENV, value: url },
|
|
89
104
|
{ key: MCP_OAUTH_SECRET_ENV, value: secret },
|
|
105
|
+
{ key: AUTH_STATE_SECRET_ENV, value: stateSecret },
|
|
90
106
|
];
|
|
91
107
|
}
|
|
92
108
|
/** Runs the job once, waits for it, deletes it. Every error is fixed text with the app id, never a cause. */
|
package/dist/plan/diff.js
CHANGED
|
@@ -48,7 +48,7 @@ import { OWN_AUTH_BLOCKED_KEY, OWN_FILES_BLOCKED_KEY, OWN_FILES_R2_BLOCKED_KEY,
|
|
|
48
48
|
import { ownDnsHostnames } from './own-dns.js';
|
|
49
49
|
import { ownHostnames } from './own-hostnames.js';
|
|
50
50
|
import { platformMailMxHost } from './platform-diff.js';
|
|
51
|
-
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, appImageAppliedScopeKey, appSecretAvailability, appSettingsOf, appStepKey, appliedAppDomains, appliedAppSettings, dbEnvAppliedScopeKey, entryImageRef, resolveAppEnv, serviceDbEnvAppliedScopeKey, versionNotAppliedLine, zitadelAppIdTokenUserinfoAppliedScopeKey, } from './platform-diff.js';
|
|
51
|
+
import { AUTH_MCP_ENV_VERSION, 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, appImageAppliedScopeKey, appSecretAvailability, appSettingsOf, appStepKey, appliedAppDomains, appliedAppSettings, dbEnvAppliedScopeKey, entryImageRef, resolveAppEnv, serviceDbEnvAppliedScopeKey, versionNotAppliedLine, zitadelAppIdTokenUserinfoAppliedScopeKey, } from './platform-diff.js';
|
|
52
52
|
import { DEFAULT_ORG_PLATFORM_READINESS, } from './platform-org.js';
|
|
53
53
|
import { PLATFORM_AUTH_BROKER_MAIL_KEY_REF } from './platform-providers.js';
|
|
54
54
|
import { companyAppPostLogoutUris, postLogoutUpdateOwed, zitadelAppPostLogoutAppliedScopeKey, } from './post-logout-uris.js';
|
|
@@ -4611,7 +4611,9 @@ export function planBrokerEntries(org, recordedIds, plannedSteps, brokerWired, b
|
|
|
4611
4611
|
// Auth ADR 0002: a broker given an `mcp` entry before it holds its database and secret exits at boot.
|
|
4612
4612
|
// A company's own sign-in step sets them and records it; a shared org's broker is the platform's, whose
|
|
4613
4613
|
// marker this plan cannot see, so `writeBrokerEntry` refuses there until the platform step has run.
|
|
4614
|
-
if (auth.mcp &&
|
|
4614
|
+
if (auth.mcp &&
|
|
4615
|
+
ownsServices(org) &&
|
|
4616
|
+
recordedIds[PLATFORM_AUTH_MCP_SCOPE_KEY] !== AUTH_MCP_ENV_VERSION) {
|
|
4615
4617
|
blocked.push({
|
|
4616
4618
|
description: `${at} is blocked, not planned: the broker's MCP database and secret are not set yet (the sign-in step sets them)`,
|
|
4617
4619
|
key,
|
package/dist/plan/own-diff.js
CHANGED
|
@@ -13,7 +13,7 @@ import { ownHostnames, passkeyRpIdFor } from './own-hostnames.js';
|
|
|
13
13
|
import { JOBS_PUBLIC_URL_SCOPE_KEY, OWN_JOBS_ROUTE_TUNNEL_SCOPE_KEY } from './own-jobs-route.js';
|
|
14
14
|
import { OWN_FILES_R2_SCOPE_KEY, OWN_SERVICES_APPLIED_SCOPE_KEY, orgTarget, } from './own-services.js';
|
|
15
15
|
import { OWN_SFU_APPLIED_HOSTNAME_SCOPE_KEY, OWN_SFU_APPLIED_NODE_IP_SCOPE_KEY, OWN_SFU_APPLIED_SLOT_SCOPE_KEY, OWN_SFU_APPLIED_VERSION_SCOPE_KEY, OWN_SFU_DEPLOY_PENDING_SCOPE_KEY, OWN_SFU_KEYS_UNDELIVERED_SCOPE_KEY, SFU_INTERNAL_URL_SCOPE_KEY, SFU_PUBLIC_URL_SCOPE_KEY, } from './own-sfu.js';
|
|
16
|
-
import { AUTH_TUNNEL_ROUTES_VERSION, AUTH_ZITADEL_RUN_OPTIONS, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_INTERNAL_URL_SCOPE_KEY, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_LOGIN_ENV_SCOPE_KEY, PLATFORM_AUTH_MCP_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_TUNNEL_ROUTES_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_SCOPE_KEY, PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, authLoginEnvMarker, orgDeclaresMcp, platformMailMxHost, } from './platform-diff.js';
|
|
16
|
+
import { AUTH_MCP_ENV_VERSION, AUTH_TUNNEL_ROUTES_VERSION, AUTH_ZITADEL_RUN_OPTIONS, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_INTERNAL_URL_SCOPE_KEY, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_LOGIN_ENV_SCOPE_KEY, PLATFORM_AUTH_MCP_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_TUNNEL_ROUTES_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_SCOPE_KEY, PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, authLoginEnvMarker, orgDeclaresMcp, platformMailMxHost, } from './platform-diff.js';
|
|
17
17
|
import { OWN_FILES_RUNNER_SCOPE_KEY } from './platform-org.js';
|
|
18
18
|
import { JOBS_ADMIN_TOKEN_UNDELIVERED_SCOPE_KEY, JOBS_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_AUTH_DB_SCOPE_KEY, PLATFORM_AUTH_LOGIN_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_SCOPE_KEY, PLATFORM_FILES_SHARE_DOMAINS_SCOPE_KEY, filesShareDomains, } from './platform-providers.js';
|
|
19
19
|
import { SIGN_IN_LOGIN_APP_SCOPE_KEY, inAppSignIn, signInLoginNames } from './sign-in.js';
|
|
@@ -273,7 +273,7 @@ export function planOwnAuth(options) {
|
|
|
273
273
|
(options.signinMailWritable === false ||
|
|
274
274
|
recordedIds[PLATFORM_AUTH_SIGNIN_MAIL_SCOPE_KEY] === signinMailMarker(org.displayName)) &&
|
|
275
275
|
// An entry with `mcp` needs the broker's database and secret (auth ADR 0002).
|
|
276
|
-
(!orgDeclaresMcp(org) || recordedIds[PLATFORM_AUTH_MCP_SCOPE_KEY] ===
|
|
276
|
+
(!orgDeclaresMcp(org) || recordedIds[PLATFORM_AUTH_MCP_SCOPE_KEY] === AUTH_MCP_ENV_VERSION) &&
|
|
277
277
|
(Boolean(recordedIds[PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY]) || !brokerHoldsAnEntry) &&
|
|
278
278
|
// In-app sign-in: the second login app is recorded once it is deployed and holds the token, which
|
|
279
279
|
// needs the login client; until the broker holds an entry that cannot be, so the step waits.
|
package/dist/plan/plan-apply.js
CHANGED
|
@@ -1207,6 +1207,8 @@ export class FoundryPlanApply {
|
|
|
1207
1207
|
host,
|
|
1208
1208
|
context,
|
|
1209
1209
|
now: this.now(),
|
|
1210
|
+
nowFn: () => this.now(),
|
|
1211
|
+
...(ownerOrgId !== null ? { ownerOrgId } : {}),
|
|
1210
1212
|
primaryUuid,
|
|
1211
1213
|
...(copies === 'defer' ? { deferCopies: true } : {}),
|
|
1212
1214
|
});
|
|
@@ -1248,6 +1250,7 @@ export class FoundryPlanApply {
|
|
|
1248
1250
|
host,
|
|
1249
1251
|
context,
|
|
1250
1252
|
now: this.now(),
|
|
1253
|
+
nowFn: () => this.now(),
|
|
1251
1254
|
primaryUuid,
|
|
1252
1255
|
...(copies === 'defer' ? { deferCopies: true } : {}),
|
|
1253
1256
|
});
|
|
@@ -87,11 +87,16 @@ export declare function authLoginEnvMarker(smtpSender: string, passkeyRpId?: str
|
|
|
87
87
|
/** The {@link authLoginEnvMarker} the login apps' env was last written with, once live on them. */
|
|
88
88
|
export declare const PLATFORM_AUTH_LOGIN_ENV_SCOPE_KEY = "auth_login_env_applied";
|
|
89
89
|
/**
|
|
90
|
-
* `
|
|
90
|
+
* `AUTH_MCP_ENV_VERSION` once the broker holds `MCP_DATABASE_URL`, `MCP_OAUTH_SECRET` and `AUTH_STATE_SECRET` (auth ADR 0002), recorded by the
|
|
91
91
|
* sign-in step when an entry of that broker declares `uses.auth.mcp`. A broker entry with `mcp` is not
|
|
92
92
|
* written before it, and an applied sign-in that wants it and lacks it plans its step again.
|
|
93
93
|
*/
|
|
94
94
|
export declare const PLATFORM_AUTH_MCP_SCOPE_KEY = "auth_mcp_env_applied";
|
|
95
|
+
/**
|
|
96
|
+
* The value of that marker. `'2'` since the broker also needs `AUTH_STATE_SECRET` (auth PR 510), so a
|
|
97
|
+
* broker applied under `'1'` plans its sign-in step once more and gets the key without a hand step.
|
|
98
|
+
*/
|
|
99
|
+
export declare const AUTH_MCP_ENV_VERSION = "2";
|
|
95
100
|
/** True when a hosting entry of the org declares `uses.auth.mcp`: its broker needs the MCP database and secret. */
|
|
96
101
|
export declare function orgDeclaresMcp(org: Pick<DeclaredOrg, 'hosting'>): boolean;
|
|
97
102
|
/**
|
|
@@ -96,11 +96,16 @@ export function authLoginEnvMarker(smtpSender, passkeyRpId) {
|
|
|
96
96
|
/** The {@link authLoginEnvMarker} the login apps' env was last written with, once live on them. */
|
|
97
97
|
export const PLATFORM_AUTH_LOGIN_ENV_SCOPE_KEY = 'auth_login_env_applied';
|
|
98
98
|
/**
|
|
99
|
-
* `
|
|
99
|
+
* `AUTH_MCP_ENV_VERSION` once the broker holds `MCP_DATABASE_URL`, `MCP_OAUTH_SECRET` and `AUTH_STATE_SECRET` (auth ADR 0002), recorded by the
|
|
100
100
|
* sign-in step when an entry of that broker declares `uses.auth.mcp`. A broker entry with `mcp` is not
|
|
101
101
|
* written before it, and an applied sign-in that wants it and lacks it plans its step again.
|
|
102
102
|
*/
|
|
103
103
|
export const PLATFORM_AUTH_MCP_SCOPE_KEY = 'auth_mcp_env_applied';
|
|
104
|
+
/**
|
|
105
|
+
* The value of that marker. `'2'` since the broker also needs `AUTH_STATE_SECRET` (auth PR 510), so a
|
|
106
|
+
* broker applied under `'1'` plans its sign-in step once more and gets the key without a hand step.
|
|
107
|
+
*/
|
|
108
|
+
export const AUTH_MCP_ENV_VERSION = '2';
|
|
104
109
|
/** True when a hosting entry of the org declares `uses.auth.mcp`: its broker needs the MCP database and secret. */
|
|
105
110
|
export function orgDeclaresMcp(org) {
|
|
106
111
|
return org.hosting.enabled && org.hosting.entries.some((e) => Boolean(e.uses?.auth?.mcp));
|
|
@@ -34,7 +34,7 @@ import { MAIL_APP_SCOPE_KEY, MAIL_DATABASE_NAME, MAIL_DATABASE_PASSWORD_ENV, MAI
|
|
|
34
34
|
import { OIKOS_INGRESS_BLOCKED, PLATFORM_OIKOS_INGRESS_KEY, oikosIngressApplied, oikosIngressZoneBlocked, provisionOikosIngress, } from './oikos-ingress.js';
|
|
35
35
|
import { recordOwnDnsHostname } from './own-dns.js';
|
|
36
36
|
import { platformNetwork, platformTarget, scopeFromPlatform, } from './own-services.js';
|
|
37
|
-
import { AUTH_TUNNEL_ROUTES_VERSION, AUTH_ZITADEL_RUN_OPTIONS, GITHUB_RELEASE_HOOK_DEPS_MISSING, GITHUB_RELEASE_WEBHOOK_SECRET_ENV, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_ACCESS_PROVIDER_APPLIED_AUTH_DOMAIN_SCOPE_KEY, PLATFORM_ACCESS_PROVIDER_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ACCESS_PROVIDER_UNDELIVERED_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_APP_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AI_APPLIED_GITREF_SCOPE_KEY, PLATFORM_AI_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_AI_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AI_MANAGEMENT_KEY_CONFLICT_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_SET_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_AI_TUNNEL_SCOPE_KEY, PLATFORM_ARCHON_ACCESS_PIN_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_AUTH_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_AUTH_ORG_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_CI_ORG_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_FILES_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_FIRST_OWNER_LINK_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY, PLATFORM_ARCHON_APP_KEY_ORG_SCOPE_KEY, PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY, PLATFORM_ARCHON_COOLIFY_READ_TOKEN_REF, PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY, PLATFORM_ARCHON_DESKTOP_CALLBACK_APPLIED_SCOPE_KEY, PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY, PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_ID_TOKEN_USERINFO_APPLIED_SCOPE_KEY, PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_KEY, PLATFORM_ARCHON_MASTER_CLOUDFLARE_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF, PLATFORM_ARCHON_ORG_DOMAIN_BLOCKED_SCOPE_KEY, PLATFORM_ARCHON_ORG_DOMAIN_SCOPE_KEY, PLATFORM_ARCHON_ORG_POLICY_SCOPE_KEY, PLATFORM_ARCHON_ORG_REGISTRATION_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_ORG_SCOPE_KEY, PLATFORM_ARCHON_OWNER_INVITE_ORG_SCOPE_KEY, PLATFORM_ARCHON_OWNER_INVITE_SCOPE_KEY, PLATFORM_ARCHON_POST_LOGOUT_APPLIED_SCOPE_KEY, PLATFORM_ARCHON_RETIRED_CLIENT_SCOPE_KEY, PLATFORM_ARCHON_UPTIME_TICKS_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_UPTIME_TOKEN_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_WEBHOOK_SECRET_DEPLOYED_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_LOGIN_ENV_SCOPE_KEY, PLATFORM_AUTH_MCP_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_ORG_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, PLATFORM_AUTH_REGISTRATION_CLOSED_SCOPE_KEY, PLATFORM_AUTH_RESERVED_ADMIN_USERNAME_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_TUNNEL_ROUTES_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_APPLIED_IMAGE_TAG_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_BACKUP_DATABASE_SCOPE_KEYS, PLATFORM_BACKUP_SERVICE_KEYS, PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_EDGE_APPLIED_USAGE_TAIL_SCOPE_KEY, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY, PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_GITHUB_RELEASE_HOOK_KEY, PLATFORM_HC_ACCESS_SCOPE_KEY, PLATFORM_HC_API_KEY_REF, PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_HC_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_HC_KEY, PLATFORM_HEARTBEATS_APPLIED_SCOPE_KEY, PLATFORM_HEARTBEATS_HOSTED_API_KEY_REF, PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY, PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_RUNNER_KEYRING_DEPLOYED_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_SCOPE_KEY, PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_MAIL_CONNECTOR_APP_SCOPE_KEY, PLATFORM_MAIL_HOST_PORTS_DIRECT, PLATFORM_MAIL_HOST_PORTS_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_RELAY_KEY, PLATFORM_MAIL_ROLE_CHECK_SCOPE_KEY, PLATFORM_MAIL_ROLE_ENSURED_SCOPE_KEY, PLATFORM_MAIL_ROLE_GRANTED_SCOPE_KEY, PLATFORM_MAIL_ROUTE_SCOPE_KEY, PLATFORM_MAIL_ROUTE_WANTED, PLATFORM_MAIL_USER_ROLE, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_OWNER_ORG_DOMAIN_BLOCKED_SCOPE_KEY, PLATFORM_OWNER_ORG_DOMAIN_SCOPE_KEY, PLATFORM_OWNER_ORG_SCOPE_KEY, PLATFORM_SFU_APPLIED_SCOPE_KEY, PLATFORM_UPTIME_ACCESS_CLIENT_ID_REF, PLATFORM_UPTIME_ACCESS_CLIENT_SECRET_REF, PLATFORM_UPTIME_ACCESS_TOKEN_ID_SCOPE_KEY, PLATFORM_UPTIME_ACCESS_TOKEN_NAME, PLATFORM_UPTIME_ACCESS_UNDELIVERED_SCOPE_KEY, PLATFORM_UPTIME_AUDIT_SEAL_TICK_ID_SCOPE_KEY, PLATFORM_UPTIME_AUDIT_SEAL_TICK_TOKEN_REF, PLATFORM_UPTIME_CHAT_ROOM_ID_SCOPE_KEY, PLATFORM_UPTIME_HEARTBEAT_CHECK_NAME, PLATFORM_UPTIME_KEY, PLATFORM_UPTIME_RELEASE_TICK_ID_SCOPE_KEY, PLATFORM_UPTIME_RELEASE_TICK_TOKEN_REF, PLATFORM_UPTIME_REMOVED_SCOPE_KEY, PLATFORM_UPTIME_SCRIPT_NAME, PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY, PLATFORM_UPTIME_TOKEN_REF, PLATFORM_USAGE_TAIL_KEY, PLATFORM_USAGE_TAIL_REMOVED_SCOPE_KEY, PLATFORM_USAGE_TAIL_SCRIPT_NAME, PLATFORM_ZITADEL_ORG_SCOPE_KEY, archonFilesMarker, archonFirstOwnerLink, archonOrgDomain, archonOrgName, authLoginEnvMarker, desiredHeartbeatCheckNames, heartbeatCheckUrlRef, orgDeclaresMcp, parseBrokerDeployRecord, parseLoginClientRecord, platformBackupHeartbeatCheckName, platformMailHostPorts, platformMailMxHost, uptimeAccessTokenConflict, uptimeEffectiveVars, uptimeRecorded, uptimeTicksSyncValue, uptimeVarsDigest, usageTailEdgeEnv, usageTailEdgeEnvValue, usageTailRecorded, } from './platform-diff.js';
|
|
37
|
+
import { AUTH_MCP_ENV_VERSION, AUTH_TUNNEL_ROUTES_VERSION, AUTH_ZITADEL_RUN_OPTIONS, GITHUB_RELEASE_HOOK_DEPS_MISSING, GITHUB_RELEASE_WEBHOOK_SECRET_ENV, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_ACCESS_PROVIDER_APPLIED_AUTH_DOMAIN_SCOPE_KEY, PLATFORM_ACCESS_PROVIDER_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ACCESS_PROVIDER_UNDELIVERED_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_APP_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AI_APPLIED_GITREF_SCOPE_KEY, PLATFORM_AI_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_AI_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AI_MANAGEMENT_KEY_CONFLICT_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_SET_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_AI_TUNNEL_SCOPE_KEY, PLATFORM_ARCHON_ACCESS_PIN_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_AUTH_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_AUTH_ORG_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_CI_ORG_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_FILES_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_FIRST_OWNER_LINK_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY, PLATFORM_ARCHON_APP_KEY_ORG_SCOPE_KEY, PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY, PLATFORM_ARCHON_COOLIFY_READ_TOKEN_REF, PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY, PLATFORM_ARCHON_DESKTOP_CALLBACK_APPLIED_SCOPE_KEY, PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY, PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_ID_TOKEN_USERINFO_APPLIED_SCOPE_KEY, PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_KEY, PLATFORM_ARCHON_MASTER_CLOUDFLARE_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF, PLATFORM_ARCHON_ORG_DOMAIN_BLOCKED_SCOPE_KEY, PLATFORM_ARCHON_ORG_DOMAIN_SCOPE_KEY, PLATFORM_ARCHON_ORG_POLICY_SCOPE_KEY, PLATFORM_ARCHON_ORG_REGISTRATION_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_ORG_SCOPE_KEY, PLATFORM_ARCHON_OWNER_INVITE_ORG_SCOPE_KEY, PLATFORM_ARCHON_OWNER_INVITE_SCOPE_KEY, PLATFORM_ARCHON_POST_LOGOUT_APPLIED_SCOPE_KEY, PLATFORM_ARCHON_RETIRED_CLIENT_SCOPE_KEY, PLATFORM_ARCHON_UPTIME_TICKS_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_UPTIME_TOKEN_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_WEBHOOK_SECRET_DEPLOYED_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_LOGIN_ENV_SCOPE_KEY, PLATFORM_AUTH_MCP_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_ORG_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, PLATFORM_AUTH_REGISTRATION_CLOSED_SCOPE_KEY, PLATFORM_AUTH_RESERVED_ADMIN_USERNAME_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_TUNNEL_ROUTES_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_APPLIED_IMAGE_TAG_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_BACKUP_DATABASE_SCOPE_KEYS, PLATFORM_BACKUP_SERVICE_KEYS, PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_EDGE_APPLIED_USAGE_TAIL_SCOPE_KEY, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY, PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_GITHUB_RELEASE_HOOK_KEY, PLATFORM_HC_ACCESS_SCOPE_KEY, PLATFORM_HC_API_KEY_REF, PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_HC_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_HC_KEY, PLATFORM_HEARTBEATS_APPLIED_SCOPE_KEY, PLATFORM_HEARTBEATS_HOSTED_API_KEY_REF, PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY, PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_RUNNER_KEYRING_DEPLOYED_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_SCOPE_KEY, PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_MAIL_CONNECTOR_APP_SCOPE_KEY, PLATFORM_MAIL_HOST_PORTS_DIRECT, PLATFORM_MAIL_HOST_PORTS_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_RELAY_KEY, PLATFORM_MAIL_ROLE_CHECK_SCOPE_KEY, PLATFORM_MAIL_ROLE_ENSURED_SCOPE_KEY, PLATFORM_MAIL_ROLE_GRANTED_SCOPE_KEY, PLATFORM_MAIL_ROUTE_SCOPE_KEY, PLATFORM_MAIL_ROUTE_WANTED, PLATFORM_MAIL_USER_ROLE, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_OWNER_ORG_DOMAIN_BLOCKED_SCOPE_KEY, PLATFORM_OWNER_ORG_DOMAIN_SCOPE_KEY, PLATFORM_OWNER_ORG_SCOPE_KEY, PLATFORM_SFU_APPLIED_SCOPE_KEY, PLATFORM_UPTIME_ACCESS_CLIENT_ID_REF, PLATFORM_UPTIME_ACCESS_CLIENT_SECRET_REF, PLATFORM_UPTIME_ACCESS_TOKEN_ID_SCOPE_KEY, PLATFORM_UPTIME_ACCESS_TOKEN_NAME, PLATFORM_UPTIME_ACCESS_UNDELIVERED_SCOPE_KEY, PLATFORM_UPTIME_AUDIT_SEAL_TICK_ID_SCOPE_KEY, PLATFORM_UPTIME_AUDIT_SEAL_TICK_TOKEN_REF, PLATFORM_UPTIME_CHAT_ROOM_ID_SCOPE_KEY, PLATFORM_UPTIME_HEARTBEAT_CHECK_NAME, PLATFORM_UPTIME_KEY, PLATFORM_UPTIME_RELEASE_TICK_ID_SCOPE_KEY, PLATFORM_UPTIME_RELEASE_TICK_TOKEN_REF, PLATFORM_UPTIME_REMOVED_SCOPE_KEY, PLATFORM_UPTIME_SCRIPT_NAME, PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY, PLATFORM_UPTIME_TOKEN_REF, PLATFORM_USAGE_TAIL_KEY, PLATFORM_USAGE_TAIL_REMOVED_SCOPE_KEY, PLATFORM_USAGE_TAIL_SCRIPT_NAME, PLATFORM_ZITADEL_ORG_SCOPE_KEY, archonFilesMarker, archonFirstOwnerLink, archonOrgDomain, archonOrgName, authLoginEnvMarker, desiredHeartbeatCheckNames, heartbeatCheckUrlRef, orgDeclaresMcp, parseBrokerDeployRecord, parseLoginClientRecord, platformBackupHeartbeatCheckName, platformMailHostPorts, platformMailMxHost, uptimeAccessTokenConflict, uptimeEffectiveVars, uptimeRecorded, uptimeTicksSyncValue, uptimeVarsDigest, usageTailEdgeEnv, usageTailEdgeEnvValue, usageTailRecorded, } from './platform-diff.js';
|
|
38
38
|
import { PLATFORM_REGISTRY_KEY, PLATFORM_REGISTRY_LOGINS_KEY, PLATFORM_REGISTRY_PASSWORDS_SET_SCOPE_KEY, PLATFORM_REGISTRY_PULL_PASSWORD_REF, registryImageTagBlocked, } from './platform-registry-keys.js';
|
|
39
39
|
import { ensureRegistryLogins, provisionPlatformRegistry, registryLoginsDrifted, } from './platform-registry.js';
|
|
40
40
|
import { PLATFORM_ARCHON_IN_APP_SCOPE_KEY, archonInAppBack, archonInAppBind, archonInAppHost, archonInAppPoint, archonInAppUnbind, } from './sign-in-archon.js';
|
|
@@ -914,7 +914,8 @@ export class FoundryPlatformStepExecutor {
|
|
|
914
914
|
if (platform.auth.enabled && this.auth) {
|
|
915
915
|
const loginClientDrifted = await authLoginClientDrifted(platform, this.auth, recordedIds);
|
|
916
916
|
// The broker lacks the MCP database and secret a shared org's entry needs (auth ADR 0002).
|
|
917
|
-
const mcpDrifted = recordedIds[PLATFORM_AUTH_MCP_SCOPE_KEY] !==
|
|
917
|
+
const mcpDrifted = recordedIds[PLATFORM_AUTH_MCP_SCOPE_KEY] !== AUTH_MCP_ENV_VERSION &&
|
|
918
|
+
(await this.mcpWanted());
|
|
918
919
|
// The broker has no registrar token yet (`ZITADEL_REGISTRAR_TOKEN`).
|
|
919
920
|
// A token minted and never held is blocked (`blockedSteps`), not drift: no apply could change it.
|
|
920
921
|
const registrarDrifted = this.auth.registrarTokenWritable === true &&
|
|
@@ -2420,7 +2421,7 @@ export async function provisionAuthStack(scope, deps, target, input) {
|
|
|
2420
2421
|
}
|
|
2421
2422
|
// The env is live on the broker (and deployed when it changed): the entry write may now carry `mcp`.
|
|
2422
2423
|
if (input.mcp)
|
|
2423
|
-
await record(PLATFORM_AUTH_MCP_SCOPE_KEY,
|
|
2424
|
+
await record(PLATFORM_AUTH_MCP_SCOPE_KEY, AUTH_MCP_ENV_VERSION);
|
|
2424
2425
|
// ---- tunnel and DNS ------------------------------------------------------
|
|
2425
2426
|
const tunnels = await network.tunnels();
|
|
2426
2427
|
const existingTunnel = await tunnels.findTunnelByName(target.names.authTunnel);
|
|
@@ -84,7 +84,11 @@ export declare function createReleaseHost(input: ReleaseHostInput): ReleaseHost;
|
|
|
84
84
|
* (like the next apply, through its `bluegreen-lb` step) writes it again.
|
|
85
85
|
*/
|
|
86
86
|
export declare function runSubjectRelease(input: {
|
|
87
|
-
store: Pick<ReleaseStore, 'openRequests' | 'consumeRequests'> & Partial<Pick<ReleaseStore, 'appendEvents'>>;
|
|
87
|
+
store: Pick<ReleaseStore, 'openRequests' | 'consumeRequests' | 'claimRequests'> & Partial<Pick<ReleaseStore, 'appendEvents'>>;
|
|
88
|
+
/** `DeclaredPlatform.ownerOrgId`: without it no go_live or set_required request runs. */
|
|
89
|
+
ownerOrgId?: string;
|
|
90
|
+
/** The clock for the repeated passes of a go-live; absent: `now` for every pass. */
|
|
91
|
+
nowFn?: () => Date;
|
|
88
92
|
scopeOwnerId: string;
|
|
89
93
|
subject: BlueGreenSubject;
|
|
90
94
|
host: ReleaseHost;
|
|
@@ -9,6 +9,9 @@ import { waitForDeploymentFinished, waitForNewDeployment, } from './deploy-healt
|
|
|
9
9
|
import { isNotFoundError } from './executor.js';
|
|
10
10
|
import { runStageCopyRequest } from './stage-copy-host.js';
|
|
11
11
|
const COMMIT = /^[0-9a-f]{40}$/;
|
|
12
|
+
const APP_RELEASE_ENV = ['APP_TAG', 'APP_RELEASE', 'APP_MINIMUM_RELEASE'];
|
|
13
|
+
/** One tick runs a pending go-live at most this many times (stage, idle, staff, open). */
|
|
14
|
+
const GO_LIVE_RUNS = 4;
|
|
12
15
|
/** A stage deploy waits at most this many polls of this long (10 minutes) for Coolify's deployment to finish. */
|
|
13
16
|
export const STAGE_DEPLOY_ATTEMPTS = 60;
|
|
14
17
|
export const STAGE_DEPLOY_INTERVAL_MS = 10_000;
|
|
@@ -109,7 +112,7 @@ export function createReleaseHost(input) {
|
|
|
109
112
|
const pinInput = () => pinInputOf(input, recorded);
|
|
110
113
|
const addressOf = (color) => colorAddress(input, recorded, color);
|
|
111
114
|
const healthColor = () => healthColorOf(recorded(), id);
|
|
112
|
-
const deployTo = async (color, digest) => {
|
|
115
|
+
const deployTo = async (color, digest, appEnv) => {
|
|
113
116
|
if (!COMMIT.test(digest))
|
|
114
117
|
throw new Error(`${input.label}: the release is not a built commit`);
|
|
115
118
|
const token = subject.reporting ? await input.healthToken() : null;
|
|
@@ -135,22 +138,40 @@ export function createReleaseHost(input) {
|
|
|
135
138
|
throw new Error(`${input.label}: reading the env of the blue app failed`);
|
|
136
139
|
}
|
|
137
140
|
}
|
|
141
|
+
// A go-live's own values replace blue's: the color boots with the tag's tag, release and minimum.
|
|
138
142
|
const refreshed = copiedEnv({ label: input.label, secret: input.secret }, subject, source, {
|
|
139
143
|
uuid: server,
|
|
140
144
|
primaryUuid: input.primaryUuid,
|
|
141
|
-
});
|
|
145
|
+
}).filter((e) => !appEnv || !APP_RELEASE_ENV.includes(e.key));
|
|
146
|
+
const release = appEnv
|
|
147
|
+
? [
|
|
148
|
+
{ key: 'APP_TAG', value: appEnv.tag },
|
|
149
|
+
{ key: 'APP_RELEASE', value: String(appEnv.release) },
|
|
150
|
+
...(appEnv.minimum === null
|
|
151
|
+
? []
|
|
152
|
+
: [{ key: 'APP_MINIMUM_RELEASE', value: String(appEnv.minimum) }]),
|
|
153
|
+
]
|
|
154
|
+
: [];
|
|
142
155
|
const reporting = token ? reportingEnv({ color, release: digest, healthToken: token }) : [];
|
|
143
156
|
try {
|
|
144
|
-
const envs = [...refreshed, ...reporting];
|
|
157
|
+
const envs = [...refreshed, ...reporting, ...release];
|
|
145
158
|
if (envs.length > 0) {
|
|
146
159
|
await coolify.bulkUpsertEnvs(appId, envs.map((e) => ({ ...e, literal: true })));
|
|
147
160
|
}
|
|
148
161
|
// A key removed from blue (a rotated-out credential) must not stay on the slot.
|
|
149
|
-
if (appId !== input.primaryBlueAppId) {
|
|
150
|
-
const keep = new Set([
|
|
162
|
+
if (appId !== input.primaryBlueAppId || (appEnv && appEnv.minimum === null)) {
|
|
163
|
+
const keep = new Set([
|
|
164
|
+
...(appId === input.primaryBlueAppId
|
|
165
|
+
? []
|
|
166
|
+
: source
|
|
167
|
+
.map((e) => e.key)
|
|
168
|
+
.filter((key) => !appEnv || !APP_RELEASE_ENV.includes(key))),
|
|
169
|
+
...envs.map((e) => e.key),
|
|
170
|
+
]);
|
|
151
171
|
for (const env of await coolify.listEnvs(appId)) {
|
|
152
172
|
if (env.uuid &&
|
|
153
173
|
!keep.has(env.key) &&
|
|
174
|
+
(appId !== input.primaryBlueAppId || env.key === 'APP_MINIMUM_RELEASE') &&
|
|
154
175
|
!env.key.startsWith('REPORTING_') &&
|
|
155
176
|
env.key !== ACTIONS_KEY_ENV) {
|
|
156
177
|
await coolify.deleteEnv(appId, env.uuid);
|
|
@@ -272,7 +293,7 @@ export function createReleaseHost(input) {
|
|
|
272
293
|
stageCopy: (request) => runStageCopyRequest({ subject, context, ports: input.stageCopy, renew: input.renew }, request),
|
|
273
294
|
async effect(effect) {
|
|
274
295
|
if (effect.kind === 'deploy')
|
|
275
|
-
await deployTo(effect.color, effect.digest);
|
|
296
|
+
await deployTo(effect.color, effect.digest, effect.appEnv);
|
|
276
297
|
else if (effect.kind === 'stop')
|
|
277
298
|
await stop(effect.color);
|
|
278
299
|
else if (effect.kind === 'deploy_stage')
|
|
@@ -355,53 +376,81 @@ export async function runSubjectRelease(input) {
|
|
|
355
376
|
context.recordScopeId(bgKey.weights(id), DEFAULT_WEIGHTS);
|
|
356
377
|
await context.persist();
|
|
357
378
|
}
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
},
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
379
|
+
/** Where a go-live stands: it moves when stage, a color's digest or the window changes. */
|
|
380
|
+
const stand = () => [
|
|
381
|
+
ids()[bgKey.stage(id)] ?? '',
|
|
382
|
+
ids()[bgKey.digest(id, 'blue')] ?? '',
|
|
383
|
+
ids()[bgKey.digest(id, 'green')] ?? '',
|
|
384
|
+
// A window never written reads as idle, so the first run's own write is not a change.
|
|
385
|
+
ids()[bgKey.window(id)] ?? '{"kind":"idle"}',
|
|
386
|
+
].join('|');
|
|
387
|
+
const pass = async (now) => {
|
|
388
|
+
await input.host.refreshStage();
|
|
389
|
+
const before = ids()[bgKey.weights(id)];
|
|
390
|
+
const result = await runRelease({
|
|
391
|
+
store: {
|
|
392
|
+
getScopeIds: async () => ({ ...ids() }),
|
|
393
|
+
openRequests: (query) => input.store.openRequests(query),
|
|
394
|
+
claimRequests: (claim) => input.store.claimRequests(claim),
|
|
395
|
+
},
|
|
396
|
+
orgId: input.scopeOwnerId,
|
|
397
|
+
...(input.ownerOrgId !== undefined ? { ownerOrgId: input.ownerOrgId } : {}),
|
|
398
|
+
entryKey: id,
|
|
399
|
+
entry: subject.config,
|
|
400
|
+
now,
|
|
401
|
+
deps: input.host.deps,
|
|
402
|
+
...(subject.reporting ? {} : { hardOnly: true }),
|
|
403
|
+
...(input.deferCopies ? { deferCopies: true } : {}),
|
|
404
|
+
});
|
|
405
|
+
for (const [key, value] of Object.entries(result.writes)) {
|
|
406
|
+
if (value === null)
|
|
407
|
+
context.forgetId(key);
|
|
408
|
+
else
|
|
409
|
+
context.recordScopeId(key, value);
|
|
387
410
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
411
|
+
await context.persist();
|
|
412
|
+
if (input.store.appendEvents && result.events.length > 0) {
|
|
413
|
+
try {
|
|
414
|
+
await input.store.appendEvents({
|
|
415
|
+
orgId: input.scopeOwnerId,
|
|
416
|
+
entryKey: id,
|
|
417
|
+
events: result.events,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
// The history is a record, not a gate: losing a line must not leave the run's requests open to replay.
|
|
422
|
+
console.error(`release ${id}: recording ${result.events.length} history lines failed`);
|
|
423
|
+
}
|
|
391
424
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
blocked: result.blocked,
|
|
401
|
-
refused: result.refused,
|
|
402
|
-
deferredCopy: result.deferredCopy,
|
|
403
|
-
copy: result.copy,
|
|
425
|
+
await input.store.consumeRequests(result.requestIds);
|
|
426
|
+
const after = parseWeights(ids()[bgKey.weights(id)]);
|
|
427
|
+
// Also when a balancer lags the weights (an earlier write failed), so the next tick retries it.
|
|
428
|
+
if (after &&
|
|
429
|
+
(formatWeights(after) !== before || balancerLags(ids(), id, formatWeights(after)))) {
|
|
430
|
+
await input.host.writeWeights();
|
|
431
|
+
}
|
|
432
|
+
return result;
|
|
404
433
|
};
|
|
434
|
+
let was = stand();
|
|
435
|
+
let result = await pass(input.now);
|
|
436
|
+
const seen = { blocked: new Set(result.blocked), refused: new Set(result.refused) };
|
|
437
|
+
let copy = result.copy;
|
|
438
|
+
let deferredCopy = result.deferredCopy;
|
|
439
|
+
// A go-live is stage, then idle, then staff, then open: each step is one run, and a step that worked makes the
|
|
440
|
+
// next one possible, so the tag goes live in one tick when nothing fails. A run that changed nothing stops it.
|
|
441
|
+
for (let more = 1; more < GO_LIVE_RUNS && result.goLiveOpen; more += 1) {
|
|
442
|
+
if (stand() === was)
|
|
443
|
+
break;
|
|
444
|
+
was = stand();
|
|
445
|
+
result = await pass(input.nowFn?.() ?? input.now);
|
|
446
|
+
for (const line of result.blocked)
|
|
447
|
+
seen.blocked.add(line);
|
|
448
|
+
for (const line of result.refused)
|
|
449
|
+
seen.refused.add(line);
|
|
450
|
+
copy ??= result.copy;
|
|
451
|
+
deferredCopy ||= result.deferredCopy;
|
|
452
|
+
}
|
|
453
|
+
return { blocked: [...seen.blocked], refused: [...seen.refused], deferredCopy, copy };
|
|
405
454
|
}
|
|
406
455
|
/**
|
|
407
456
|
* Runs the stage copy a release run decided on and records what came of it. Call it after
|
package/dist/release/decide.d.ts
CHANGED
|
@@ -79,6 +79,15 @@ export type ReleaseEffect = {
|
|
|
79
79
|
kind: 'deploy';
|
|
80
80
|
color: Color;
|
|
81
81
|
digest: string;
|
|
82
|
+
/**
|
|
83
|
+
* A go-live's `APP_TAG`, `APP_RELEASE` and `APP_MINIMUM_RELEASE` (removed when `minimum` is null). The host
|
|
84
|
+
* writes them to this color with the deploy, so the color boots with them; the serving color is never touched.
|
|
85
|
+
*/
|
|
86
|
+
appEnv?: {
|
|
87
|
+
tag: string;
|
|
88
|
+
release: number;
|
|
89
|
+
minimum: number | null;
|
|
90
|
+
};
|
|
82
91
|
} | {
|
|
83
92
|
kind: 'stop';
|
|
84
93
|
color: Color;
|
package/dist/release/decide.js
CHANGED
|
@@ -161,7 +161,7 @@ function applyActions(r, actions, now, out, kind) {
|
|
|
161
161
|
out.refused.push('promote refused: stage has no digest');
|
|
162
162
|
else if (cur.held.includes(digest))
|
|
163
163
|
out.refused.push('promote refused: digest is held');
|
|
164
|
-
else if (digest === cur.digests[cur.live])
|
|
164
|
+
else if (digest === cur.digests[cur.live] && !a.redeploy)
|
|
165
165
|
out.refused.push('promote refused: digest is already live');
|
|
166
166
|
else {
|
|
167
167
|
const to = otherColor(cur.live);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Recorded } from './decide.js';
|
|
2
|
+
import type { ReleaseAction, ReleaseEvent, ReleasedVersion } from './types.js';
|
|
3
|
+
export type GoLiveAction = Extract<ReleaseAction, {
|
|
4
|
+
kind: 'go_live';
|
|
5
|
+
}>;
|
|
6
|
+
export type SetRequiredAction = Extract<ReleaseAction, {
|
|
7
|
+
kind: 'set_required';
|
|
8
|
+
}>;
|
|
9
|
+
export type App = 'boule' | 'agora';
|
|
10
|
+
/** A go-live tries each deploy step this many ticks before it is refused. */
|
|
11
|
+
export declare const GO_LIVE_TRIES = 3;
|
|
12
|
+
/** `bg.<entry>.golive`: `<request id>|<stage deploys tried>|<release reserved by the idle deploy>|<idle deploys failed>`. */
|
|
13
|
+
export interface GoLiveState {
|
|
14
|
+
requestId: number;
|
|
15
|
+
tries: number;
|
|
16
|
+
release: number | null;
|
|
17
|
+
idle: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function parseGoLive(value: string | undefined): GoLiveState | null;
|
|
20
|
+
export declare function formatGoLive(s: GoLiveState): string;
|
|
21
|
+
/** What one due `go_live` request comes to in this run. */
|
|
22
|
+
export interface GoLivePlan {
|
|
23
|
+
/** For `decide`: `deploy_stage`, `promote` or `open` (never more than one). */
|
|
24
|
+
actions: ReleaseAction[];
|
|
25
|
+
/** The `promote` among `actions`, when there is one: its deploy carries the tag's env. */
|
|
26
|
+
promote?: ReleaseAction;
|
|
27
|
+
/** The tag completed in this run: record it. */
|
|
28
|
+
record: boolean;
|
|
29
|
+
/** The request is consumed. False: it stays open and the next run judges it again. */
|
|
30
|
+
consume: boolean;
|
|
31
|
+
/** The runner starts this go-live: the request is claimed before any effect, so it can no longer be cancelled or moved. */
|
|
32
|
+
claim: boolean;
|
|
33
|
+
/** The env the idle color is deployed with (only with `promote`). */
|
|
34
|
+
appEnv?: {
|
|
35
|
+
tag: string;
|
|
36
|
+
release: number;
|
|
37
|
+
minimum: number | null;
|
|
38
|
+
};
|
|
39
|
+
/** The tries to carry into `bg.<entry>.golive` when the idle deploy worked or failed (only with `promote`). */
|
|
40
|
+
carry?: {
|
|
41
|
+
tries: number;
|
|
42
|
+
idle: number;
|
|
43
|
+
};
|
|
44
|
+
/** `bg.<entry>.golive` after this run (null removes it); not used with `promote`. */
|
|
45
|
+
state?: GoLiveState | null;
|
|
46
|
+
refused?: string;
|
|
47
|
+
blocked?: string;
|
|
48
|
+
events: Array<Pick<ReleaseEvent, 'kind' | 'reason'>>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Decides one `go_live`. Stage is deployed at the tag's commit when it differs, then the idle color is deployed
|
|
52
|
+
* with the tag's env (`promote`, also for a commit the live color already runs), then it is opened (the staff hold
|
|
53
|
+
* is passed here: stage was the review) only when the idle color runs and answers, and the request waits until
|
|
54
|
+
* the release completes, one color at 100 % with the window closed. Only then is the version recorded, and only
|
|
55
|
+
* when `bg.<entry>.golive` holds the release number this request's own idle deploy reserved: a tag no color
|
|
56
|
+
* carries is never recorded. Each deploy step is tried `GO_LIVE_TRIES` ticks, a history line for each retry.
|
|
57
|
+
*/
|
|
58
|
+
export declare function planGoLive(input: {
|
|
59
|
+
app: App;
|
|
60
|
+
action: GoLiveAction;
|
|
61
|
+
requestId: number;
|
|
62
|
+
recorded: Recorded;
|
|
63
|
+
ids: Readonly<Record<string, string>>;
|
|
64
|
+
released: readonly ReleasedVersion[];
|
|
65
|
+
now: Date;
|
|
66
|
+
/** The new color's observation (only meaningful while a release runs). */
|
|
67
|
+
observation: {
|
|
68
|
+
status: 'running' | 'crash_loop' | 'other';
|
|
69
|
+
address: 'ok' | 'failed' | 'unchecked';
|
|
70
|
+
};
|
|
71
|
+
}): GoLivePlan;
|
|
72
|
+
/** The highest release at or below `release` that is required; null when none. */
|
|
73
|
+
export declare function minimumFor(versions: readonly ReleasedVersion[], release: number): number | null;
|
|
74
|
+
/** A free release number: the unix second of `now`, or the next free one when a version already took it. */
|
|
75
|
+
export declare function releaseNumber(now: Date, versions: readonly ReleasedVersion[]): number;
|
|
76
|
+
/** The scope writes that record `v`. */
|
|
77
|
+
export declare function recordWrites(v: ReleasedVersion): Record<string, string>;
|