@wtfalch/foundry 0.46.0 → 0.48.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/build-json.d.ts +28 -0
- package/dist/build-json.js +95 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +6 -2
- package/dist/migrations/0058_app_requests.sql +32 -0
- package/dist/migrations/0059_org_builtin_blue_green.sql +9 -0
- package/dist/org.d.ts +47 -5
- package/dist/org.js +176 -128
- package/dist/plan/archon-blue-green.js +15 -1
- package/dist/plan/blue-green-apply.d.ts +35 -0
- package/dist/plan/blue-green-apply.js +167 -16
- package/dist/plan/blue-green.d.ts +97 -2
- package/dist/plan/blue-green.js +214 -13
- package/dist/plan/database-dump.d.ts +37 -0
- package/dist/plan/database-dump.js +101 -0
- package/dist/plan/diff.js +9 -5
- package/dist/plan/executor.d.ts +48 -0
- package/dist/plan/executor.js +87 -28
- package/dist/plan/health-targets.js +1 -2
- package/dist/plan/hostnames.d.ts +2 -2
- package/dist/plan/hostnames.js +6 -6
- package/dist/plan/index.d.ts +3 -1
- package/dist/plan/index.js +5 -1
- package/dist/plan/locked-hostnames.d.ts +1 -0
- package/dist/plan/locked-hostnames.js +1 -0
- package/dist/plan/plan-apply.d.ts +30 -1
- package/dist/plan/plan-apply.js +86 -8
- package/dist/plan/platform-diff.js +2 -1
- package/dist/plan/platform-providers.js +5 -0
- package/dist/plan/providers-readers.js +15 -4
- package/dist/plan/proxy-labels.d.ts +13 -1
- package/dist/plan/proxy-labels.js +26 -3
- package/dist/plan/release-host.d.ts +1 -1
- package/dist/plan/release-host.js +36 -14
- package/dist/plan/stage-env.d.ts +38 -0
- package/dist/plan/stage-env.js +52 -0
- package/dist/plan/stage-guards.d.ts +104 -0
- package/dist/plan/stage-guards.js +149 -0
- package/dist/plan/stage-isolation.d.ts +57 -0
- package/dist/plan/stage-isolation.js +248 -0
- package/dist/plan/worker-app.js +3 -1
- package/dist/refuse-org.d.ts +7 -0
- package/dist/refuse-org.js +12 -0
- package/dist/release/decide.d.ts +5 -0
- package/dist/release/decide.js +10 -6
- package/dist/release/health.d.ts +6 -1
- package/dist/release/health.js +10 -2
- package/dist/release/run.d.ts +2 -0
- package/dist/release/run.js +2 -1
- package/dist/requested-changes.d.ts +50 -4
- package/dist/requested-changes.js +9 -1
- package/dist/store/postgres.d.ts +5 -1
- package/dist/store/postgres.js +86 -8
- package/dist/store/schema.d.ts +111 -1
- package/dist/store/schema.js +7 -0
- package/dist/store.d.ts +24 -1
- package/dist/worker-app-config.d.ts +8 -2
- package/dist/worker-app-config.js +20 -6
- package/package.json +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type WorkerAppConfig } from './worker-app-config.js';
|
|
3
|
+
/**
|
|
4
|
+
* The `build.json` a company's release carries: what Archon needs to deploy one build of a Worker
|
|
5
|
+
* app. Same shapes as `WorkerAppConfig`; `repo` and `commit` say where the build came from.
|
|
6
|
+
* Whether `artifactUrl` is an asset of that release is checked by the reader that fetched it.
|
|
7
|
+
*/
|
|
8
|
+
export interface BuildJson {
|
|
9
|
+
repo: string;
|
|
10
|
+
commit: string;
|
|
11
|
+
artifactUrl: string;
|
|
12
|
+
artifactSha256: string;
|
|
13
|
+
compatibilityDate?: string;
|
|
14
|
+
compatibilityFlags?: 'nodejs_compat'[];
|
|
15
|
+
durableObjects: WorkerAppConfig['durableObjects'];
|
|
16
|
+
containers?: WorkerAppConfig['containers'];
|
|
17
|
+
migrationsSha256?: string;
|
|
18
|
+
}
|
|
19
|
+
export type BuildJsonErrorCode = 'unknown_field' | 'missing_field' | 'wrong_type' | 'not_https' | 'bad_sha256';
|
|
20
|
+
/** Thrown by {@link parseBuildJson}. `path` is dotted into the build.json; the message never carries a value. */
|
|
21
|
+
export declare class BuildJsonError extends Error {
|
|
22
|
+
readonly code: BuildJsonErrorCode;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
constructor(code: BuildJsonErrorCode, path: string);
|
|
25
|
+
}
|
|
26
|
+
export declare const buildJsonSchema: z.ZodType<BuildJson>;
|
|
27
|
+
/** Parses a build.json. Throws {@link BuildJsonError} for the first problem. */
|
|
28
|
+
export declare function parseBuildJson(input: unknown): BuildJson;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { WORKER_APP_COMPATIBILITY_FLAGS, WORKER_APP_INSTANCE_TYPES, WORKER_APP_MAX_INSTANCES, } from './worker-app-config.js';
|
|
3
|
+
/** Thrown by {@link parseBuildJson}. `path` is dotted into the build.json; the message never carries a value. */
|
|
4
|
+
export class BuildJsonError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
path;
|
|
7
|
+
constructor(code, path) {
|
|
8
|
+
super(`build.json refused (${code}) at ${path || '(root)'}`);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.path = path;
|
|
11
|
+
this.name = 'BuildJsonError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
15
|
+
const DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
16
|
+
const sha256 = z.string().regex(SHA256_HEX, { message: 'bad_sha256' });
|
|
17
|
+
const text = z.string().min(1);
|
|
18
|
+
const httpsUrl = z.string().superRefine((value, ctx) => {
|
|
19
|
+
let url = null;
|
|
20
|
+
try {
|
|
21
|
+
url = new URL(value);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// not a URL: refused below
|
|
25
|
+
}
|
|
26
|
+
if (url === null || url.protocol !== 'https:') {
|
|
27
|
+
ctx.addIssue({ code: 'custom', message: 'not_https' });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const durableObjects = z.strictObject({
|
|
31
|
+
classes: z.record(z.string(), z.string()),
|
|
32
|
+
migrations: z.array(z.strictObject({
|
|
33
|
+
tag: z.string(),
|
|
34
|
+
newSqliteClasses: z.array(z.string()).optional(),
|
|
35
|
+
deletedClasses: z.array(z.string()).optional(),
|
|
36
|
+
renamedClasses: z.array(z.strictObject({ from: z.string(), to: z.string() })).optional(),
|
|
37
|
+
})),
|
|
38
|
+
});
|
|
39
|
+
const container = z.strictObject({
|
|
40
|
+
className: z.string(),
|
|
41
|
+
image: z.strictObject({
|
|
42
|
+
repository: z.string(),
|
|
43
|
+
digest: z.string().regex(DIGEST, { message: 'wrong_type' }),
|
|
44
|
+
}),
|
|
45
|
+
instanceType: z.string().refine((v) => WORKER_APP_INSTANCE_TYPES.has(v), {
|
|
46
|
+
message: 'wrong_type',
|
|
47
|
+
}),
|
|
48
|
+
maxInstances: z.number().int().min(1).max(WORKER_APP_MAX_INSTANCES),
|
|
49
|
+
});
|
|
50
|
+
export const buildJsonSchema = z.strictObject({
|
|
51
|
+
repo: text,
|
|
52
|
+
commit: text,
|
|
53
|
+
artifactUrl: httpsUrl,
|
|
54
|
+
artifactSha256: sha256,
|
|
55
|
+
compatibilityDate: z.string().optional(),
|
|
56
|
+
compatibilityFlags: z
|
|
57
|
+
.array(z.string().refine((v) => WORKER_APP_COMPATIBILITY_FLAGS.has(v), { message: 'wrong_type' }))
|
|
58
|
+
.optional(),
|
|
59
|
+
durableObjects,
|
|
60
|
+
containers: z.array(container).optional(),
|
|
61
|
+
migrationsSha256: sha256.optional(),
|
|
62
|
+
});
|
|
63
|
+
function valueAt(input, path) {
|
|
64
|
+
let current = input;
|
|
65
|
+
for (const key of path) {
|
|
66
|
+
if (current === null || typeof current !== 'object')
|
|
67
|
+
return undefined;
|
|
68
|
+
current = current[key];
|
|
69
|
+
}
|
|
70
|
+
return current;
|
|
71
|
+
}
|
|
72
|
+
function dotted(path) {
|
|
73
|
+
return path.map(String).join('.');
|
|
74
|
+
}
|
|
75
|
+
/** Parses a build.json. Throws {@link BuildJsonError} for the first problem. */
|
|
76
|
+
export function parseBuildJson(input) {
|
|
77
|
+
const result = buildJsonSchema.safeParse(input);
|
|
78
|
+
if (result.success)
|
|
79
|
+
return result.data;
|
|
80
|
+
const issue = result.error.issues[0];
|
|
81
|
+
if (!issue)
|
|
82
|
+
throw new BuildJsonError('wrong_type', '');
|
|
83
|
+
if (issue.code === 'unrecognized_keys') {
|
|
84
|
+
const base = dotted(issue.path);
|
|
85
|
+
const key = issue.keys[0] ?? '';
|
|
86
|
+
throw new BuildJsonError('unknown_field', base ? `${base}.${key}` : key);
|
|
87
|
+
}
|
|
88
|
+
const path = dotted(issue.path);
|
|
89
|
+
if (issue.message === 'not_https' || issue.message === 'bad_sha256') {
|
|
90
|
+
throw new BuildJsonError(issue.message, path);
|
|
91
|
+
}
|
|
92
|
+
if (valueAt(input, issue.path) === undefined)
|
|
93
|
+
throw new BuildJsonError('missing_field', path);
|
|
94
|
+
throw new BuildJsonError('wrong_type', path);
|
|
95
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, HostingEntry, WorkerAppConfig, EntryGate, EntryVolume, HostingEntryUses, EntryAuth, EntryFiles, EntryAi, EnvValue, EnvHeldSecret, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, OwnerIdentity, OwnerMailbox, DeclaredOrg, OrgServiceDatabase, OrgServiceSchema, ServicesConfig, SignInConfig, VersionPin, VersionPinPart, } from './org.js';
|
|
2
|
-
export { APP_ENTRY_KEY, DEFAULT_GATE_LIMIT, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, orgServiceDatabase, orgServiceDatabases, SFU_SLOT_MAX, VERSION_PIN_PARTS,
|
|
1
|
+
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, HostingEntry, BlueGreenConfig, OrgBuiltinApp, OrgRefusal, OrgRefusalCode, WorkerAppConfig, EntryGate, EntryVolume, HostingEntryUses, EntryAuth, EntryFiles, EntryAi, EnvValue, EnvHeldSecret, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, OwnerIdentity, OwnerMailbox, DeclaredOrg, OrgServiceDatabase, OrgServiceSchema, ServicesConfig, SignInConfig, VersionPin, VersionPinPart, } from './org.js';
|
|
2
|
+
export { APP_ENTRY_KEY, DEFAULT_GATE_LIMIT, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, orgServiceDatabase, orgServiceDatabases, SFU_SLOT_MAX, VERSION_PIN_PARTS, isMainEntry, mainEntry, heldEnvRef, ownerMailboxes, ownsServices, validateBuyerIdentity, validateHosting, validateMail, validateOwners, validateServices, validateCustomers, validateSignIn, validateVersionPin, validateSite, } from './org.js';
|
|
3
|
+
export { refuseOrg } from './refuse-org.js';
|
|
4
|
+
export { workerAppHasBuild } from './worker-app-config.js';
|
|
3
5
|
export type { DeclaredPlatform, PlatformService, PlatformServiceKey, PlatformSfuService, HeartbeatsProvider, } from './platform.js';
|
|
4
6
|
export { PLATFORM_SERVICE_KEYS, HEARTBEATS_PROVIDERS, defaultPlatformHostname, validatePlatform, } from './platform.js';
|
|
5
7
|
import { type PinnedVersions } from './bootstrap/versions.js';
|
|
6
8
|
export type { PinnedVersions };
|
|
7
9
|
export declare const PINNED_VERSIONS: Readonly<PinnedVersions>;
|
|
8
|
-
export type { RequestableField, CallsChange, CustomersChange, StorageLimitChange, AiLimitChange, DomainChange, MailTierChange, RequestedChangeValue, ChangeClassification, RequestedChangeState, RequestedChange, } from './requested-changes.js';
|
|
10
|
+
export type { RequestableField, CallsChange, CustomersChange, StorageLimitChange, AiLimitChange, DomainChange, MailTierChange, RequestedChangeValue, ChangeClassification, RequestedChangeState, RequestedChange, AppChange, BuildChange, FailureCode, RequestedBy, } from './requested-changes.js';
|
|
11
|
+
export type { BuildJson, BuildJsonErrorCode } from './build-json.js';
|
|
12
|
+
export { buildJsonSchema, parseBuildJson, BuildJsonError } from './build-json.js';
|
|
9
13
|
export { REQUESTABLE_FIELDS, classifyChange, RequestedChangeError } from './requested-changes.js';
|
|
10
14
|
export type { SlugRejectionReason, SlugRejection } from './slug.js';
|
|
11
15
|
export { validateSlug, slugFromName, RESERVED_SLUGS } from './slug.js';
|
|
@@ -25,13 +29,15 @@ export { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryAudit
|
|
|
25
29
|
export type { LiveResource, ZitadelReader, CoolifyReader, CloudflareReader, FilesReader, AiReader, CmsReader, LiveReaders, PlatformCloudflareReader, PlatformCoolifyReader, PlatformLiveReaders, StepAction, PlannedStep, StepSystem, PlanDetail, OrgPostgresPlan, PlatformPlanDetail, MailRoute, AdoptCandidate, AdoptInput, AdoptPlan, AdoptSnapshot, AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, FoundryApplyOptions, UnconfirmedUpgrade, } from './plan/index.js';
|
|
26
30
|
export { COMPANY_DESTINATION_NAME_PREFIX, COMPANY_NETWORKS, DEFAULT_MAX_DESTINATIONS, DestinationCapacityError, PLATFORM_NETWORK, companyNetworkName, ensureDestination, findDestination, placed, placedOnPlatform, validateNetworkName, } from './plan/index.js';
|
|
27
31
|
export { AdoptRefusedError } from './plan/index.js';
|
|
32
|
+
export { DumpError, dumpNow } from './plan/index.js';
|
|
33
|
+
export type { DatabaseDump } from './plan/index.js';
|
|
28
34
|
export { InvalidMigrationConfirmationError, ORG_POSTGRES_MIGRATION_CONFIRMED_SCOPE_KEY, OrgPostgresMigrationBackupMissingError, OrgPostgresMigrationNotConfirmedError, } from './plan/index.js';
|
|
29
35
|
export type { OrgPostgresMigrationConfirmation } from './plan/index.js';
|
|
30
36
|
export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './plan/index.js';
|
|
31
37
|
export type { BackupGatePorts } from './plan/index.js';
|
|
32
38
|
export { createEdgeAccess, createEdgeDns, edgeDnsRecordOwners, createEdgeRateLimits, createEdgeTunnels, createEdgeWorkers, createEdgeZones, } from './plan/index.js';
|
|
33
39
|
export type { AccessAppSummary, AccessDecision, AccessPolicyRule, AccessPort, DnsPort, EdgePlatformAccess, EdgePlatformDns, EdgePlatformRateLimits, EdgePlatformWorkers, EdgePlatformZones, EdgeZonesOptions, ZonePort, EdgeRateLimitsOptions, RateLimitPort, RateLimitRuleInput, EdgeWorkersOptions, EdgePlatformTunnels, TunnelPort, } from './plan/index.js';
|
|
34
|
-
export { readLiveResources, readLivePlatformResources, computePlanDetail, toPlan, planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_SFU_APPLIED_SCOPE_KEY, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, UpgradeConfirmationRequiredError, planNeedsConfirmation, } from './plan/index.js';
|
|
40
|
+
export { readLiveResources, readLivePlatformResources, computePlanDetail, toPlan, planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_SFU_APPLIED_SCOPE_KEY, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, UpgradeConfirmationRequiredError, planNeedsConfirmation, stepEntryKey, } from './plan/index.js';
|
|
35
41
|
export type { HeldKeyResolver, OrgPlatformReadiness } from './plan/index.js';
|
|
36
42
|
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesForOwn, agoraHostValuesFromPlatform, heldOwnKeyRefs, heldPlatformKeyRefs, orgPlatformReadinessFor, orgPlatformReadinessFrom, } from './plan/index.js';
|
|
37
43
|
export type { PlatformOrgClientFactories } from './plan/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
export { APP_ENTRY_KEY, DEFAULT_GATE_LIMIT, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, orgServiceDatabase, orgServiceDatabases, SFU_SLOT_MAX, VERSION_PIN_PARTS,
|
|
1
|
+
export { APP_ENTRY_KEY, DEFAULT_GATE_LIMIT, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, orgServiceDatabase, orgServiceDatabases, SFU_SLOT_MAX, VERSION_PIN_PARTS, isMainEntry, mainEntry, heldEnvRef, ownerMailboxes, ownsServices, validateBuyerIdentity, validateHosting, validateMail, validateOwners, validateServices, validateCustomers, validateSignIn, validateVersionPin, validateSite, } from './org.js';
|
|
2
|
+
export { refuseOrg } from './refuse-org.js';
|
|
3
|
+
export { workerAppHasBuild } from './worker-app-config.js';
|
|
2
4
|
export { PLATFORM_SERVICE_KEYS, HEARTBEATS_PROVIDERS, defaultPlatformHostname, validatePlatform, } from './platform.js';
|
|
3
5
|
// archon#273: the pinned platform service versions the planner deploys, so a
|
|
4
6
|
// host can show declared against running. Readonly: it is the planner's own
|
|
5
7
|
// object, not a copy.
|
|
6
8
|
import { VERSIONS } from './bootstrap/versions.js';
|
|
7
9
|
export const PINNED_VERSIONS = VERSIONS;
|
|
10
|
+
export { buildJsonSchema, parseBuildJson, BuildJsonError } from './build-json.js';
|
|
8
11
|
export { REQUESTABLE_FIELDS, classifyChange, RequestedChangeError } from './requested-changes.js';
|
|
9
12
|
export { validateSlug, slugFromName, RESERVED_SLUGS } from './slug.js';
|
|
10
13
|
export { StaleLeaseError } from './store.js';
|
|
@@ -15,10 +18,11 @@ export { createPostgresReleaseStore } from './store/postgres.js';
|
|
|
15
18
|
export { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryAuditEvents, foundryDeclaredPlatform, foundryReleaseRequests, foundryRequestedChanges, } from './store/schema.js';
|
|
16
19
|
export { COMPANY_DESTINATION_NAME_PREFIX, COMPANY_NETWORKS, DEFAULT_MAX_DESTINATIONS, DestinationCapacityError, PLATFORM_NETWORK, companyNetworkName, ensureDestination, findDestination, placed, placedOnPlatform, validateNetworkName, } from './plan/index.js';
|
|
17
20
|
export { AdoptRefusedError } from './plan/index.js';
|
|
21
|
+
export { DumpError, dumpNow } from './plan/index.js';
|
|
18
22
|
export { InvalidMigrationConfirmationError, ORG_POSTGRES_MIGRATION_CONFIRMED_SCOPE_KEY, OrgPostgresMigrationBackupMissingError, OrgPostgresMigrationNotConfirmedError, } from './plan/index.js';
|
|
19
23
|
export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './plan/index.js';
|
|
20
24
|
export { createEdgeAccess, createEdgeDns, edgeDnsRecordOwners, createEdgeRateLimits, createEdgeTunnels, createEdgeWorkers, createEdgeZones, } from './plan/index.js';
|
|
21
|
-
export { readLiveResources, readLivePlatformResources, computePlanDetail, toPlan, planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_SFU_APPLIED_SCOPE_KEY, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, UpgradeConfirmationRequiredError, planNeedsConfirmation, } from './plan/index.js';
|
|
25
|
+
export { readLiveResources, readLivePlatformResources, computePlanDetail, toPlan, planPlatform, toPlatformPlan, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_SFU_APPLIED_SCOPE_KEY, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, DeclaredPlatformNotFoundError, PlatformStalePlanError, PlatformConfirmationRequiredError, UpgradeConfirmationRequiredError, planNeedsConfirmation, stepEntryKey, } from './plan/index.js';
|
|
22
26
|
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesForOwn, agoraHostValuesFromPlatform, heldOwnKeyRefs, heldPlatformKeyRefs, orgPlatformReadinessFor, orgPlatformReadinessFrom, } from './plan/index.js';
|
|
23
27
|
export { PlatformOrgBlockedError, PlatformOrgPorts } from './plan/index.js';
|
|
24
28
|
export { OWN_FILES_R2_SCOPE_KEY, OWN_FILES_R2_UNDELIVERED_SCOPE_KEY, OWN_SERVICES_APPLIED_SCOPE_KEY, OwnServicesBlockedError, orgTarget, } from './plan/index.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
-- @wtfalch/foundry: a company's app requests (826 items 4 and 5). A Save in Boule or a release
|
|
2
|
+
-- build puts one hosting entry (`app`) or deploys one build (`build`); Archon applies it within the
|
|
3
|
+
-- plan's limits. Mirrors src/store/schema.ts's `foundryRequestedChanges`.
|
|
4
|
+
--
|
|
5
|
+
-- Additive only, like 0057_org_customers.sql (which also widens this table's field check): new
|
|
6
|
+
-- nullable columns, `updated_at` with a default, a partial unique index, and wider checks. Every
|
|
7
|
+
-- existing row satisfies the wider checks and reads back with null in the new columns, so the
|
|
8
|
+
-- migration is safe on live rows when the container starts.
|
|
9
|
+
|
|
10
|
+
alter table foundry_requested_changes
|
|
11
|
+
add column if not exists requester jsonb,
|
|
12
|
+
add column if not exists idempotency_key text,
|
|
13
|
+
add column if not exists reason jsonb,
|
|
14
|
+
add column if not exists updated_at timestamptz not null default now();
|
|
15
|
+
|
|
16
|
+
-- One request per (org, key): a repeated Save or webhook delivery returns the first row.
|
|
17
|
+
create unique index if not exists foundry_requested_changes_idempotency_idx
|
|
18
|
+
on foundry_requested_changes (org_id, idempotency_key)
|
|
19
|
+
where idempotency_key is not null;
|
|
20
|
+
|
|
21
|
+
-- `applying`: Archon is applying it. `failed`: it ended without applying; `reason` says why.
|
|
22
|
+
alter table foundry_requested_changes
|
|
23
|
+
drop constraint if exists foundry_requested_changes_state_check;
|
|
24
|
+
alter table foundry_requested_changes
|
|
25
|
+
add constraint foundry_requested_changes_state_check
|
|
26
|
+
check (state in ('auto', 'needs_approval', 'approved', 'rejected', 'applied', 'applying', 'failed'));
|
|
27
|
+
|
|
28
|
+
alter table foundry_requested_changes
|
|
29
|
+
drop constraint if exists foundry_requested_changes_field_check;
|
|
30
|
+
alter table foundry_requested_changes
|
|
31
|
+
add constraint foundry_requested_changes_field_check
|
|
32
|
+
check (field in ('calls', 'customers', 'storageLimit', 'aiLimit', 'domain', 'mailTier', 'app', 'build'));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- @wtfalch/foundry: `boule_blue_green` and `agora_blue_green` are `org.boule.blueGreen` and
|
|
2
|
+
-- `org.agora.blueGreen` as JSON ({ steps?, stepHoldMinutes? }); null (the default) keeps the company's
|
|
3
|
+
-- Boule or Agora on one app. Copies 0056_platform_archon_blue_green.sql: nullable columns added with
|
|
4
|
+
-- `if not exists`, so it is safe to run at container start on live rows.
|
|
5
|
+
-- Mirrors src/store/schema.ts's `declaredOrgs`.
|
|
6
|
+
|
|
7
|
+
alter table foundry_declared_orgs
|
|
8
|
+
add column if not exists boule_blue_green jsonb,
|
|
9
|
+
add column if not exists agora_blue_green jsonb;
|
package/dist/org.d.ts
CHANGED
|
@@ -282,6 +282,17 @@ export type AccessRule = {
|
|
|
282
282
|
};
|
|
283
283
|
/** The key of the entry that is today's one app: the Coolify app named `<slug>`. */
|
|
284
284
|
export declare const APP_ENTRY_KEY = "app";
|
|
285
|
+
/** Why a validator refused. `code` is stable and never reworded; `message` is for people. */
|
|
286
|
+
export type OrgRefusalCode = 'hosting_tunnel_needs_hosting' | 'issuer_app_name_missing' | 'issuer_app_name_not_unique' | 'issuer_app_kind_unknown' | 'issuer_app_name_reserved_agora' | 'issuer_app_name_reserved_boule' | 'hosting_needs_entry' | 'hosting_main_count' | 'entry_key_not_unique' | 'ai_webhook_path_not_unique' | 'worker_route_not_unique' | 'database_not_unique' | 'access_hostname_not_unique' | 'access_hostname_invalid' | 'access_needs_hosting' | 'access_kind_unknown' | 'access_service_token_name_missing' | 'access_hostname_locked' | 'access_hostname_not_served' | 'gate_limit_invalid' | 'gate_needs_domain' | 'gate_key_invalid' | 'gate_key_not_unique' | 'gate_needs_hostname' | 'gate_hostname_not_entry_hostname' | 'gate_hostname_held_twice' | 'gate_hostname_locked' | 'gate_limit_exceeded' | 'wildcard_needs_domain' | 'wildcard_held_twice' | 'hostnames_need_domain' | 'hostname_needed' | 'hostname_invalid' | 'hostname_not_unique' | 'hostname_locked' | 'entry_key_invalid' | 'entry_key_reserved' | 'entry_repo_invalid' | 'entry_folder_invalid' | 'entry_branch_empty' | 'entry_build_pack_unknown' | 'entry_build_pack_unavailable' | 'entry_port_invalid' | 'entry_health_path_invalid' | 'entry_capabilities_invalid' | 'entry_database_needs_hosting' | 'blue_green_needs_hostname' | 'env_name_empty' | 'env_name_reserved' | 'env_source_platform_mail_key' | 'env_source_unknown' | 'env_shared_secret_name_invalid' | 'env_held_secret_name_mismatch' | 'env_held_secret_name_invalid' | 'env_looks_like_secret' | 'builtin_app_invalid' | 'blue_green_not_object' | 'blue_green_steps_length' | 'blue_green_steps_range' | 'blue_green_steps_order' | 'blue_green_steps_end' | 'blue_green_hold_invalid' | 'volumes_not_list' | 'volumes_too_many' | 'volume_name_invalid' | 'volume_name_logs' | 'volume_name_not_unique' | 'volume_mount_path_invalid' | 'volume_mount_path_too_long' | 'volume_mount_path_log_dir' | 'volume_mount_path_forbidden' | 'volume_mount_path_not_unique' | 'auth_needs_hosting' | 'auth_users_invalid' | 'auth_registration_invalid' | 'registration_open_needs_customers' | 'auth_replaces_broker_entry_invalid' | 'auth_replaces_broker_entry_owned' | 'auth_needs_exact_hostname' | 'mcp_needs_open_customers' | 'mcp_path_invalid' | 'mcp_onboarding_path_invalid' | 'auth_sign_in_invalid' | 'email_code_needs_customers' | 'client_name_taken' | 'ai_needs_hosting' | 'ai_offerings_invalid' | 'ai_permissions_invalid' | 'ai_webhook_path_invalid' | 'ai_webhook_needs_exact_hostname' | 'ai_env_clash' | 'files_needs_hosting' | 'files_collections_invalid' | 'files_permissions_invalid' | 'files_share_host_invalid' | 'files_share_host_needs_domain' | 'files_env_clash' | 'worker_needs_hosting' | 'worker_needs_repo' | 'worker_key_too_long' | 'worker_artifact_invalid' | 'worker_route_invalid' | 'worker_route_off_domain' | 'worker_rate_limit_name_missing' | 'worker_rate_limit_namespace_missing' | 'worker_rate_limit_invalid' | 'worker_rate_limit_period_invalid' | 'worker_app_invalid' | 'edge_not_true' | 'edge_needs_hosting' | 'edge_needs_domain' | 'edge_env_clash' | 'customers_invalid' | 'customers_sign_up_needs_enabled' | 'registration_open_needs_sign_up';
|
|
287
|
+
/**
|
|
288
|
+
* One refusal: a stable `code`, the dotted `path` into {@link DeclaredOrg} it is about
|
|
289
|
+
* (`hosting.entries.terminal.uses.auth.registration`) and the sentence for people.
|
|
290
|
+
*/
|
|
291
|
+
export interface OrgRefusal {
|
|
292
|
+
code: OrgRefusalCode;
|
|
293
|
+
path: string;
|
|
294
|
+
message: string;
|
|
295
|
+
}
|
|
285
296
|
/**
|
|
286
297
|
* One app the org runs. `database` and `gates` are meaningless without
|
|
287
298
|
* `HostingConfig.enabled`, so `validateHosting` requires it before either is
|
|
@@ -305,6 +316,11 @@ export interface HostingEntry {
|
|
|
305
316
|
buildPack: HostingBuildPack;
|
|
306
317
|
/** The port the app listens on. `null` is Coolify's default. */
|
|
307
318
|
port: number | null;
|
|
319
|
+
/**
|
|
320
|
+
* The org's main app: served at the org's main address (`<slug>.oikos.host`, or the domain's
|
|
321
|
+
* apex), whatever its key. An entry keyed {@link APP_ENTRY_KEY} with `main` unset is main.
|
|
322
|
+
*/
|
|
323
|
+
main?: boolean;
|
|
308
324
|
/** The path that answers 200 when the app is healthy. `null` is none. */
|
|
309
325
|
healthPath: string | null;
|
|
310
326
|
/**
|
|
@@ -369,6 +385,14 @@ export interface HostingEntry {
|
|
|
369
385
|
*/
|
|
370
386
|
blueGreen?: BlueGreenConfig;
|
|
371
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* `boule` and `agora` on a company: the built-in apps foundry runs for every company (`<slug>-boule`,
|
|
390
|
+
* `<slug>-agora`). Only blue/green is declared here; absent means off. Optional so every existing literal and
|
|
391
|
+
* fixture compiles unchanged.
|
|
392
|
+
*/
|
|
393
|
+
export interface OrgBuiltinApp {
|
|
394
|
+
blueGreen?: BlueGreenConfig;
|
|
395
|
+
}
|
|
372
396
|
/** The traffic steps (percent to the new color) and the hold at each step. Both default in `plan/blue-green.ts`. */
|
|
373
397
|
export interface BlueGreenConfig {
|
|
374
398
|
/** Ascending, each 1..100, ending at 100. */
|
|
@@ -551,8 +575,10 @@ export interface EnvHeldSecret {
|
|
|
551
575
|
from: 'heldSecret';
|
|
552
576
|
name: string;
|
|
553
577
|
}
|
|
554
|
-
/**
|
|
555
|
-
export declare function
|
|
578
|
+
/** True for the entry marked `main`, and for an entry keyed {@link APP_ENTRY_KEY} that leaves `main` unset. */
|
|
579
|
+
export declare function isMainEntry(entry: Pick<HostingEntry, 'key' | 'main'>): boolean;
|
|
580
|
+
/** The org's main app: the entry marked `main`, else the entry keyed {@link APP_ENTRY_KEY}. */
|
|
581
|
+
export declare function mainEntry(hosting: Pick<HostingConfig, 'entries'>): HostingEntry | undefined;
|
|
556
582
|
/**
|
|
557
583
|
* The closed set of values the plan already mints that `HostingEntry.env`
|
|
558
584
|
* may map an app's env var name to (#243): the org's own Files organisation
|
|
@@ -585,19 +611,27 @@ export declare const heldEnvRef: (orgId: string, entryKey: string, name: string)
|
|
|
585
611
|
* - `tunnel` requires `enabled` -- it means nothing without the apps running.
|
|
586
612
|
* - Every `issuerApps[]` entry has a non-empty `name`, unique within the
|
|
587
613
|
* list, and a `kind` that is one of `IssuerAppKind`'s three values.
|
|
588
|
-
* - `enabled` requires
|
|
614
|
+
* - `enabled` requires at least one entry, and at most one entry is the main app ({@link isMainEntry}).
|
|
589
615
|
* - Every `entries[]` entry follows {@link validateHostingEntry}; keys are
|
|
590
616
|
* unique, and at most one entry has a `database`, because the one
|
|
591
617
|
* `<slug>_rt` runtime role cannot serve two apps.
|
|
592
618
|
*/
|
|
593
|
-
export declare function validateHosting(hosting: HostingConfig, names?:
|
|
619
|
+
export declare function validateHosting(hosting: HostingConfig, names?: HostingNames): string | null;
|
|
620
|
+
/** What {@link refuseHosting} knows of the org's names (see `plan/locked-hostnames.ts`'s `hostingNames`). */
|
|
621
|
+
export interface HostingNames {
|
|
594
622
|
domain: string | null;
|
|
595
623
|
locked: ReadonlySet<string>;
|
|
596
624
|
slug?: string;
|
|
597
625
|
siteOwnsApex?: boolean;
|
|
598
|
-
|
|
626
|
+
/** Boule or Agora declares `blueGreen`: the blue/green key names are reserved for the entries too. */
|
|
627
|
+
builtinBlueGreen?: boolean;
|
|
628
|
+
}
|
|
629
|
+
/** {@link validateHosting} with the code and path of the first refusal. */
|
|
630
|
+
export declare function refuseHosting(hosting: HostingConfig, names?: HostingNames): OrgRefusal | null;
|
|
599
631
|
/** `blueGreen`: steps ascending, each a whole number 1..100, the last 100; a hold of 1..1440 minutes. */
|
|
600
632
|
export declare function validateBlueGreen(value: unknown, at: string): string | null;
|
|
633
|
+
/** {@link refuseBlueGreen} for the `blueGreen` of the company's Boule and Agora; the same codes. */
|
|
634
|
+
export declare function refuseBuiltinBlueGreen(org: Pick<DeclaredOrg, 'boule' | 'agora'>): OrgRefusal | null;
|
|
601
635
|
export declare function validateDomain(domain: string): string | null;
|
|
602
636
|
/**
|
|
603
637
|
* A broker entry name foundry writes itself: Archon's, and any company's Boule.
|
|
@@ -715,6 +749,10 @@ export interface CustomersConfig {
|
|
|
715
749
|
export declare function validateCustomers(org: Pick<DeclaredOrg, 'customers'> & {
|
|
716
750
|
hosting: Pick<HostingConfig, 'entries'>;
|
|
717
751
|
}): string | null;
|
|
752
|
+
/** {@link validateCustomers} with the code and path of the refusal. */
|
|
753
|
+
export declare function refuseCustomers(org: Pick<DeclaredOrg, 'customers'> & {
|
|
754
|
+
hosting: Pick<HostingConfig, 'entries'>;
|
|
755
|
+
}): OrgRefusal | null;
|
|
718
756
|
/** The only reader of `services.own`. Every company runs its own services, the platform's owner company too; a company without a domain runs them on oikos.host. */
|
|
719
757
|
export declare function ownsServices(org: Pick<DeclaredOrg, 'services'>): boolean;
|
|
720
758
|
/** The pure part of the `services` rules; that no two orgs share an `sfuSlot` is the store's check. */
|
|
@@ -1009,6 +1047,10 @@ export interface DeclaredOrg {
|
|
|
1009
1047
|
services?: ServicesConfig;
|
|
1010
1048
|
/** Absent means `{ inApp: false }`. Optional so every existing literal and fixture compiles unchanged. */
|
|
1011
1049
|
signIn?: SignInConfig;
|
|
1050
|
+
/** The company's Boule. Absent means no blue/green. */
|
|
1051
|
+
boule?: OrgBuiltinApp;
|
|
1052
|
+
/** The company's Agora, a static app: its release moves on hard fails and time only. Absent means no blue/green. */
|
|
1053
|
+
agora?: OrgBuiltinApp;
|
|
1012
1054
|
/** Absent means `{ enabled: false, signUp: false }`. Optional so every existing literal and fixture compiles unchanged. */
|
|
1013
1055
|
customers?: CustomersConfig;
|
|
1014
1056
|
/** Parts of this company held on their own version. Absent or null follows the platform. Ignored for the owner company. */
|