@substrat-run/control-plane-api 0.147.0 → 0.149.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 (80) hide show
  1. package/dist/api.d.ts +67 -4
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +1068 -418
  4. package/dist/api.js.map +1 -1
  5. package/dist/attachment-sweep.d.ts +29 -0
  6. package/dist/attachment-sweep.d.ts.map +1 -0
  7. package/dist/attachment-sweep.js +66 -0
  8. package/dist/attachment-sweep.js.map +1 -0
  9. package/dist/browser.d.ts +3 -1
  10. package/dist/browser.d.ts.map +1 -1
  11. package/dist/browser.js +3 -0
  12. package/dist/browser.js.map +1 -1
  13. package/dist/cf-observability.d.ts.map +1 -1
  14. package/dist/cf-observability.js +3 -1
  15. package/dist/cf-observability.js.map +1 -1
  16. package/dist/deploy.d.ts +26 -11
  17. package/dist/deploy.d.ts.map +1 -1
  18. package/dist/deploy.js +33 -18
  19. package/dist/deploy.js.map +1 -1
  20. package/dist/errors.d.ts +19 -1
  21. package/dist/errors.d.ts.map +1 -1
  22. package/dist/errors.js +18 -4
  23. package/dist/errors.js.map +1 -1
  24. package/dist/index.d.ts +15 -5
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +10 -4
  27. package/dist/index.js.map +1 -1
  28. package/dist/lake-gateway.d.ts +54 -0
  29. package/dist/lake-gateway.d.ts.map +1 -0
  30. package/dist/lake-gateway.js +158 -0
  31. package/dist/lake-gateway.js.map +1 -0
  32. package/dist/observability.d.ts +28 -0
  33. package/dist/observability.d.ts.map +1 -1
  34. package/dist/observability.js +35 -0
  35. package/dist/observability.js.map +1 -1
  36. package/dist/platform-entry.generated.d.ts +1 -1
  37. package/dist/platform-entry.generated.d.ts.map +1 -1
  38. package/dist/platform-entry.generated.js +2 -2
  39. package/dist/platform-entry.generated.js.map +1 -1
  40. package/dist/platform-scope-stores.d.ts +65 -12
  41. package/dist/platform-scope-stores.d.ts.map +1 -1
  42. package/dist/platform-scope-stores.js +121 -20
  43. package/dist/platform-scope-stores.js.map +1 -1
  44. package/dist/r2-sql-engine.d.ts +50 -0
  45. package/dist/r2-sql-engine.d.ts.map +1 -0
  46. package/dist/r2-sql-engine.js +126 -0
  47. package/dist/r2-sql-engine.js.map +1 -0
  48. package/dist/scope-copy-cleanup.d.ts +13 -1
  49. package/dist/scope-copy-cleanup.d.ts.map +1 -1
  50. package/dist/scope-copy-cleanup.js +14 -3
  51. package/dist/scope-copy-cleanup.js.map +1 -1
  52. package/dist/staff-client.d.ts +10 -2
  53. package/dist/staff-client.d.ts.map +1 -1
  54. package/dist/staff-client.js +6 -1
  55. package/dist/staff-client.js.map +1 -1
  56. package/dist/store-entry.d.ts +90 -0
  57. package/dist/store-entry.d.ts.map +1 -0
  58. package/dist/store-entry.generated.d.ts +60 -0
  59. package/dist/store-entry.generated.d.ts.map +1 -0
  60. package/dist/store-entry.generated.js +62 -0
  61. package/dist/store-entry.generated.js.map +1 -0
  62. package/dist/store-entry.js +178 -0
  63. package/dist/store-entry.js.map +1 -0
  64. package/dist/store-rebuild.d.ts +142 -0
  65. package/dist/store-rebuild.d.ts.map +1 -0
  66. package/dist/store-rebuild.js +223 -0
  67. package/dist/store-rebuild.js.map +1 -0
  68. package/dist/store-releases.d.ts +158 -0
  69. package/dist/store-releases.d.ts.map +1 -0
  70. package/dist/store-releases.js +332 -0
  71. package/dist/store-releases.js.map +1 -0
  72. package/dist/store-script.d.ts +98 -0
  73. package/dist/store-script.d.ts.map +1 -0
  74. package/dist/store-script.js +188 -0
  75. package/dist/store-script.js.map +1 -0
  76. package/dist/vertical-client.d.ts +33 -2
  77. package/dist/vertical-client.d.ts.map +1 -1
  78. package/dist/vertical-client.js +58 -3
  79. package/dist/vertical-client.js.map +1 -1
  80. package/package.json +7 -7
@@ -0,0 +1,332 @@
1
+ /**
2
+ * A store driver's releases: stored, canaried, rolled out and rolled back (D-63, #2300).
3
+ *
4
+ * The bytes live outside the directory, content-addressed under `<driver>/<sha256>`, where the
5
+ * sha256 is `storeDriverReleaseDigest` of the bundle, computed here when it arrives and again by
6
+ * the ensurer before every upload of it, restore and rollback included. The directory holds who
7
+ * uploaded which release from which commit, every rollout, and the current good release
8
+ * (`store-driver-releases.ts` in the kernel).
9
+ *
10
+ * A rollout is one request across a fleet. It holds the driver exclusively for its whole length,
11
+ * renewing the hold before every upload, so no push or serve rebuilds a script between the
12
+ * rollout's read and its upload, and two rollouts never interleave. It canaries the release on the
13
+ * platform's own object first, and no vertical's script moves unless every reserved operation
14
+ * answered there. Then it moves each vertical that ever declared the driver, at most
15
+ * {@link ROLLOUT_VERTICALS_PER_REQUEST} uploads and {@link ROLLOUT_WALKS_PER_REQUEST} verticals
16
+ * walked per request. A bigger fleet ends the request `paused`, and posting the same rollout (or
17
+ * rollback) again continues it from the attempt's walk ledger (#2312), so a fleet of any size
18
+ * converges, a rollback included. A request that stops on a failure ends `failed` or `diverged`.
19
+ */
20
+ import { STORE_RELEASE_MAX_BYTES, scopeStoreCanaryObjectName, storeDriverReleaseDigest, storeDriverReleaseUpload, } from '@substrat-run/contracts';
21
+ import { ControlPlaneError } from '@substrat-run/control-plane-client';
22
+ import { createUlid, ulid, } from '@substrat-run/kernel';
23
+ import { assertPythonDriverModules } from './store-entry.js';
24
+ /** The key a release's bytes are stored under. */
25
+ export const storeReleaseKey = (driver, sha256) => `${driver}/${sha256}`;
26
+ /** How long a rollout's exclusive hold lives between renewals. */
27
+ export const ROLLOUT_HOLD_TTL_MS = 10 * 60_000;
28
+ /**
29
+ * How long a push's or a serve's shared hold lives. It is renewed before each driver's upload; one
30
+ * driver's upload (a settings read and at most one upload per script) must fit inside it, or a
31
+ * rollout could take the driver before that upload lands. The same lease as a rollout's.
32
+ */
33
+ export const PUSH_HOLD_TTL_MS = 10 * 60_000;
34
+ /**
35
+ * The verticals one rollout request moves. Each costs a settings read and at most one upload,
36
+ * plus a hold renewal, so a request stays well inside a Worker invocation's subrequest budget.
37
+ * A fleet bigger than this is moved over several requests (the rollout answers `complete: false`).
38
+ */
39
+ export const ROLLOUT_VERTICALS_PER_REQUEST = 100;
40
+ /**
41
+ * The verticals one rollout request walks. Every one costs a settings read and a hold renewal even
42
+ * when it is already at the release, so a request that reaches this many ends `paused` and the
43
+ * next continues from the walk ledger (#2312). It also bounds what a failure returns: an attempt
44
+ * that reached more ends `diverged` without returning any, and a rollback, paged the same way,
45
+ * converges them.
46
+ */
47
+ export const ROLLOUT_WALKS_PER_REQUEST = 300;
48
+ const decodeBase64 = (text) => {
49
+ let binary;
50
+ try {
51
+ binary = atob(text);
52
+ }
53
+ catch {
54
+ throw new ControlPlaneError(422, 'a release module is not base64');
55
+ }
56
+ return Uint8Array.from(binary, (ch) => ch.charCodeAt(0));
57
+ };
58
+ /**
59
+ * A release bundle as uploaded or as stored, decoded and judged: the upload body's shape, the
60
+ * decoded size cap, and the Python driver's module rule. Refusals are 422 (413 for the size).
61
+ */
62
+ export function decodeStoreDriverRelease(raw) {
63
+ const parsed = storeDriverReleaseUpload.safeParse(raw);
64
+ if (!parsed.success) {
65
+ throw new ControlPlaneError(422, `not a store driver release: ${parsed.error.issues.map((i) => i.message).join('; ')}`);
66
+ }
67
+ let total = 0;
68
+ const modules = parsed.data.modules.map((module) => {
69
+ const content = decodeBase64(module.contentBase64);
70
+ total += content.byteLength;
71
+ if (total > STORE_RELEASE_MAX_BYTES) {
72
+ throw new ControlPlaneError(413, `a store driver release carries at most ${STORE_RELEASE_MAX_BYTES} module bytes`);
73
+ }
74
+ return { name: module.name, content };
75
+ });
76
+ const release = { sourceCommit: parsed.data.sourceCommit, entry: parsed.data.entry, modules };
77
+ try {
78
+ assertPythonDriverModules(release);
79
+ }
80
+ catch (e) {
81
+ throw new ControlPlaneError(422, e instanceof Error ? e.message : String(e));
82
+ }
83
+ return release;
84
+ }
85
+ /** The stored form: the upload body again, so one decoder reads both. */
86
+ function encodeStoreDriverRelease(release) {
87
+ const base64 = (bytes) => {
88
+ let text = '';
89
+ for (let i = 0; i < bytes.length; i += 0x8000)
90
+ text += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
91
+ return btoa(text);
92
+ };
93
+ return new TextEncoder().encode(JSON.stringify({
94
+ sourceCommit: release.sourceCommit,
95
+ entry: release.entry,
96
+ modules: release.modules.map((m) => ({ name: m.name, contentBase64: base64(m.content) })),
97
+ }));
98
+ }
99
+ /**
100
+ * Store a release's bytes and record them (D-63: bytes may be published before the pin lands).
101
+ * The bytes go first, so a recorded release always has bytes; a retry of the same bundle writes
102
+ * the same key and records nothing new.
103
+ */
104
+ export async function storeDriverRelease(input) {
105
+ const release = decodeStoreDriverRelease(input.body);
106
+ const sha256 = await storeDriverReleaseDigest(release).catch((e) => {
107
+ throw new ControlPlaneError(422, e instanceof Error ? e.message : String(e));
108
+ });
109
+ const held = await input.admin.getStoreDriverRelease(input.actor, input.driver, sha256);
110
+ if (held && held.sourceCommit !== release.sourceCommit) {
111
+ throw new ControlPlaneError(409, `release ${sha256} of '${input.driver}' is stored as built from ${held.sourceCommit}`);
112
+ }
113
+ if (!held)
114
+ await input.bytes.put(storeReleaseKey(input.driver, sha256), encodeStoreDriverRelease(release));
115
+ return input.admin.recordStoreDriverRelease(input.actor, { driver: input.driver, sha256, sourceCommit: release.sourceCommit });
116
+ }
117
+ /**
118
+ * The ensurer's loader over the stored bytes: a named release, or the driver's current good one.
119
+ * Each answer carries the sha256 it was read as, which the ensurer holds the bytes to.
120
+ */
121
+ export function storedReleaseLoader(input) {
122
+ return async ({ driver, sha256 }) => {
123
+ const wanted = sha256 ?? (await input.admin.storeDriverState(input.actor, driver)).current?.sha256;
124
+ if (!wanted)
125
+ return undefined;
126
+ const raw = await input.bytes.get(storeReleaseKey(driver, wanted));
127
+ if (!raw)
128
+ return undefined;
129
+ let json;
130
+ try {
131
+ json = JSON.parse(new TextDecoder().decode(raw));
132
+ }
133
+ catch {
134
+ throw new ControlPlaneError(422, `driver '${driver}' release ${wanted} is stored as something other than a release`);
135
+ }
136
+ return { ...decodeStoreDriverRelease(json), sha256: wanted };
137
+ };
138
+ }
139
+ /**
140
+ * A canary object's name. Each id comes from a mint of its own, so its 80 random bits are fresh
141
+ * from `crypto.getRandomValues`: the shared mint would make the second id the first plus one
142
+ * within a millisecond, and either id would then give the other away.
143
+ */
144
+ export const canaryObjectName = (binding) => scopeStoreCanaryObjectName(createUlid()(), createUlid()(), binding);
145
+ const tablesOf = (answer) => answer.tables;
146
+ /**
147
+ * Every reserved operation, on a fresh object of the platform's own (D-63): its tenant and scope
148
+ * ids are new ulids, from the runtime's CSPRNG, never in the directory, and its vertical segment
149
+ * is no vertical's. Each answer is parsed by the call and asserted here; the first that is not
150
+ * what an object holding no data answers fails the canary. The object is wiped at the end.
151
+ */
152
+ export async function canaryStoreRelease(input) {
153
+ const target = { scriptName: input.scriptName, objectName: canaryObjectName(input.binding ?? 'CANARY') };
154
+ const step = async (operation, check, body) => {
155
+ let answer;
156
+ try {
157
+ answer = await input.call(target, operation, body);
158
+ }
159
+ catch (e) {
160
+ throw new ControlPlaneError(502, `canary refused: ${operation} failed (${e instanceof Error ? e.message : String(e)})`);
161
+ }
162
+ const wrong = check(answer);
163
+ if (wrong)
164
+ throw new ControlPlaneError(502, `canary refused: ${operation} ${wrong}`);
165
+ return answer;
166
+ };
167
+ const ok = () => null;
168
+ await step('substrat.size', ok);
169
+ const exported = tablesOf(await step('substrat.export', (a) => (tablesOf(a).some((t) => t.rows.length > 0) ? 'exported rows from an object holding none' : null)));
170
+ await step('substrat.import', (a) => (a.tables === exported.length ? null : `imported ${a.tables} of ${exported.length} tables`), { tables: exported });
171
+ await step('substrat.export', (a) => (JSON.stringify(tablesOf(a)) === JSON.stringify(exported) ? null : 'did not export what it imported'));
172
+ await step('substrat.erase', (a) => (a.erased === 0 ? null : 'erased rows of a subject the object never held'), { subjectId: ulid() });
173
+ await step('substrat.wipe', ok);
174
+ await step('substrat.export', (a) => (tablesOf(a).some((t) => t.rows.length > 0) ? 'still holds rows after its wipe' : null));
175
+ }
176
+ const message = (e) => (e instanceof Error ? e.message : String(e));
177
+ const schemaOf = (registration, sha256) => sha256 === null ? undefined : registration.releases.find((pin) => pin.sha256 === sha256)?.schema;
178
+ /** The hold lapsed, or another holder ended the rollout: nothing more may be moved under it. */
179
+ class HoldLost extends ControlPlaneError {
180
+ constructor(driver) {
181
+ super(409, `the hold on driver '${driver}' lapsed or its rollout was ended by another; post the rollout again to converge it`);
182
+ }
183
+ }
184
+ /**
185
+ * Move every script of a driver to a stored, pinned release (D-63): hold, canary, then each
186
+ * vertical. A failure after a vertical moved returns every vertical the attempt reached to the
187
+ * release the rollout started from, rehashing those bytes as it uploads them, and ends `failed`.
188
+ * It ends `diverged` instead, naming what is where, when there is nothing to return to (a driver's
189
+ * first rollout), when returning would lower the stored schema, when the attempt reached more
190
+ * verticals than one request returns, or when a return fails: the next rollout or rollback
191
+ * converges every script. The hold is renewed before every upload, the returns included, and a
192
+ * hold that cannot be renewed stops everything at once: another holder may be moving the same
193
+ * scripts, so nothing is returned either. The hold is released however it ends.
194
+ *
195
+ * A fleet bigger than one request is paged (#2312). Each request walks at most
196
+ * {@link ROLLOUT_WALKS_PER_REQUEST} verticals and uploads at most `perRequest` scripts; one that runs out
197
+ * ends `paused`, and its end records what it walked in the attempt's ledger. Posting the same
198
+ * rollout (or rollback) again continues the attempt and walks only declarations the ledger does
199
+ * not hold, in (vertical, binding) order: a vertical declared between two requests, wherever it
200
+ * sorts, is walked by the next one, and one walked is never walked again. A request ends `good`
201
+ * only when the pending list it read under its exclusive hold was the whole of it and it walked
202
+ * every vertical on it: no push can create a script while the hold is held, so nothing declared
203
+ * can be left on another release.
204
+ */
205
+ export async function runStoreDriverRollout(deps) {
206
+ const { admin, actor, registration, sha256 } = deps;
207
+ const driver = registration.driver;
208
+ const holder = ulid();
209
+ if (!(await admin.holdStoreDriver(actor, { driver, holder, mode: 'exclusive', ttlMs: ROLLOUT_HOLD_TTL_MS }))) {
210
+ throw new ControlPlaneError(409, `driver '${driver}' is being rolled out or a vertical declaring it is being pushed; try again shortly`);
211
+ }
212
+ try {
213
+ const walkMax = deps.perRequestWalks ?? ROLLOUT_WALKS_PER_REQUEST;
214
+ const page = await admin.storeDriverRolloutPending(actor, { driver, sha256, action: deps.action, limit: walkMax });
215
+ if (page.unreadable.length > 0) {
216
+ throw new ControlPlaneError(409, `driver '${driver}' cannot be rolled out: ${page.unreadable.join(', ')} ha${page.unreadable.length === 1 ? 's a version' : 've versions'} whose stores cannot be named`);
217
+ }
218
+ const row = await admin.beginStoreDriverRollout(actor, { driver, sha256, action: deps.action, holder });
219
+ const end = (state, detail) => admin.finishStoreDriverRollout(actor, { driver, seq: row.seq, holder, state, detail });
220
+ const renew = async () => {
221
+ const held = await admin.holdStoreDriver(actor, { driver, holder, mode: 'exclusive', ttlMs: ROLLOUT_HOLD_TTL_MS, renew: true, rolloutSeq: row.seq });
222
+ if (!held)
223
+ throw new HoldLost(driver);
224
+ };
225
+ try {
226
+ await deps.ensureCanary(sha256).then(({ scriptName }) => canaryStoreRelease({ call: deps.callCanary, scriptName }));
227
+ }
228
+ catch (e) {
229
+ // No vertical's script moved in this request; the current release stays. A row the end
230
+ // cannot write (the hold lapsed) reads as diverged, which is what it then is.
231
+ await end('failed', message(e)).catch(() => undefined);
232
+ throw e;
233
+ }
234
+ const budget = deps.perRequest ?? ROLLOUT_VERTICALS_PER_REQUEST;
235
+ const moved = [];
236
+ // Every vertical this request reached. A request that pauses failed at none of them, so its
237
+ // paused end records exactly the verticals whose ensure answered.
238
+ const reached = [];
239
+ for (const target of page.verticals) {
240
+ if (moved.length >= budget)
241
+ break;
242
+ try {
243
+ await renew();
244
+ reached.push(target);
245
+ const stores = target.stores.map((s) => ({ binding: s.binding, driver }));
246
+ if (await deps.ensure({ vertical: target.vertical, stores, to: sha256 }))
247
+ moved.push(target.vertical);
248
+ }
249
+ catch (e) {
250
+ const detail = e instanceof HoldLost
251
+ ? { state: 'diverged', text: `Nothing was returned: ${reachedText(page.walked, reached)} may run ${sha256}.` }
252
+ : await restore(deps, row, page.walked, reached, renew);
253
+ await end(detail.state, `${target.vertical}: ${message(e)}. ${detail.text}`).catch(() => undefined);
254
+ throw new ControlPlaneError(e instanceof ControlPlaneError ? e.status : 502, `${deps.action} of '${driver}' to ${sha256} stopped at ${target.vertical} (${message(e)}). ${detail.text}`);
255
+ }
256
+ }
257
+ const remaining = page.pending - reached.length;
258
+ if (remaining > 0) {
259
+ const rollout = await admin.finishStoreDriverRollout(actor, {
260
+ driver,
261
+ seq: row.seq,
262
+ holder,
263
+ state: 'paused',
264
+ detail: `this request walked ${reached.length} and moved ${moved.length}; ${remaining} vertical(s) not walked yet: post the same ${deps.action} again`,
265
+ walked: reached,
266
+ });
267
+ return { rollout, moved, complete: false, remaining };
268
+ }
269
+ return { rollout: await end('good', null), moved, complete: true, remaining: 0 };
270
+ }
271
+ finally {
272
+ await admin.releaseStoreDriverHold(actor, { driver, holder }).catch(() => undefined);
273
+ }
274
+ }
275
+ /** The verticals an attempt reached, for a refusal: earlier requests' count, then this one's names. */
276
+ const reachedText = (before, reached) => {
277
+ const names = reached.map((t) => t.vertical).join(', ');
278
+ if (before === 0)
279
+ return names || 'no vertical';
280
+ return `${before} vertical(s) earlier requests of this attempt walked${names ? ` and ${names}` : ''}`;
281
+ };
282
+ /**
283
+ * Return every vertical a failed attempt reached to the release it started from, where that is
284
+ * allowed, renewing the hold before each upload: the ones its earlier requests walked (the
285
+ * ledger), then the ones this request reached. A hold that cannot be renewed stops the return.
286
+ * More than one request returns ({@link ROLLOUT_WALKS_PER_REQUEST}) returns nothing and ends diverged.
287
+ */
288
+ async function restore(deps, row, before, reached, renew) {
289
+ const from = row.fromSha256;
290
+ const all = reachedText(before, reached);
291
+ if (before + reached.length === 0)
292
+ return { state: 'failed', text: 'No vertical moved.' };
293
+ if (from === null) {
294
+ return { state: 'diverged', text: `There is no earlier release to return to; ${all} may run ${deps.sha256}.` };
295
+ }
296
+ const leaving = schemaOf(deps.registration, deps.sha256);
297
+ const returning = schemaOf(deps.registration, from);
298
+ if (leaving === undefined || returning === undefined || returning < leaving) {
299
+ return {
300
+ state: 'diverged',
301
+ text: `Returning to ${from} would lower the stored schema (${leaving} to ${returning}), so ${all} may run ${deps.sha256}.`,
302
+ };
303
+ }
304
+ const max = deps.perRequestWalks ?? ROLLOUT_WALKS_PER_REQUEST;
305
+ if (before + reached.length > max) {
306
+ return {
307
+ state: 'diverged',
308
+ text: `The attempt reached ${before + reached.length} verticals, more than one request returns (${max}), so none was returned and they may run ${deps.sha256}: post a rollback, which converges every script on ${from} over as many requests as it takes.`,
309
+ };
310
+ }
311
+ const earlier = before === 0 ? [] : (await deps.admin.storeDriverRolloutWalked(deps.actor, { driver: row.driver, seq: row.seq, limit: max })).verticals;
312
+ const back = [...earlier, ...reached];
313
+ const stuck = [];
314
+ for (const [i, target] of back.entries()) {
315
+ try {
316
+ await renew();
317
+ }
318
+ catch {
319
+ return { state: 'diverged', text: `The hold was lost while returning; ${back.slice(i).map((t) => t.vertical).join(', ')} may run ${deps.sha256}.` };
320
+ }
321
+ try {
322
+ await deps.ensure({ vertical: target.vertical, stores: target.stores.map((s) => ({ binding: s.binding, driver: deps.registration.driver })), to: from });
323
+ }
324
+ catch (e) {
325
+ stuck.push(`${target.vertical} (${message(e)})`);
326
+ }
327
+ }
328
+ return stuck.length === 0
329
+ ? { state: 'failed', text: `${back.map((t) => t.vertical).join(', ')} returned to ${from}.` }
330
+ : { state: 'diverged', text: `Could not return ${stuck.join(', ')} to ${from}.` };
331
+ }
332
+ //# sourceMappingURL=store-releases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-releases.js","sourceRoot":"","sources":["../src/store-releases.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ import { type ScopeStoreNeed, type StoreDriverRegistration } from '@substrat-run/contracts';
2
+ import { type PythonStoreDriverRelease } from './store-entry.js';
3
+ /**
4
+ * The binding that records which pinned release a store script runs: the release bundle's
5
+ * sha256 (D-63). In the entry's reserved prefix, so the driver's env never carries it.
6
+ */
7
+ export declare const STORE_RELEASE_VAR = "SUBSTRAT_STORE_RELEASE";
8
+ /**
9
+ * The binding that records what else the script was built from: a digest over every module the
10
+ * upload sent and its configuration. A changed entry, verifier, key list or flag changes it.
11
+ */
12
+ export declare const STORE_BUILD_VAR = "SUBSTRAT_STORE_BUILD";
13
+ /** One release of a driver's code, as the publish flow stores it (#2297). */
14
+ export interface StoreDriverReleaseArtifact extends PythonStoreDriverRelease {
15
+ /** The commit the bundle was built from. A reviewer reads it; the upload checks the sha256. */
16
+ readonly sourceCommit: string;
17
+ /**
18
+ * The sha256 the loader read these bytes as (its storage key). When present, the bytes must
19
+ * hash to it, so a store that answers other bytes under a key is refused, not uploaded.
20
+ */
21
+ readonly sha256?: string;
22
+ }
23
+ export interface ScopeStoreEnsurerOptions {
24
+ accountId: string;
25
+ namespace: string;
26
+ apiToken: string;
27
+ /** The `caller` audience's public keys, raw Ed25519, base64url. A list, so a key can rotate. */
28
+ callerPublicKeys: readonly string[];
29
+ /** The `platform` audience's public keys, in the same form. Never one of the caller's. */
30
+ platformPublicKeys: readonly string[];
31
+ drivers?: readonly StoreDriverRegistration[];
32
+ /**
33
+ * A release's bytes. With `sha256`, that release: the one the script runs, or the one a
34
+ * rollout moves it to. Without it, the driver's current good release (#2300). Undefined when
35
+ * there is none.
36
+ */
37
+ loadRelease: (input: {
38
+ readonly driver: string;
39
+ readonly vertical: string;
40
+ readonly sha256?: string;
41
+ }) => Promise<StoreDriverReleaseArtifact | undefined>;
42
+ }
43
+ /**
44
+ * What the platform builds every store script from, as one digest (#2309): the entry, its
45
+ * verifier, the compatibility date and flags, and both key lists. It is the build digest of one
46
+ * fixed probe release, so it changes exactly when the platform's half of every script's build
47
+ * changes (a key added, dropped or revoked, an entry or verifier fix) and never when a driver's
48
+ * release does. The fleet rebuild converges every script on it.
49
+ */
50
+ export declare function storePlatformFingerprint(keys: Pick<ScopeStoreEnsurerOptions, 'callerPublicKeys' | 'platformPublicKeys'>): Promise<string>;
51
+ /**
52
+ * Put every (driver, vertical) store script a vertical's stores need in place, before the
53
+ * vertical's own upload (#2210, D-63). A push or a serve never moves a script to another
54
+ * release: only a rollout or a rollback does (#2300), and it says so with `to`.
55
+ *
56
+ * - No `to`, no script yet: it is uploaded at the driver's current good release.
57
+ * - No `to`, a script: it is rebuilt at the release it already runs, and uploaded again only when
58
+ * what it was built from changed (the platform's entry, the verifier, a key list, a flag), so a
59
+ * fix or a key rotation reaches it without a release moving.
60
+ * - `to`: the script is moved to that release, or left alone when it already runs it, built the
61
+ * same way. Re-running a rollout therefore converges and uploads nothing twice.
62
+ *
63
+ * Every upload hashes the module bytes it is about to send and refuses (422) bytes whose
64
+ * sha256 the driver's registration does not pin, or that are not the release asked for. A driver
65
+ * with no release to run is refused (409). A script is never moved to a release whose pinned
66
+ * `schema` is lower than that of the release it runs (409): its objects may have migrated. The
67
+ * runtime's own failures are 502. Answers how many scripts were uploaded.
68
+ */
69
+ export declare function createScopeStoreEnsurer(opts: ScopeStoreEnsurerOptions): (input: {
70
+ vertical: string;
71
+ stores: readonly ScopeStoreNeed[];
72
+ to?: string;
73
+ /** The fleet rebuild (#2309): a script that does not exist is left alone, never uploaded. */
74
+ existingOnly?: boolean;
75
+ }) => Promise<{
76
+ uploaded: number;
77
+ }>;
78
+ /**
79
+ * Put a driver's release in the platform's canary script (D-63, #2300), where it is canaried
80
+ * before any vertical's script moves: `scopeStoreCanaryScriptName(driver)`, built and checked as a
81
+ * vertical's script is, except that it may move to a lower `schema`: every canary object is fresh
82
+ * and wiped, so none holds migrated data. With `existingOnly` instead of `to`, the fleet rebuild
83
+ * (#2309): the canary script is rebuilt at the release it runs, and left alone when there is none.
84
+ * Answers the script's name and whether it was uploaded.
85
+ */
86
+ export declare function createStoreCanaryEnsurer(opts: ScopeStoreEnsurerOptions): (input: StoreCanaryTarget) => Promise<{
87
+ scriptName: string;
88
+ uploaded: boolean;
89
+ }>;
90
+ /** Where the canary script goes: to a release (a rollout), or rebuilt where it is (the fleet rebuild). */
91
+ export type StoreCanaryTarget = {
92
+ driver: string;
93
+ to: string;
94
+ } | {
95
+ driver: string;
96
+ existingOnly: true;
97
+ };
98
+ //# sourceMappingURL=store-script.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-script.d.ts","sourceRoot":"","sources":["../src/store-script.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,uBAAuB,EAE7B,MAAM,yBAAyB,CAAC;AAIjC,OAAO,EAAwB,KAAK,wBAAwB,EAA0B,MAAM,kBAAkB,CAAC;AAE/G;;;GAGG;AACH,eAAO,MAAM,iBAAiB,2BAA6C,CAAC;AAE5E;;;GAGG;AACH,eAAO,MAAM,eAAe,yBAA2C,CAAC;AAExE,6EAA6E;AAC7E,MAAM,WAAW,0BAA2B,SAAQ,wBAAwB;IAC1E,+FAA+F;IAC/F,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,gGAAgG;IAChG,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,0FAA0F;IAC1F,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,OAAO,CAAC,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAC7C;;;;OAIG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE;QACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;KAC1B,KAAK,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC,CAAC;CACvD;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,CAAC,wBAAwB,EAAE,kBAAkB,GAAG,oBAAoB,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ/I;AAYD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,wBAAwB,WAE/C;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,6FAA6F;IAC7F,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,KAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAMlC;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,wBAAwB,WAEhD,iBAAiB,KAAG,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAW5F;AAED,0GAA0G;AAC1G,MAAM,MAAM,iBAAiB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,IAAI,CAAA;CAAE,CAAC"}
@@ -0,0 +1,188 @@
1
+ import { SCOPE_STORE_CANARY, STORE_DRIVERS, scopeStoreCanaryScriptName, storeDriverReleaseDigest, } from '@substrat-run/contracts';
2
+ import { ControlPlaneError } from '@substrat-run/control-plane-client';
3
+ import { scopeStoreUploadPlan } from './deploy.js';
4
+ import { STORE_OBJECT_CLASS, STORE_RESERVED_BINDING_PREFIX } from './store-entry.generated.js';
5
+ import { withPythonStoreEntry } from './store-entry.js';
6
+ /**
7
+ * The binding that records which pinned release a store script runs: the release bundle's
8
+ * sha256 (D-63). In the entry's reserved prefix, so the driver's env never carries it.
9
+ */
10
+ export const STORE_RELEASE_VAR = `${STORE_RESERVED_BINDING_PREFIX}_RELEASE`;
11
+ /**
12
+ * The binding that records what else the script was built from: a digest over every module the
13
+ * upload sent and its configuration. A changed entry, verifier, key list or flag changes it.
14
+ */
15
+ export const STORE_BUILD_VAR = `${STORE_RESERVED_BINDING_PREFIX}_BUILD`;
16
+ /**
17
+ * What the platform builds every store script from, as one digest (#2309): the entry, its
18
+ * verifier, the compatibility date and flags, and both key lists. It is the build digest of one
19
+ * fixed probe release, so it changes exactly when the platform's half of every script's build
20
+ * changes (a key added, dropped or revoked, an entry or verifier fix) and never when a driver's
21
+ * release does. The fleet rebuild converges every script on it.
22
+ */
23
+ export async function storePlatformFingerprint(keys) {
24
+ const probe = 'driver_substrat_probe/probe.py';
25
+ return buildDigest(withPythonStoreEntry({ entry: probe, modules: [{ name: probe, content: new Uint8Array() }] }, { className: 'Probe', script: 'substrat-store-probe', callerPublicKeys: keys.callerPublicKeys, platformPublicKeys: keys.platformPublicKeys }));
26
+ }
27
+ /** Everything the upload sends, as one digest, so a script built from anything else differs. */
28
+ async function buildDigest(script) {
29
+ const { modules, ...config } = script;
30
+ const named = modules.map((module) => ({ name: `module/${module.name}/${module.contentType}`, content: module.content }));
31
+ return storeDriverReleaseDigest({
32
+ entry: named[0].name,
33
+ modules: [...named, { name: 'config', content: new TextEncoder().encode(JSON.stringify(config)) }],
34
+ });
35
+ }
36
+ /**
37
+ * Put every (driver, vertical) store script a vertical's stores need in place, before the
38
+ * vertical's own upload (#2210, D-63). A push or a serve never moves a script to another
39
+ * release: only a rollout or a rollback does (#2300), and it says so with `to`.
40
+ *
41
+ * - No `to`, no script yet: it is uploaded at the driver's current good release.
42
+ * - No `to`, a script: it is rebuilt at the release it already runs, and uploaded again only when
43
+ * what it was built from changed (the platform's entry, the verifier, a key list, a flag), so a
44
+ * fix or a key rotation reaches it without a release moving.
45
+ * - `to`: the script is moved to that release, or left alone when it already runs it, built the
46
+ * same way. Re-running a rollout therefore converges and uploads nothing twice.
47
+ *
48
+ * Every upload hashes the module bytes it is about to send and refuses (422) bytes whose
49
+ * sha256 the driver's registration does not pin, or that are not the release asked for. A driver
50
+ * with no release to run is refused (409). A script is never moved to a release whose pinned
51
+ * `schema` is lower than that of the release it runs (409): its objects may have migrated. The
52
+ * runtime's own failures are 502. Answers how many scripts were uploaded.
53
+ */
54
+ export function createScopeStoreEnsurer(opts) {
55
+ const ensure = scriptEnsurer(opts);
56
+ return async (input) => {
57
+ const plan = scopeStoreUploadPlan({ vertical: input.vertical, stores: input.stores, drivers: opts.drivers });
58
+ let uploaded = 0;
59
+ for (const script of plan.scripts)
60
+ if (await ensure(script, input.to, { existingOnly: input.existingOnly }))
61
+ uploaded++;
62
+ return { uploaded };
63
+ };
64
+ }
65
+ /**
66
+ * Put a driver's release in the platform's canary script (D-63, #2300), where it is canaried
67
+ * before any vertical's script moves: `scopeStoreCanaryScriptName(driver)`, built and checked as a
68
+ * vertical's script is, except that it may move to a lower `schema`: every canary object is fresh
69
+ * and wiped, so none holds migrated data. With `existingOnly` instead of `to`, the fleet rebuild
70
+ * (#2309): the canary script is rebuilt at the release it runs, and left alone when there is none.
71
+ * Answers the script's name and whether it was uploaded.
72
+ */
73
+ export function createStoreCanaryEnsurer(opts) {
74
+ const ensure = scriptEnsurer(opts);
75
+ return async (input) => {
76
+ const driver = (opts.drivers ?? STORE_DRIVERS).find((k) => k.driver === input.driver);
77
+ if (!driver)
78
+ throw new ControlPlaneError(403, `scope store driver '${input.driver}' is not registered`);
79
+ const scriptName = scopeStoreCanaryScriptName(driver.driver);
80
+ const uploaded = await ensure({ driver: driver.driver, vertical: SCOPE_STORE_CANARY, scriptName, className: driver.className, releases: driver.releases }, 'to' in input ? input.to : undefined, { anySchema: true, existingOnly: 'existingOnly' in input });
81
+ return { scriptName, uploaded };
82
+ };
83
+ }
84
+ function scriptEnsurer(opts) {
85
+ const scriptUrl = (name) => `https://api.cloudflare.com/client/v4/accounts/${opts.accountId}` +
86
+ `/workers/dispatch/namespaces/${opts.namespace}/scripts/${encodeURIComponent(name)}`;
87
+ return async (script, to, rule = {}) => {
88
+ const current = await recorded(scriptUrl(script.scriptName));
89
+ if (rule.existingOnly && !current.exists)
90
+ return false;
91
+ if (current.exists && !current.release) {
92
+ // Not an upload of this code. Which release it runs cannot be told, so it is not guessed.
93
+ throw new ControlPlaneError(409, `store script '${script.scriptName}' records no release`);
94
+ }
95
+ const wanted = to ?? current.release;
96
+ const release = await opts.loadRelease({
97
+ driver: script.driver,
98
+ vertical: script.vertical,
99
+ ...(wanted ? { sha256: wanted } : {}),
100
+ });
101
+ if (!release) {
102
+ throw new ControlPlaneError(409, wanted
103
+ ? `driver '${script.driver}' release ${wanted} is not stored`
104
+ : `driver '${script.driver}' has no release rolled out to '${script.vertical}'`);
105
+ }
106
+ // The bytes about to be sent, copied once and hashed now: never a hash taken when they
107
+ // arrived (D-63). The copy is what is built from and sent, so the hashed bytes are the
108
+ // sent bytes whatever the loader keeps or shares.
109
+ const loaded = {
110
+ entry: release.entry,
111
+ modules: release.modules.map((module) => ({ name: module.name, content: new Uint8Array(module.content) })),
112
+ };
113
+ const sha256 = await storeDriverReleaseDigest(loaded).catch((e) => {
114
+ throw new ControlPlaneError(422, e instanceof Error ? e.message : String(e));
115
+ });
116
+ const asked = wanted ?? release.sha256;
117
+ if (asked && sha256 !== asked) {
118
+ throw new ControlPlaneError(422, `driver '${script.driver}' release ${asked} loaded as other bytes (${sha256})`);
119
+ }
120
+ const pin = script.releases.find((p) => p.sha256 === sha256);
121
+ if (!pin) {
122
+ throw new ControlPlaneError(422, `driver '${script.driver}' release ${sha256} is not pinned in its registration`);
123
+ }
124
+ if (current.release && current.release !== sha256 && !rule.anySchema) {
125
+ const running = script.releases.find((p) => p.sha256 === current.release);
126
+ if (!running) {
127
+ throw new ControlPlaneError(409, `store script '${script.scriptName}' runs ${current.release}, which is not pinned, so its schema cannot be judged`);
128
+ }
129
+ if (pin.schema < running.schema) {
130
+ throw new ControlPlaneError(409, `store script '${script.scriptName}' runs schema ${running.schema}; release ${sha256} has schema ${pin.schema}, and a script never moves to a lower one. ` +
131
+ `To undo a schema-raising rollout that stopped part way, converge it forward: post that rollout again`);
132
+ }
133
+ }
134
+ let built;
135
+ try {
136
+ built = withPythonStoreEntry(loaded, {
137
+ className: script.className,
138
+ script: script.scriptName,
139
+ callerPublicKeys: opts.callerPublicKeys,
140
+ platformPublicKeys: opts.platformPublicKeys,
141
+ });
142
+ }
143
+ catch (e) {
144
+ throw new ControlPlaneError(422, e instanceof Error ? e.message : String(e));
145
+ }
146
+ const build = await buildDigest(built);
147
+ if (current.exists && current.release === sha256 && current.build === build)
148
+ return false;
149
+ await put(scriptUrl(script.scriptName), script.scriptName, built, { sha256, build, first: !current.exists });
150
+ return true;
151
+ };
152
+ async function recorded(url) {
153
+ const res = await fetch(`${url}/settings`, { headers: { authorization: `Bearer ${opts.apiToken}` } });
154
+ if (res.status === 404)
155
+ return { exists: false };
156
+ if (!res.ok)
157
+ throw new ControlPlaneError(502, `store script settings failed (${res.status})`);
158
+ const body = (await res.json());
159
+ const text = (name) => body.result?.bindings?.find((b) => b.name === name)?.text;
160
+ return { exists: true, release: text(STORE_RELEASE_VAR), build: text(STORE_BUILD_VAR) };
161
+ }
162
+ async function put(url, scriptName, built, upload) {
163
+ const metadata = {
164
+ main_module: built.mainModule,
165
+ compatibility_date: built.compatibilityDate,
166
+ compatibility_flags: built.compatibilityFlags,
167
+ bindings: [
168
+ ...built.bindings,
169
+ { type: 'plain_text', name: STORE_RELEASE_VAR, text: upload.sha256 },
170
+ { type: 'plain_text', name: STORE_BUILD_VAR, text: upload.build },
171
+ ],
172
+ // The first upload declares the object class. Two pushes that both find no script both
173
+ // send this tag; the second is refused by the runtime (502), and a retry finds the script.
174
+ ...(upload.first ? { migrations: { new_tag: 'v1', new_sqlite_classes: [STORE_OBJECT_CLASS] } } : {}),
175
+ };
176
+ const form = new FormData();
177
+ form.set('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }), 'metadata.json');
178
+ for (const module of built.modules) {
179
+ // Sent as built, not copied again: every module is the ensurer's own copy or the entry's
180
+ // own encoding, so its buffer is an ArrayBuffer, and these are the bytes that were hashed.
181
+ form.set(module.name, new Blob([module.content], { type: module.contentType }), module.name);
182
+ }
183
+ const res = await fetch(url, { method: 'PUT', headers: { authorization: `Bearer ${opts.apiToken}` }, body: form });
184
+ if (!res.ok)
185
+ throw new ControlPlaneError(502, `store script upload failed (${res.status}) for '${scriptName}'`);
186
+ }
187
+ }
188
+ //# sourceMappingURL=store-script.js.map