@substrat-run/adapter-cloudflare 0.148.0 → 0.150.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/control-plane-do.d.ts +87 -27
- package/dist/control-plane-do.d.ts.map +1 -1
- package/dist/control-plane-do.js +135 -52
- package/dist/control-plane-do.js.map +1 -1
- package/dist/host.d.ts +40 -11
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +282 -85
- package/dist/host.js.map +1 -1
- package/dist/route-resolver.js +2 -2
- package/dist/route-resolver.js.map +1 -1
- package/dist/scope-do.d.ts +7 -0
- package/dist/scope-do.d.ts.map +1 -1
- package/dist/scope-do.js +164 -18
- package/dist/scope-do.js.map +1 -1
- package/package.json +9 -9
package/dist/control-plane-do.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DurableObject } from 'cloudflare:workers';
|
|
2
|
-
import { membershipFencesBackfillSql, MEMBERSHIP_FENCES_DDL, MEMBERSHIP_FENCES_TABLE, membershipFencesTableExists, MEMBERSHIP_FENCE_SINCE_SQL, RAISE_MEMBERSHIP_FENCE_SQL, memberAddedAudit, orgChangeBound, tenantCoverage, IMPERSONATION_COLUMNS, IMPERSONATION_DDL, impersonationByIdQuery, impersonationListQuery, impersonationRowValues, ISSUE_RETENTION_DAYS, telemetryRetentionStatements, FINDINGS_DDL, SCOPE_STORAGE_DDL, forgetScopeStorage, listScopeStorageAttemptRows, listScopeStorageRows, listScopeStoreStorageRows, pruneScopeStorageRows, recordScopeStorageRows, CONNECT_LINKS_DDL, consumeConnectLinkRow, insertConnectLink as insertConnectLinkRow, listConnectLinks as listConnectLinkRows, readConnectLink as readConnectLinkRow, restoreConnectLinkRow, revokeConnectLinkRow, createFindingRule, findingOfOpsFailure, findingOfSweepRun, pruneFindings, listFindingRules, listFindings, observeFinding, revokeFindingRule, setFindingStatus, assertRowLimit, OPS_FAILURE_RETENTION_DAYS, SWEEP_RUN_RETENTION_DAYS, SWEEP_RUNS_INTENT_INDEX, sweepRunsIntentHasKind, PEER_SWITCHES_DDL, PEER_BINDINGS_DDL, SWITCH_OWED_DDL, clearSwitchOwed, markSwitchOwed, scopeOwesSwitch, switchesOwedOf, SWITCH_KINDS, SYSTEM_SWITCHES_DDL, forgetSwitchesOf, listSystemSwitchRecords, recordSwitchedOff, recordSwitchedOn, recordWriteSuperseded, restoreSwitchRecord, scopesSwitchedOffFor, switchRecordStatesOf, switchRecordsOf, switchesBackfillSqlOf, switchesDdlOf, switchesTableExists, switchesTableOf, tenantHeldOf, VERSION_MIGRATIONS_DDL, VERTICAL_SCOPE_STORES_DDL,
|
|
2
|
+
import { membershipFencesBackfillSql, MEMBERSHIP_FENCES_DDL, MEMBERSHIP_FENCES_TABLE, membershipFencesTableExists, MEMBERSHIP_FENCE_SINCE_SQL, RAISE_MEMBERSHIP_FENCE_SQL, memberAddedAudit, orgChangeBound, tenantCoverage, IMPERSONATION_COLUMNS, IMPERSONATION_DDL, impersonationByIdQuery, impersonationListQuery, impersonationRowValues, ISSUE_RETENTION_DAYS, telemetryRetentionStatements, FINDINGS_DDL, SCOPE_STORAGE_DDL, forgetScopeStorage, listScopeStorageAttemptRows, listScopeStorageRows, listScopeStoreStorageRows, pruneScopeStorageRows, recordScopeStorageRows, CONNECT_LINKS_DDL, consumeConnectLinkRow, insertConnectLink as insertConnectLinkRow, listConnectLinks as listConnectLinkRows, readConnectLink as readConnectLinkRow, restoreConnectLinkRow, revokeConnectLinkRow, createFindingRule, findingOfOpsFailure, findingOfSweepRun, pruneFindings, listFindingRules, listFindings, observeFinding, revokeFindingRule, setFindingStatus, assertRowLimit, OPS_FAILURE_RETENTION_DAYS, SWEEP_RUN_RETENTION_DAYS, SWEEP_RUNS_INTENT_INDEX, sweepRunsIntentHasKind, PEER_SWITCHES_DDL, PEER_BINDINGS_DDL, SWITCH_OWED_DDL, clearSwitchOwed, markSwitchOwed, scopeOwesSwitch, switchesOwedOf, SWITCH_KINDS, SYSTEM_SWITCHES_DDL, forgetSwitchesOf, listSystemSwitchRecords, recordSwitchedOff, recordSwitchedOn, recordWriteSuperseded, restoreSwitchRecord, scopesSwitchedOffFor, switchRecordStatesOf, switchRecordsOf, switchesBackfillSqlOf, switchesDdlOf, switchesTableExists, switchesTableOf, tenantHeldOf, VERSION_MIGRATIONS_DDL, VERTICAL_SCOPE_STORES_DDL, STORE_DRIVER_RELEASES_DDL, legacyStoreDriverConflict, legacyStoreDriverRenames, beginStoreDriverRolloutIn, finishStoreDriverRolloutIn, holdStoreDriverIn, advanceStoreDriverRebuildIn, claimStoreDriverRebuildIn, storeDriverRebuildIn, driverDeclarationsOf, recordStoreDriverReleaseIn, releaseStoreDriverHoldIn, storeDriverReleaseIn, storeDriverStateIn, storeDriverRolloutPendingIn, storeDriverRolloutWalkedIn, forgetVerticalScopeStores, indexVersionScopeStores, verticalScopeStoresOf, splitVersionMigrationsBatch, versionMigrationsOf, versionsAwaitSplit, versionsAwaitDigest, deriveMigrationDigestsBatch, deriveVersionMigrationDigest, writeVersionMigrations, MODEL_USAGE_RETENTION_DAYS, DO_SQL_LIMITS, ulid, isPrimaryScopeRow, samePeerVertical, peerVerticalSpellings, resolveVerticalInstanceFrom, resolvePeerInstanceFrom, LEGACY_SCOPE_ROWS_BACKFILL, loadDirectoryDump, redactSubjectDirectoryText, ISSUE_EXEMPLAR_OWNER_BACKFILL_SQL, issueExemplarOwner, SETTLE_INTENT_SQL, SETTLE_OUTCOME_SQL, settleOutcomeParamsOf, unknownOutcomeOf, ADMIN_LOG_INDEX_DDL, ADMIN_LOG_INDEXES_SQL, readAuditedOperations, } from '@substrat-run/kernel';
|
|
3
3
|
import { replyOf } from './do-reply.js';
|
|
4
4
|
import { switchSqlOver } from './scope-do.js';
|
|
5
5
|
import { blankSqlComments, boundShapesColumn, executableSqlStatements, installsReconciledOf, PROVISIONED_VERSIONS_SQL, provisionedVersionsParams, } from '@substrat-run/kernel';
|
|
6
|
-
import { COPY_BACKFILL_SCOPE_SQL, COPY_BACKFILL_SQL, COPY_BACKFILL_SUPERSEDE_SQL, COPY_CLAIM_SQL, COPY_EXPIRED_SQL, copyBackfillParams, copyBackfillRefusal, SUBJECT_ERASURE_CLAIM_SQL, subjectErasureClaimParams, COPY_MOVE_CONFIRM_SQL, COPY_MOVE_LIVE_PREDICATE, SCOPE_COPY_LEASE_MS, SCOPE_SCRIPT_COPY_COLUMNS, copyMoveConfirmParams, copyMoveLiveParams, } from '@substrat-run/kernel';
|
|
6
|
+
import { COPY_BACKFILL_SCOPE_SQL, COPY_BACKFILL_SQL, COPY_BACKFILL_SUPERSEDE_SQL, COPY_CLAIM_SQL, COPY_EXPIRED_SQL, copyBackfillParams, copyBackfillRefusal, SUBJECT_ERASURE_CLAIM_SQL, subjectErasureClaimParams, COPY_MOVE_CONFIRM_SQL, COPY_MOVE_LIVE_PREDICATE, SCOPE_COPY_LEASE_MS, SERVES_NOW_CONDITION, SERVES_NOW_PREDICATE, servesNowParams, SCOPE_SCRIPT_COPY_COLUMNS, copyMoveConfirmParams, copyMoveLiveParams, } from '@substrat-run/kernel';
|
|
7
7
|
import { doBuiltColumnsOf, doRedactionSql } from './sql.js';
|
|
8
8
|
import { assertReplayableDump, opsFailureFingerprint, ROUTE_SCOPE_HOSTNAMES_MAX, SCOPE_GATE_REASONS, substratError } from '@substrat-run/contracts';
|
|
9
9
|
/**
|
|
@@ -247,8 +247,8 @@ const DIRECTORY_DDL = `
|
|
|
247
247
|
created_at TEXT NOT NULL,
|
|
248
248
|
PRIMARY KEY (tenant_id, vertical, binding)
|
|
249
249
|
);
|
|
250
|
-
-- A store
|
|
251
|
-
${
|
|
250
|
+
-- A store driver's stored releases, rollouts and holds (D-63, #2300).
|
|
251
|
+
${STORE_DRIVER_RELEASES_DDL}
|
|
252
252
|
CREATE TABLE IF NOT EXISTS verticals (
|
|
253
253
|
slug TEXT PRIMARY KEY,
|
|
254
254
|
name TEXT NOT NULL,
|
|
@@ -307,6 +307,9 @@ const DIRECTORY_DDL = `
|
|
|
307
307
|
migration_count INTEGER,
|
|
308
308
|
-- 1 once the SQL is out of manifest_json. NULL = stored before #1764, not yet backfilled.
|
|
309
309
|
migrations_split INTEGER,
|
|
310
|
+
-- 1 once migration_digest is the one derived from the stored migrations (#2339).
|
|
311
|
+
-- NULL = stored before, still the pushed digest; backfilled, and derived on read until then.
|
|
312
|
+
migration_digest_derived INTEGER,
|
|
310
313
|
created_at TEXT NOT NULL,
|
|
311
314
|
UNIQUE (vertical_slug, version)
|
|
312
315
|
);
|
|
@@ -779,16 +782,22 @@ export const REPLIED_METHODS = [
|
|
|
779
782
|
'linkIdentity',
|
|
780
783
|
// #1748: refuses an expiry outside its bounds with validation_failed.
|
|
781
784
|
'createFindingRule',
|
|
782
|
-
// #2286: refuses a
|
|
785
|
+
// #2286: refuses a scope-store binding moved to another driver with conflict, and an unreadable manifest with unavailable.
|
|
783
786
|
'insertVersion',
|
|
784
787
|
'verticalScopeStores',
|
|
785
|
-
// #2300: a store
|
|
788
|
+
// #2300: a store driver's release, rollout and hold writes refuse with conflict, not_found,
|
|
786
789
|
// validation_failed or unavailable.
|
|
787
|
-
'
|
|
788
|
-
'
|
|
789
|
-
|
|
790
|
-
'
|
|
791
|
-
'
|
|
790
|
+
'recordStoreDriverRelease',
|
|
791
|
+
'storeDriverDeclarations',
|
|
792
|
+
// #2312: a rollout page refuses a limit that is not positive, and an unknown rollout.
|
|
793
|
+
'storeDriverRolloutPending',
|
|
794
|
+
'storeDriverRolloutWalked',
|
|
795
|
+
'holdStoreDriver',
|
|
796
|
+
'beginStoreDriverRollout',
|
|
797
|
+
'finishStoreDriverRollout',
|
|
798
|
+
// #2309: a fleet rebuild's lease write refuses with conflict or validation_failed.
|
|
799
|
+
'claimStoreDriverRebuild',
|
|
800
|
+
'advanceStoreDriverRebuild',
|
|
792
801
|
];
|
|
793
802
|
export class ControlPlaneDO extends DurableObject {
|
|
794
803
|
sql;
|
|
@@ -813,7 +822,7 @@ export class ControlPlaneDO extends DurableObject {
|
|
|
813
822
|
* instead (`importDump`).
|
|
814
823
|
*/
|
|
815
824
|
async armVersionMigrationsBackfill() {
|
|
816
|
-
if (!versionsAwaitSplit(this.kernelSql))
|
|
825
|
+
if (!versionsAwaitSplit(this.kernelSql) && !versionsAwaitDigest(this.kernelSql))
|
|
817
826
|
return;
|
|
818
827
|
// An alarm already set is kept: it is the backfill's own next run, and a backoff retry
|
|
819
828
|
// pulled forward to 1 s by any ordinary request would make the backoff meaningless.
|
|
@@ -834,6 +843,9 @@ export class ControlPlaneDO extends DurableObject {
|
|
|
834
843
|
let more;
|
|
835
844
|
try {
|
|
836
845
|
more = this.ctx.storage.transactionSync(() => splitVersionMigrationsBatch(this.kernelSql)).more;
|
|
846
|
+
// #2339: then re-derive the migration digests of versions stored before the server did.
|
|
847
|
+
// Not in the transaction: the digest is computed between its read and its guarded write.
|
|
848
|
+
more = (await deriveMigrationDigestsBatch(this.kernelSql)).more || more;
|
|
837
849
|
}
|
|
838
850
|
catch (err) {
|
|
839
851
|
const failures = ((await this.ctx.storage.get(BACKFILL_FAILURES_KEY)) ?? 0) + 1;
|
|
@@ -908,6 +920,18 @@ export class ControlPlaneDO extends DurableObject {
|
|
|
908
920
|
* creates each table with its backfill; `holdFences` does the same for #1184's removal fence.
|
|
909
921
|
*/
|
|
910
922
|
buildDirectorySchema({ holdSwitchRecords, holdFences = false }) {
|
|
923
|
+
// A directory built before the store driver rename keeps its rows under the old names (and one
|
|
924
|
+
// that ran pre-rename code again, a rollback, holds both names), and
|
|
925
|
+
// the DDL below would only create empty tables beside them. Probed, not attempted: an empty
|
|
926
|
+
// `SELECT` reads the columns here, and an emptied directory (a restore) has nothing to move.
|
|
927
|
+
const tables = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table'`).toArray().map((t) => t.name);
|
|
928
|
+
const hasRows = (table) => this.sql.exec(`SELECT 1 FROM "${table}" LIMIT 1`).toArray().length > 0;
|
|
929
|
+
// Asked before the moves below, which drop a name with no rows.
|
|
930
|
+
const bothNames = legacyStoreDriverConflict(tables, hasRows);
|
|
931
|
+
if (bothNames)
|
|
932
|
+
console.error(bothNames);
|
|
933
|
+
for (const stmt of legacyStoreDriverRenames(tables, (table) => doBuiltColumnsOf(this.sql, table), hasRows))
|
|
934
|
+
this.sql.exec(stmt);
|
|
911
935
|
const held = holdSwitchRecords.map(switchesTableOf);
|
|
912
936
|
for (const stmt of DIRECTORY_DDL_PLAN.loop) {
|
|
913
937
|
if (held.some((table) => stmt.includes(table)))
|
|
@@ -920,6 +944,8 @@ export class ControlPlaneDO extends DurableObject {
|
|
|
920
944
|
// #1764's, held back by `planDirectoryDdl`: its index names a column added just above.
|
|
921
945
|
for (const stmt of DIRECTORY_DDL_PLAN.afterColumns)
|
|
922
946
|
this.sql.exec(stmt);
|
|
947
|
+
// #2322: a migration's declared moves and discards, on a table created before they were carried.
|
|
948
|
+
this.addColumn('vertical_version_migrations', 'declarations TEXT');
|
|
923
949
|
}
|
|
924
950
|
/**
|
|
925
951
|
* The directory's own migration path (control-plane.md §7). A singleton DO that
|
|
@@ -1137,6 +1163,8 @@ export class ControlPlaneDO extends DurableObject {
|
|
|
1137
1163
|
// #1764: the SQL migrations moved out of the manifest, and the backfill's progress.
|
|
1138
1164
|
this.addColumn('vertical_versions', 'migration_count INTEGER');
|
|
1139
1165
|
this.addColumn('vertical_versions', 'migrations_split INTEGER');
|
|
1166
|
+
// #2339: whether the migration digest is the derived one; NULL rows are backfilled.
|
|
1167
|
+
this.addColumn('vertical_versions', 'migration_digest_derived INTEGER');
|
|
1140
1168
|
// #33: the SKU flag learns to express a plan. All nullable — a legacy row
|
|
1141
1169
|
// reads as a perpetual boolean flag, exactly its pre-widening semantics.
|
|
1142
1170
|
this.addColumn('_substrat_entitlements', 'expires_at TEXT');
|
|
@@ -1259,8 +1287,9 @@ export class ControlPlaneDO extends DurableObject {
|
|
|
1259
1287
|
// next construction arms the backfill, since it finds none set.
|
|
1260
1288
|
try {
|
|
1261
1289
|
await this.ctx.storage.delete(BACKFILL_FAILURES_KEY);
|
|
1262
|
-
if (versionsAwaitSplit(this.kernelSql))
|
|
1290
|
+
if (versionsAwaitSplit(this.kernelSql) || versionsAwaitDigest(this.kernelSql)) {
|
|
1263
1291
|
await this.ctx.storage.setAlarm(Date.now() + BACKFILL_PAUSE_MS);
|
|
1292
|
+
}
|
|
1264
1293
|
}
|
|
1265
1294
|
catch (err) {
|
|
1266
1295
|
console.error('substrat: the restore committed, but its version-migrations backfill was not armed', err);
|
|
@@ -1483,32 +1512,47 @@ export class ControlPlaneDO extends DurableObject {
|
|
|
1483
1512
|
' ORDER BY tenant_id, vertical, binding', ...params)
|
|
1484
1513
|
.toArray();
|
|
1485
1514
|
}
|
|
1486
|
-
// -- store-
|
|
1487
|
-
// Kernel code over the directory (store-
|
|
1515
|
+
// -- store-driver releases, rollouts and holds (D-63, #2300) ---------------------
|
|
1516
|
+
// Kernel code over the directory (store-driver-releases.ts), shared with the SQLite host. Each
|
|
1488
1517
|
// call is one transaction, and the object runs one at a time, so a hold is taken atomically.
|
|
1489
|
-
|
|
1490
|
-
return this.ctx.storage.transactionSync(() =>
|
|
1518
|
+
recordStoreDriverRelease(input) {
|
|
1519
|
+
return this.ctx.storage.transactionSync(() => recordStoreDriverReleaseIn(this.kernelSql, input));
|
|
1520
|
+
}
|
|
1521
|
+
getStoreDriverRelease(driver, sha256) {
|
|
1522
|
+
return storeDriverReleaseIn(this.kernelSql, driver, sha256);
|
|
1523
|
+
}
|
|
1524
|
+
storeDriverState(driver) {
|
|
1525
|
+
return this.ctx.storage.transactionSync(() => storeDriverStateIn(this.kernelSql, driver, Date.now()));
|
|
1526
|
+
}
|
|
1527
|
+
storeDriverDeclarations(driver) {
|
|
1528
|
+
return this.ctx.storage.transactionSync(() => driverDeclarationsOf(this.kernelSql, driver));
|
|
1529
|
+
}
|
|
1530
|
+
storeDriverRolloutPending(input) {
|
|
1531
|
+
return this.ctx.storage.transactionSync(() => storeDriverRolloutPendingIn(this.kernelSql, input));
|
|
1491
1532
|
}
|
|
1492
|
-
|
|
1493
|
-
return
|
|
1533
|
+
storeDriverRolloutWalked(input) {
|
|
1534
|
+
return this.ctx.storage.transactionSync(() => storeDriverRolloutWalkedIn(this.kernelSql, input));
|
|
1494
1535
|
}
|
|
1495
|
-
|
|
1496
|
-
return this.ctx.storage.transactionSync(() =>
|
|
1536
|
+
holdStoreDriver(input) {
|
|
1537
|
+
return this.ctx.storage.transactionSync(() => holdStoreDriverIn(this.kernelSql, { ...input, nowMs: Date.now() }));
|
|
1497
1538
|
}
|
|
1498
|
-
|
|
1499
|
-
|
|
1539
|
+
releaseStoreDriverHold(input) {
|
|
1540
|
+
releaseStoreDriverHoldIn(this.kernelSql, input);
|
|
1500
1541
|
}
|
|
1501
|
-
|
|
1502
|
-
return this.ctx.storage.transactionSync(() =>
|
|
1542
|
+
beginStoreDriverRollout(input) {
|
|
1543
|
+
return this.ctx.storage.transactionSync(() => beginStoreDriverRolloutIn(this.kernelSql, { ...input, nowMs: Date.now() }));
|
|
1503
1544
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1545
|
+
finishStoreDriverRollout(input) {
|
|
1546
|
+
return this.ctx.storage.transactionSync(() => finishStoreDriverRolloutIn(this.kernelSql, { ...input, nowMs: Date.now() }));
|
|
1506
1547
|
}
|
|
1507
|
-
|
|
1508
|
-
return
|
|
1548
|
+
storeDriverRebuild(driver) {
|
|
1549
|
+
return storeDriverRebuildIn(this.kernelSql, driver);
|
|
1509
1550
|
}
|
|
1510
|
-
|
|
1511
|
-
return this.ctx.storage.transactionSync(() =>
|
|
1551
|
+
claimStoreDriverRebuild(input) {
|
|
1552
|
+
return this.ctx.storage.transactionSync(() => claimStoreDriverRebuildIn(this.kernelSql, { ...input, nowMs: Date.now() }));
|
|
1553
|
+
}
|
|
1554
|
+
advanceStoreDriverRebuild(input) {
|
|
1555
|
+
return this.ctx.storage.transactionSync(() => advanceStoreDriverRebuildIn(this.kernelSql, { ...input, nowMs: Date.now() }));
|
|
1512
1556
|
}
|
|
1513
1557
|
// -- scope lifecycle (control-plane.md §4.1/§4.2) ---------------------------
|
|
1514
1558
|
/**
|
|
@@ -2169,10 +2213,17 @@ export class ControlPlaneDO extends DurableObject {
|
|
|
2169
2213
|
const sql = 'SELECT * FROM verticals' + (where.length ? ` WHERE ${where.join(' AND ')}` : '') + tail;
|
|
2170
2214
|
return this.sql.exec(sql, ...params).toArray();
|
|
2171
2215
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2216
|
+
/**
|
|
2217
|
+
* One version. A version stored before the server derived migration digests (#2339) is derived
|
|
2218
|
+
* first, so a gate never compares the digest its push sent while the backfill has not reached it.
|
|
2219
|
+
*/
|
|
2220
|
+
async readVersion(id) {
|
|
2221
|
+
const read = () => this.sql.exec('SELECT * FROM vertical_versions WHERE id = ?', id).toArray()[0];
|
|
2222
|
+
const row = read();
|
|
2223
|
+
if (row?.migration_digest_derived !== null)
|
|
2224
|
+
return row;
|
|
2225
|
+
await deriveVersionMigrationDigest(this.kernelSql, id);
|
|
2226
|
+
return read();
|
|
2176
2227
|
}
|
|
2177
2228
|
/**
|
|
2178
2229
|
* A version and its SQL migrations (#1764), in one transaction. `manifestJson` arrives with
|
|
@@ -2184,10 +2235,10 @@ export class ControlPlaneDO extends DurableObject {
|
|
|
2184
2235
|
this.sql.exec(`INSERT INTO vertical_versions
|
|
2185
2236
|
(id, vertical_slug, version, manifest_digest, permission_digest,
|
|
2186
2237
|
migration_digest, deployment_ref, admission, admission_note, manifest_json,
|
|
2187
|
-
origin_json, migration_count, migrations_split, created_at)
|
|
2188
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)`, v.id, v.verticalSlug, v.version, v.manifestDigest, v.permissionDigest, v.migrationDigest, v.deploymentRef, v.admission, v.admissionNote, v.manifestJson, v.originJson, v.migrations?.length ?? null, v.createdAt);
|
|
2238
|
+
origin_json, migration_count, migrations_split, migration_digest_derived, created_at)
|
|
2239
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 1, ?)`, v.id, v.verticalSlug, v.version, v.manifestDigest, v.permissionDigest, v.migrationDigest, v.deploymentRef, v.admission, v.admissionNote, v.manifestJson, v.originJson, v.migrations?.length ?? null, v.createdAt);
|
|
2189
2240
|
writeVersionMigrations(this.kernelSql, v.id, v.migrations);
|
|
2190
|
-
// #2286: the version's scope-store bindings, refusing one
|
|
2241
|
+
// #2286: the version's scope-store bindings, refusing one moved to another driver, in the insert's transaction.
|
|
2191
2242
|
indexVersionScopeStores(this.kernelSql, v.verticalSlug, v.id, v.manifestJson);
|
|
2192
2243
|
});
|
|
2193
2244
|
}
|
|
@@ -2215,27 +2266,45 @@ export class ControlPlaneDO extends DurableObject {
|
|
|
2215
2266
|
setScopeExpiresAt(scopeId, expiresAt) {
|
|
2216
2267
|
this.sql.exec('UPDATE scopes SET expires_at = ? WHERE scope_id = ?', expiresAt, scopeId);
|
|
2217
2268
|
}
|
|
2218
|
-
listVersions(verticalSlug, page) {
|
|
2269
|
+
async listVersions(verticalSlug, page) {
|
|
2219
2270
|
const where = ['vertical_slug = ?'];
|
|
2220
2271
|
const params = [verticalSlug];
|
|
2221
2272
|
const tail = keysetTail(where, params, 'id', page);
|
|
2222
2273
|
// Named columns, never `manifest_json` itself. `json_valid` first: a manifest that is not
|
|
2223
2274
|
// JSON lists with null surfaces, as `outboundOfManifestJson` reads it, rather than
|
|
2224
2275
|
// failing the whole page on `->`.
|
|
2225
|
-
|
|
2276
|
+
const read = () => this.sql
|
|
2226
2277
|
.exec(`SELECT id, vertical_slug, version, manifest_digest, permission_digest, migration_digest,
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2278
|
+
migration_digest_derived, deployment_ref, admission, admission_note, origin_json, created_at,
|
|
2279
|
+
CASE WHEN json_valid(manifest_json) THEN manifest_json -> '$.outbound' END AS outbound_json,
|
|
2280
|
+
CASE WHEN json_valid(manifest_json) THEN manifest_json -> '$.calls' END AS calls_json
|
|
2281
|
+
FROM vertical_versions WHERE ${where.join(' AND ')}${tail}`, ...params)
|
|
2231
2282
|
.toArray();
|
|
2283
|
+
const rows = read();
|
|
2284
|
+
// #2339: a page holding versions whose digest is still the pushed one derives them first.
|
|
2285
|
+
const underived = rows.filter((r) => r.migration_digest_derived === null);
|
|
2286
|
+
if (underived.length === 0)
|
|
2287
|
+
return rows;
|
|
2288
|
+
for (const r of underived)
|
|
2289
|
+
await deriveVersionMigrationDigest(this.kernelSql, r.id);
|
|
2290
|
+
return read();
|
|
2232
2291
|
}
|
|
2233
2292
|
setAdmission(id, admission, note) {
|
|
2234
2293
|
this.sql.exec('UPDATE vertical_versions SET admission = ?, admission_note = ? WHERE id = ?', admission, note, id);
|
|
2235
2294
|
}
|
|
2236
|
-
/**
|
|
2237
|
-
|
|
2238
|
-
|
|
2295
|
+
/**
|
|
2296
|
+
* False when a conditional bind lost its compare-and-set (the host throws, as above). #2347:
|
|
2297
|
+
* `servingRef` moves the route in the same write, and `expectedServing` holds it to what the
|
|
2298
|
+
* target lineage's serving script runs, read in this same synchronous transaction.
|
|
2299
|
+
*/
|
|
2300
|
+
bindScopeVersion(scopeId, versionId, verticalSlug, expectedVersionId, expectedErasureEpoch, confirmMove, servingRef, expectedServing) {
|
|
2301
|
+
return servingRef === undefined
|
|
2302
|
+
? this.moveScopeRow('vertical_version_id = ?, vertical = ?', [versionId, verticalSlug], scopeId, { expectedVersionId, expectedErasureEpoch, confirmMove, expectedServing })
|
|
2303
|
+
: this.moveScopeRow('vertical_version_id = ?, vertical = ?, serving_ref = ?', [versionId, verticalSlug, servingRef], scopeId, { expectedVersionId, expectedErasureEpoch, confirmMove, expectedServing });
|
|
2304
|
+
}
|
|
2305
|
+
/** #2347: whether a lineage's serving script still runs the version a rebind was judged against. */
|
|
2306
|
+
servesNow(expected) {
|
|
2307
|
+
return this.sql.exec(`SELECT 1 FROM verticals WHERE ${SERVES_NOW_CONDITION}`, ...servesNowParams(expected)).toArray().length > 0;
|
|
2239
2308
|
}
|
|
2240
2309
|
/**
|
|
2241
2310
|
* Move a scope's binding or route (#1722), conditionally: on the binding the caller read, on
|
|
@@ -2249,7 +2318,8 @@ export class ControlPlaneDO extends DurableObject {
|
|
|
2249
2318
|
const moved = this.sql.exec(`UPDATE scopes SET ${set} WHERE scope_id = ?
|
|
2250
2319
|
${cond.expectedVersionId === undefined ? '' : 'AND vertical_version_id IS ?'}
|
|
2251
2320
|
${cond.expectedErasureEpoch === undefined ? '' : 'AND COALESCE(erasure_epoch, 0) = ? AND reap_claimed_at IS NULL'}
|
|
2252
|
-
${cond.confirmMove === undefined ? '' : COPY_MOVE_LIVE_PREDICATE}
|
|
2321
|
+
${cond.confirmMove === undefined ? '' : COPY_MOVE_LIVE_PREDICATE}
|
|
2322
|
+
${cond.expectedServing === undefined ? '' : SERVES_NOW_PREDICATE}`, ...setArgs, scopeId, ...(cond.expectedVersionId === undefined ? [] : [cond.expectedVersionId]), ...(cond.expectedErasureEpoch === undefined ? [] : [cond.expectedErasureEpoch]), ...(cond.confirmMove === undefined ? [] : copyMoveLiveParams(cond.confirmMove, now)), ...(cond.expectedServing === undefined ? [] : servesNowParams(cond.expectedServing)));
|
|
2253
2323
|
if (moved.rowsWritten === 0)
|
|
2254
2324
|
return false;
|
|
2255
2325
|
if (cond.confirmMove) {
|
|
@@ -2423,10 +2493,23 @@ export class ControlPlaneDO extends DurableObject {
|
|
|
2423
2493
|
.exec('SELECT * FROM vertical_channels WHERE vertical_slug = ? AND channel = ?', verticalSlug, channel)
|
|
2424
2494
|
.toArray()[0];
|
|
2425
2495
|
}
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2496
|
+
/**
|
|
2497
|
+
* #2333: move a channel's pointer, and record the move, only if it still names `from` (null: the
|
|
2498
|
+
* channel has none yet). A compare-and-set in one synchronous transaction, so no other promote
|
|
2499
|
+
* runs between the read and the write: a promote judged its gates against `from` across awaits,
|
|
2500
|
+
* and the one that lost the race is told so (`false`) and writes nothing.
|
|
2501
|
+
*/
|
|
2502
|
+
moveChannel(from, history) {
|
|
2503
|
+
return this.ctx.storage.transactionSync(() => {
|
|
2504
|
+
const now = this.readChannel(history.vertical_slug, history.channel);
|
|
2505
|
+
if ((now?.version_id ?? null) !== from)
|
|
2506
|
+
return false;
|
|
2507
|
+
this.sql.exec(`INSERT INTO vertical_channels (vertical_slug, channel, version_id, updated_at)
|
|
2508
|
+
VALUES (?, ?, ?, ?)
|
|
2509
|
+
ON CONFLICT (vertical_slug, channel) DO UPDATE SET version_id = ?, updated_at = ?`, history.vertical_slug, history.channel, history.version_id, history.at, history.version_id, history.at);
|
|
2510
|
+
this.insertChannelHistory(history);
|
|
2511
|
+
return true;
|
|
2512
|
+
});
|
|
2430
2513
|
}
|
|
2431
2514
|
listChannels(verticalSlug, page) {
|
|
2432
2515
|
const where = ['vertical_slug = ?'];
|