@wtfalch/foundry 0.60.0 → 0.62.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.
Files changed (236) hide show
  1. package/dist/apps/cloudflare.d.ts +173 -0
  2. package/dist/apps/cloudflare.js +17 -0
  3. package/dist/apps/deploy.d.ts +116 -0
  4. package/dist/apps/deploy.js +791 -0
  5. package/dist/apps/github.d.ts +47 -0
  6. package/dist/apps/github.js +201 -0
  7. package/dist/apps/index.d.ts +9 -0
  8. package/dist/apps/index.js +9 -0
  9. package/dist/apps/migrations.d.ts +110 -0
  10. package/dist/apps/migrations.js +458 -0
  11. package/dist/audit/connector.d.ts +43 -0
  12. package/dist/audit/connector.js +22 -0
  13. package/dist/audit/evidence.d.ts +49 -0
  14. package/dist/audit/evidence.js +56 -0
  15. package/dist/audit/index.d.ts +9 -0
  16. package/dist/audit/index.js +9 -0
  17. package/dist/audit/migrations.d.ts +5 -0
  18. package/dist/audit/migrations.js +5 -0
  19. package/dist/audit/registry.d.ts +49 -0
  20. package/dist/audit/registry.js +44 -0
  21. package/dist/audit/seal.d.ts +49 -0
  22. package/dist/audit/seal.js +131 -0
  23. package/dist/audit/sealer-role.d.ts +33 -0
  24. package/dist/audit/sealer-role.js +56 -0
  25. package/dist/audit/signer.d.ts +40 -0
  26. package/dist/audit/signer.js +78 -0
  27. package/dist/audit/tick.d.ts +36 -0
  28. package/dist/audit/tick.js +87 -0
  29. package/dist/catalogue/copy-en.d.ts +1 -0
  30. package/dist/catalogue/copy-en.js +129 -0
  31. package/dist/catalogue/index.d.ts +9 -0
  32. package/dist/catalogue/index.js +21 -0
  33. package/dist/catalogue/integrations.d.ts +4 -0
  34. package/dist/catalogue/integrations.js +104 -0
  35. package/dist/catalogue/systems.d.ts +7 -0
  36. package/dist/catalogue/systems.js +31 -0
  37. package/dist/catalogue/types.d.ts +110 -0
  38. package/dist/catalogue/types.js +1 -0
  39. package/dist/entry-rules.d.ts +53 -0
  40. package/dist/entry-rules.js +71 -0
  41. package/dist/hosted-label.d.ts +13 -0
  42. package/dist/hosted-label.js +81 -0
  43. package/dist/index.d.ts +14 -5
  44. package/dist/index.js +7 -3
  45. package/dist/migrations/0062_hosting_github.sql +6 -0
  46. package/dist/migrations/0063_boule_apps.sql +7 -0
  47. package/dist/migrations/0064_requested_changes_boule_apps.sql +6 -0
  48. package/dist/migrations/0065_native_build_runs.sql +32 -0
  49. package/dist/migrations/0066_org_kosmos.sql +10 -0
  50. package/dist/modules/ai.d.ts +3 -0
  51. package/dist/modules/ai.js +64 -0
  52. package/dist/modules/assistants.d.ts +3 -0
  53. package/dist/modules/assistants.js +72 -0
  54. package/dist/modules/audit-ledger.d.ts +29 -0
  55. package/dist/modules/audit-ledger.js +49 -0
  56. package/dist/modules/audit.d.ts +7 -0
  57. package/dist/modules/audit.js +44 -0
  58. package/dist/modules/database.d.ts +23 -0
  59. package/dist/modules/database.js +189 -0
  60. package/dist/modules/email.d.ts +3 -0
  61. package/dist/modules/email.js +18 -0
  62. package/dist/modules/entry-parts.d.ts +23 -0
  63. package/dist/modules/entry-parts.js +63 -0
  64. package/dist/modules/files.d.ts +3 -0
  65. package/dist/modules/files.js +64 -0
  66. package/dist/modules/index.d.ts +74 -0
  67. package/dist/modules/index.js +774 -0
  68. package/dist/modules/jobs.d.ts +3 -0
  69. package/dist/modules/jobs.js +23 -0
  70. package/dist/modules/organisations.d.ts +3 -0
  71. package/dist/modules/organisations.js +20 -0
  72. package/dist/modules/outputs.d.ts +9 -0
  73. package/dist/modules/outputs.js +38 -0
  74. package/dist/modules/sign-in.d.ts +3 -0
  75. package/dist/modules/sign-in.js +197 -0
  76. package/dist/modules/signed-records.d.ts +3 -0
  77. package/dist/modules/signed-records.js +32 -0
  78. package/dist/modules/types.d.ts +194 -0
  79. package/dist/modules/types.js +1 -0
  80. package/dist/modules/workspaces.d.ts +3 -0
  81. package/dist/modules/workspaces.js +75 -0
  82. package/dist/native-build.d.ts +70 -0
  83. package/dist/native-build.js +92 -0
  84. package/dist/org.d.ts +184 -24
  85. package/dist/org.js +444 -49
  86. package/dist/plan/agora.d.ts +1 -1
  87. package/dist/plan/agora.js +4 -0
  88. package/dist/plan/apps-db-route.d.ts +90 -0
  89. package/dist/plan/apps-db-route.js +168 -0
  90. package/dist/plan/apps-zones.d.ts +56 -0
  91. package/dist/plan/apps-zones.js +118 -0
  92. package/dist/plan/archon-blue-green.js +11 -1
  93. package/dist/plan/blue-green-apply.d.ts +37 -1
  94. package/dist/plan/blue-green-apply.js +55 -15
  95. package/dist/plan/blue-green.d.ts +60 -8
  96. package/dist/plan/blue-green.js +115 -10
  97. package/dist/plan/boule.d.ts +23 -1
  98. package/dist/plan/boule.js +35 -2
  99. package/dist/plan/broker-entries.d.ts +28 -3
  100. package/dist/plan/broker-entries.js +74 -3
  101. package/dist/plan/broker-registrar.d.ts +168 -0
  102. package/dist/plan/broker-registrar.js +440 -0
  103. package/dist/plan/broker-workspaces-database.d.ts +48 -0
  104. package/dist/plan/broker-workspaces-database.js +87 -0
  105. package/dist/plan/deploy-health.js +9 -2
  106. package/dist/plan/desktop-callback.js +15 -7
  107. package/dist/plan/diff.d.ts +8 -2
  108. package/dist/plan/diff.js +552 -104
  109. package/dist/plan/edge-access.d.ts +6 -0
  110. package/dist/plan/edge-access.js +1 -0
  111. package/dist/plan/edge-dns.d.ts +6 -0
  112. package/dist/plan/edge-dns.js +9 -7
  113. package/dist/plan/entry-ai-key.d.ts +1 -1
  114. package/dist/plan/entry-ai-key.js +2 -2
  115. package/dist/plan/entry-audit.d.ts +58 -0
  116. package/dist/plan/entry-audit.js +122 -0
  117. package/dist/plan/entry-customer-grants.d.ts +75 -0
  118. package/dist/plan/entry-customer-grants.js +154 -0
  119. package/dist/plan/entry-edge-key.d.ts +26 -1
  120. package/dist/plan/entry-edge-key.js +42 -3
  121. package/dist/plan/entry-jobs.d.ts +64 -0
  122. package/dist/plan/entry-jobs.js +92 -0
  123. package/dist/plan/entry-repo-key-apply.d.ts +55 -0
  124. package/dist/plan/entry-repo-key-apply.js +185 -0
  125. package/dist/plan/entry-repo-key.d.ts +92 -0
  126. package/dist/plan/entry-repo-key.js +235 -0
  127. package/dist/plan/entry-workspaces.d.ts +55 -0
  128. package/dist/plan/entry-workspaces.js +90 -0
  129. package/dist/plan/executor.d.ts +231 -19
  130. package/dist/plan/executor.js +1648 -129
  131. package/dist/plan/gates.d.ts +1 -1
  132. package/dist/plan/gates.js +62 -9
  133. package/dist/plan/github-deploy-keys.d.ts +78 -0
  134. package/dist/plan/github-deploy-keys.js +305 -0
  135. package/dist/plan/github-source.d.ts +53 -0
  136. package/dist/plan/github-source.js +140 -0
  137. package/dist/plan/held-key-refs.d.ts +12 -0
  138. package/dist/plan/held-key-refs.js +12 -0
  139. package/dist/plan/hostnames.d.ts +9 -0
  140. package/dist/plan/hostnames.js +28 -1
  141. package/dist/plan/index.d.ts +9 -0
  142. package/dist/plan/index.js +6 -0
  143. package/dist/plan/jobs-scheduler-http.js +3 -0
  144. package/dist/plan/keys-registrar-http.d.ts +32 -0
  145. package/dist/plan/keys-registrar-http.js +105 -0
  146. package/dist/plan/keys-registrar.d.ts +67 -0
  147. package/dist/plan/keys-registrar.js +103 -0
  148. package/dist/plan/kosmos.d.ts +56 -0
  149. package/dist/plan/kosmos.js +84 -0
  150. package/dist/plan/live.d.ts +1 -1
  151. package/dist/plan/locked-hostnames.d.ts +2 -2
  152. package/dist/plan/locked-hostnames.js +7 -4
  153. package/dist/plan/login-host.js +11 -5
  154. package/dist/plan/machine-paths.d.ts +36 -0
  155. package/dist/plan/machine-paths.js +137 -0
  156. package/dist/plan/mcp.d.ts +1 -1
  157. package/dist/plan/mcp.js +49 -17
  158. package/dist/plan/org-apps-admin.d.ts +108 -0
  159. package/dist/plan/org-apps-admin.js +434 -0
  160. package/dist/plan/org-names.d.ts +3 -3
  161. package/dist/plan/org-names.js +15 -2
  162. package/dist/plan/org-postgres-migrate.js +1 -0
  163. package/dist/plan/own-diff.d.ts +23 -0
  164. package/dist/plan/own-diff.js +53 -0
  165. package/dist/plan/own-hostnames.d.ts +2 -0
  166. package/dist/plan/own-hostnames.js +1 -0
  167. package/dist/plan/own-jobs-route.d.ts +47 -0
  168. package/dist/plan/own-jobs-route.js +91 -0
  169. package/dist/plan/own-limits.d.ts +6 -0
  170. package/dist/plan/own-limits.js +1 -0
  171. package/dist/plan/own-services.d.ts +4 -0
  172. package/dist/plan/own-services.js +4 -0
  173. package/dist/plan/plan-apply.d.ts +39 -0
  174. package/dist/plan/plan-apply.js +163 -9
  175. package/dist/plan/platform-diff.d.ts +2 -2
  176. package/dist/plan/platform-diff.js +7 -2
  177. package/dist/plan/platform-org.d.ts +30 -0
  178. package/dist/plan/platform-org.js +12 -1
  179. package/dist/plan/platform-providers.d.ts +2 -0
  180. package/dist/plan/platform-providers.js +13 -2
  181. package/dist/plan/post-logout-uris.d.ts +1 -1
  182. package/dist/plan/post-logout-uris.js +12 -3
  183. package/dist/plan/private-pages.d.ts +26 -0
  184. package/dist/plan/private-pages.js +39 -0
  185. package/dist/plan/providers-readers.js +20 -1
  186. package/dist/plan/proxy-labels.d.ts +12 -1
  187. package/dist/plan/proxy-labels.js +32 -2
  188. package/dist/plan/release-host.d.ts +6 -2
  189. package/dist/plan/release-host.js +36 -8
  190. package/dist/plan/stage-env.d.ts +19 -1
  191. package/dist/plan/stage-env.js +25 -1
  192. package/dist/plan/stage-guards.d.ts +20 -0
  193. package/dist/plan/stage-guards.js +24 -0
  194. package/dist/plan/stage-isolation.d.ts +24 -0
  195. package/dist/plan/stage-isolation.js +29 -1
  196. package/dist/plan/worker-app-database.d.ts +46 -42
  197. package/dist/plan/worker-app-database.js +150 -153
  198. package/dist/plan/worker-app.d.ts +5 -65
  199. package/dist/plan/worker-app.js +28 -105
  200. package/dist/plan/worker.d.ts +2 -2
  201. package/dist/plan/worker.js +2 -1
  202. package/dist/plan/workspace-client.d.ts +25 -0
  203. package/dist/plan/workspace-client.js +48 -0
  204. package/dist/plan/workspace-sign-in.d.ts +55 -0
  205. package/dist/plan/workspace-sign-in.js +273 -0
  206. package/dist/plan/workspace-worker.d.ts +4 -0
  207. package/dist/plan/workspace-worker.js +13 -0
  208. package/dist/plan/workspace.d.ts +101 -0
  209. package/dist/plan/workspace.js +305 -0
  210. package/dist/release/decide.d.ts +30 -1
  211. package/dist/release/decide.js +73 -11
  212. package/dist/release/health.d.ts +8 -3
  213. package/dist/release/health.js +58 -5
  214. package/dist/release/migration-compatibility.d.ts +66 -0
  215. package/dist/release/migration-compatibility.js +340 -0
  216. package/dist/release/run.d.ts +2 -5
  217. package/dist/release/run.js +4 -0
  218. package/dist/release/state.d.ts +3 -1
  219. package/dist/release/state.js +6 -0
  220. package/dist/release/types.d.ts +20 -0
  221. package/dist/requested-changes.d.ts +11 -2
  222. package/dist/requested-changes.js +13 -1
  223. package/dist/service-labels.d.ts +4 -0
  224. package/dist/service-labels.js +11 -0
  225. package/dist/slug.d.ts +2 -0
  226. package/dist/slug.js +20 -7
  227. package/dist/store/postgres.js +25 -2
  228. package/dist/store/schema.d.ts +73 -1
  229. package/dist/store/schema.js +10 -0
  230. package/dist/worker-app-config.d.ts +1 -11
  231. package/dist/worker-app-config.js +4 -17
  232. package/dist/worker-app-migrations.d.ts +105 -0
  233. package/dist/worker-app-migrations.js +166 -0
  234. package/dist/worker-app-upload.d.ts +103 -0
  235. package/dist/worker-app-upload.js +128 -0
  236. package/package.json +19 -2
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The app's own builds: files of a release of the app's own GitHub repository (campaign
3
+ * 2026-10-20, FM4). Only `https://github.com/<owner>/<name>/releases/download/<tag>/<file>` of
4
+ * exactly that repository is accepted. A private repository is read with the company's GitHub
5
+ * token through the API's asset endpoint; the token is sent to api.github.com only, never to the
6
+ * download it redirects to, and never reaches an error.
7
+ */
8
+ export interface ReleaseAsset {
9
+ owner: string;
10
+ name: string;
11
+ tag: string;
12
+ file: string;
13
+ }
14
+ /** `owner/name` with GitHub's own name rules, or `null`. `.` and `..` are not names. */
15
+ export declare function parseAppRepo(repo: unknown): {
16
+ owner: string;
17
+ name: string;
18
+ } | null;
19
+ /**
20
+ * The release asset `url` names when it is a file of a release of `repo` (owner and name compared
21
+ * as GitHub does, ignoring case), else `null`.
22
+ */
23
+ export declare function releaseAssetOf(url: unknown, repo: string): ReleaseAsset | null;
24
+ /** A failed read: `code` says why (kept by `withCallTimeout`), `status` when GitHub answered; never the token or a body. */
25
+ export declare class ReleaseAssetError extends Error {
26
+ readonly code: 'not_found' | 'refused' | 'too_large' | 'failed';
27
+ readonly status?: number | undefined;
28
+ constructor(code: 'not_found' | 'refused' | 'too_large' | 'failed', status?: number | undefined);
29
+ }
30
+ /**
31
+ * Where a download may be sent: GitHub's own release storage. GitHub has served release files
32
+ * from `objects.githubusercontent.com`, `github-releases.githubusercontent.com` and (since 2025)
33
+ * `release-assets.githubusercontent.com`; all are `*.githubusercontent.com`, which only GitHub
34
+ * serves. Any other host is refused, so a redirect cannot point the read anywhere else.
35
+ */
36
+ export declare const isReleaseDownloadHost: (host: string) => boolean;
37
+ /**
38
+ * Downloads `asset`. With `token`: the release by tag, the asset by name, then its bytes through
39
+ * the asset endpoint (`Accept: application/octet-stream`); the redirect GitHub answers with is
40
+ * followed without the token. Without: the public download URL.
41
+ */
42
+ export declare function fetchReleaseAsset(asset: ReleaseAsset, opts: {
43
+ token?: string | null;
44
+ fetch: typeof fetch;
45
+ timeoutMs: number;
46
+ maxBytes: number;
47
+ }): Promise<Uint8Array>;
@@ -0,0 +1,201 @@
1
+ /**
2
+ * The app's own builds: files of a release of the app's own GitHub repository (campaign
3
+ * 2026-10-20, FM4). Only `https://github.com/<owner>/<name>/releases/download/<tag>/<file>` of
4
+ * exactly that repository is accepted. A private repository is read with the company's GitHub
5
+ * token through the API's asset endpoint; the token is sent to api.github.com only, never to the
6
+ * download it redirects to, and never reaches an error.
7
+ */
8
+ const REPO = /^([A-Za-z0-9](?:[A-Za-z0-9-]{0,38}))\/([A-Za-z0-9._-]{1,100})$/;
9
+ const RELEASE_ASSET = /^https:\/\/github\.com\/([A-Za-z0-9-]{1,39})\/([A-Za-z0-9._-]{1,100})\/releases\/download\/([^/?#\s]{1,200})\/([^/?#\s]{1,200})$/;
10
+ const GITHUB_API = 'https://api.github.com';
11
+ const TOKEN = /^[\x21-\x7e]{1,512}$/;
12
+ /** `owner/name` with GitHub's own name rules, or `null`. `.` and `..` are not names. */
13
+ export function parseAppRepo(repo) {
14
+ if (typeof repo !== 'string')
15
+ return null;
16
+ const match = REPO.exec(repo);
17
+ if (!match?.[1] || !match[2] || /^\.+$/.test(match[2]))
18
+ return null;
19
+ return { owner: match[1], name: match[2] };
20
+ }
21
+ /**
22
+ * The release asset `url` names when it is a file of a release of `repo` (owner and name compared
23
+ * as GitHub does, ignoring case), else `null`.
24
+ */
25
+ export function releaseAssetOf(url, repo) {
26
+ const own = parseAppRepo(repo);
27
+ if (!own || typeof url !== 'string')
28
+ return null;
29
+ const match = RELEASE_ASSET.exec(url);
30
+ if (!match)
31
+ return null;
32
+ const [, owner = '', name = '', rawTag = '', rawFile = ''] = match;
33
+ if (owner.toLowerCase() !== own.owner.toLowerCase())
34
+ return null;
35
+ if (name.toLowerCase() !== own.name.toLowerCase())
36
+ return null;
37
+ let tag;
38
+ let file;
39
+ try {
40
+ tag = decodeURIComponent(rawTag);
41
+ file = decodeURIComponent(rawFile);
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ if ([tag, file].some((part) => /^\.+$/.test(part) || /[/\\\s]/.test(part) || part === '')) {
47
+ return null;
48
+ }
49
+ return { owner, name, tag, file };
50
+ }
51
+ /** A failed read: `code` says why (kept by `withCallTimeout`), `status` when GitHub answered; never the token or a body. */
52
+ export class ReleaseAssetError extends Error {
53
+ code;
54
+ status;
55
+ constructor(code, status) {
56
+ super(`release asset ${code}${status ? ` (${status})` : ''}`);
57
+ this.code = code;
58
+ this.status = status;
59
+ this.name = 'ReleaseAssetError';
60
+ }
61
+ }
62
+ /** The release's JSON (one entry per asset): far below this even for a release with hundreds. */
63
+ const RELEASE_JSON_MAX_BYTES = 2 * 1024 * 1024;
64
+ const REDIRECTS_MAX = 5;
65
+ /**
66
+ * Where a download may be sent: GitHub's own release storage. GitHub has served release files
67
+ * from `objects.githubusercontent.com`, `github-releases.githubusercontent.com` and (since 2025)
68
+ * `release-assets.githubusercontent.com`; all are `*.githubusercontent.com`, which only GitHub
69
+ * serves. Any other host is refused, so a redirect cannot point the read anywhere else.
70
+ */
71
+ export const isReleaseDownloadHost = (host) => host === 'github.com' || /^[a-z0-9-]+(\.[a-z0-9-]+)*\.githubusercontent\.com$/.test(host);
72
+ /**
73
+ * The body, at most `maxBytes`, read as it streams: a missing or false content-length cannot make
74
+ * it read more. `deadline` bounds the read even when the fetch ignores its signal.
75
+ */
76
+ async function readCapped(response, maxBytes, deadline) {
77
+ const length = Number(response.headers.get('content-length') ?? '');
78
+ if (Number.isFinite(length) && length > maxBytes) {
79
+ await response.body?.cancel().catch(() => { });
80
+ throw new ReleaseAssetError('too_large');
81
+ }
82
+ const body = response.body;
83
+ if (!body)
84
+ return new Uint8Array(0);
85
+ const reader = body.getReader();
86
+ const parts = [];
87
+ let size = 0;
88
+ const timedOut = new Promise((_, reject) => {
89
+ if (deadline.aborted)
90
+ reject(new ReleaseAssetError('failed'));
91
+ deadline.addEventListener('abort', () => reject(new ReleaseAssetError('failed')), {
92
+ once: true,
93
+ });
94
+ });
95
+ timedOut.catch(() => { });
96
+ try {
97
+ for (;;) {
98
+ const { done, value } = await Promise.race([reader.read(), timedOut]);
99
+ if (done)
100
+ break;
101
+ size += value.byteLength;
102
+ if (size > maxBytes)
103
+ throw new ReleaseAssetError('too_large');
104
+ parts.push(value);
105
+ }
106
+ }
107
+ catch (error) {
108
+ await reader.cancel().catch(() => { });
109
+ throw error;
110
+ }
111
+ const bytes = new Uint8Array(size);
112
+ let at = 0;
113
+ for (const part of parts) {
114
+ bytes.set(part, at);
115
+ at += part.byteLength;
116
+ }
117
+ return bytes;
118
+ }
119
+ const failure = (status) => new ReleaseAssetError(status === 404 ? 'not_found' : status === 401 || status === 403 ? 'refused' : 'failed', status);
120
+ /**
121
+ * Downloads `asset`. With `token`: the release by tag, the asset by name, then its bytes through
122
+ * the asset endpoint (`Accept: application/octet-stream`); the redirect GitHub answers with is
123
+ * followed without the token. Without: the public download URL.
124
+ */
125
+ export async function fetchReleaseAsset(asset, opts) {
126
+ // Each request (and the read of its body) has its own deadline.
127
+ const deadline = () => AbortSignal.timeout(opts.timeoutMs);
128
+ const path = `${encodeURIComponent(asset.owner)}/${encodeURIComponent(asset.name)}`;
129
+ /** Follows redirects by hand, without the token, to GitHub's release storage only. */
130
+ const download = async (start) => {
131
+ let url = start;
132
+ for (let hop = 0; hop <= REDIRECTS_MAX; hop++) {
133
+ let target;
134
+ try {
135
+ target = new URL(url);
136
+ }
137
+ catch {
138
+ throw new ReleaseAssetError('failed');
139
+ }
140
+ if (target.protocol !== 'https:' || !isReleaseDownloadHost(target.hostname)) {
141
+ throw new ReleaseAssetError('failed');
142
+ }
143
+ const signal = deadline();
144
+ const response = await opts.fetch(target.toString(), { redirect: 'manual', signal });
145
+ if (response.status >= 300 && response.status < 400) {
146
+ await response.body?.cancel().catch(() => { });
147
+ url = new URL(response.headers.get('location') ?? '', target).toString();
148
+ continue;
149
+ }
150
+ if (!response.ok)
151
+ throw failure(response.status);
152
+ return readCapped(response, opts.maxBytes, signal);
153
+ }
154
+ throw new ReleaseAssetError('failed');
155
+ };
156
+ if (!opts.token) {
157
+ return download(`https://github.com/${path}/releases/download/${encodeURIComponent(asset.tag)}/${encodeURIComponent(asset.file)}`);
158
+ }
159
+ if (!TOKEN.test(opts.token))
160
+ throw new ReleaseAssetError('refused');
161
+ const headers = {
162
+ Authorization: `Bearer ${opts.token}`,
163
+ 'X-GitHub-Api-Version': '2022-11-28',
164
+ 'User-Agent': 'wtfalch-foundry',
165
+ };
166
+ const releaseSignal = deadline();
167
+ const release = await opts.fetch(`${GITHUB_API}/repos/${path}/releases/tags/${encodeURIComponent(asset.tag)}`, { headers: { ...headers, Accept: 'application/vnd.github+json' }, signal: releaseSignal });
168
+ if (!release.ok)
169
+ throw failure(release.status);
170
+ let body = null;
171
+ try {
172
+ body = JSON.parse(new TextDecoder().decode(await readCapped(release, RELEASE_JSON_MAX_BYTES, releaseSignal)));
173
+ }
174
+ catch (error) {
175
+ if (error instanceof ReleaseAssetError && error.code === 'too_large') {
176
+ throw new ReleaseAssetError('failed', release.status);
177
+ }
178
+ body = null;
179
+ }
180
+ const found = (Array.isArray(body?.assets) ? body.assets : []).find((a) => a?.name === asset.file);
181
+ if (!found || typeof found.id !== 'number' || !Number.isSafeInteger(found.id)) {
182
+ throw new ReleaseAssetError('not_found');
183
+ }
184
+ if (typeof found.size === 'number' && found.size > opts.maxBytes) {
185
+ throw new ReleaseAssetError('too_large');
186
+ }
187
+ const answerSignal = deadline();
188
+ const answer = await opts.fetch(`${GITHUB_API}/repos/${path}/releases/assets/${found.id}`, {
189
+ headers: { ...headers, Accept: 'application/octet-stream' },
190
+ redirect: 'manual',
191
+ signal: answerSignal,
192
+ });
193
+ if (answer.status >= 300 && answer.status < 400) {
194
+ await answer.body?.cancel().catch(() => { });
195
+ // The signed download needs no token, and must never see it.
196
+ return download(answer.headers.get('location') ?? '');
197
+ }
198
+ if (!answer.ok)
199
+ throw failure(answer.status);
200
+ return readCapped(answer, opts.maxBytes, answerSignal);
201
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `@wtfalch/foundry/apps`: one Worker app into a company's own Cloudflare account. Boule calls
3
+ * these in-process with the company's own token (campaign 2026-10-20, FM3).
4
+ */
5
+ export { type AppCopy, type AppsDeps, type AppsFailure, type AppsResult, type DeployWorkerAppInput, type RemoveWorkerAppInput, appHyperdriveName, appScriptName, deployWorkerApp, mcpMachinePaths, removeWorkerApp, } from './deploy.js';
6
+ export { type ApplyWorkerAppMigrationsDeps, type ApplyWorkerAppMigrationsInput, type DatabaseOwnerAddress, type MigrationsResult, type MigrationsStep, RELEASE_MIGRATION_SOURCE, type RuntimeRoleOrigin, type RuntimeRolePrivileges, type WorkerAppDatabaseRunner, applyWorkerAppMigrations, migrationChecksum, postgresMigrationRunner, runtimeRoleMarker, } from './migrations.js';
7
+ export { type ReleaseAsset, ReleaseAssetError, fetchReleaseAsset, parseAppRepo, releaseAssetOf, } from './github.js';
8
+ export { type WorkerAppMigrationRunner, migrationsUrl } from '../worker-app-migrations.js';
9
+ export { APPS_CLOUDFLARE_TOKEN_PERMISSIONS, type AppsCloudflare, type HyperdriveConnection, } from './cloudflare.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `@wtfalch/foundry/apps`: one Worker app into a company's own Cloudflare account. Boule calls
3
+ * these in-process with the company's own token (campaign 2026-10-20, FM3).
4
+ */
5
+ export { appHyperdriveName, appScriptName, deployWorkerApp, mcpMachinePaths, removeWorkerApp, } from './deploy.js';
6
+ export { RELEASE_MIGRATION_SOURCE, applyWorkerAppMigrations, migrationChecksum, postgresMigrationRunner, runtimeRoleMarker, } from './migrations.js';
7
+ export { ReleaseAssetError, fetchReleaseAsset, parseAppRepo, releaseAssetOf, } from './github.js';
8
+ export { migrationsUrl } from '../worker-app-migrations.js';
9
+ export { APPS_CLOUDFLARE_TOKEN_PERMISSIONS, } from './cloudflare.js';
@@ -0,0 +1,110 @@
1
+ import { type WorkerAppMigrationRunner } from '../worker-app-migrations.js';
2
+ /**
3
+ * The build's database changes (`migrations.json`) into the company's own Postgres (campaign
4
+ * 2026-10-20, FM4). Boule calls it before `deployWorkerApp`, with the owner address of the
5
+ * company's database and the address the app's runtime role uses (the one Boule then hands
6
+ * `deployWorkerApp` as `database.url`). The same steps as the platform's Worker app database
7
+ * (`runWorkerAppMigrations`): the file's sha256 is checked against build.json's
8
+ * `migrationsSha256`, only the migrations the database's ledger lacks run, in order, as the owner,
9
+ * then the runtime role gets DML on the tables, loses UPDATE, DELETE and TRUNCATE on the
10
+ * append-only ones and gets EXECUTE on the granted functions.
11
+ *
12
+ * The ledger is `_migrations_sources`, source `release`, the one `@wtfalch/db`'s runner writes
13
+ * and the platform's host reads, so a database moved between the two keeps its history.
14
+ *
15
+ * Before anything runs, a runtime role that already exists must be one this function made for
16
+ * this database (its comment says so), and every migration the ledger holds must still have the
17
+ * text it ran with (the ledger's checksum). After the role work, the role is read back: each
18
+ * append-only table must exist and the role must hold no UPDATE, DELETE or TRUNCATE on it, also
19
+ * not through PUBLIC or a role it belongs to, and it must hold EXECUTE on each granted function.
20
+ *
21
+ * A call that does not answer in time is cancelled on the server. A change that was running then
22
+ * may or may not have finished; the ledger and the runner's lock make a rerun safe either way,
23
+ * except for a change marked `-- db:no-transaction`, which may have kept part of itself.
24
+ *
25
+ * Never throws. A failure is `{ ok: false, step, message }` in plain words; no message holds
26
+ * an address, a password, SQL or anything the database answered beyond its SQLSTATE.
27
+ */
28
+ export interface ApplyWorkerAppMigrationsInput {
29
+ /** Owns the schema: the migrations run as this role. */
30
+ ownerUrl: string;
31
+ /** The role the app runs as (through Hyperdrive). It is created, or its password updated, from this address. */
32
+ runtimeUrl: string;
33
+ /** The release's `migrations.json`, or `null` when the release has none. */
34
+ migrationsJson: string | null;
35
+ /** build.json's `database.migrationsSha256`. Required when `migrationsJson` is given. */
36
+ migrationsSha256?: string | undefined;
37
+ /** build.json's `database.appendOnly`. */
38
+ appendOnly?: string[] | undefined;
39
+ /** build.json's `database.grants`. */
40
+ grants?: string[] | undefined;
41
+ }
42
+ /** The owner as the runner sees it. */
43
+ export interface DatabaseOwnerAddress {
44
+ url: string;
45
+ }
46
+ /** What the runtime role may do on what build.json names, read back after the role work. */
47
+ export interface RuntimeRolePrivileges {
48
+ tables: {
49
+ name: string;
50
+ exists: boolean;
51
+ update: boolean;
52
+ delete: boolean;
53
+ truncate: boolean;
54
+ }[];
55
+ functions: {
56
+ signature: string;
57
+ exists: boolean;
58
+ execute: boolean;
59
+ }[];
60
+ }
61
+ /** Who made the runtime role: none yet, this function for this database, or someone else. */
62
+ export type RuntimeRoleOrigin = 'absent' | 'ours' | 'other';
63
+ /** The shared runner, plus what the company-Postgres path checks before and after it. */
64
+ export interface WorkerAppDatabaseRunner extends WorkerAppMigrationRunner<DatabaseOwnerAddress> {
65
+ /** The ledger's checksum of each applied migration (`null` when the ledger has none for it). */
66
+ appliedChecksums(owner: DatabaseOwnerAddress): Promise<Record<string, string | null>>;
67
+ /** Whether `user` exists, and if so whether this function made it for this database. */
68
+ runtimeRoleOrigin(owner: DatabaseOwnerAddress, user: string): Promise<RuntimeRoleOrigin>;
69
+ /** After `ensureRuntimeRole`: the role's rights on the named tables and functions. */
70
+ runtimeRolePrivileges(owner: DatabaseOwnerAddress, input: {
71
+ user: string;
72
+ appendOnly: string[];
73
+ grants: string[];
74
+ }): Promise<RuntimeRolePrivileges>;
75
+ /** Closes every connection the runner has open now, without waiting for the work on it. */
76
+ abort(): Promise<void>;
77
+ }
78
+ export interface ApplyWorkerAppMigrationsDeps {
79
+ /** The SQL runner. Default: `@wtfalch/db`'s migrate and runtime-role (`postgresMigrationRunner`). */
80
+ sql?: WorkerAppDatabaseRunner;
81
+ /** Test hooks: the timeout of the ledger read and the role work, and of one migration. */
82
+ timeoutMs?: number;
83
+ migrationTimeoutMs?: number;
84
+ }
85
+ export type MigrationsStep = 'check' | 'database' | 'migrations' | 'role';
86
+ export type MigrationsResult = {
87
+ ok: true;
88
+ applied: string[];
89
+ ran: string[];
90
+ steps: string[];
91
+ } | {
92
+ ok: false;
93
+ step: MigrationsStep;
94
+ message: string;
95
+ ran: string[];
96
+ steps: string[];
97
+ };
98
+ /** The ledger source the release's migrations are recorded under. */
99
+ export declare const RELEASE_MIGRATION_SOURCE = "release";
100
+ /** The comment a runtime role this function made carries: it names the database it was made for. */
101
+ export declare const runtimeRoleMarker: (database: string) => string;
102
+ /** The ledger's checksum of a migration's text, as `@wtfalch/db` writes it (CRLF read as LF). */
103
+ export declare const migrationChecksum: (sql: string) => string;
104
+ /**
105
+ * Runs the release's database changes into the company's Postgres and makes the app's role
106
+ * true. Rerunning is safe: applied migrations are skipped by the ledger.
107
+ */
108
+ export declare function applyWorkerAppMigrations(input: ApplyWorkerAppMigrationsInput, deps?: ApplyWorkerAppMigrationsDeps): Promise<MigrationsResult>;
109
+ /** The default runner, on `@wtfalch/db` (loaded on first use, so only a caller of it needs the package). */
110
+ export declare function postgresMigrationRunner(): WorkerAppDatabaseRunner;