@substrat-run/control-plane-api 0.147.0 → 0.148.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 (57) hide show
  1. package/dist/api.d.ts +56 -1
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +801 -379
  4. package/dist/api.js.map +1 -1
  5. package/dist/deploy.d.ts +4 -3
  6. package/dist/deploy.d.ts.map +1 -1
  7. package/dist/deploy.js +3 -3
  8. package/dist/deploy.js.map +1 -1
  9. package/dist/errors.d.ts +19 -1
  10. package/dist/errors.d.ts.map +1 -1
  11. package/dist/errors.js +18 -4
  12. package/dist/errors.js.map +1 -1
  13. package/dist/index.d.ts +10 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +6 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/lake-gateway.d.ts +54 -0
  18. package/dist/lake-gateway.d.ts.map +1 -0
  19. package/dist/lake-gateway.js +158 -0
  20. package/dist/lake-gateway.js.map +1 -0
  21. package/dist/platform-entry.generated.d.ts +1 -1
  22. package/dist/platform-entry.generated.d.ts.map +1 -1
  23. package/dist/platform-entry.generated.js +2 -2
  24. package/dist/platform-entry.generated.js.map +1 -1
  25. package/dist/platform-scope-stores.d.ts +59 -6
  26. package/dist/platform-scope-stores.d.ts.map +1 -1
  27. package/dist/platform-scope-stores.js +117 -16
  28. package/dist/platform-scope-stores.js.map +1 -1
  29. package/dist/r2-sql-engine.d.ts +50 -0
  30. package/dist/r2-sql-engine.d.ts.map +1 -0
  31. package/dist/r2-sql-engine.js +126 -0
  32. package/dist/r2-sql-engine.js.map +1 -0
  33. package/dist/scope-copy-cleanup.d.ts +12 -0
  34. package/dist/scope-copy-cleanup.d.ts.map +1 -1
  35. package/dist/scope-copy-cleanup.js +14 -3
  36. package/dist/scope-copy-cleanup.js.map +1 -1
  37. package/dist/store-entry.d.ts +90 -0
  38. package/dist/store-entry.d.ts.map +1 -0
  39. package/dist/store-entry.generated.d.ts +60 -0
  40. package/dist/store-entry.generated.d.ts.map +1 -0
  41. package/dist/store-entry.generated.js +62 -0
  42. package/dist/store-entry.generated.js.map +1 -0
  43. package/dist/store-entry.js +178 -0
  44. package/dist/store-entry.js.map +1 -0
  45. package/dist/store-releases.d.ts +147 -0
  46. package/dist/store-releases.d.ts.map +1 -0
  47. package/dist/store-releases.js +298 -0
  48. package/dist/store-releases.js.map +1 -0
  49. package/dist/store-script.d.ts +80 -0
  50. package/dist/store-script.d.ts.map +1 -0
  51. package/dist/store-script.js +173 -0
  52. package/dist/store-script.js.map +1 -0
  53. package/dist/vertical-client.d.ts +4 -0
  54. package/dist/vertical-client.d.ts.map +1 -1
  55. package/dist/vertical-client.js +10 -1
  56. package/dist/vertical-client.js.map +1 -1
  57. package/package.json +7 -7
@@ -0,0 +1,147 @@
1
+ /**
2
+ * A store kind's releases: stored, canaried, rolled out and rolled back (D-63, #2300).
3
+ *
4
+ * The bytes live outside the directory, content-addressed under `<kind>/<sha256>`, where the
5
+ * sha256 is `storeKindReleaseDigest` 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-kind-releases.ts` in the kernel).
9
+ *
10
+ * A rollout is one request across a fleet. It holds the kind 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 kind, at most
15
+ * {@link ROLLOUT_VERTICALS_PER_REQUEST} uploads per request. A bigger fleet ends the request
16
+ * `paused`, and posting the same rollout (or rollback) again continues it, walking past the scripts
17
+ * already there. A request that stops on a failure ends `failed` or `diverged`. One request walks at
18
+ * most {@link ROLLOUT_FLEET_MAX} verticals, a rollback included, until #2312.
19
+ */
20
+ import { type PlatformActorId, type ReservedStoreOperation, type ScopeStoreNeed, type StoreKindRegistration } from '@substrat-run/contracts';
21
+ import { type HostAdmin, type StoreKindRolloutAction, type StoreKindRolloutRecord } from '@substrat-run/kernel';
22
+ import type { ScopeStoreEnsurerOptions, StoreKindReleaseArtifact } from './store-script.js';
23
+ /** Where a release's bytes are kept: an object store keyed by kind and sha256. */
24
+ export interface StoreReleaseBytes {
25
+ get(key: string): Promise<Uint8Array | undefined>;
26
+ put(key: string, bytes: Uint8Array): Promise<void>;
27
+ }
28
+ /** The key a release's bytes are stored under. */
29
+ export declare const storeReleaseKey: (kind: string, sha256: string) => string;
30
+ /** How long a rollout's exclusive hold lives between renewals. */
31
+ export declare const ROLLOUT_HOLD_TTL_MS: number;
32
+ /**
33
+ * How long a push's or a serve's shared hold lives. It is renewed before each kind's upload; one
34
+ * kind's upload (a settings read and at most one upload per script) must fit inside it, or a
35
+ * rollout could take the kind before that upload lands. The same lease as a rollout's.
36
+ */
37
+ export declare const PUSH_HOLD_TTL_MS: number;
38
+ /**
39
+ * The verticals one rollout request moves. Each costs a settings read and at most one upload,
40
+ * plus a hold renewal, so a request stays well inside a Worker invocation's subrequest budget.
41
+ * A fleet bigger than this is moved over several requests (the rollout answers `complete: false`).
42
+ */
43
+ export declare const ROLLOUT_VERTICALS_PER_REQUEST = 100;
44
+ /**
45
+ * The most verticals one rollout walks. Every one costs a settings read and a hold renewal even
46
+ * when it is already at the release, so a bigger fleet would outrun one invocation; it is refused
47
+ * (409) rather than left unable to converge, and that blocks a rollback too. Raising it needs a
48
+ * cursor on the rollout row: #2312.
49
+ */
50
+ export declare const ROLLOUT_FLEET_MAX = 300;
51
+ type LoadRelease = ScopeStoreEnsurerOptions['loadRelease'];
52
+ /**
53
+ * A release bundle as uploaded or as stored, decoded and judged: the upload body's shape, the
54
+ * decoded size cap, and the Python kind's module rule. Refusals are 422 (413 for the size).
55
+ */
56
+ export declare function decodeStoreKindRelease(raw: unknown): Omit<StoreKindReleaseArtifact, 'sha256'>;
57
+ /**
58
+ * Store a release's bytes and record them (D-63: bytes may be published before the pin lands).
59
+ * The bytes go first, so a recorded release always has bytes; a retry of the same bundle writes
60
+ * the same key and records nothing new.
61
+ */
62
+ export declare function storeKindRelease(input: {
63
+ admin: HostAdmin;
64
+ actor: PlatformActorId;
65
+ bytes: StoreReleaseBytes;
66
+ kind: string;
67
+ body: unknown;
68
+ }): Promise<import("@substrat-run/kernel").StoreKindReleaseRecord>;
69
+ /**
70
+ * The ensurer's loader over the stored bytes: a named release, or the kind's current good one.
71
+ * Each answer carries the sha256 it was read as, which the ensurer holds the bytes to.
72
+ */
73
+ export declare function storedReleaseLoader(input: {
74
+ admin: HostAdmin;
75
+ actor: PlatformActorId;
76
+ bytes: StoreReleaseBytes;
77
+ }): LoadRelease;
78
+ /** The reserved calls a canary makes, at the canary script. */
79
+ export type CanaryCall = (target: {
80
+ scriptName: string;
81
+ objectName: string;
82
+ }, operation: ReservedStoreOperation, input?: unknown) => Promise<unknown>;
83
+ /**
84
+ * A canary object's name. Each id comes from a mint of its own, so its 80 random bits are fresh
85
+ * from `crypto.getRandomValues`: the shared mint would make the second id the first plus one
86
+ * within a millisecond, and either id would then give the other away.
87
+ */
88
+ export declare const canaryObjectName: (binding: string) => string;
89
+ /**
90
+ * Every reserved operation, on a fresh object of the platform's own (D-63): its tenant and scope
91
+ * ids are new ulids, from the runtime's CSPRNG, never in the directory, and its vertical segment
92
+ * is no vertical's. Each answer is parsed by the call and asserted here; the first that is not
93
+ * what an object holding no data answers fails the canary. The object is wiped at the end.
94
+ */
95
+ export declare function canaryStoreRelease(input: {
96
+ call: CanaryCall;
97
+ scriptName: string;
98
+ binding?: string;
99
+ }): Promise<void>;
100
+ export interface StoreKindRolloutDeps {
101
+ admin: HostAdmin;
102
+ actor: PlatformActorId;
103
+ registration: StoreKindRegistration;
104
+ sha256: string;
105
+ action: StoreKindRolloutAction;
106
+ /**
107
+ * Move one vertical's scripts of the kind to `to` (the push's ensurer, with `to`). Answers
108
+ * whether anything was uploaded: false for a script already there, built the same way.
109
+ */
110
+ ensure: (input: {
111
+ vertical: string;
112
+ stores: readonly ScopeStoreNeed[];
113
+ to: string;
114
+ }) => Promise<boolean>;
115
+ /** Put `to` in the kind's canary script; answers its name. */
116
+ ensureCanary: (to: string) => Promise<{
117
+ scriptName: string;
118
+ }>;
119
+ callCanary: CanaryCall;
120
+ /** For a test: a smaller batch than {@link ROLLOUT_VERTICALS_PER_REQUEST}. */
121
+ perRequest?: number;
122
+ }
123
+ export interface StoreKindRolloutResult {
124
+ rollout: StoreKindRolloutRecord;
125
+ /** The verticals whose scripts this request uploaded. */
126
+ moved: string[];
127
+ /** False when verticals remain: post the same rollout again. */
128
+ complete: boolean;
129
+ }
130
+ /**
131
+ * Move every script of a kind to a stored, pinned release (D-63): hold, canary, then each
132
+ * vertical. A failure after a vertical moved returns every vertical it reached to the release
133
+ * the rollout started from, rehashing those bytes as it uploads them, and ends `failed`. It ends
134
+ * `diverged` instead, naming what is where, when there is nothing to return to (a kind's first
135
+ * rollout), when returning would lower the stored schema, or when a return fails: the next
136
+ * rollout or rollback converges every script. The hold is renewed before every upload, the
137
+ * returns included, and a hold that cannot be renewed stops everything at once: another holder
138
+ * may be moving the same scripts, so nothing is returned either. The hold is released however it
139
+ * ends.
140
+ *
141
+ * Each request uploads at most `perRequest` scripts. A vertical already at the release is
142
+ * walked past without counting, so posting the same rollout again continues where the last
143
+ * request stopped, and a fleet of any size within {@link ROLLOUT_FLEET_MAX} converges.
144
+ */
145
+ export declare function runStoreKindRollout(deps: StoreKindRolloutDeps): Promise<StoreKindRolloutResult>;
146
+ export {};
147
+ //# sourceMappingURL=store-releases.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-releases.d.ts","sourceRoot":"","sources":["../src/store-releases.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,298 @@
1
+ /**
2
+ * A store kind's releases: stored, canaried, rolled out and rolled back (D-63, #2300).
3
+ *
4
+ * The bytes live outside the directory, content-addressed under `<kind>/<sha256>`, where the
5
+ * sha256 is `storeKindReleaseDigest` 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-kind-releases.ts` in the kernel).
9
+ *
10
+ * A rollout is one request across a fleet. It holds the kind 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 kind, at most
15
+ * {@link ROLLOUT_VERTICALS_PER_REQUEST} uploads per request. A bigger fleet ends the request
16
+ * `paused`, and posting the same rollout (or rollback) again continues it, walking past the scripts
17
+ * already there. A request that stops on a failure ends `failed` or `diverged`. One request walks at
18
+ * most {@link ROLLOUT_FLEET_MAX} verticals, a rollback included, until #2312.
19
+ */
20
+ import { STORE_RELEASE_MAX_BYTES, scopeStoreCanaryObjectName, storeKindReleaseDigest, storeKindReleaseUpload, } from '@substrat-run/contracts';
21
+ import { ControlPlaneError } from '@substrat-run/control-plane-client';
22
+ import { createUlid, ulid } from '@substrat-run/kernel';
23
+ import { assertPythonKindModules } from './store-entry.js';
24
+ /** The key a release's bytes are stored under. */
25
+ export const storeReleaseKey = (kind, sha256) => `${kind}/${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 kind's upload; one
30
+ * kind's upload (a settings read and at most one upload per script) must fit inside it, or a
31
+ * rollout could take the kind 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 most verticals one rollout walks. Every one costs a settings read and a hold renewal even
42
+ * when it is already at the release, so a bigger fleet would outrun one invocation; it is refused
43
+ * (409) rather than left unable to converge, and that blocks a rollback too. Raising it needs a
44
+ * cursor on the rollout row: #2312.
45
+ */
46
+ export const ROLLOUT_FLEET_MAX = 300;
47
+ const decodeBase64 = (text) => {
48
+ let binary;
49
+ try {
50
+ binary = atob(text);
51
+ }
52
+ catch {
53
+ throw new ControlPlaneError(422, 'a release module is not base64');
54
+ }
55
+ return Uint8Array.from(binary, (ch) => ch.charCodeAt(0));
56
+ };
57
+ /**
58
+ * A release bundle as uploaded or as stored, decoded and judged: the upload body's shape, the
59
+ * decoded size cap, and the Python kind's module rule. Refusals are 422 (413 for the size).
60
+ */
61
+ export function decodeStoreKindRelease(raw) {
62
+ const parsed = storeKindReleaseUpload.safeParse(raw);
63
+ if (!parsed.success) {
64
+ throw new ControlPlaneError(422, `not a store kind release: ${parsed.error.issues.map((i) => i.message).join('; ')}`);
65
+ }
66
+ let total = 0;
67
+ const modules = parsed.data.modules.map((module) => {
68
+ const content = decodeBase64(module.contentBase64);
69
+ total += content.byteLength;
70
+ if (total > STORE_RELEASE_MAX_BYTES) {
71
+ throw new ControlPlaneError(413, `a store kind release carries at most ${STORE_RELEASE_MAX_BYTES} module bytes`);
72
+ }
73
+ return { name: module.name, content };
74
+ });
75
+ const release = { sourceCommit: parsed.data.sourceCommit, entry: parsed.data.entry, modules };
76
+ try {
77
+ assertPythonKindModules(release);
78
+ }
79
+ catch (e) {
80
+ throw new ControlPlaneError(422, e instanceof Error ? e.message : String(e));
81
+ }
82
+ return release;
83
+ }
84
+ /** The stored form: the upload body again, so one decoder reads both. */
85
+ function encodeStoreKindRelease(release) {
86
+ const base64 = (bytes) => {
87
+ let text = '';
88
+ for (let i = 0; i < bytes.length; i += 0x8000)
89
+ text += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
90
+ return btoa(text);
91
+ };
92
+ return new TextEncoder().encode(JSON.stringify({
93
+ sourceCommit: release.sourceCommit,
94
+ entry: release.entry,
95
+ modules: release.modules.map((m) => ({ name: m.name, contentBase64: base64(m.content) })),
96
+ }));
97
+ }
98
+ /**
99
+ * Store a release's bytes and record them (D-63: bytes may be published before the pin lands).
100
+ * The bytes go first, so a recorded release always has bytes; a retry of the same bundle writes
101
+ * the same key and records nothing new.
102
+ */
103
+ export async function storeKindRelease(input) {
104
+ const release = decodeStoreKindRelease(input.body);
105
+ const sha256 = await storeKindReleaseDigest(release).catch((e) => {
106
+ throw new ControlPlaneError(422, e instanceof Error ? e.message : String(e));
107
+ });
108
+ const held = await input.admin.getStoreKindRelease(input.actor, input.kind, sha256);
109
+ if (held && held.sourceCommit !== release.sourceCommit) {
110
+ throw new ControlPlaneError(409, `release ${sha256} of '${input.kind}' is stored as built from ${held.sourceCommit}`);
111
+ }
112
+ if (!held)
113
+ await input.bytes.put(storeReleaseKey(input.kind, sha256), encodeStoreKindRelease(release));
114
+ return input.admin.recordStoreKindRelease(input.actor, { kind: input.kind, sha256, sourceCommit: release.sourceCommit });
115
+ }
116
+ /**
117
+ * The ensurer's loader over the stored bytes: a named release, or the kind's current good one.
118
+ * Each answer carries the sha256 it was read as, which the ensurer holds the bytes to.
119
+ */
120
+ export function storedReleaseLoader(input) {
121
+ return async ({ kind, sha256 }) => {
122
+ const wanted = sha256 ?? (await input.admin.storeKindState(input.actor, kind)).current?.sha256;
123
+ if (!wanted)
124
+ return undefined;
125
+ const raw = await input.bytes.get(storeReleaseKey(kind, wanted));
126
+ if (!raw)
127
+ return undefined;
128
+ let json;
129
+ try {
130
+ json = JSON.parse(new TextDecoder().decode(raw));
131
+ }
132
+ catch {
133
+ throw new ControlPlaneError(422, `kind '${kind}' release ${wanted} is stored as something other than a release`);
134
+ }
135
+ return { ...decodeStoreKindRelease(json), sha256: wanted };
136
+ };
137
+ }
138
+ /**
139
+ * A canary object's name. Each id comes from a mint of its own, so its 80 random bits are fresh
140
+ * from `crypto.getRandomValues`: the shared mint would make the second id the first plus one
141
+ * within a millisecond, and either id would then give the other away.
142
+ */
143
+ export const canaryObjectName = (binding) => scopeStoreCanaryObjectName(createUlid()(), createUlid()(), binding);
144
+ const tablesOf = (answer) => answer.tables;
145
+ /**
146
+ * Every reserved operation, on a fresh object of the platform's own (D-63): its tenant and scope
147
+ * ids are new ulids, from the runtime's CSPRNG, never in the directory, and its vertical segment
148
+ * is no vertical's. Each answer is parsed by the call and asserted here; the first that is not
149
+ * what an object holding no data answers fails the canary. The object is wiped at the end.
150
+ */
151
+ export async function canaryStoreRelease(input) {
152
+ const target = { scriptName: input.scriptName, objectName: canaryObjectName(input.binding ?? 'CANARY') };
153
+ const step = async (operation, check, body) => {
154
+ let answer;
155
+ try {
156
+ answer = await input.call(target, operation, body);
157
+ }
158
+ catch (e) {
159
+ throw new ControlPlaneError(502, `canary refused: ${operation} failed (${e instanceof Error ? e.message : String(e)})`);
160
+ }
161
+ const wrong = check(answer);
162
+ if (wrong)
163
+ throw new ControlPlaneError(502, `canary refused: ${operation} ${wrong}`);
164
+ return answer;
165
+ };
166
+ const ok = () => null;
167
+ await step('substrat.size', ok);
168
+ const exported = tablesOf(await step('substrat.export', (a) => (tablesOf(a).some((t) => t.rows.length > 0) ? 'exported rows from an object holding none' : null)));
169
+ await step('substrat.import', (a) => (a.tables === exported.length ? null : `imported ${a.tables} of ${exported.length} tables`), { tables: exported });
170
+ await step('substrat.export', (a) => (JSON.stringify(tablesOf(a)) === JSON.stringify(exported) ? null : 'did not export what it imported'));
171
+ await step('substrat.erase', (a) => (a.erased === 0 ? null : 'erased rows of a subject the object never held'), { subjectId: ulid() });
172
+ await step('substrat.wipe', ok);
173
+ await step('substrat.export', (a) => (tablesOf(a).some((t) => t.rows.length > 0) ? 'still holds rows after its wipe' : null));
174
+ }
175
+ const message = (e) => (e instanceof Error ? e.message : String(e));
176
+ const schemaOf = (registration, sha256) => sha256 === null ? undefined : registration.releases.find((pin) => pin.sha256 === sha256)?.schema;
177
+ /** The hold lapsed, or another holder ended the rollout: nothing more may be moved under it. */
178
+ class HoldLost extends ControlPlaneError {
179
+ constructor(kind) {
180
+ super(409, `the hold on kind '${kind}' lapsed or its rollout was ended by another; post the rollout again to converge it`);
181
+ }
182
+ }
183
+ /**
184
+ * Move every script of a kind to a stored, pinned release (D-63): hold, canary, then each
185
+ * vertical. A failure after a vertical moved returns every vertical it reached to the release
186
+ * the rollout started from, rehashing those bytes as it uploads them, and ends `failed`. It ends
187
+ * `diverged` instead, naming what is where, when there is nothing to return to (a kind's first
188
+ * rollout), when returning would lower the stored schema, or when a return fails: the next
189
+ * rollout or rollback converges every script. The hold is renewed before every upload, the
190
+ * returns included, and a hold that cannot be renewed stops everything at once: another holder
191
+ * may be moving the same scripts, so nothing is returned either. The hold is released however it
192
+ * ends.
193
+ *
194
+ * Each request uploads at most `perRequest` scripts. A vertical already at the release is
195
+ * walked past without counting, so posting the same rollout again continues where the last
196
+ * request stopped, and a fleet of any size within {@link ROLLOUT_FLEET_MAX} converges.
197
+ */
198
+ export async function runStoreKindRollout(deps) {
199
+ const { admin, actor, registration, sha256 } = deps;
200
+ const kind = registration.kind;
201
+ const holder = ulid();
202
+ if (!(await admin.holdStoreKind(actor, { kind, holder, mode: 'exclusive', ttlMs: ROLLOUT_HOLD_TTL_MS }))) {
203
+ throw new ControlPlaneError(409, `kind '${kind}' is being rolled out or a vertical declaring it is being pushed; try again shortly`);
204
+ }
205
+ try {
206
+ const declared = await admin.storeKindDeclarations(actor, kind);
207
+ if (declared.unreadable.length > 0) {
208
+ throw new ControlPlaneError(409, `kind '${kind}' cannot be rolled out: ${declared.unreadable.join(', ')} ha${declared.unreadable.length === 1 ? 's a version' : 've versions'} whose stores cannot be named`);
209
+ }
210
+ if (declared.verticals.length > ROLLOUT_FLEET_MAX) {
211
+ throw new ControlPlaneError(409, `kind '${kind}' is declared by ${declared.verticals.length} verticals; a ${deps.action} walks at most ${ROLLOUT_FLEET_MAX}, so neither a rollout nor a rollback can run until rollouts page with a cursor (#2312)`);
212
+ }
213
+ const row = await admin.beginStoreKindRollout(actor, { kind, sha256, action: deps.action, holder });
214
+ const end = (state, detail) => admin.finishStoreKindRollout(actor, { kind, seq: row.seq, holder, state, detail });
215
+ const renew = async () => {
216
+ const held = await admin.holdStoreKind(actor, { kind, holder, mode: 'exclusive', ttlMs: ROLLOUT_HOLD_TTL_MS, renew: true, rolloutSeq: row.seq });
217
+ if (!held)
218
+ throw new HoldLost(kind);
219
+ };
220
+ try {
221
+ await deps.ensureCanary(sha256).then(({ scriptName }) => canaryStoreRelease({ call: deps.callCanary, scriptName }));
222
+ }
223
+ catch (e) {
224
+ // No vertical's script moved; the current release stays. A row the end cannot write (the
225
+ // hold lapsed) reads as diverged, which is what it then is.
226
+ await end('failed', message(e)).catch(() => undefined);
227
+ throw e;
228
+ }
229
+ const targets = declared.verticals.map((v) => ({ vertical: v.vertical, stores: v.stores.map((s) => ({ binding: s.binding, kind })) }));
230
+ const budget = deps.perRequest ?? ROLLOUT_VERTICALS_PER_REQUEST;
231
+ // Every vertical walked: the ones moved now, and the ones an earlier request moved.
232
+ const reached = [];
233
+ const moved = [];
234
+ for (const target of targets) {
235
+ if (moved.length >= budget) {
236
+ const rest = targets.length - reached.length;
237
+ const rollout = await end('paused', `this request moved ${moved.length}; ${rest} vertical(s) not walked yet: post the same ${deps.action} again`);
238
+ return { rollout, moved, complete: false };
239
+ }
240
+ try {
241
+ await renew();
242
+ reached.push(target.vertical);
243
+ if (await deps.ensure({ vertical: target.vertical, stores: target.stores, to: sha256 }))
244
+ moved.push(target.vertical);
245
+ }
246
+ catch (e) {
247
+ const detail = e instanceof HoldLost
248
+ ? { state: 'diverged', text: `Nothing was returned: ${reached.join(', ') || 'no vertical'} may run ${sha256}.` }
249
+ : await restore(deps, row.fromSha256, reached, targets, renew);
250
+ await end(detail.state, `${target.vertical}: ${message(e)}. ${detail.text}`).catch(() => undefined);
251
+ throw new ControlPlaneError(e instanceof ControlPlaneError ? e.status : 502, `rollout of '${kind}' to ${sha256} stopped at ${target.vertical} (${message(e)}). ${detail.text}`);
252
+ }
253
+ }
254
+ return { rollout: await end('good', null), moved, complete: true };
255
+ }
256
+ finally {
257
+ await admin.releaseStoreKindHold(actor, { kind, holder }).catch(() => undefined);
258
+ }
259
+ }
260
+ /**
261
+ * Return every vertical a failed rollout reached to the release it started from, where that is
262
+ * allowed, renewing the hold before each upload. A hold that cannot be renewed stops the return.
263
+ */
264
+ async function restore(deps, from, reached, verticals, renew) {
265
+ if (reached.length === 0)
266
+ return { state: 'failed', text: 'No vertical moved.' };
267
+ if (from === null) {
268
+ return { state: 'diverged', text: `There is no earlier release to return to; ${reached.join(', ')} may run ${deps.sha256}.` };
269
+ }
270
+ const leaving = schemaOf(deps.registration, deps.sha256);
271
+ const returning = schemaOf(deps.registration, from);
272
+ if (leaving === undefined || returning === undefined || returning < leaving) {
273
+ return {
274
+ state: 'diverged',
275
+ text: `Returning to ${from} would lower the stored schema (${leaving} to ${returning}), so ${reached.join(', ')} may run ${deps.sha256}.`,
276
+ };
277
+ }
278
+ const stuck = [];
279
+ for (const [i, vertical] of reached.entries()) {
280
+ try {
281
+ await renew();
282
+ }
283
+ catch {
284
+ return { state: 'diverged', text: `The hold was lost while returning; ${reached.slice(i).join(', ')} may run ${deps.sha256}.` };
285
+ }
286
+ const target = verticals.find((v) => v.vertical === vertical);
287
+ try {
288
+ await deps.ensure({ vertical, stores: target.stores, to: from });
289
+ }
290
+ catch (e) {
291
+ stuck.push(`${vertical} (${message(e)})`);
292
+ }
293
+ }
294
+ return stuck.length === 0
295
+ ? { state: 'failed', text: `${reached.join(', ')} returned to ${from}.` }
296
+ : { state: 'diverged', text: `Could not return ${stuck.join(', ')} to ${from}.` };
297
+ }
298
+ //# 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,80 @@
1
+ import { type ScopeStoreNeed, type StoreKindRegistration } from '@substrat-run/contracts';
2
+ import { type PythonStoreKindRelease } 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 kind'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 kind's code, as the publish flow stores it (#2297). */
14
+ export interface StoreKindReleaseArtifact extends PythonStoreKindRelease {
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
+ kinds?: readonly StoreKindRegistration[];
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 kind's current good release (#2300). Undefined when
35
+ * there is none.
36
+ */
37
+ loadRelease: (input: {
38
+ readonly kind: string;
39
+ readonly vertical: string;
40
+ readonly sha256?: string;
41
+ }) => Promise<StoreKindReleaseArtifact | undefined>;
42
+ }
43
+ /**
44
+ * Put every (kind, vertical) store script a vertical's stores need in place, before the
45
+ * vertical's own upload (#2210, D-63). A push or a serve never moves a script to another
46
+ * release: only a rollout or a rollback does (#2300), and it says so with `to`.
47
+ *
48
+ * - No `to`, no script yet: it is uploaded at the kind's current good release.
49
+ * - No `to`, a script: it is rebuilt at the release it already runs, and uploaded again only when
50
+ * what it was built from changed (the platform's entry, the verifier, a key list, a flag), so a
51
+ * fix or a key rotation reaches it without a release moving.
52
+ * - `to`: the script is moved to that release, or left alone when it already runs it, built the
53
+ * same way. Re-running a rollout therefore converges and uploads nothing twice.
54
+ *
55
+ * Every upload hashes the module bytes it is about to send and refuses (422) bytes whose
56
+ * sha256 the kind's registration does not pin, or that are not the release asked for. A kind
57
+ * with no release to run is refused (409). A script is never moved to a release whose pinned
58
+ * `schema` is lower than that of the release it runs (409): its objects may have migrated. The
59
+ * runtime's own failures are 502. Answers how many scripts were uploaded.
60
+ */
61
+ export declare function createScopeStoreEnsurer(opts: ScopeStoreEnsurerOptions): (input: {
62
+ vertical: string;
63
+ stores: readonly ScopeStoreNeed[];
64
+ to?: string;
65
+ }) => Promise<{
66
+ uploaded: number;
67
+ }>;
68
+ /**
69
+ * Put a kind's release in the platform's canary script (D-63, #2300), where it is canaried
70
+ * before any vertical's script moves: `scopeStoreCanaryScriptName(kind)`, built and checked as a
71
+ * vertical's script is, except that it may move to a lower `schema`: every canary object is fresh
72
+ * and wiped, so none holds migrated data. Answers the script's name.
73
+ */
74
+ export declare function createStoreCanaryEnsurer(opts: ScopeStoreEnsurerOptions): (input: {
75
+ kind: string;
76
+ to: string;
77
+ }) => Promise<{
78
+ scriptName: string;
79
+ }>;
80
+ //# 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,qBAAqB,EAE3B,MAAM,yBAAyB,CAAC;AAIjC,OAAO,EAAwB,KAAK,sBAAsB,EAA0B,MAAM,kBAAkB,CAAC;AAE7G;;;GAGG;AACH,eAAO,MAAM,iBAAiB,2BAA6C,CAAC;AAE5E;;;GAGG;AACH,eAAO,MAAM,eAAe,yBAA2C,CAAC;AAExE,2EAA2E;AAC3E,MAAM,WAAW,wBAAyB,SAAQ,sBAAsB;IACtE,+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,KAAK,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACzC;;;;OAIG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE;QACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;KAC1B,KAAK,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAAC;CACrD;AAYD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,wBAAwB,WAE/C;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,KAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAM1H;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,wBAAwB,WAEhD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,KAAG,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAWpF"}