@wtfalch/foundry 0.78.0 → 0.79.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/catalogue/copy-en.js +5 -0
- package/dist/catalogue/integrations.js +5 -0
- package/dist/mcp-resources/client.d.ts +16 -0
- package/dist/mcp-resources/client.js +69 -0
- package/dist/mcp-resources/index.d.ts +3 -0
- package/dist/mcp-resources/index.js +3 -0
- package/dist/mcp-resources/schema.d.ts +1707 -0
- package/dist/mcp-resources/schema.js +253 -0
- package/dist/mcp-resources/validation.d.ts +21 -0
- package/dist/mcp-resources/validation.js +97 -0
- package/dist/modules/ai.js +14 -3
- package/dist/modules/customer-grants.js +1 -1
- package/dist/modules/email.d.ts +2 -1
- package/dist/modules/email.js +3 -2
- package/dist/modules/files.js +4 -1
- package/dist/modules/index.js +4 -1
- package/dist/modules/jobs.js +1 -1
- package/dist/modules/mcp-resources.d.ts +2 -0
- package/dist/modules/mcp-resources.js +61 -0
- package/dist/modules/outputs.js +1 -0
- package/dist/modules/repository.d.ts +2 -0
- package/dist/modules/repository.js +3 -1
- package/dist/modules/workspaces.js +1 -1
- package/dist/org.d.ts +3 -0
- package/dist/org.js +10 -7
- package/dist/plan/ai-apps-minter.d.ts +79 -0
- package/dist/plan/ai-apps-minter.js +263 -0
- package/dist/plan/ai-boule-key.d.ts +1 -1
- package/dist/plan/ai-boule-key.js +10 -3
- package/dist/plan/broker-entries.js +4 -4
- package/dist/plan/diff.d.ts +6 -0
- package/dist/plan/diff.js +33 -13
- package/dist/plan/entry-ai-key.d.ts +2 -0
- package/dist/plan/entry-ai-key.js +4 -2
- package/dist/plan/entry-customer-grants.d.ts +1 -1
- package/dist/plan/entry-customer-grants.js +4 -2
- package/dist/plan/entry-files-key.d.ts +10 -0
- package/dist/plan/entry-files-key.js +6 -1
- package/dist/plan/entry-jobs-boule-key.js +1 -1
- package/dist/plan/entry-jobs.js +1 -1
- package/dist/plan/entry-repo-key.js +2 -2
- package/dist/plan/entry-workspaces.d.ts +5 -0
- package/dist/plan/entry-workspaces.js +3 -1
- package/dist/plan/executor.d.ts +27 -0
- package/dist/plan/executor.js +125 -4
- package/dist/plan/files-apps-minter.d.ts +131 -0
- package/dist/plan/files-apps-minter.js +336 -0
- package/dist/plan/files-boule-key.js +2 -2
- package/dist/plan/files-space-alert.js +3 -3
- package/dist/plan/jobs-apps-key.d.ts +29 -0
- package/dist/plan/jobs-apps-key.js +228 -0
- package/dist/plan/jobs-project-settings.js +2 -0
- package/dist/plan/jobs-scheduler-http.d.ts +7 -0
- package/dist/plan/jobs-scheduler-http.js +32 -1
- package/dist/plan/own-jobs-route.d.ts +1 -1
- package/dist/plan/own-jobs-route.js +1 -1
- package/dist/plan/plan-apply.d.ts +6 -0
- package/dist/plan/plan-apply.js +45 -3
- package/dist/plan/platform-diff.d.ts +2 -1
- package/dist/plan/platform-diff.js +11 -4
- package/dist/plan/usage-tail-worker.js +6 -2
- package/dist/release/state.d.ts +1 -1
- package/dist/store/schema.d.ts +2 -2
- package/dist/worker-app-config.js +11 -2
- package/package.json +5 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { appsOn } from '../org.js';
|
|
2
|
+
export const JOBS_APPS_PROJECT = 'jobs_apps_project';
|
|
3
|
+
export const JOBS_APPS_KEY = 'jobs_apps_key';
|
|
4
|
+
export const JOBS_APPS_UNDELIVERED = 'jobs_apps_key_undelivered';
|
|
5
|
+
export const JOBS_APPS_APPLIED = 'jobs_apps_key_applied';
|
|
6
|
+
export const JOBS_APPS_EXPIRES = 'jobs_apps_key_expires';
|
|
7
|
+
export const jobsAppsRef = (org) => `jobs:apps-key:${org}`;
|
|
8
|
+
export const jobsAppsProject = (org) => `boule-apps-${org}`;
|
|
9
|
+
const DAY = 86_400_000;
|
|
10
|
+
export function planJobsAppsKey(org, ids, steps, ready, usable, capable, now) {
|
|
11
|
+
const key = `jobs:apps-key:${org.id}`;
|
|
12
|
+
const block = (description) => ({
|
|
13
|
+
steps: [],
|
|
14
|
+
blocked: [{ key, description: `${org.slug}'s apps jobs key is blocked: ${description}` }],
|
|
15
|
+
});
|
|
16
|
+
if (!appsOn(org)) {
|
|
17
|
+
if ((ids[JOBS_APPS_KEY] ||
|
|
18
|
+
ids[JOBS_APPS_UNDELIVERED] ||
|
|
19
|
+
ids[JOBS_APPS_APPLIED] ||
|
|
20
|
+
ids.jobs_apps_key_withdrawing) &&
|
|
21
|
+
(!capable || !usable))
|
|
22
|
+
return block('the scheduler capability must be available to withdraw its provisioning key');
|
|
23
|
+
return ids[JOBS_APPS_KEY] ||
|
|
24
|
+
ids[JOBS_APPS_UNDELIVERED] ||
|
|
25
|
+
ids[JOBS_APPS_APPLIED] ||
|
|
26
|
+
ids.jobs_apps_key_withdrawing
|
|
27
|
+
? {
|
|
28
|
+
steps: [
|
|
29
|
+
{
|
|
30
|
+
action: {
|
|
31
|
+
system: 'jobs',
|
|
32
|
+
kind: 'apps-key',
|
|
33
|
+
key: org.id,
|
|
34
|
+
desired: { mode: 'retire' },
|
|
35
|
+
},
|
|
36
|
+
step: {
|
|
37
|
+
description: `revoke ${org.slug}'s Jobs provisioning key, remove JOBS_APPS_* from Boule and restart it; keep existing app projects, runtime credentials, schedules and history`,
|
|
38
|
+
destroys: true,
|
|
39
|
+
migrates: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
blocked: [],
|
|
44
|
+
}
|
|
45
|
+
: { steps: [], blocked: [] };
|
|
46
|
+
}
|
|
47
|
+
if (!capable)
|
|
48
|
+
return block('the host has not enabled company-scoped project provisioning on its scheduler');
|
|
49
|
+
if (!usable || !ready.jobsPublicUrl)
|
|
50
|
+
return block('the scheduler and its public route are not ready');
|
|
51
|
+
if (!ready.canHoldSecrets)
|
|
52
|
+
return block('the host cannot hold credentials');
|
|
53
|
+
if (!ids[`coolify:boule:${org.id}`] &&
|
|
54
|
+
!steps.some((s) => s.action.system === 'coolify' && s.action.kind === 'boule' && s.action.key === org.id))
|
|
55
|
+
return block('Boule is not recorded or planned');
|
|
56
|
+
if (ids[JOBS_APPS_UNDELIVERED] && ids[JOBS_APPS_UNDELIVERED] !== ids[JOBS_APPS_KEY])
|
|
57
|
+
return block(`key ${ids[JOBS_APPS_UNDELIVERED]} was not held; inspect and revoke the undelivered key before retrying`);
|
|
58
|
+
if (ids[JOBS_APPS_KEY] &&
|
|
59
|
+
ids[JOBS_APPS_APPLIED] === ready.jobsPublicUrl &&
|
|
60
|
+
Date.parse(ids[JOBS_APPS_EXPIRES] ?? '') - now > 30 * DAY &&
|
|
61
|
+
!ids[JOBS_APPS_UNDELIVERED])
|
|
62
|
+
return { steps: [], blocked: [] };
|
|
63
|
+
return {
|
|
64
|
+
steps: [
|
|
65
|
+
{
|
|
66
|
+
action: {
|
|
67
|
+
system: 'jobs',
|
|
68
|
+
kind: 'apps-key',
|
|
69
|
+
key: org.id,
|
|
70
|
+
desired: {
|
|
71
|
+
mode: 'ensure',
|
|
72
|
+
url: ready.jobsPublicUrl,
|
|
73
|
+
expiresAt: new Date(now + 365 * DAY).toISOString(),
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
step: {
|
|
77
|
+
description: `give ${org.slug}'s Boule a company-scoped jobs project provisioner and restart Boule`,
|
|
78
|
+
destroys: false,
|
|
79
|
+
migrates: false,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
blocked: [],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** Every scheduler/held-key/Coolify call is bounded, and no provider error text can carry a secret out. */
|
|
87
|
+
export async function applyJobsAppsKey(deps, desired) {
|
|
88
|
+
const at = `jobs:apps-key for ${deps.org.slug}`;
|
|
89
|
+
const call = async (work) => {
|
|
90
|
+
let timer;
|
|
91
|
+
try {
|
|
92
|
+
return await Promise.race([
|
|
93
|
+
work(),
|
|
94
|
+
new Promise((_, reject) => {
|
|
95
|
+
timer = setTimeout(() => reject(new Error('timeout')), deps.timeoutMs ?? 30_000);
|
|
96
|
+
}),
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
throw new Error(`${at}: a scheduler, held-key or Boule call did not finish; retry after checking the recorded custody state`);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const id = deps.bouleAppId;
|
|
107
|
+
const project = jobsAppsProject(deps.org.id);
|
|
108
|
+
const mint = deps.scheduler.mintProjectKey?.bind(deps.scheduler);
|
|
109
|
+
const revoke = deps.scheduler.revokeProjectKey?.bind(deps.scheduler);
|
|
110
|
+
if (!mint || !revoke)
|
|
111
|
+
throw new Error(`${at}: the scheduler cannot mint and revoke scoped keys`);
|
|
112
|
+
const revokeOne = (key) => call(async () => {
|
|
113
|
+
try {
|
|
114
|
+
await revoke(project, key);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error?.status !== 404)
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
const ids = deps.recorded();
|
|
122
|
+
const old = ids[JOBS_APPS_KEY];
|
|
123
|
+
const undelivered = ids[JOBS_APPS_UNDELIVERED];
|
|
124
|
+
if (desired.mode === 'retire') {
|
|
125
|
+
deps.record('jobs_apps_key_withdrawing', '1');
|
|
126
|
+
deps.record(JOBS_APPS_PROJECT, project);
|
|
127
|
+
if (!ids.jobs_apps_project_base_url && ids[JOBS_APPS_APPLIED])
|
|
128
|
+
deps.record('jobs_apps_project_base_url', ids[JOBS_APPS_APPLIED]);
|
|
129
|
+
await deps.persist();
|
|
130
|
+
const revoked = new Set([old, undelivered, ids.jobs_apps_key_retiring].filter((key) => Boolean(key) && key !== 'pending'));
|
|
131
|
+
if (undelivered === 'pending') {
|
|
132
|
+
const list = deps.scheduler.listProjectKeys?.bind(deps.scheduler);
|
|
133
|
+
if (!list)
|
|
134
|
+
throw new Error(`${at}: the scheduler cannot inspect the ambiguous provisioning mint; no custody was cleared`);
|
|
135
|
+
for (const key of await call(() => list(project)))
|
|
136
|
+
if (!key.revoked && key.principal === 'boule' && key.scopes.includes('projects:manage'))
|
|
137
|
+
revoked.add(key.id);
|
|
138
|
+
if (revoked.size > 8)
|
|
139
|
+
throw new Error(`${at}: too many management keys to withdraw in one bounded apply; inspect this namespace`);
|
|
140
|
+
}
|
|
141
|
+
for (const key of revoked)
|
|
142
|
+
await revokeOne(key);
|
|
143
|
+
// Record withdrawal before a restart: re-enabling Apps after a failed env cleanup must mint anew.
|
|
144
|
+
for (const field of [
|
|
145
|
+
JOBS_APPS_KEY,
|
|
146
|
+
JOBS_APPS_UNDELIVERED,
|
|
147
|
+
JOBS_APPS_EXPIRES,
|
|
148
|
+
'jobs_apps_key_retiring',
|
|
149
|
+
])
|
|
150
|
+
deps.forget(field);
|
|
151
|
+
await call(() => deps.heldKeys.put(jobsAppsRef(deps.org.id), ''));
|
|
152
|
+
await deps.persist();
|
|
153
|
+
if (id) {
|
|
154
|
+
const rows = await call(() => deps.coolify.listEnvs(id));
|
|
155
|
+
for (const row of rows)
|
|
156
|
+
if (row.key.startsWith('JOBS_APPS_') && row.uuid)
|
|
157
|
+
await call(() => deps.coolify.deleteEnv(id, row.uuid));
|
|
158
|
+
await call(() => deps.coolify.restart(id));
|
|
159
|
+
}
|
|
160
|
+
deps.forget(JOBS_APPS_APPLIED);
|
|
161
|
+
deps.forget('jobs_apps_key_withdrawing');
|
|
162
|
+
await deps.persist();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (!id)
|
|
166
|
+
throw new Error(`${at}: Boule is not recorded`);
|
|
167
|
+
if (undelivered && undelivered !== old)
|
|
168
|
+
throw new Error(`${at}: undelivered key ${undelivered}; nothing was minted`);
|
|
169
|
+
let key = old ? await call(() => deps.heldKeys.resolve(jobsAppsRef(deps.org.id))) : null;
|
|
170
|
+
const expired = Date.parse(ids[JOBS_APPS_EXPIRES] ?? '') - Date.now() < 30 * DAY;
|
|
171
|
+
if (!key || expired) {
|
|
172
|
+
deps.record(JOBS_APPS_PROJECT, project);
|
|
173
|
+
await deps.persist();
|
|
174
|
+
const registered = await call(() => deps.scheduler.registerProject({
|
|
175
|
+
name: project,
|
|
176
|
+
baseUrl: ids.jobs_apps_project_base_url ?? desired.url,
|
|
177
|
+
}));
|
|
178
|
+
deps.record('jobs_apps_project_base_url', ids.jobs_apps_project_base_url ?? desired.url);
|
|
179
|
+
await deps.persist();
|
|
180
|
+
if (registered.secret)
|
|
181
|
+
deps.secret(registered.secret);
|
|
182
|
+
if (registered.syncKey)
|
|
183
|
+
deps.secret(registered.syncKey);
|
|
184
|
+
// Persist intent before the non-repeatable mint. An ambiguous timeout never mints twice.
|
|
185
|
+
deps.record(JOBS_APPS_UNDELIVERED, 'pending');
|
|
186
|
+
await deps.persist();
|
|
187
|
+
const issued = await call(() => mint(project, {
|
|
188
|
+
principal: 'boule',
|
|
189
|
+
scopes: ['projects:manage'],
|
|
190
|
+
expiresAt: desired.expiresAt,
|
|
191
|
+
}));
|
|
192
|
+
deps.record(JOBS_APPS_UNDELIVERED, issued.id);
|
|
193
|
+
await deps.persist();
|
|
194
|
+
deps.secret(issued.key);
|
|
195
|
+
let held = false;
|
|
196
|
+
for (let attempt = 0; attempt < 3 && !held; attempt++)
|
|
197
|
+
try {
|
|
198
|
+
await call(() => deps.heldKeys.put(jobsAppsRef(deps.org.id), issued.key));
|
|
199
|
+
held = true;
|
|
200
|
+
}
|
|
201
|
+
catch { }
|
|
202
|
+
if (!held || !issued.key)
|
|
203
|
+
throw new Error(`${at}: key ${issued.id} was not held; nothing else was written`);
|
|
204
|
+
if (old && old !== issued.id)
|
|
205
|
+
deps.record('jobs_apps_key_retiring', old);
|
|
206
|
+
deps.record(JOBS_APPS_KEY, issued.id);
|
|
207
|
+
deps.record(JOBS_APPS_EXPIRES, desired.expiresAt);
|
|
208
|
+
await deps.persist();
|
|
209
|
+
key = issued.key;
|
|
210
|
+
// The namespace belongs to the project, not its key, so renewal can revoke the old manager.
|
|
211
|
+
}
|
|
212
|
+
const retiring = deps.recorded().jobs_apps_key_retiring;
|
|
213
|
+
if (retiring) {
|
|
214
|
+
await revokeOne(retiring);
|
|
215
|
+
deps.forget('jobs_apps_key_retiring');
|
|
216
|
+
await deps.persist();
|
|
217
|
+
}
|
|
218
|
+
deps.secret(key);
|
|
219
|
+
await call(() => deps.coolify.bulkUpsertEnvs(id, [
|
|
220
|
+
{ key: 'JOBS_APPS_URL', value: desired.url, literal: true },
|
|
221
|
+
{ key: 'JOBS_APPS_KEY', value: key, literal: true },
|
|
222
|
+
]));
|
|
223
|
+
await call(() => deps.coolify.restart(id));
|
|
224
|
+
deps.forget('jobs_apps_key_withdrawing');
|
|
225
|
+
deps.record(JOBS_APPS_APPLIED, desired.url);
|
|
226
|
+
deps.forget(JOBS_APPS_UNDELIVERED);
|
|
227
|
+
await deps.persist();
|
|
228
|
+
}
|
|
@@ -25,6 +25,8 @@ export function planJobsProjectSettings(org, ids, planned, ready, capable) {
|
|
|
25
25
|
const steps = [];
|
|
26
26
|
const blocked = [];
|
|
27
27
|
for (const entry of org.hosting.entries) {
|
|
28
|
+
if (entry.workerApp)
|
|
29
|
+
continue;
|
|
28
30
|
const settings = jobsProjectSettings(org, entry.key);
|
|
29
31
|
if (!settings)
|
|
30
32
|
continue;
|
|
@@ -35,10 +35,17 @@ export declare class HttpJobsSchedulerClient implements JobsSchedulerPort {
|
|
|
35
35
|
mintProjectKey(project: string, grant: {
|
|
36
36
|
principal: string;
|
|
37
37
|
scopes: readonly string[];
|
|
38
|
+
expiresAt?: string;
|
|
38
39
|
}): Promise<{
|
|
39
40
|
id: string;
|
|
40
41
|
key: string;
|
|
41
42
|
}>;
|
|
43
|
+
listProjectKeys(project: string): Promise<{
|
|
44
|
+
id: string;
|
|
45
|
+
principal: string;
|
|
46
|
+
scopes: string[];
|
|
47
|
+
revoked: boolean;
|
|
48
|
+
}[]>;
|
|
42
49
|
/** `DELETE /admin/projects/<name>/keys/<id>`. A refusal throws with the status only (404: no such key). */
|
|
43
50
|
revokeProjectKey(project: string, keyId: string): Promise<void>;
|
|
44
51
|
/** `DELETE /admin/projects/<name>`. A refusal throws with the status only. */
|
|
@@ -71,7 +71,11 @@ export class HttpJobsSchedulerClient {
|
|
|
71
71
|
authorization: `Bearer ${this.adminToken}`,
|
|
72
72
|
'content-type': 'application/json',
|
|
73
73
|
},
|
|
74
|
-
body: JSON.stringify({
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
principal: grant.principal,
|
|
76
|
+
scopes: [...grant.scopes],
|
|
77
|
+
...(grant.expiresAt ? { expiresAt: grant.expiresAt } : {}),
|
|
78
|
+
}),
|
|
75
79
|
signal: AbortSignal.timeout(ADMIN_CALL_TIMEOUT_MS),
|
|
76
80
|
});
|
|
77
81
|
const body = await response.json().catch(() => undefined);
|
|
@@ -88,6 +92,33 @@ export class HttpJobsSchedulerClient {
|
|
|
88
92
|
}
|
|
89
93
|
return { id: fields.id, key: fields.key };
|
|
90
94
|
}
|
|
95
|
+
async listProjectKeys(project) {
|
|
96
|
+
const response = await this.fetchImpl(`${this.baseUrl}/admin/projects/${encodeURIComponent(project)}/keys`, {
|
|
97
|
+
headers: { authorization: `Bearer ${this.adminToken}` },
|
|
98
|
+
redirect: 'error',
|
|
99
|
+
signal: AbortSignal.timeout(ADMIN_CALL_TIMEOUT_MS),
|
|
100
|
+
});
|
|
101
|
+
if (response.status !== 200)
|
|
102
|
+
throw new Error('jobs scheduler: project key listing did not answer');
|
|
103
|
+
const body = await response.json();
|
|
104
|
+
if (!body || typeof body !== 'object' || !Array.isArray(body.keys))
|
|
105
|
+
throw new Error('jobs scheduler: invalid key listing');
|
|
106
|
+
return body.keys.map((value) => {
|
|
107
|
+
const key = value;
|
|
108
|
+
if (!key ||
|
|
109
|
+
typeof key.id !== 'string' ||
|
|
110
|
+
typeof key.principal !== 'string' ||
|
|
111
|
+
!Array.isArray(key.scopes) ||
|
|
112
|
+
!key.scopes.every((scope) => typeof scope === 'string'))
|
|
113
|
+
throw new Error('jobs scheduler: invalid key summary');
|
|
114
|
+
return {
|
|
115
|
+
id: key.id,
|
|
116
|
+
principal: key.principal,
|
|
117
|
+
scopes: key.scopes,
|
|
118
|
+
revoked: Boolean(key.revokedAt),
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
}
|
|
91
122
|
/** `DELETE /admin/projects/<name>/keys/<id>`. A refusal throws with the status only (404: no such key). */
|
|
92
123
|
async revokeProjectKey(project, keyId) {
|
|
93
124
|
const response = await this.fetchImpl(`${this.baseUrl}/admin/projects/${encodeURIComponent(project)}/keys/${encodeURIComponent(keyId)}`, {
|
|
@@ -20,7 +20,7 @@ export declare const OWN_JOBS_ROUTE_CONNECTOR_APP_SCOPE_KEY = "coolify_own_jobs_
|
|
|
20
20
|
* `/deliveries`) and what Boule reads and presses for one project with that project's key (`/project/*`,
|
|
21
21
|
* jobs#79, foundry#1119). `/admin/*` is not on the internet; foundry reaches it on the Coolify network.
|
|
22
22
|
*/
|
|
23
|
-
export declare const JOBS_ENTRY_PATH_PATTERN = "^/(sync$|timers(/|$)|deliveries$|project/)";
|
|
23
|
+
export declare const JOBS_ENTRY_PATH_PATTERN = "^/(sync$|timers(/|$)|deliveries$|project/|projects/)";
|
|
24
24
|
/** The path pattern the route's ingress was last written with; a different pattern plans the route again. */
|
|
25
25
|
export declare const OWN_JOBS_ROUTE_PATHS_SCOPE_KEY = "own_jobs_route_paths";
|
|
26
26
|
export declare const JOBS_ROUTE_CALL_TIMEOUT_MS = 60000;
|
|
@@ -21,7 +21,7 @@ export const OWN_JOBS_ROUTE_CONNECTOR_APP_SCOPE_KEY = 'coolify_own_jobs_connecto
|
|
|
21
21
|
* `/deliveries`) and what Boule reads and presses for one project with that project's key (`/project/*`,
|
|
22
22
|
* jobs#79, foundry#1119). `/admin/*` is not on the internet; foundry reaches it on the Coolify network.
|
|
23
23
|
*/
|
|
24
|
-
export const JOBS_ENTRY_PATH_PATTERN = '^/(sync$|timers(/|$)|deliveries$|project/)';
|
|
24
|
+
export const JOBS_ENTRY_PATH_PATTERN = '^/(sync$|timers(/|$)|deliveries$|project/|projects/)';
|
|
25
25
|
/** The path pattern the route's ingress was last written with; a different pattern plans the route again. */
|
|
26
26
|
export const OWN_JOBS_ROUTE_PATHS_SCOPE_KEY = 'own_jobs_route_paths';
|
|
27
27
|
export const JOBS_ROUTE_CALL_TIMEOUT_MS = 60_000;
|
|
@@ -628,6 +628,7 @@ export interface FoundryPlanApplyOptions {
|
|
|
628
628
|
* `uses.jobs` entry's Boule key is reported blocked.
|
|
629
629
|
*/
|
|
630
630
|
jobsProjectKeys?: boolean;
|
|
631
|
+
jobsAppProjects?: boolean;
|
|
631
632
|
/** The host can deliver per-app Files space alerts. */
|
|
632
633
|
serviceAlertMail?: boolean;
|
|
633
634
|
filesSpaceAlerts?: boolean;
|
|
@@ -639,6 +640,8 @@ export interface FoundryPlanApplyOptions {
|
|
|
639
640
|
* below it. Without it Boule's ai key is reported blocked.
|
|
640
641
|
*/
|
|
641
642
|
aiBouleReads?: boolean;
|
|
643
|
+
/** Off until AI scoped revocation and the host adapter are deployed. */
|
|
644
|
+
aiAppsKeys?: boolean;
|
|
642
645
|
/** C2: true when the host's executor was built with a `WorkerAppDatabasePort`. */
|
|
643
646
|
workerAppDatabase?: boolean;
|
|
644
647
|
/** True when that port has `deletableTables`. A `workerApp.database.noDelete` is blocked without it. */
|
|
@@ -807,10 +810,12 @@ export declare class FoundryPlanApply implements PlanApply {
|
|
|
807
810
|
private readonly analyticsPort;
|
|
808
811
|
private readonly reportingPort;
|
|
809
812
|
private readonly jobsProjectKeys;
|
|
813
|
+
private readonly jobsAppProjects;
|
|
810
814
|
private readonly serviceAlertMail;
|
|
811
815
|
private readonly filesSpaceAlerts;
|
|
812
816
|
private readonly jobsProjectSettings;
|
|
813
817
|
private readonly aiBouleReads;
|
|
818
|
+
private readonly aiAppsKeys;
|
|
814
819
|
private readonly workerAppDatabase;
|
|
815
820
|
private readonly workerAppNoDeleteCheck;
|
|
816
821
|
private readonly workerAppRegistryAccountId;
|
|
@@ -876,6 +881,7 @@ export declare class FoundryPlanApply implements PlanApply {
|
|
|
876
881
|
* Throws {@link InvalidMigrationConfirmationError} for a confirmation that is not valid.
|
|
877
882
|
*/
|
|
878
883
|
confirmOrgPostgresMigration(orgId: OrgId, confirmation: OrgPostgresMigrationConfirmation): Promise<void>;
|
|
884
|
+
private aiHeld;
|
|
879
885
|
private computeAdopt;
|
|
880
886
|
apply(plan: Plan, confirmedOrgName: string, options?: FoundryApplyOptions): Promise<AppliedState>;
|
|
881
887
|
/**
|
package/dist/plan/plan-apply.js
CHANGED
|
@@ -23,7 +23,9 @@ import { entryAiUndeliveredScopeKey } from './entry-ai-key.js';
|
|
|
23
23
|
import { BOULE_EDGE_ENTRY_KEY, bouleEdgeKeyWanted, entryEdgeRetiredScopeKey, entryEdgeUndeliveredScopeKey, } from './entry-edge-key.js';
|
|
24
24
|
import { FILES_BOULE_KEY_SCOPE_KEY, FILES_BOULE_UNDELIVERED_SCOPE_KEY, FILES_ROOT_UNDELIVERED_SCOPE_KEY, entryFilesUndeliveredScopeKey, } from './entry-files-key.js';
|
|
25
25
|
import { probeRepoKeys, repoKeyProbeTargets, repoKeysInPlay, } from './entry-repo-key.js';
|
|
26
|
+
import { FILES_APPS_MINTER_SCOPE_KEY, FILES_APPS_MINTER_UNDELIVERED_SCOPE_KEY, } from './files-apps-minter.js';
|
|
26
27
|
import { PLATFORM_GITHUB_APP_REFS } from './held-key-refs.js';
|
|
28
|
+
import { JOBS_APPS_KEY, JOBS_APPS_PROJECT, JOBS_APPS_UNDELIVERED, jobsAppsProject, } from './jobs-apps-key.js';
|
|
27
29
|
import { KEYS_REGISTRAR_MINT_STARTED_SCOPE_KEY, KEYS_REGISTRAR_SCOPE_KEY, KEYS_REGISTRAR_UNDELIVERED_SCOPE_KEY, } from './keys-registrar.js';
|
|
28
30
|
import { readLivePlatformResources, readLiveResources, } from './live.js';
|
|
29
31
|
import { hostingNames } from './locked-hostnames.js';
|
|
@@ -195,10 +197,12 @@ export class FoundryPlanApply {
|
|
|
195
197
|
analyticsPort;
|
|
196
198
|
reportingPort;
|
|
197
199
|
jobsProjectKeys;
|
|
200
|
+
jobsAppProjects;
|
|
198
201
|
serviceAlertMail;
|
|
199
202
|
filesSpaceAlerts;
|
|
200
203
|
jobsProjectSettings;
|
|
201
204
|
aiBouleReads;
|
|
205
|
+
aiAppsKeys;
|
|
202
206
|
workerAppDatabase;
|
|
203
207
|
workerAppNoDeleteCheck;
|
|
204
208
|
workerAppRegistryAccountId;
|
|
@@ -248,10 +252,12 @@ export class FoundryPlanApply {
|
|
|
248
252
|
this.analyticsPort = options.analyticsPort ?? false;
|
|
249
253
|
this.reportingPort = options.reportingPort ?? false;
|
|
250
254
|
this.jobsProjectKeys = options.jobsProjectKeys ?? false;
|
|
255
|
+
this.jobsAppProjects = options.jobsAppProjects ?? false;
|
|
251
256
|
this.filesSpaceAlerts = options.filesSpaceAlerts ?? false;
|
|
252
257
|
this.serviceAlertMail = options.serviceAlertMail ?? false;
|
|
253
258
|
this.jobsProjectSettings = options.jobsProjectSettings ?? false;
|
|
254
259
|
this.aiBouleReads = options.aiBouleReads ?? false;
|
|
260
|
+
this.aiAppsKeys = options.aiAppsKeys ?? false;
|
|
255
261
|
this.workerAppDatabase = options.workerAppDatabase ?? false;
|
|
256
262
|
this.workerAppNoDeleteCheck = options.workerAppNoDeleteCheck ?? false;
|
|
257
263
|
this.workerAppRegistryAccountId = options.workerAppRegistryAccountId;
|
|
@@ -480,6 +486,23 @@ export class FoundryPlanApply {
|
|
|
480
486
|
await record('recorded');
|
|
481
487
|
}));
|
|
482
488
|
}
|
|
489
|
+
async aiHeld(ref) {
|
|
490
|
+
let timer;
|
|
491
|
+
try {
|
|
492
|
+
return Boolean(await Promise.race([
|
|
493
|
+
this.platformDeps?.heldKeys?.resolve(ref),
|
|
494
|
+
new Promise((_, reject) => {
|
|
495
|
+
timer = setTimeout(() => reject(new Error('timeout')), 15_000);
|
|
496
|
+
}),
|
|
497
|
+
]));
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
clearTimeout(timer);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
483
506
|
async computeAdopt(id, input) {
|
|
484
507
|
const org = await this.store.get(id);
|
|
485
508
|
if (!org)
|
|
@@ -1727,11 +1750,17 @@ export class FoundryPlanApply {
|
|
|
1727
1750
|
analyticsPort: this.analyticsPort,
|
|
1728
1751
|
reportingPort: this.reportingPort,
|
|
1729
1752
|
jobsProjectKeys: this.jobsProjectKeys,
|
|
1753
|
+
jobsAppProjects: this.jobsAppProjects,
|
|
1730
1754
|
filesSpaceAlerts: this.filesSpaceAlerts,
|
|
1731
1755
|
serviceAlertMail: this.serviceAlertMail,
|
|
1732
1756
|
serviceAlertMailCurrent: await this.readServiceAlertMail(org, platformReadiness, recorded),
|
|
1733
1757
|
jobsProjectSettings: this.jobsProjectSettings,
|
|
1734
1758
|
aiBouleReads: this.aiBouleReads,
|
|
1759
|
+
aiAppsKeys: this.aiAppsKeys,
|
|
1760
|
+
aiAppsMinterHeld: this.aiAppsKeys && recorded.ai_apps_minter
|
|
1761
|
+
? await this.aiHeld(`ai:apps-minter:${org.id}`)
|
|
1762
|
+
: true,
|
|
1763
|
+
aiAppsRootKeyHeld: this.aiAppsKeys ? await this.aiHeld(`ai:key:${org.slug}`) : false,
|
|
1735
1764
|
workerAppDatabase: this.workerAppDatabase,
|
|
1736
1765
|
workerAppNoDeleteCheck: this.workerAppNoDeleteCheck,
|
|
1737
1766
|
workerAppRegistryAccountId: this.workerAppRegistryAccountId,
|
|
@@ -2520,6 +2549,11 @@ function destroyTargets(serviceIds, orgId) {
|
|
|
2520
2549
|
'edge:key:',
|
|
2521
2550
|
];
|
|
2522
2551
|
const targets = [];
|
|
2552
|
+
// Remove the provisioner project before its scheduler host. The service cascades to managed apps.
|
|
2553
|
+
if (serviceIds[JOBS_APPS_PROJECT] ||
|
|
2554
|
+
serviceIds[JOBS_APPS_KEY] ||
|
|
2555
|
+
serviceIds[JOBS_APPS_UNDELIVERED])
|
|
2556
|
+
targets.push({ system: 'jobs', kind: 'apps-projects', key: orgId, id: jobsAppsProject(orgId) });
|
|
2523
2557
|
const adopted = adoptedResourceIds(serviceIds);
|
|
2524
2558
|
for (const prefix of ORDERED_PREFIXES) {
|
|
2525
2559
|
if (prefix === 'cloudflare:worker:') {
|
|
@@ -2678,7 +2712,9 @@ function keptDescriptions(serviceIds) {
|
|
|
2678
2712
|
}
|
|
2679
2713
|
function destroyStepDescriptions(targets) {
|
|
2680
2714
|
return [
|
|
2681
|
-
...targets.map((t) =>
|
|
2715
|
+
...targets.map((t) => t.system === 'jobs' && t.kind === 'apps-projects'
|
|
2716
|
+
? `delete Jobs namespace ${t.id}, including its app projects, schedules, history and keys`
|
|
2717
|
+
: `delete ${t.system} ${t.kind} ${t.key}`),
|
|
2682
2718
|
'delete the declared org',
|
|
2683
2719
|
];
|
|
2684
2720
|
}
|
|
@@ -2812,6 +2848,8 @@ const STEP_FIELDS = {
|
|
|
2812
2848
|
'coolify:reporting': 'hosting.entries.uses.reporting',
|
|
2813
2849
|
'coolify:reporting-boule': 'boule.apps',
|
|
2814
2850
|
'coolify:org-apps-admin': 'boule.apps',
|
|
2851
|
+
'files:apps-minter': 'boule.apps',
|
|
2852
|
+
'jobs:apps-key': 'boule.apps',
|
|
2815
2853
|
'cloudflare:apps-db-route': 'boule.apps',
|
|
2816
2854
|
'cloudflare:apps-zones': 'boule.apps',
|
|
2817
2855
|
};
|
|
@@ -2938,8 +2976,12 @@ function failedFields(org, detail, completed, serviceIds) {
|
|
|
2938
2976
|
// F7: a registrar key minted and never delivered stays unresolved until a later apply revokes it.
|
|
2939
2977
|
if (serviceIds[KEYS_REGISTRAR_UNDELIVERED_SCOPE_KEY])
|
|
2940
2978
|
failed.add('boule.apps');
|
|
2941
|
-
// F4 (#925): the same for an apps admin role or a route service token minted and never delivered
|
|
2942
|
-
|
|
2979
|
+
// F4 (#925): the same for an apps admin role or a route service token minted and never delivered, and
|
|
2980
|
+
// Boule's apps files key minted and not yet held or written (a marker that differs from its guard).
|
|
2981
|
+
if ((serviceIds[FILES_APPS_MINTER_UNDELIVERED_SCOPE_KEY] &&
|
|
2982
|
+
serviceIds[FILES_APPS_MINTER_UNDELIVERED_SCOPE_KEY] !==
|
|
2983
|
+
serviceIds[FILES_APPS_MINTER_SCOPE_KEY]) ||
|
|
2984
|
+
serviceIds[ORG_APPS_ADMIN_UNDELIVERED_SCOPE_KEY] ||
|
|
2943
2985
|
serviceIds[remoteDatabaseKeys(APPS_DB_ENTRY_KEY).undelivered]) {
|
|
2944
2986
|
failed.add('boule.apps');
|
|
2945
2987
|
}
|
|
@@ -107,7 +107,7 @@ export declare const PLATFORM_AUTH_MCP_SCOPE_KEY = "auth_mcp_env_applied";
|
|
|
107
107
|
* broker applied under `'1'` plans its sign-in step once more and gets the key without a hand step.
|
|
108
108
|
*/
|
|
109
109
|
export declare const AUTH_MCP_ENV_VERSION = "2";
|
|
110
|
-
/**
|
|
110
|
+
/** Legacy and configurable MCP integrations both need the broker's MCP database and secret. */
|
|
111
111
|
export declare function orgDeclaresMcp(org: Pick<DeclaredOrg, 'hosting'>): boolean;
|
|
112
112
|
/**
|
|
113
113
|
* The `gitRef` the platform's login, in-app login and broker apps were last built from (value: the ref). A
|
|
@@ -1262,6 +1262,7 @@ export declare const PLATFORM_USAGE_TAIL_SCRIPT_NAME = "platform-usage-tail";
|
|
|
1262
1262
|
export declare const PLATFORM_USAGE_TAIL_KEY_REF = "usage-tail:worker:key";
|
|
1263
1263
|
export declare const PLATFORM_USAGE_TAIL_KEY_UNDELIVERED_SCOPE_KEY = "usage_tail_key_undelivered";
|
|
1264
1264
|
export declare const PLATFORM_USAGE_TAIL_KEY_SET_SCOPE_KEY = "usage_tail_key_set";
|
|
1265
|
+
export declare const PLATFORM_USAGE_TAIL_APPLIED_URL_SCOPE_KEY = "usage_tail_applied_url";
|
|
1265
1266
|
export declare const PLATFORM_USAGE_TAIL_APPLIED_TAG_SCOPE_KEY = "usage_tail_applied_tag";
|
|
1266
1267
|
export declare const PLATFORM_USAGE_TAIL_APPLIED_SHA256_SCOPE_KEY = "usage_tail_applied_sha256";
|
|
1267
1268
|
export declare const PLATFORM_USAGE_TAIL_REMOVED_SCOPE_KEY = "usage_tail_removed";
|
|
@@ -117,9 +117,10 @@ export const PLATFORM_AUTH_MCP_SCOPE_KEY = 'auth_mcp_env_applied';
|
|
|
117
117
|
* broker applied under `'1'` plans its sign-in step once more and gets the key without a hand step.
|
|
118
118
|
*/
|
|
119
119
|
export const AUTH_MCP_ENV_VERSION = '2';
|
|
120
|
-
/**
|
|
120
|
+
/** Legacy and configurable MCP integrations both need the broker's MCP database and secret. */
|
|
121
121
|
export function orgDeclaresMcp(org) {
|
|
122
|
-
return org.hosting.enabled &&
|
|
122
|
+
return (org.hosting.enabled &&
|
|
123
|
+
org.hosting.entries.some((e) => Boolean(e.uses?.auth?.mcp || e.uses?.auth?.mcpResources)));
|
|
123
124
|
}
|
|
124
125
|
/**
|
|
125
126
|
* The `gitRef` the platform's login, in-app login and broker apps were last built from (value: the ref). A
|
|
@@ -1461,6 +1462,7 @@ export const PLATFORM_USAGE_TAIL_SCRIPT_NAME = 'platform-usage-tail';
|
|
|
1461
1462
|
export const PLATFORM_USAGE_TAIL_KEY_REF = 'usage-tail:worker:key';
|
|
1462
1463
|
export const PLATFORM_USAGE_TAIL_KEY_UNDELIVERED_SCOPE_KEY = 'usage_tail_key_undelivered';
|
|
1463
1464
|
export const PLATFORM_USAGE_TAIL_KEY_SET_SCOPE_KEY = 'usage_tail_key_set';
|
|
1465
|
+
export const PLATFORM_USAGE_TAIL_APPLIED_URL_SCOPE_KEY = 'usage_tail_applied_url';
|
|
1464
1466
|
export const PLATFORM_USAGE_TAIL_APPLIED_TAG_SCOPE_KEY = 'usage_tail_applied_tag';
|
|
1465
1467
|
export const PLATFORM_USAGE_TAIL_APPLIED_SHA256_SCOPE_KEY = 'usage_tail_applied_sha256';
|
|
1466
1468
|
export const PLATFORM_USAGE_TAIL_REMOVED_SCOPE_KEY = 'usage_tail_removed';
|
|
@@ -3488,12 +3490,17 @@ function planUsageTail(platform, recordedIds, executorBlocked, edgeApplied) {
|
|
|
3488
3490
|
const applied = deployed &&
|
|
3489
3491
|
Boolean(recordedIds[PLATFORM_USAGE_TAIL_KEY_SET_SCOPE_KEY]) &&
|
|
3490
3492
|
recordedIds[PLATFORM_USAGE_TAIL_APPLIED_TAG_SCOPE_KEY] === usageTail.tag &&
|
|
3491
|
-
recordedIds[PLATFORM_USAGE_TAIL_APPLIED_SHA256_SCOPE_KEY] === usageTail.sha256
|
|
3493
|
+
recordedIds[PLATFORM_USAGE_TAIL_APPLIED_SHA256_SCOPE_KEY] === usageTail.sha256 &&
|
|
3494
|
+
recordedIds[PLATFORM_USAGE_TAIL_APPLIED_URL_SCOPE_KEY] === `https://${platform.edge.hostname}`;
|
|
3492
3495
|
if (applied)
|
|
3493
3496
|
return { step: null, blocked: [] };
|
|
3494
3497
|
return {
|
|
3495
3498
|
step: {
|
|
3496
|
-
action: action({
|
|
3499
|
+
action: action({
|
|
3500
|
+
gitRef: usageTail.tag,
|
|
3501
|
+
sha256: usageTail.sha256,
|
|
3502
|
+
url: `https://${platform.edge.hostname}`,
|
|
3503
|
+
}),
|
|
3497
3504
|
step: {
|
|
3498
3505
|
description: `deploy the usage tail Worker ${PLATFORM_USAGE_TAIL_SCRIPT_NAME} from edge release ${usageTail.tag}, with its held key`,
|
|
3499
3506
|
destroys: false,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { usageTailArtifactUrl } from '../platform.js';
|
|
3
|
-
import { PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_USAGE_TAIL_APPLIED_SHA256_SCOPE_KEY, PLATFORM_USAGE_TAIL_APPLIED_TAG_SCOPE_KEY, PLATFORM_USAGE_TAIL_KEY, PLATFORM_USAGE_TAIL_KEY_REF, PLATFORM_USAGE_TAIL_KEY_SET_SCOPE_KEY, PLATFORM_USAGE_TAIL_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_USAGE_TAIL_REMOVED_SCOPE_KEY, PLATFORM_USAGE_TAIL_SCRIPT_NAME, } from './platform-diff.js';
|
|
3
|
+
import { PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_USAGE_TAIL_APPLIED_SHA256_SCOPE_KEY, PLATFORM_USAGE_TAIL_APPLIED_TAG_SCOPE_KEY, PLATFORM_USAGE_TAIL_APPLIED_URL_SCOPE_KEY, PLATFORM_USAGE_TAIL_KEY, PLATFORM_USAGE_TAIL_KEY_REF, PLATFORM_USAGE_TAIL_KEY_SET_SCOPE_KEY, PLATFORM_USAGE_TAIL_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_USAGE_TAIL_REMOVED_SCOPE_KEY, PLATFORM_USAGE_TAIL_SCRIPT_NAME, } from './platform-diff.js';
|
|
4
4
|
/** The date Cloudflare's Workers runtime compatibility is pinned to; no Workers feature here needs a later one. */
|
|
5
5
|
const USAGE_TAIL_COMPATIBILITY_DATE = '2026-10-01';
|
|
6
6
|
/**
|
|
@@ -28,6 +28,7 @@ export async function provisionPlatformUsageTail(context, deps) {
|
|
|
28
28
|
PLATFORM_USAGE_TAIL_KEY_UNDELIVERED_SCOPE_KEY,
|
|
29
29
|
PLATFORM_USAGE_TAIL_KEY_SET_SCOPE_KEY,
|
|
30
30
|
PLATFORM_USAGE_TAIL_APPLIED_TAG_SCOPE_KEY,
|
|
31
|
+
PLATFORM_USAGE_TAIL_APPLIED_URL_SCOPE_KEY,
|
|
31
32
|
PLATFORM_USAGE_TAIL_APPLIED_SHA256_SCOPE_KEY,
|
|
32
33
|
]) {
|
|
33
34
|
context.recordScopeId(key, '');
|
|
@@ -64,7 +65,9 @@ export async function provisionPlatformUsageTail(context, deps) {
|
|
|
64
65
|
artifactUrl: usageTailArtifactUrl(usageTail.tag),
|
|
65
66
|
artifactSha256: usageTail.sha256,
|
|
66
67
|
compatibilityDate: USAGE_TAIL_COMPATIBILITY_DATE,
|
|
67
|
-
bindings: [
|
|
68
|
+
bindings: [
|
|
69
|
+
{ type: 'plain_text', name: 'EDGE_USAGE_URL', text: `https://${platform.edge.hostname}` },
|
|
70
|
+
],
|
|
68
71
|
});
|
|
69
72
|
// The script is foundry's from here on. Recorded now, not at the end: after
|
|
70
73
|
// a later failure the next plan must repair this Worker, not take it for a
|
|
@@ -84,6 +87,7 @@ export async function provisionPlatformUsageTail(context, deps) {
|
|
|
84
87
|
// Last, after every write the applied values depend on.
|
|
85
88
|
context.recordScopeId(PLATFORM_USAGE_TAIL_APPLIED_TAG_SCOPE_KEY, usageTail.tag);
|
|
86
89
|
context.recordScopeId(PLATFORM_USAGE_TAIL_APPLIED_SHA256_SCOPE_KEY, usageTail.sha256);
|
|
90
|
+
context.recordScopeId(PLATFORM_USAGE_TAIL_APPLIED_URL_SCOPE_KEY, `https://${platform.edge.hostname}`);
|
|
87
91
|
return { resourceId: PLATFORM_USAGE_TAIL_SCRIPT_NAME };
|
|
88
92
|
}
|
|
89
93
|
/** Rule 1: delivery is tried up to `deliverAttempts` times; the error names the ref only, never the cause. */
|
package/dist/release/state.d.ts
CHANGED
|
@@ -137,8 +137,8 @@ export declare const releaseEventPayload: z.ZodObject<{
|
|
|
137
137
|
}, z.core.$strip>;
|
|
138
138
|
export declare const releaseEventKind: z.ZodEnum<{
|
|
139
139
|
step: "step";
|
|
140
|
-
open: "open";
|
|
141
140
|
check: "check";
|
|
141
|
+
open: "open";
|
|
142
142
|
passed: "passed";
|
|
143
143
|
hard_fail: "hard_fail";
|
|
144
144
|
promote: "promote";
|
package/dist/store/schema.d.ts
CHANGED
|
@@ -1556,7 +1556,7 @@ export declare const foundryAuditEvents: import("drizzle-orm/pg-core").PgTableWi
|
|
|
1556
1556
|
tableName: "foundry_audit_events";
|
|
1557
1557
|
dataType: "string";
|
|
1558
1558
|
columnType: "PgText";
|
|
1559
|
-
data: "applied" | "
|
|
1559
|
+
data: "applied" | "refused" | "failed" | "destroyed";
|
|
1560
1560
|
driverParam: string;
|
|
1561
1561
|
notNull: false;
|
|
1562
1562
|
hasDefault: false;
|
|
@@ -1568,7 +1568,7 @@ export declare const foundryAuditEvents: import("drizzle-orm/pg-core").PgTableWi
|
|
|
1568
1568
|
identity: undefined;
|
|
1569
1569
|
generated: undefined;
|
|
1570
1570
|
}, {}, {
|
|
1571
|
-
$type: "applied" | "
|
|
1571
|
+
$type: "applied" | "refused" | "failed" | "destroyed";
|
|
1572
1572
|
}>;
|
|
1573
1573
|
reason: import("drizzle-orm/pg-core").PgColumn<{
|
|
1574
1574
|
name: "reason";
|
|
@@ -196,9 +196,18 @@ export function validateEntryWorkerApp(entry, hosting, names) {
|
|
|
196
196
|
if (Object.keys(entry.env).length > 0) {
|
|
197
197
|
return `${at} has no Coolify app, so env does not apply: use workerApp.vars or workerApp.secrets`;
|
|
198
198
|
}
|
|
199
|
-
const unsupported = Object.keys(entry.uses ?? {}).find((use) => use !== 'auth' &&
|
|
199
|
+
const unsupported = Object.keys(entry.uses ?? {}).find((use) => use !== 'auth' &&
|
|
200
|
+
use !== 'audit' &&
|
|
201
|
+
use !== 'analytics' &&
|
|
202
|
+
use !== 'reporting' &&
|
|
203
|
+
use !== 'mail' &&
|
|
204
|
+
use !== 'files' &&
|
|
205
|
+
use !== 'customerGrants' &&
|
|
206
|
+
use !== 'repo' &&
|
|
207
|
+
use !== 'jobs' &&
|
|
208
|
+
use !== 'ai');
|
|
200
209
|
if (unsupported)
|
|
201
|
-
return `${at} supports uses.auth, uses.audit, uses.analytics and uses.
|
|
210
|
+
return `${at} supports uses.auth, uses.audit, uses.analytics, uses.reporting, uses.mail, uses.files, uses.jobs, uses.customerGrants, uses.repo and uses.ai only, not uses.${unsupported}`;
|
|
202
211
|
if (entry.key.length > WORKER_NAME_MAX_LENGTH - WORKER_NAME_FIXED_LENGTH) {
|
|
203
212
|
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`;
|
|
204
213
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wtfalch/foundry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.79.0",
|
|
4
4
|
"description": "Foundry's SDK: the declared org's types, the plan and apply contracts, and the state store's interface.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"types": "./dist/modules/index.d.ts",
|
|
20
20
|
"default": "./dist/modules/index.js"
|
|
21
21
|
},
|
|
22
|
+
"./mcp-resources": {
|
|
23
|
+
"types": "./dist/mcp-resources/index.d.ts",
|
|
24
|
+
"default": "./dist/mcp-resources/index.js"
|
|
25
|
+
},
|
|
22
26
|
"./catalogue": {
|
|
23
27
|
"types": "./dist/catalogue/index.d.ts",
|
|
24
28
|
"default": "./dist/catalogue/index.js"
|