@wtfalch/foundry 0.59.0 → 0.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apps/cloudflare.d.ts +173 -0
- package/dist/apps/cloudflare.js +17 -0
- package/dist/apps/deploy.d.ts +116 -0
- package/dist/apps/deploy.js +791 -0
- package/dist/apps/github.d.ts +47 -0
- package/dist/apps/github.js +201 -0
- package/dist/apps/index.d.ts +9 -0
- package/dist/apps/index.js +9 -0
- package/dist/apps/migrations.d.ts +110 -0
- package/dist/apps/migrations.js +458 -0
- package/dist/audit/connector.d.ts +43 -0
- package/dist/audit/connector.js +22 -0
- package/dist/audit/evidence.d.ts +49 -0
- package/dist/audit/evidence.js +56 -0
- package/dist/audit/index.d.ts +9 -0
- package/dist/audit/index.js +9 -0
- package/dist/audit/migrations.d.ts +5 -0
- package/dist/audit/migrations.js +5 -0
- package/dist/audit/registry.d.ts +49 -0
- package/dist/audit/registry.js +44 -0
- package/dist/audit/seal.d.ts +49 -0
- package/dist/audit/seal.js +131 -0
- package/dist/audit/sealer-role.d.ts +33 -0
- package/dist/audit/sealer-role.js +56 -0
- package/dist/audit/signer.d.ts +40 -0
- package/dist/audit/signer.js +78 -0
- package/dist/audit/tick.d.ts +36 -0
- package/dist/audit/tick.js +87 -0
- package/dist/catalogue/copy-en.d.ts +1 -0
- package/dist/catalogue/copy-en.js +129 -0
- package/dist/catalogue/index.d.ts +9 -0
- package/dist/catalogue/index.js +21 -0
- package/dist/catalogue/integrations.d.ts +4 -0
- package/dist/catalogue/integrations.js +104 -0
- package/dist/catalogue/systems.d.ts +7 -0
- package/dist/catalogue/systems.js +31 -0
- package/dist/catalogue/types.d.ts +110 -0
- package/dist/catalogue/types.js +1 -0
- package/dist/entry-rules.d.ts +53 -0
- package/dist/entry-rules.js +71 -0
- package/dist/hosted-label.d.ts +13 -0
- package/dist/hosted-label.js +81 -0
- package/dist/index.d.ts +10 -5
- package/dist/index.js +5 -3
- package/dist/migrations/0062_hosting_github.sql +6 -0
- package/dist/migrations/0063_boule_apps.sql +7 -0
- package/dist/migrations/0064_requested_changes_boule_apps.sql +6 -0
- package/dist/modules/ai.d.ts +3 -0
- package/dist/modules/ai.js +64 -0
- package/dist/modules/assistants.d.ts +3 -0
- package/dist/modules/assistants.js +72 -0
- package/dist/modules/audit-ledger.d.ts +29 -0
- package/dist/modules/audit-ledger.js +49 -0
- package/dist/modules/audit.d.ts +7 -0
- package/dist/modules/audit.js +44 -0
- package/dist/modules/database.d.ts +23 -0
- package/dist/modules/database.js +189 -0
- package/dist/modules/email.d.ts +3 -0
- package/dist/modules/email.js +18 -0
- package/dist/modules/entry-parts.d.ts +23 -0
- package/dist/modules/entry-parts.js +63 -0
- package/dist/modules/files.d.ts +3 -0
- package/dist/modules/files.js +64 -0
- package/dist/modules/index.d.ts +74 -0
- package/dist/modules/index.js +774 -0
- package/dist/modules/jobs.d.ts +3 -0
- package/dist/modules/jobs.js +23 -0
- package/dist/modules/organisations.d.ts +3 -0
- package/dist/modules/organisations.js +20 -0
- package/dist/modules/outputs.d.ts +9 -0
- package/dist/modules/outputs.js +38 -0
- package/dist/modules/sign-in.d.ts +3 -0
- package/dist/modules/sign-in.js +197 -0
- package/dist/modules/signed-records.d.ts +3 -0
- package/dist/modules/signed-records.js +32 -0
- package/dist/modules/types.d.ts +194 -0
- package/dist/modules/types.js +1 -0
- package/dist/modules/workspaces.d.ts +3 -0
- package/dist/modules/workspaces.js +75 -0
- package/dist/org.d.ts +163 -22
- package/dist/org.js +419 -47
- package/dist/plan/apps-db-route.d.ts +90 -0
- package/dist/plan/apps-db-route.js +168 -0
- package/dist/plan/apps-zones.d.ts +56 -0
- package/dist/plan/apps-zones.js +118 -0
- package/dist/plan/archon-blue-green.js +11 -1
- package/dist/plan/blue-green-apply.d.ts +37 -1
- package/dist/plan/blue-green-apply.js +55 -15
- package/dist/plan/blue-green.d.ts +41 -1
- package/dist/plan/blue-green.js +80 -2
- package/dist/plan/boule.d.ts +23 -1
- package/dist/plan/boule.js +35 -2
- package/dist/plan/broker-entries.d.ts +28 -3
- package/dist/plan/broker-entries.js +74 -3
- package/dist/plan/broker-registrar.d.ts +168 -0
- package/dist/plan/broker-registrar.js +440 -0
- package/dist/plan/broker-workspaces-database.d.ts +48 -0
- package/dist/plan/broker-workspaces-database.js +87 -0
- package/dist/plan/desktop-callback.js +12 -7
- package/dist/plan/diff.d.ts +8 -2
- package/dist/plan/diff.js +373 -42
- package/dist/plan/edge-access.d.ts +6 -0
- package/dist/plan/edge-dns.d.ts +6 -0
- package/dist/plan/edge-dns.js +9 -7
- package/dist/plan/entry-ai-key.d.ts +1 -1
- package/dist/plan/entry-ai-key.js +2 -2
- package/dist/plan/entry-audit.d.ts +58 -0
- package/dist/plan/entry-audit.js +122 -0
- package/dist/plan/entry-customer-grants.d.ts +75 -0
- package/dist/plan/entry-customer-grants.js +154 -0
- package/dist/plan/entry-edge-key.d.ts +26 -1
- package/dist/plan/entry-edge-key.js +42 -3
- package/dist/plan/entry-jobs.d.ts +64 -0
- package/dist/plan/entry-jobs.js +92 -0
- package/dist/plan/entry-repo-key-apply.d.ts +55 -0
- package/dist/plan/entry-repo-key-apply.js +185 -0
- package/dist/plan/entry-repo-key.d.ts +92 -0
- package/dist/plan/entry-repo-key.js +235 -0
- package/dist/plan/entry-workspaces.d.ts +55 -0
- package/dist/plan/entry-workspaces.js +90 -0
- package/dist/plan/executor.d.ts +191 -1
- package/dist/plan/executor.js +1505 -90
- package/dist/plan/gates.d.ts +1 -1
- package/dist/plan/gates.js +62 -9
- package/dist/plan/github-deploy-keys.d.ts +78 -0
- package/dist/plan/github-deploy-keys.js +305 -0
- package/dist/plan/github-source.d.ts +53 -0
- package/dist/plan/github-source.js +140 -0
- package/dist/plan/held-key-refs.d.ts +12 -0
- package/dist/plan/held-key-refs.js +12 -0
- package/dist/plan/hostnames.d.ts +9 -0
- package/dist/plan/hostnames.js +28 -1
- package/dist/plan/index.d.ts +9 -0
- package/dist/plan/index.js +6 -0
- package/dist/plan/jobs-scheduler-http.js +3 -0
- package/dist/plan/keys-registrar-http.d.ts +32 -0
- package/dist/plan/keys-registrar-http.js +105 -0
- package/dist/plan/keys-registrar.d.ts +67 -0
- package/dist/plan/keys-registrar.js +103 -0
- package/dist/plan/live.d.ts +1 -1
- package/dist/plan/locked-hostnames.d.ts +2 -2
- package/dist/plan/locked-hostnames.js +2 -2
- package/dist/plan/login-host.js +11 -5
- package/dist/plan/machine-paths.d.ts +36 -0
- package/dist/plan/machine-paths.js +137 -0
- package/dist/plan/mcp.d.ts +1 -1
- package/dist/plan/mcp.js +49 -17
- package/dist/plan/org-apps-admin.d.ts +108 -0
- package/dist/plan/org-apps-admin.js +434 -0
- package/dist/plan/org-names.js +12 -1
- package/dist/plan/own-diff.d.ts +23 -0
- package/dist/plan/own-diff.js +53 -0
- package/dist/plan/own-hostnames.d.ts +2 -0
- package/dist/plan/own-hostnames.js +1 -0
- package/dist/plan/own-jobs-route.d.ts +47 -0
- package/dist/plan/own-jobs-route.js +91 -0
- package/dist/plan/own-services.d.ts +4 -0
- package/dist/plan/own-services.js +4 -0
- package/dist/plan/plan-apply.d.ts +39 -0
- package/dist/plan/plan-apply.js +163 -9
- package/dist/plan/platform-diff.d.ts +2 -2
- package/dist/plan/platform-diff.js +7 -2
- package/dist/plan/platform-org.d.ts +30 -0
- package/dist/plan/platform-org.js +12 -1
- package/dist/plan/platform-providers.d.ts +2 -0
- package/dist/plan/platform-providers.js +7 -2
- package/dist/plan/post-logout-uris.js +7 -1
- package/dist/plan/private-pages.d.ts +26 -0
- package/dist/plan/private-pages.js +39 -0
- package/dist/plan/proxy-labels.d.ts +12 -1
- package/dist/plan/proxy-labels.js +32 -2
- package/dist/plan/release-host.d.ts +6 -2
- package/dist/plan/release-host.js +36 -8
- package/dist/plan/stage-env.d.ts +19 -1
- package/dist/plan/stage-env.js +25 -1
- package/dist/plan/stage-guards.d.ts +20 -0
- package/dist/plan/stage-guards.js +24 -0
- package/dist/plan/stage-isolation.d.ts +24 -0
- package/dist/plan/stage-isolation.js +29 -1
- package/dist/plan/worker-app-database.d.ts +47 -40
- package/dist/plan/worker-app-database.js +150 -152
- package/dist/plan/worker-app.d.ts +6 -65
- package/dist/plan/worker-app.js +29 -105
- package/dist/plan/worker.d.ts +2 -2
- package/dist/plan/worker.js +2 -1
- package/dist/plan/workspace-client.d.ts +25 -0
- package/dist/plan/workspace-client.js +48 -0
- package/dist/plan/workspace-sign-in.d.ts +55 -0
- package/dist/plan/workspace-sign-in.js +273 -0
- package/dist/plan/workspace-worker.d.ts +4 -0
- package/dist/plan/workspace-worker.js +13 -0
- package/dist/plan/workspace.d.ts +101 -0
- package/dist/plan/workspace.js +305 -0
- package/dist/release/decide.d.ts +30 -1
- package/dist/release/decide.js +73 -11
- package/dist/release/health.d.ts +8 -3
- package/dist/release/health.js +58 -5
- package/dist/release/run.d.ts +2 -5
- package/dist/release/run.js +4 -0
- package/dist/release/state.d.ts +3 -1
- package/dist/release/state.js +6 -0
- package/dist/release/types.d.ts +20 -0
- package/dist/requested-changes.d.ts +11 -2
- package/dist/requested-changes.js +11 -0
- package/dist/service-labels.d.ts +4 -0
- package/dist/service-labels.js +11 -0
- package/dist/slug.d.ts +2 -0
- package/dist/slug.js +10 -6
- package/dist/store/postgres.js +12 -2
- package/dist/store/schema.d.ts +37 -1
- package/dist/store/schema.js +4 -0
- package/dist/worker-app-config.d.ts +6 -10
- package/dist/worker-app-config.js +22 -17
- package/dist/worker-app-migrations.d.ts +105 -0
- package/dist/worker-app-migrations.js +166 -0
- package/dist/worker-app-upload.d.ts +103 -0
- package/dist/worker-app-upload.js +128 -0
- package/package.json +18 -1
package/dist/release/run.js
CHANGED
|
@@ -25,6 +25,10 @@ export async function runRelease(input) {
|
|
|
25
25
|
actions,
|
|
26
26
|
steps: input.entry.steps,
|
|
27
27
|
stepHoldMinutes: input.entry.stepHoldMinutes,
|
|
28
|
+
advance: input.entry.advance,
|
|
29
|
+
minRequests: input.entry.minRequests,
|
|
30
|
+
errorMargin: input.entry.errorMargin,
|
|
31
|
+
staffFirst: input.entry.staffFirst,
|
|
28
32
|
now,
|
|
29
33
|
observation: { health, ...seen },
|
|
30
34
|
...(input.hardOnly ? { hardOnly: true } : {}),
|
package/dist/release/state.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type
|
|
2
|
+
import { type Recorded, resolveReleaseConfig } from './decide.js';
|
|
3
3
|
import type { Color, ReleaseAction, ReleaseSignal, ReleaseState, WindowState } from './types.js';
|
|
4
4
|
/**
|
|
5
5
|
* The persistence the release runner and Archon share. `getScopeIds` is the
|
|
@@ -52,6 +52,8 @@ export declare function readSignal(ids: Record<string, string>, entryKey: string
|
|
|
52
52
|
export declare function readReleaseState(store: ReleaseStore, input: {
|
|
53
53
|
orgId: string;
|
|
54
54
|
entryKey: string;
|
|
55
|
+
/** The entry's `blueGreen` as declared; defaults are resolved into `config`. */
|
|
56
|
+
entry?: Parameters<typeof resolveReleaseConfig>[0];
|
|
55
57
|
}): Promise<ReleaseState | null>;
|
|
56
58
|
declare const actionSchema: z.ZodType<ReleaseAction>;
|
|
57
59
|
/** C2: ask the runner to apply an action on its next run. Archon audits it. */
|
package/dist/release/state.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { STAGE_REF_PATTERN, resolveReleaseConfig } from './decide.js';
|
|
2
3
|
/** Scope keys (C3). `entry` is a hosting entry key: never `.` or `:`. */
|
|
3
4
|
export function releaseKeys(entry) {
|
|
4
5
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(entry))
|
|
@@ -33,6 +34,7 @@ const storedWindow = z.discriminatedUnion('kind', [
|
|
|
33
34
|
z.object({ kind: z.literal('passed'), at: z.string() }),
|
|
34
35
|
z.object({ kind: z.literal('rolled_back'), at: z.string(), reason: z.string() }),
|
|
35
36
|
z.object({ kind: z.literal('cap'), since: z.string() }),
|
|
37
|
+
z.object({ kind: z.literal('staff'), since: z.string() }),
|
|
36
38
|
]);
|
|
37
39
|
const releaseField = z
|
|
38
40
|
.object({
|
|
@@ -110,6 +112,7 @@ export async function readReleaseState(store, input) {
|
|
|
110
112
|
window: r.window,
|
|
111
113
|
signal: readSignal(ids, input.entryKey),
|
|
112
114
|
held: r.held,
|
|
115
|
+
config: resolveReleaseConfig(input.entry),
|
|
113
116
|
};
|
|
114
117
|
}
|
|
115
118
|
const actionSchema = z.discriminatedUnion('kind', [
|
|
@@ -117,6 +120,9 @@ const actionSchema = z.discriminatedUnion('kind', [
|
|
|
117
120
|
z.object({ kind: z.literal('rollback') }),
|
|
118
121
|
z.object({ kind: z.literal('clear_held'), digest: z.string().min(1) }),
|
|
119
122
|
z.object({ kind: z.literal('decide_at_cap'), decision: z.enum(['pass', 'rollback']) }),
|
|
123
|
+
z.object({ kind: z.literal('approve_step') }),
|
|
124
|
+
z.object({ kind: z.literal('open') }),
|
|
125
|
+
z.object({ kind: z.literal('deploy_stage'), ref: z.string().regex(STAGE_REF_PATTERN) }),
|
|
120
126
|
]);
|
|
121
127
|
/** C2: ask the runner to apply an action on its next run. Archon audits it. */
|
|
122
128
|
export async function requestReleaseAction(store, input) {
|
package/dist/release/types.d.ts
CHANGED
|
@@ -22,6 +22,11 @@ export type WindowState = {
|
|
|
22
22
|
} | {
|
|
23
23
|
kind: 'cap';
|
|
24
24
|
since: string;
|
|
25
|
+
}
|
|
26
|
+
/** Deployed on the idle color at weight 0, waiting for an `open` action (staffFirst). */
|
|
27
|
+
| {
|
|
28
|
+
kind: 'staff';
|
|
29
|
+
since: string;
|
|
25
30
|
};
|
|
26
31
|
export interface ReleaseSignal {
|
|
27
32
|
requests: number;
|
|
@@ -29,6 +34,13 @@ export interface ReleaseSignal {
|
|
|
29
34
|
baselineRate: number | null;
|
|
30
35
|
verdict: 'pass' | 'fail' | 'hard_fail' | 'inconclusive';
|
|
31
36
|
}
|
|
37
|
+
/** The release kind of an entry, defaults resolved. */
|
|
38
|
+
export interface ReleaseConfig {
|
|
39
|
+
advance: 'time' | 'signal' | 'approval';
|
|
40
|
+
steps: number[];
|
|
41
|
+
stepHoldMinutes: number;
|
|
42
|
+
staffFirst: boolean;
|
|
43
|
+
}
|
|
32
44
|
export interface ReleaseState {
|
|
33
45
|
entryKey: string;
|
|
34
46
|
live: Color;
|
|
@@ -38,6 +50,7 @@ export interface ReleaseState {
|
|
|
38
50
|
window: WindowState;
|
|
39
51
|
signal: ReleaseSignal | null;
|
|
40
52
|
held: string[];
|
|
53
|
+
config: ReleaseConfig;
|
|
41
54
|
}
|
|
42
55
|
export type ReleaseAction = {
|
|
43
56
|
kind: 'promote';
|
|
@@ -49,6 +62,13 @@ export type ReleaseAction = {
|
|
|
49
62
|
} | {
|
|
50
63
|
kind: 'decide_at_cap';
|
|
51
64
|
decision: 'pass' | 'rollback';
|
|
65
|
+
} | {
|
|
66
|
+
kind: 'approve_step';
|
|
67
|
+
} | {
|
|
68
|
+
kind: 'open';
|
|
69
|
+
} | {
|
|
70
|
+
kind: 'deploy_stage';
|
|
71
|
+
ref: string;
|
|
52
72
|
};
|
|
53
73
|
export declare const COLORS: readonly Color[];
|
|
54
74
|
export declare function otherColor(color: Color): Color;
|
|
@@ -16,7 +16,7 @@ import type { PlanDetail } from './plan/diff.js';
|
|
|
16
16
|
* lowering or leaving a limit unchanged -- raising either one needs
|
|
17
17
|
* approval too, until a billing field exists to say what the raise costs.
|
|
18
18
|
*/
|
|
19
|
-
export type RequestableField = 'calls' | 'customers' | 'storageLimit' | 'aiLimit' | 'domain' | 'mailTier' | 'app' | 'build';
|
|
19
|
+
export type RequestableField = 'calls' | 'customers' | 'storageLimit' | 'aiLimit' | 'domain' | 'mailTier' | 'app' | 'build' | 'bouleApps';
|
|
20
20
|
export declare const REQUESTABLE_FIELDS: readonly RequestableField[];
|
|
21
21
|
/** Turn the org's own calls Matrix stack (`org.ts`'s `CallsConfig.enabled`) on or off. */
|
|
22
22
|
export interface CallsChange {
|
|
@@ -75,12 +75,21 @@ export interface BuildChange {
|
|
|
75
75
|
release: string;
|
|
76
76
|
build: BuildJson;
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Turn `org.boule.apps` on or off (`org.ts`'s `BouleApp.apps`). The company's own Boule turns it on when it makes
|
|
80
|
+
* its first app, so no infra is provisioned that is not in use. On is `auto`. Off retires the edge key, both
|
|
81
|
+
* registrar keys and the apps database route (steps with `destroys: true`), so it always needs a person.
|
|
82
|
+
*/
|
|
83
|
+
export interface BouleAppsChange {
|
|
84
|
+
field: 'bouleApps';
|
|
85
|
+
enabled: boolean;
|
|
86
|
+
}
|
|
78
87
|
/**
|
|
79
88
|
* One company-requested change to its own declared org, keyed by
|
|
80
89
|
* {@link RequestableField}. `classifyChange` and the store both switch on
|
|
81
90
|
* `.field`.
|
|
82
91
|
*/
|
|
83
|
-
export type RequestedChangeValue = CallsChange | CustomersChange | StorageLimitChange | AiLimitChange | DomainChange | MailTierChange | AppChange | BuildChange;
|
|
92
|
+
export type RequestedChangeValue = CallsChange | CustomersChange | StorageLimitChange | AiLimitChange | DomainChange | MailTierChange | AppChange | BuildChange | BouleAppsChange;
|
|
84
93
|
/**
|
|
85
94
|
* `auto`: nothing further is required -- archon may carry the request
|
|
86
95
|
* straight into a plan and apply it. `approval`: a human must approve the
|
|
@@ -8,6 +8,7 @@ export const REQUESTABLE_FIELDS = [
|
|
|
8
8
|
'mailTier',
|
|
9
9
|
'app',
|
|
10
10
|
'build',
|
|
11
|
+
'bouleApps',
|
|
11
12
|
];
|
|
12
13
|
/**
|
|
13
14
|
* Thrown by {@link classifyChange} (and by the requested-change store's
|
|
@@ -44,6 +45,8 @@ function applyRequestedChange(org, change) {
|
|
|
44
45
|
return { ...org, domain: change.domain };
|
|
45
46
|
case 'mailTier':
|
|
46
47
|
return { ...org, mail: { ...org.mail, mode: change.mode } };
|
|
48
|
+
case 'bouleApps':
|
|
49
|
+
return { ...org, boule: { ...org.boule, apps: change.enabled } };
|
|
47
50
|
case 'app':
|
|
48
51
|
case 'build':
|
|
49
52
|
return org;
|
|
@@ -123,6 +126,14 @@ export function classifyChange(org, change, plan) {
|
|
|
123
126
|
// Archon validates the whole company for these; nothing waits for approval.
|
|
124
127
|
if (change.field === 'app' || change.field === 'build')
|
|
125
128
|
return 'auto';
|
|
129
|
+
if (change.field === 'bouleApps') {
|
|
130
|
+
if (typeof change.enabled !== 'boolean') {
|
|
131
|
+
throw new RequestedChangeError('bouleApps needs enabled, true or false');
|
|
132
|
+
}
|
|
133
|
+
// Turning on provisions only what the first app needs. Turning off retires credentials and routes
|
|
134
|
+
// (`destroys: true` steps), so it needs a person whether or not a plan was given.
|
|
135
|
+
return change.enabled ? 'auto' : 'approval';
|
|
136
|
+
}
|
|
126
137
|
if (change.field === 'customers') {
|
|
127
138
|
validateCustomersChange(change);
|
|
128
139
|
// The org after the change must still pass the rule apply checks, for example open registration
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The labels an own org's services answer on, so no hosting entry may take them. */
|
|
2
|
+
export declare const OWN_SERVICE_LABELS: readonly ["auth", "mail", "files", "sfu"];
|
|
3
|
+
/** The first label of every name foundry itself serves for a company: its own services, Agora, Boule, the cms studio, calls, the sign-in host and file shares. */
|
|
4
|
+
export declare const FOUNDRY_SERVICE_LABELS: ReadonlySet<string>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The labels an own org's services answer on, so no hosting entry may take them. */
|
|
2
|
+
export const OWN_SERVICE_LABELS = ['auth', 'mail', 'files', 'sfu'];
|
|
3
|
+
/** The first label of every name foundry itself serves for a company: its own services, Agora, Boule, the cms studio, calls, the sign-in host and file shares. */
|
|
4
|
+
export const FOUNDRY_SERVICE_LABELS = new Set([
|
|
5
|
+
...OWN_SERVICE_LABELS,
|
|
6
|
+
'agora',
|
|
7
|
+
'boule',
|
|
8
|
+
'cms',
|
|
9
|
+
'calls',
|
|
10
|
+
'share',
|
|
11
|
+
]);
|
package/dist/slug.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
export declare const RESERVED_SLUGS: ReadonlySet<string>;
|
|
3
3
|
/** Every platform app, tunnel, project and database name starts `platform-`, so no company slug may. */
|
|
4
4
|
export declare const RESERVED_SLUG_PREFIX = "platform-";
|
|
5
|
+
/** Whether `label` ends in a suffix reserved for another org's own hostname (`-stage`, `-pr-<n>`, `-calls`, `-agora`, `-boule`, `-auth`, `-mail`, `-files`, `-sfu`, `-cms`). */
|
|
6
|
+
export declare function hasBarredSlugSuffix(label: string): boolean;
|
|
5
7
|
/**
|
|
6
8
|
* Why {@link validateSlug} refused a candidate slug, by name so a caller —
|
|
7
9
|
* `store/postgres.ts`'s `DeclaredOrgError`, eventually the operator page —
|
package/dist/slug.js
CHANGED
|
@@ -151,6 +151,15 @@ const OWN_SERVICE_SUFFIX = /-(auth|mail|files|sfu|cms)$/;
|
|
|
151
151
|
* `names-`.
|
|
152
152
|
*/
|
|
153
153
|
const NAMES_PREFIX = /^names(-|$)/;
|
|
154
|
+
/** Whether `label` ends in a suffix reserved for another org's own hostname (`-stage`, `-pr-<n>`, `-calls`, `-agora`, `-boule`, `-auth`, `-mail`, `-files`, `-sfu`, `-cms`). */
|
|
155
|
+
export function hasBarredSlugSuffix(label) {
|
|
156
|
+
return (STAGE_SUFFIX.test(label) ||
|
|
157
|
+
PR_SUFFIX.test(label) ||
|
|
158
|
+
CALLS_SUFFIX.test(label) ||
|
|
159
|
+
AGORA_SUFFIX.test(label) ||
|
|
160
|
+
BOULE_SUFFIX.test(label) ||
|
|
161
|
+
OWN_SERVICE_SUFFIX.test(label));
|
|
162
|
+
}
|
|
154
163
|
/**
|
|
155
164
|
* Validates a candidate org slug against every rule #28 decided. Returns
|
|
156
165
|
* `null` when the slug is acceptable, or a {@link SlugRejection} naming
|
|
@@ -177,12 +186,7 @@ export function validateSlug(slug) {
|
|
|
177
186
|
message: `slug '${slug}' is 'names' or starts with 'names-', a prefix edge reserves for its own platform tunnels and refuses to create a company tunnel under (#535)`,
|
|
178
187
|
};
|
|
179
188
|
}
|
|
180
|
-
if (
|
|
181
|
-
PR_SUFFIX.test(slug) ||
|
|
182
|
-
CALLS_SUFFIX.test(slug) ||
|
|
183
|
-
AGORA_SUFFIX.test(slug) ||
|
|
184
|
-
BOULE_SUFFIX.test(slug) ||
|
|
185
|
-
OWN_SERVICE_SUFFIX.test(slug)) {
|
|
189
|
+
if (hasBarredSlugSuffix(slug)) {
|
|
186
190
|
return {
|
|
187
191
|
reason: 'environment-suffix',
|
|
188
192
|
message: `slug '${slug}' ends with an environment suffix ('-stage', '-pr-<digits>', '-calls', '-agora', '-boule', '-auth', '-mail', '-files', '-sfu' or '-cms') reserved for another org's own hostname (#28, #145, #164, #161) — 'acme-calls' is only legal as acme's calls hostname, 'acme-agora' only as acme's Agora hostname, 'acme-boule' only as acme's Boule hostname, never as an org slug in its own right`,
|
package/dist/store/postgres.js
CHANGED
|
@@ -161,8 +161,15 @@ function rowToOrg(row) {
|
|
|
161
161
|
? { customers: { enabled: row.customersEnabled, signUp: row.customersSignUp } }
|
|
162
162
|
: {}),
|
|
163
163
|
// Omitted while unset. Validated on read: a stored value is not trusted.
|
|
164
|
-
...(row.bouleBlueGreen
|
|
165
|
-
? {
|
|
164
|
+
...(row.bouleBlueGreen || row.bouleApps
|
|
165
|
+
? {
|
|
166
|
+
boule: {
|
|
167
|
+
...(row.bouleBlueGreen
|
|
168
|
+
? { blueGreen: readBlueGreen(row.bouleBlueGreen, 'boule') }
|
|
169
|
+
: {}),
|
|
170
|
+
...(row.bouleApps ? { apps: true } : {}),
|
|
171
|
+
},
|
|
172
|
+
}
|
|
166
173
|
: {}),
|
|
167
174
|
...(row.agoraBlueGreen
|
|
168
175
|
? { agora: { blueGreen: readBlueGreen(row.agoraBlueGreen, 'agora') } }
|
|
@@ -177,6 +184,7 @@ function rowToOrg(row) {
|
|
|
177
184
|
entries: row.hostingEntries.map((entry) => storedEntryToEntry(entry, row.hostingUsesDomain)),
|
|
178
185
|
gateLimit: row.hostingGateLimit,
|
|
179
186
|
repos: row.hostingRepos,
|
|
187
|
+
...(row.hostingGithub ? { github: row.hostingGithub } : {}),
|
|
180
188
|
},
|
|
181
189
|
sso: { enabled: row.ssoEnabled },
|
|
182
190
|
guests: { enabled: row.guestsEnabled },
|
|
@@ -230,6 +238,7 @@ function orgToRow(org) {
|
|
|
230
238
|
customersEnabled: org.customers?.enabled ?? false,
|
|
231
239
|
customersSignUp: org.customers?.signUp ?? false,
|
|
232
240
|
bouleBlueGreen: org.boule?.blueGreen ?? null,
|
|
241
|
+
bouleApps: org.boule?.apps ?? false,
|
|
233
242
|
agoraBlueGreen: org.agora?.blueGreen ?? null,
|
|
234
243
|
servicesOwn: org.services?.own ?? false,
|
|
235
244
|
servicesSfuSlot: org.services?.sfuSlot ?? null,
|
|
@@ -243,6 +252,7 @@ function orgToRow(org) {
|
|
|
243
252
|
hostingEntries: org.hosting.entries,
|
|
244
253
|
hostingGateLimit: org.hosting.gateLimit,
|
|
245
254
|
hostingRepos: normalizeHostingRepos(org.hosting.repos),
|
|
255
|
+
hostingGithub: org.hosting.github ?? null,
|
|
246
256
|
ssoEnabled: org.sso.enabled,
|
|
247
257
|
guestsEnabled: org.guests.enabled,
|
|
248
258
|
soloEnabled: org.solo.enabled,
|
package/dist/store/schema.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppEnvSource, BlueGreenConfig, HostingEntry, IssuerApp, OwnerIdentity } from '../org.js';
|
|
1
|
+
import type { AppEnvSource, BlueGreenConfig, HostingEntry, HostingGithub, IssuerApp, OwnerIdentity } from '../org.js';
|
|
2
2
|
import type { Plan } from '../plan.js';
|
|
3
3
|
import type { Checkpoint, DestroyRecord } from '../state.js';
|
|
4
4
|
/**
|
|
@@ -444,6 +444,23 @@ export declare const declaredOrgs: import("drizzle-orm/pg-core").PgTableWithColu
|
|
|
444
444
|
}, {}, {
|
|
445
445
|
$type: BlueGreenConfig | null;
|
|
446
446
|
}>;
|
|
447
|
+
bouleApps: import("drizzle-orm/pg-core").PgColumn<{
|
|
448
|
+
name: "boule_apps";
|
|
449
|
+
tableName: "foundry_declared_orgs";
|
|
450
|
+
dataType: "boolean";
|
|
451
|
+
columnType: "PgBoolean";
|
|
452
|
+
data: boolean;
|
|
453
|
+
driverParam: boolean;
|
|
454
|
+
notNull: true;
|
|
455
|
+
hasDefault: true;
|
|
456
|
+
isPrimaryKey: false;
|
|
457
|
+
isAutoincrement: false;
|
|
458
|
+
hasRuntimeDefault: false;
|
|
459
|
+
enumValues: undefined;
|
|
460
|
+
baseColumn: never;
|
|
461
|
+
identity: undefined;
|
|
462
|
+
generated: undefined;
|
|
463
|
+
}, {}, {}>;
|
|
447
464
|
versionPin: import("drizzle-orm/pg-core").PgColumn<{
|
|
448
465
|
name: "version_pin";
|
|
449
466
|
tableName: "foundry_declared_orgs";
|
|
@@ -711,6 +728,25 @@ export declare const declaredOrgs: import("drizzle-orm/pg-core").PgTableWithColu
|
|
|
711
728
|
}, {}, {
|
|
712
729
|
$type: string[];
|
|
713
730
|
}>;
|
|
731
|
+
hostingGithub: import("drizzle-orm/pg-core").PgColumn<{
|
|
732
|
+
name: "hosting_github";
|
|
733
|
+
tableName: "foundry_declared_orgs";
|
|
734
|
+
dataType: "json";
|
|
735
|
+
columnType: "PgJsonb";
|
|
736
|
+
data: HostingGithub | null;
|
|
737
|
+
driverParam: unknown;
|
|
738
|
+
notNull: false;
|
|
739
|
+
hasDefault: false;
|
|
740
|
+
isPrimaryKey: false;
|
|
741
|
+
isAutoincrement: false;
|
|
742
|
+
hasRuntimeDefault: false;
|
|
743
|
+
enumValues: undefined;
|
|
744
|
+
baseColumn: never;
|
|
745
|
+
identity: undefined;
|
|
746
|
+
generated: undefined;
|
|
747
|
+
}, {}, {
|
|
748
|
+
$type: HostingGithub | null;
|
|
749
|
+
}>;
|
|
714
750
|
ssoEnabled: import("drizzle-orm/pg-core").PgColumn<{
|
|
715
751
|
name: "sso_enabled";
|
|
716
752
|
tableName: "foundry_declared_orgs";
|
package/dist/store/schema.js
CHANGED
|
@@ -46,6 +46,8 @@ export const declaredOrgs = pgTable('foundry_declared_orgs', {
|
|
|
46
46
|
// OrgBuiltinApp.blueGreen (org.ts) of Boule and Agora; `migrations/0059_org_builtin_blue_green.sql`. Null is off.
|
|
47
47
|
bouleBlueGreen: jsonb('boule_blue_green').$type(),
|
|
48
48
|
agoraBlueGreen: jsonb('agora_blue_green').$type(),
|
|
49
|
+
// BouleApp.apps (org.ts): Boule runs apps for the company; `migrations/0063_boule_apps.sql`.
|
|
50
|
+
bouleApps: boolean('boule_apps').notNull().default(false),
|
|
49
51
|
// VersionPin (org.ts): null follows the platform. `migrations/0053_org_version_pin.sql`.
|
|
50
52
|
versionPin: jsonb('version_pin').$type(),
|
|
51
53
|
// SiteConfig (org.ts, T10). A brand new org has no cms site.
|
|
@@ -76,6 +78,8 @@ export const declaredOrgs = pgTable('foundry_declared_orgs', {
|
|
|
76
78
|
hostingGateLimit: integer('hosting_gate_limit').notNull().default(3),
|
|
77
79
|
// HostingConfig.repos (org.ts); migrations/0060. Lowercase `wtfalch/<name>` strings.
|
|
78
80
|
hostingRepos: jsonb('hosting_repos').notNull().default([]).$type(),
|
|
81
|
+
// HostingConfig.github (org.ts); migrations/0062. Null: the company has no GitHub App installation.
|
|
82
|
+
hostingGithub: jsonb('hosting_github').$type(),
|
|
79
83
|
ssoEnabled: boolean('sso_enabled').notNull().default(false),
|
|
80
84
|
guestsEnabled: boolean('guests_enabled').notNull().default(false),
|
|
81
85
|
soloEnabled: boolean('solo_enabled').notNull().default(false),
|
|
@@ -60,10 +60,14 @@ export interface WorkerAppConfig {
|
|
|
60
60
|
* or truncate. `migrationsSha256` (64 lowercase hex) pins the release's
|
|
61
61
|
* `migrations.json`: required when the release has one, and foundry refuses
|
|
62
62
|
* to parse bytes whose sha256 differs. Omit it only for a release with no
|
|
63
|
-
* `migrations.json`; one that has the file is then refused.
|
|
63
|
+
* `migrations.json`; one that has the file is then refused. `grants` names
|
|
64
|
+
* functions the runtime role may execute, as `name(type, ...)` or
|
|
65
|
+
* `schema.name(type, ...)`: the security definer functions a migration
|
|
66
|
+
* revoked from public, such as `@wtfalch/audit`'s ledger functions.
|
|
64
67
|
*/
|
|
65
68
|
database?: {
|
|
66
69
|
appendOnly?: string[];
|
|
70
|
+
grants?: string[];
|
|
67
71
|
migrationsSha256?: string;
|
|
68
72
|
};
|
|
69
73
|
/** Plain vars. Never secrets. */
|
|
@@ -76,14 +80,8 @@ export declare const WORKER_APP_COMPATIBILITY_FLAGS: ReadonlySet<string>;
|
|
|
76
80
|
export declare const WORKER_APP_INSTANCE_TYPES: ReadonlySet<string>;
|
|
77
81
|
/** The most instances one container class may scale to. */
|
|
78
82
|
export declare const WORKER_APP_MAX_INSTANCES = 100;
|
|
79
|
-
|
|
80
|
-
export declare const WORKER_APP_KEYS_SECRET = "KEYS_CREDENTIAL";
|
|
83
|
+
export { WORKER_APP_APPEND_ONLY_MAX, WORKER_APP_GRANTS_MAX, WORKER_APP_KEYS_SECRET, WORKER_APP_RESERVED_NAMES, } from './entry-rules.js';
|
|
81
84
|
export declare const WORKER_APP_KEYS_URL_VAR = "KEYS_URL";
|
|
82
|
-
/**
|
|
83
|
-
* Names foundry writes into a Worker app's environment itself, so a
|
|
84
|
-
* declaration may not use them as a binding, secret or var.
|
|
85
|
-
*/
|
|
86
|
-
export declare const WORKER_APP_RESERVED_NAMES: RegExp;
|
|
87
85
|
/** True when the entry needs the org's Postgres database: a Coolify app with `database`, or a Worker app with `workerApp.database`. */
|
|
88
86
|
export declare const entryHasDatabase: (entry: {
|
|
89
87
|
database: boolean;
|
|
@@ -93,8 +91,6 @@ export declare const entryHasDatabase: (entry: {
|
|
|
93
91
|
}) => boolean;
|
|
94
92
|
/** The Hyperdrive binding of a Worker app with `database`. */
|
|
95
93
|
export declare const WORKER_APP_HYPERDRIVE_BINDING = "HYPERDRIVE";
|
|
96
|
-
/** The most tables `database.appendOnly` may name, and the shape of one name (`table` or `schema.table`). */
|
|
97
|
-
export declare const WORKER_APP_APPEND_ONLY_MAX = 100;
|
|
98
94
|
/** Hex characters of the org id's sha256 in a host id. */
|
|
99
95
|
export declare const WORKER_APP_HOST_ID_HASH_LENGTH = 12;
|
|
100
96
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { WORKER_APP_ENV_NAME as ENV_NAME, WORKER_APP_FUNCTION_SIGNATURE as FUNCTION_SIGNATURE, SECRET_LIKE_NAME as SECRET_NAME, WORKER_APP_TABLE_NAME as TABLE_NAME, WORKER_APP_APPEND_ONLY_MAX, WORKER_APP_GRANTS_MAX, WORKER_APP_KEYS_SECRET, WORKER_APP_RESERVED_NAMES, } from './entry-rules.js';
|
|
2
3
|
import { validateSlug } from './slug.js';
|
|
3
4
|
/** The compatibility flags a Worker app may ask for. */
|
|
4
5
|
export const WORKER_APP_COMPATIBILITY_FLAGS = new Set(['nodejs_compat']);
|
|
@@ -12,21 +13,12 @@ export const WORKER_APP_INSTANCE_TYPES = new Set([
|
|
|
12
13
|
]);
|
|
13
14
|
/** The most instances one container class may scale to. */
|
|
14
15
|
export const WORKER_APP_MAX_INSTANCES = 100;
|
|
15
|
-
|
|
16
|
-
export const WORKER_APP_KEYS_SECRET = 'KEYS_CREDENTIAL';
|
|
16
|
+
export { WORKER_APP_APPEND_ONLY_MAX, WORKER_APP_GRANTS_MAX, WORKER_APP_KEYS_SECRET, WORKER_APP_RESERVED_NAMES, } from './entry-rules.js';
|
|
17
17
|
export const WORKER_APP_KEYS_URL_VAR = 'KEYS_URL';
|
|
18
|
-
/**
|
|
19
|
-
* Names foundry writes into a Worker app's environment itself, so a
|
|
20
|
-
* declaration may not use them as a binding, secret or var.
|
|
21
|
-
*/
|
|
22
|
-
export const WORKER_APP_RESERVED_NAMES = /^(AUTH_.*|KEYS_.*|MCP_.*|HYPERDRIVE)$/;
|
|
23
18
|
/** True when the entry needs the org's Postgres database: a Coolify app with `database`, or a Worker app with `workerApp.database`. */
|
|
24
19
|
export const entryHasDatabase = (entry) => entry.database || Boolean(entry.workerApp?.database);
|
|
25
20
|
/** The Hyperdrive binding of a Worker app with `database`. */
|
|
26
21
|
export const WORKER_APP_HYPERDRIVE_BINDING = 'HYPERDRIVE';
|
|
27
|
-
/** The most tables `database.appendOnly` may name, and the shape of one name (`table` or `schema.table`). */
|
|
28
|
-
export const WORKER_APP_APPEND_ONLY_MAX = 100;
|
|
29
|
-
const TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,62}(\.[A-Za-z_][A-Za-z0-9_]{0,62})?$/;
|
|
30
22
|
/** The custody Worker's host id shape for a derived (app) host, and its limit. */
|
|
31
23
|
const HOST_ID = /^app-[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
32
24
|
const HOST_ID_MAX_LENGTH = 64;
|
|
@@ -55,12 +47,10 @@ const LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
|
55
47
|
const RELEASE_ASSET_URL = /^https:\/\/github\.com\/wtfalch\/([A-Za-z0-9._-]+)\/releases\/download\/([A-Za-z0-9._+-]+)\/([A-Za-z0-9._+-]+)$/;
|
|
56
48
|
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
57
49
|
const DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
58
|
-
const ENV_NAME = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
59
50
|
const CLASS_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
|
|
60
51
|
const MIGRATION_TAG = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
61
52
|
const IMAGE_REPOSITORY = /^[a-z0-9]+([._-][a-z0-9]+)*(:[0-9]+)?(\/[a-z0-9]+([._-][a-z0-9]+)*)+$/;
|
|
62
53
|
const COMPAT_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
63
|
-
const SECRET_NAME = /_(KEY|SECRET|TOKEN|PASSWORD)$/;
|
|
64
54
|
const VAR_MAX_LENGTH = 1000;
|
|
65
55
|
/**
|
|
66
56
|
* Checks an artifact reference: an https URL with no userinfo, query or
|
|
@@ -203,9 +193,9 @@ export function validateEntryWorkerApp(entry, hosting, names) {
|
|
|
203
193
|
if (Object.keys(entry.env).length > 0) {
|
|
204
194
|
return `${at} has no Coolify app, so env does not apply: use workerApp.vars or workerApp.secrets`;
|
|
205
195
|
}
|
|
206
|
-
const unsupported = Object.keys(entry.uses ?? {}).find((use) => use !== 'auth');
|
|
196
|
+
const unsupported = Object.keys(entry.uses ?? {}).find((use) => use !== 'auth' && use !== 'audit');
|
|
207
197
|
if (unsupported)
|
|
208
|
-
return `${at} supports uses.auth only, not uses.${unsupported}`;
|
|
198
|
+
return `${at} supports uses.auth and uses.audit only, not uses.${unsupported}`;
|
|
209
199
|
if (entry.key.length > WORKER_NAME_MAX_LENGTH - WORKER_NAME_FIXED_LENGTH) {
|
|
210
200
|
return `${at} entry key is too long: the deployed Worker's name (an 8-character org hash, the key, and "${WORKER_APP_NAME_SUFFIX}") must fit Cloudflare's ${WORKER_NAME_MAX_LENGTH}-character limit`;
|
|
211
201
|
}
|
|
@@ -342,11 +332,26 @@ function validateWorkerAppDatabase(at, app) {
|
|
|
342
332
|
if (database === undefined)
|
|
343
333
|
return null;
|
|
344
334
|
if (typeof database !== 'object' || database === null || Array.isArray(database)) {
|
|
345
|
-
return `${at} database must be an object: { appendOnly?: string[]; migrationsSha256?: string }`;
|
|
335
|
+
return `${at} database must be an object: { appendOnly?: string[]; grants?: string[]; migrationsSha256?: string }`;
|
|
346
336
|
}
|
|
347
|
-
const unknown = Object.keys(database).find((key) => key !== 'appendOnly' && key !== 'migrationsSha256');
|
|
337
|
+
const unknown = Object.keys(database).find((key) => key !== 'appendOnly' && key !== 'grants' && key !== 'migrationsSha256');
|
|
348
338
|
if (unknown) {
|
|
349
|
-
return `${at} database.${unknown} is not a setting: only appendOnly and migrationsSha256`;
|
|
339
|
+
return `${at} database.${unknown} is not a setting: only appendOnly, grants and migrationsSha256`;
|
|
340
|
+
}
|
|
341
|
+
const grants = database.grants;
|
|
342
|
+
if (grants !== undefined) {
|
|
343
|
+
if (!Array.isArray(grants) || grants.length > WORKER_APP_GRANTS_MAX) {
|
|
344
|
+
return `${at} database.grants must be a list of at most ${WORKER_APP_GRANTS_MAX} function signatures`;
|
|
345
|
+
}
|
|
346
|
+
const seenGrants = new Set();
|
|
347
|
+
for (const signature of grants) {
|
|
348
|
+
if (typeof signature !== 'string' || !FUNCTION_SIGNATURE.test(signature)) {
|
|
349
|
+
return `${at} database.grants names '${String(signature).slice(0, 80)}', which is not a function signature (name(type, ...) or schema.name(type, ...))`;
|
|
350
|
+
}
|
|
351
|
+
if (seenGrants.has(signature))
|
|
352
|
+
return `${at} database.grants names '${signature}' twice`;
|
|
353
|
+
seenGrants.add(signature);
|
|
354
|
+
}
|
|
350
355
|
}
|
|
351
356
|
const sha = database.migrationsSha256;
|
|
352
357
|
if (sha !== undefined && (typeof sha !== 'string' || !SHA256_HEX.test(sha))) {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Worker app's database changes (the release's `migrations.json`), pure and shared: the
|
|
3
|
+
* platform apply (`plan/worker-app-database.ts`) and `@wtfalch/foundry/apps` both run them
|
|
4
|
+
* through `runWorkerAppMigrations` with their own SQL runner. No node beyond crypto, no network.
|
|
5
|
+
*/
|
|
6
|
+
/** One migration of a release's `migrations.json`. */
|
|
7
|
+
export interface DatabaseMigration {
|
|
8
|
+
id: string;
|
|
9
|
+
sql: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The SQL a Worker app's database changes need, as the owner `O` of the database: the platform
|
|
13
|
+
* path reaches it through an Access tunnel (`HyperdriveOrigin`), a company's own Postgres by URL
|
|
14
|
+
* (`@wtfalch/foundry/apps`). Every method is idempotent.
|
|
15
|
+
*/
|
|
16
|
+
export interface WorkerAppMigrationRunner<O> {
|
|
17
|
+
/** The migration ids the database's ledger holds, in the order applied. */
|
|
18
|
+
appliedMigrationIds(owner: O): Promise<string[]>;
|
|
19
|
+
/** Runs `migration.sql` and records `migration.id` in the ledger in one transaction. */
|
|
20
|
+
applyMigration(owner: O, migration: DatabaseMigration): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Creates or updates the `runtime.user` role with `runtime.password`, grants it DML on the
|
|
23
|
+
* tables, revokes UPDATE, DELETE and TRUNCATE from it on each `appendOnly` table, and grants
|
|
24
|
+
* it EXECUTE on each `grants` function.
|
|
25
|
+
*/
|
|
26
|
+
ensureRuntimeRole(input: {
|
|
27
|
+
owner: O;
|
|
28
|
+
runtime: {
|
|
29
|
+
user: string;
|
|
30
|
+
password: string;
|
|
31
|
+
};
|
|
32
|
+
appendOnly: string[];
|
|
33
|
+
grants: string[];
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export declare const WORKER_APP_MIGRATION_TIMEOUT_MS = 300000;
|
|
37
|
+
export declare const MIGRATIONS_ASSET_NAME = "migrations.json";
|
|
38
|
+
/**
|
|
39
|
+
* Reads a release's `migrations.json`. `null` text is an absent file: no migrations. A file that is
|
|
40
|
+
* not a list of `{id, sql}` with unique ids is refused, naming no SQL.
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseMigrations(text: string | null): DatabaseMigration[];
|
|
43
|
+
/**
|
|
44
|
+
* A present `migrations.json` runs as the database owner, so its bytes must be the ones the
|
|
45
|
+
* declaration pinned. No file needs no digest; a file with no declared digest, or a different
|
|
46
|
+
* one, is refused before it is parsed. The message names neither the SQL nor the wanted digest.
|
|
47
|
+
*/
|
|
48
|
+
export declare function verifyMigrationsDigest(text: string | null, declared: string | undefined): void;
|
|
49
|
+
/** The URL of `migrations.json` beside the module: the same release, another file. */
|
|
50
|
+
export declare const migrationsUrl: (artifactUrl: string) => string;
|
|
51
|
+
/**
|
|
52
|
+
* The migrations the database does not hold, in order. The applied ledger must be the first
|
|
53
|
+
* migrations of the file: a missing or reordered one is refused (migrations are append-only).
|
|
54
|
+
*/
|
|
55
|
+
export declare function unappliedDatabaseMigrations(all: readonly DatabaseMigration[], applied: readonly string[]): DatabaseMigration[];
|
|
56
|
+
/** A migration the platform adds for a module (today `uses.audit`: the pinned `@wtfalch/audit` migrations). */
|
|
57
|
+
export interface PlatformMigration extends DatabaseMigration {
|
|
58
|
+
}
|
|
59
|
+
/** A platform migration not run because the app already ships it, and how that was known. */
|
|
60
|
+
export interface SkippedPlatformMigration {
|
|
61
|
+
id: string;
|
|
62
|
+
/** `same id` or `same sql` (a copy under another id), plain words for the log. */
|
|
63
|
+
why: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The platform migrations still to run, in order, and the ones skipped because the release's own list carries
|
|
67
|
+
* them (same id, or the same SQL under another id: Candor ships its own copy of the audit migrations).
|
|
68
|
+
* A platform migration the database's ledger already holds is neither: it ran before. Never applies twice.
|
|
69
|
+
*/
|
|
70
|
+
export declare function pendingPlatformMigrations(platform: readonly PlatformMigration[], own: readonly DatabaseMigration[], applied: readonly string[]): {
|
|
71
|
+
pending: PlatformMigration[];
|
|
72
|
+
skipped: SkippedPlatformMigration[];
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* The build's database changes, shared by the platform apply and `@wtfalch/foundry/apps`:
|
|
76
|
+
* `text` (the release's `migrations.json`, `null` when it has none) is checked against
|
|
77
|
+
* `want.migrationsSha256`, parsed, the unapplied migrations run in order as the owner (each
|
|
78
|
+
* recorded by the runner's ledger), then the runtime role is made true. A failed migration
|
|
79
|
+
* throws; those before it stay applied. Messages start with `at` and name no SQL.
|
|
80
|
+
*/
|
|
81
|
+
export declare function runWorkerAppMigrations<O>(runner: WorkerAppMigrationRunner<O>, owner: O, text: string | null, want: {
|
|
82
|
+
appendOnly: readonly string[];
|
|
83
|
+
grants?: readonly string[];
|
|
84
|
+
migrationsSha256?: string;
|
|
85
|
+
}, runtime: {
|
|
86
|
+
user: string;
|
|
87
|
+
password: string;
|
|
88
|
+
}, opts: {
|
|
89
|
+
at: string;
|
|
90
|
+
call: <T>(label: string, task: () => Promise<T>, ms?: number) => Promise<T>;
|
|
91
|
+
migrationTimeoutMs?: number;
|
|
92
|
+
/** After each migration, with every id applied so far. */
|
|
93
|
+
onApplied?: (done: string[]) => Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Migrations a module needs that the app need not ship (`uses.audit`). They run first, each once, unless the
|
|
96
|
+
* release's own list carries the same one; their ids are not part of the release's append-only check.
|
|
97
|
+
*/
|
|
98
|
+
platform?: readonly PlatformMigration[];
|
|
99
|
+
/** One plain line per skipped platform migration. */
|
|
100
|
+
log?: (line: string) => void;
|
|
101
|
+
}): Promise<{
|
|
102
|
+
applied: string[];
|
|
103
|
+
ran: string[];
|
|
104
|
+
skipped: SkippedPlatformMigration[];
|
|
105
|
+
}>;
|