@wtfalch/foundry 0.45.1 → 0.47.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/org.d.ts +68 -5
- package/dist/org.js +287 -135
- package/dist/plan/agora.d.ts +14 -0
- package/dist/plan/agora.js +21 -0
- package/dist/plan/backup-gate.d.ts +15 -0
- package/dist/plan/backup-gate.js +38 -0
- package/dist/plan/boule-owners-http.d.ts +12 -0
- package/dist/plan/boule-owners-http.js +59 -0
- package/dist/plan/boule.d.ts +38 -2
- package/dist/plan/boule.js +72 -1
- package/dist/plan/company-backups.d.ts +7 -1
- package/dist/plan/company-backups.js +42 -2
- package/dist/plan/cookie-domain.d.ts +41 -0
- package/dist/plan/cookie-domain.js +71 -0
- package/dist/plan/desktop-auth.d.ts +58 -0
- package/dist/plan/desktop-auth.js +144 -0
- package/dist/plan/desktop-callback.d.ts +26 -0
- package/dist/plan/desktop-callback.js +59 -0
- package/dist/plan/diff.d.ts +13 -4
- package/dist/plan/diff.js +277 -88
- package/dist/plan/executor.d.ts +136 -6
- package/dist/plan/executor.js +732 -105
- package/dist/plan/health-gaps.js +5 -0
- 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 +4 -1
- package/dist/plan/index.js +3 -1
- package/dist/plan/job-app.d.ts +23 -0
- package/dist/plan/job-app.js +41 -0
- package/dist/plan/live.d.ts +6 -0
- package/dist/plan/live.js +16 -0
- package/dist/plan/login-host.d.ts +28 -0
- package/dist/plan/login-host.js +36 -0
- package/dist/plan/org-names.js +9 -1
- package/dist/plan/org-postgres-job.d.ts +22 -0
- package/dist/plan/org-postgres-job.js +91 -0
- package/dist/plan/org-postgres-migrate.d.ts +115 -0
- package/dist/plan/org-postgres-migrate.js +466 -0
- package/dist/plan/org-postgres.d.ts +90 -0
- package/dist/plan/org-postgres.js +293 -0
- package/dist/plan/plan-apply.d.ts +60 -3
- package/dist/plan/plan-apply.js +174 -8
- package/dist/plan/platform-diff.d.ts +45 -0
- package/dist/plan/platform-diff.js +159 -4
- package/dist/plan/platform-org.d.ts +2 -0
- package/dist/plan/platform-org.js +4 -0
- package/dist/plan/platform-providers.d.ts +12 -2
- package/dist/plan/platform-providers.js +282 -9
- package/dist/plan/post-logout-uris.d.ts +22 -0
- package/dist/plan/post-logout-uris.js +43 -0
- package/dist/plan/providers-readers.d.ts +10 -1
- package/dist/plan/providers-readers.js +56 -5
- package/dist/plan/sign-in-bindings.d.ts +4 -0
- package/dist/plan/sign-in-bindings.js +10 -0
- package/dist/plan/sign-in-org.d.ts +19 -6
- package/dist/plan/sign-in-org.js +35 -10
- package/dist/plan/sign-in.d.ts +12 -5
- package/dist/plan/sign-in.js +12 -5
- package/dist/plan/signin-db-move.js +10 -34
- package/dist/plan/worker-app.js +3 -1
- package/dist/refuse-org.d.ts +7 -0
- package/dist/refuse-org.js +10 -0
- 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 +67 -8
- package/dist/store/schema.d.ts +72 -0
- package/dist/store/schema.js +4 -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 +5 -5
|
@@ -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, 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, SFU_SLOT_MAX, VERSION_PIN_PARTS,
|
|
1
|
+
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, HostingEntry, 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,11 +29,13 @@ 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 { InvalidMigrationConfirmationError, ORG_POSTGRES_MIGRATION_CONFIRMED_SCOPE_KEY, OrgPostgresMigrationBackupMissingError, OrgPostgresMigrationNotConfirmedError, } from './plan/index.js';
|
|
33
|
+
export type { OrgPostgresMigrationConfirmation } from './plan/index.js';
|
|
28
34
|
export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './plan/index.js';
|
|
29
35
|
export type { BackupGatePorts } from './plan/index.js';
|
|
30
36
|
export { createEdgeAccess, createEdgeDns, edgeDnsRecordOwners, createEdgeRateLimits, createEdgeTunnels, createEdgeWorkers, createEdgeZones, } from './plan/index.js';
|
|
31
37
|
export type { AccessAppSummary, AccessDecision, AccessPolicyRule, AccessPort, DnsPort, EdgePlatformAccess, EdgePlatformDns, EdgePlatformRateLimits, EdgePlatformWorkers, EdgePlatformZones, EdgeZonesOptions, ZonePort, EdgeRateLimitsOptions, RateLimitPort, RateLimitRuleInput, EdgeWorkersOptions, EdgePlatformTunnels, TunnelPort, } from './plan/index.js';
|
|
32
|
-
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';
|
|
38
|
+
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';
|
|
33
39
|
export type { HeldKeyResolver, OrgPlatformReadiness } from './plan/index.js';
|
|
34
40
|
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesForOwn, agoraHostValuesFromPlatform, heldOwnKeyRefs, heldPlatformKeyRefs, orgPlatformReadinessFor, orgPlatformReadinessFrom, } from './plan/index.js';
|
|
35
41
|
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, 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,9 +18,10 @@ 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 { InvalidMigrationConfirmationError, ORG_POSTGRES_MIGRATION_CONFIRMED_SCOPE_KEY, OrgPostgresMigrationBackupMissingError, OrgPostgresMigrationNotConfirmedError, } from './plan/index.js';
|
|
18
22
|
export { NetworkMoveBackupMissingError, UpgradeBackupMissingError } from './plan/index.js';
|
|
19
23
|
export { createEdgeAccess, createEdgeDns, edgeDnsRecordOwners, createEdgeRateLimits, createEdgeTunnels, createEdgeWorkers, createEdgeZones, } from './plan/index.js';
|
|
20
|
-
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';
|
|
24
|
+
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';
|
|
21
25
|
export { DEFAULT_ORG_PLATFORM_READINESS, ORG_PLATFORM_KEY_REFS, agoraHostValuesForOwn, agoraHostValuesFromPlatform, heldOwnKeyRefs, heldPlatformKeyRefs, orgPlatformReadinessFor, orgPlatformReadinessFrom, } from './plan/index.js';
|
|
22
26
|
export { PlatformOrgBlockedError, PlatformOrgPorts } from './plan/index.js';
|
|
23
27
|
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'));
|
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_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' | '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
|
/**
|
|
@@ -551,8 +567,10 @@ export interface EnvHeldSecret {
|
|
|
551
567
|
from: 'heldSecret';
|
|
552
568
|
name: string;
|
|
553
569
|
}
|
|
554
|
-
/**
|
|
555
|
-
export declare function
|
|
570
|
+
/** True for the entry marked `main`, and for an entry keyed {@link APP_ENTRY_KEY} that leaves `main` unset. */
|
|
571
|
+
export declare function isMainEntry(entry: Pick<HostingEntry, 'key' | 'main'>): boolean;
|
|
572
|
+
/** The org's main app: the entry marked `main`, else the entry keyed {@link APP_ENTRY_KEY}. */
|
|
573
|
+
export declare function mainEntry(hosting: Pick<HostingConfig, 'entries'>): HostingEntry | undefined;
|
|
556
574
|
/**
|
|
557
575
|
* The closed set of values the plan already mints that `HostingEntry.env`
|
|
558
576
|
* may map an app's env var name to (#243): the org's own Files organisation
|
|
@@ -585,17 +603,21 @@ export declare const heldEnvRef: (orgId: string, entryKey: string, name: string)
|
|
|
585
603
|
* - `tunnel` requires `enabled` -- it means nothing without the apps running.
|
|
586
604
|
* - Every `issuerApps[]` entry has a non-empty `name`, unique within the
|
|
587
605
|
* list, and a `kind` that is one of `IssuerAppKind`'s three values.
|
|
588
|
-
* - `enabled` requires
|
|
606
|
+
* - `enabled` requires at least one entry, and at most one entry is the main app ({@link isMainEntry}).
|
|
589
607
|
* - Every `entries[]` entry follows {@link validateHostingEntry}; keys are
|
|
590
608
|
* unique, and at most one entry has a `database`, because the one
|
|
591
609
|
* `<slug>_rt` runtime role cannot serve two apps.
|
|
592
610
|
*/
|
|
593
|
-
export declare function validateHosting(hosting: HostingConfig, names?:
|
|
611
|
+
export declare function validateHosting(hosting: HostingConfig, names?: HostingNames): string | null;
|
|
612
|
+
/** What {@link refuseHosting} knows of the org's names (see `plan/locked-hostnames.ts`'s `hostingNames`). */
|
|
613
|
+
export interface HostingNames {
|
|
594
614
|
domain: string | null;
|
|
595
615
|
locked: ReadonlySet<string>;
|
|
596
616
|
slug?: string;
|
|
597
617
|
siteOwnsApex?: boolean;
|
|
598
|
-
}
|
|
618
|
+
}
|
|
619
|
+
/** {@link validateHosting} with the code and path of the first refusal. */
|
|
620
|
+
export declare function refuseHosting(hosting: HostingConfig, names?: HostingNames): OrgRefusal | null;
|
|
599
621
|
/** `blueGreen`: steps ascending, each a whole number 1..100, the last 100; a hold of 1..1440 minutes. */
|
|
600
622
|
export declare function validateBlueGreen(value: unknown, at: string): string | null;
|
|
601
623
|
export declare function validateDomain(domain: string): string | null;
|
|
@@ -715,6 +737,10 @@ export interface CustomersConfig {
|
|
|
715
737
|
export declare function validateCustomers(org: Pick<DeclaredOrg, 'customers'> & {
|
|
716
738
|
hosting: Pick<HostingConfig, 'entries'>;
|
|
717
739
|
}): string | null;
|
|
740
|
+
/** {@link validateCustomers} with the code and path of the refusal. */
|
|
741
|
+
export declare function refuseCustomers(org: Pick<DeclaredOrg, 'customers'> & {
|
|
742
|
+
hosting: Pick<HostingConfig, 'entries'>;
|
|
743
|
+
}): OrgRefusal | null;
|
|
718
744
|
/** 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
745
|
export declare function ownsServices(org: Pick<DeclaredOrg, 'services'>): boolean;
|
|
720
746
|
/** The pure part of the `services` rules; that no two orgs share an `sfuSlot` is the store's check. */
|
|
@@ -857,7 +883,19 @@ export interface OwnerIdentity {
|
|
|
857
883
|
givenName: string;
|
|
858
884
|
familyName: string;
|
|
859
885
|
mailboxAddress?: string;
|
|
886
|
+
/** Their position in the company, free text such as "CEO". Seeded into Boule once; Boule owns it afterwards. */
|
|
887
|
+
roleAtCompany?: string;
|
|
888
|
+
/** Free text, seeded into Boule once. */
|
|
889
|
+
phone?: string;
|
|
860
890
|
}
|
|
891
|
+
/**
|
|
892
|
+
* `null` when the owner's sign-in name (the part of the work address before the
|
|
893
|
+
* `@`) and display names may be used; otherwise a fixed sentence. The sign-in
|
|
894
|
+
* name gets the broker's whole check, reserved names included. The given and
|
|
895
|
+
* family names are free text and only refuse a slur. The sentence never holds
|
|
896
|
+
* the value.
|
|
897
|
+
*/
|
|
898
|
+
export declare function ownerNameProblem(mailboxAddress: string | null | undefined, givenName: string, familyName: string): string | null;
|
|
861
899
|
/**
|
|
862
900
|
* `null` when `owners` is valid; otherwise the reason it is not, same shape as
|
|
863
901
|
* {@link validateBuyerIdentity} and `../slug.ts`'s `validateSlug`. An empty
|
|
@@ -1086,6 +1124,10 @@ export interface OrgServiceSchema {
|
|
|
1086
1124
|
runtimeRole: string;
|
|
1087
1125
|
/** Where this schema's existence is recorded, once provisioned. */
|
|
1088
1126
|
scopeKey: string;
|
|
1127
|
+
/** False for the four matrix homes: one login role is owner and runtime, because Synapse and MAS run their own migrations. */
|
|
1128
|
+
split: boolean;
|
|
1129
|
+
/** The recorded-id key of this home's Coolify database today, or null when the home has none (desktop-auth, or a company with no app database yet). A live company stays on that database until its migration records the home as moved. */
|
|
1130
|
+
legacyKey: (org: DeclaredOrg) => string | null;
|
|
1089
1131
|
}
|
|
1090
1132
|
/**
|
|
1091
1133
|
* The full, fixed schema topology every org's shared Postgres server
|
|
@@ -1119,3 +1161,24 @@ export interface OrgServiceSchema {
|
|
|
1119
1161
|
* create.
|
|
1120
1162
|
*/
|
|
1121
1163
|
export declare const ORG_SERVICE_SCHEMAS: readonly OrgServiceSchema[];
|
|
1164
|
+
/** One service's home on the org's Postgres server, with the names it is created under (#444). */
|
|
1165
|
+
export interface OrgServiceDatabase {
|
|
1166
|
+
/** The {@link ORG_SERVICE_SCHEMAS} key. */
|
|
1167
|
+
schema: string;
|
|
1168
|
+
database: string;
|
|
1169
|
+
ownerRole: string;
|
|
1170
|
+
/** Equals {@link ownerRole} when {@link split} is false. */
|
|
1171
|
+
runtimeRole: string;
|
|
1172
|
+
split: boolean;
|
|
1173
|
+
/** Synapse only: created from template0 with LC_COLLATE and LC_CTYPE 'C'. */
|
|
1174
|
+
collateC: boolean;
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Pure. The names a home is created under on the org's server. A role name is
|
|
1178
|
+
* cluster-wide, so the live runtime role names are kept: where a service's
|
|
1179
|
+
* runtime role is `<database>_rt` today, the database keeps its live name.
|
|
1180
|
+
* A new and a migrated company use the same names.
|
|
1181
|
+
*/
|
|
1182
|
+
export declare function orgServiceDatabase(org: DeclaredOrg, home: OrgServiceSchema): OrgServiceDatabase;
|
|
1183
|
+
/** Every home's names for `org`. Throws when two homes would share a database or a role (a slug such as `chat` collides with chat's own service). */
|
|
1184
|
+
export declare function orgServiceDatabases(org: DeclaredOrg): OrgServiceDatabase[];
|