@substrat-run/adapter-cloudflare 0.127.0 → 0.129.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 +20 -8
- package/dist/control-plane-do.d.ts.map +1 -1
- package/dist/control-plane-do.js +68 -27
- package/dist/control-plane-do.js.map +1 -1
- package/dist/host.d.ts +15 -4
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +34 -4
- package/dist/host.js.map +1 -1
- package/dist/scope-do.d.ts.map +1 -1
- package/dist/scope-do.js +74 -44
- package/dist/scope-do.js.map +1 -1
- package/dist/sql.d.ts +8 -0
- package/dist/sql.d.ts.map +1 -1
- package/dist/sql.js +11 -0
- package/dist/sql.js.map +1 -1
- package/package.json +6 -6
package/dist/scope-do.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAcL,KAAK,WAAW,EAqCjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAmCL,KAAK,SAAS,EAmBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAoEtB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAcL,KAAK,WAAW,EAqCjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAmCL,KAAK,SAAS,EAmBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAoEtB,MAAM,sBAAsB,CAAC;AAsF9B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAsbD;;;GAGG;AACH,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAgDxG;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,SAAS,CAOxD;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAwDxD;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAAE,EAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,GACxD,KAAK,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,UAAU,KAAK,aAAa,CAq9JjE"}
|
package/dist/scope-do.js
CHANGED
|
@@ -4,10 +4,10 @@ import { ATTACHMENT_ADDED, ATTACHMENT_REMOVED, attachmentRecord, domainEvent, do
|
|
|
4
4
|
import { ulid, DO_SQL_LIMITS, createUlid, assertAllowed, ConnectionSealingKeyUnavailableError, noSealingKeyMessage, sealTo, assertReadOnlyQuery, entitlementDenial, platformRequestHistoryQuery, platformRequestOf, PLATFORM_REQUEST_COLUMNS, PLATFORM_REQUEST_REDACTION_SQL, platformRequestRedactionParams, platformRequestRedactionQuery, intentPayloadCarriesSubject, redactSubjectJobRuns, JOB_RUN_PATCH_SQL, JOB_STEP_RECORD_SQL, DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, seatScopeTuple, effectiveRoleGrantQuery, switchRecordedOff, switchSystemSchedules, peerGrantsStatus, systemGrantsStatus, systemScheduleState, systemSwitchedOff, denialListQuery, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialSummaryBuckets, PermissionDenied, assertImpersonationWrites, assertModuleEnqueueableKind, impersonationStampOf, createAtomic, NotSearchable, isSearchIndexTable, searchIndexDdl, searchIndexPlans, NotListable, listIndexPlans, moduleMigrations, listQuery, cursorOf, searchLimit, searchMatchExpression, searchQuery, IDEMPOTENCY_DDL, assertIdempotencyKey, assertPermissionKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, entityVersionQuery, entityVersionOf, assertIfMatch, OUTBOX_ENTITY_INDEX, SCHEDULE_STATE_DDL, SCHEDULE_STATE_REBUILD, scheduleStateHasKind, VERTICAL_EVENTS_DDL, EXPORT_HOPS_SQL, IMPORT_CURSORS_SQL, IMPORT_CURSOR_OF_SQL, OUTBOX_MARK_SQL, exportedSinceQuery, emittedSinceQuery, emittedReportOf, EMITTED_REPORT_CAP, IMPORT_CURSOR_ADVANCE_SQL, IMPORT_RECORD_SQL, moveImportCursor, CrossVerticalRegistry, exportReadPlan, exportReadQuery, planExportBatch, withheldNote, JOB_RUN_DDL, jobRunListLimit, } from '@substrat-run/kernel';
|
|
5
5
|
import { isUpgradeRequest, readSubscription, LIVE_FANOUT_LIMIT, LIVE_MODE_HEADER, LIVE_PRINCIPAL_HEADER, LIVE_SCOPE_HEADER, LIVE_SUBSCRIBE_PATH, LIVE_TENANT_HEADER, } from './live-reads.js';
|
|
6
6
|
import { OperationQueue } from './serialization.js';
|
|
7
|
-
import { doScopedSql, doSpineSql } from './sql.js';
|
|
8
|
-
import { actorOf, admitPeer, collectPeers, switchPeer, assertNoSecret, CAPABILITY_DDL, CAPABILITY_EXCHANGE_OPERATION, capabilityAttachmentWriteRefused, createCapabilityVerbs, createEntityEdgeVerbs, exchangeCapability, guardSecrets, mintBecomeCapability, redactSecrets, redactSecretText, moduleLog, resolveCapabilitySession, revokeCapabilityAsPlatform, domainEventOf, facetEvents, readDeadLetters, readHistory, readInvocation, readUndrainedOutbox, walkEventCause, walkEventEffects, } from '@substrat-run/kernel';
|
|
7
|
+
import { doScopedSql, doSpineColumnsOf, doSpineSql } from './sql.js';
|
|
8
|
+
import { actorOf, admitPeer, collectPeers, switchPeer, assertNoSecret, CAPABILITY_DDL, CAPABILITY_EXCHANGE_OPERATION, capabilityAttachmentWriteRefused, createCapabilityVerbs, createEntityEdgeVerbs, exchangeCapability, guardSecrets, mintBecomeCapability, redactSecrets, redactSecretText, moduleLog, resolveCapabilitySession, revokeCapabilityAsPlatform, domainEventOf, facetEvents, readDeadLetters, readLifecycleFlow, readHistory, readInvocation, readUndrainedOutbox, walkEventCause, walkEventEffects, } from '@substrat-run/kernel';
|
|
9
9
|
import { createDoTupleChecker, createLocalControlPlaneReader } from './checker.js';
|
|
10
|
-
import { repointScopeGrants } from '@substrat-run/kernel';
|
|
10
|
+
import { assertNoSpineReference, assertSpineTablesBuilt, dumpRowsInsert, isSpineTable, repointScopeGrants, spineColumnAdditions } from '@substrat-run/kernel';
|
|
11
11
|
/**
|
|
12
12
|
* The key marking a scope DO whose storage was destroyed (`destroyStorage`).
|
|
13
13
|
* Written after `deleteAll()` so it survives the wipe, and never cleared: `reaped`
|
|
@@ -1190,6 +1190,10 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
1190
1190
|
deadLetters(input) {
|
|
1191
1191
|
return readDeadLetters({ sql: doScopedSql(this.sql) }, { limit: input.limit, cursor: input.cursor });
|
|
1192
1192
|
}
|
|
1193
|
+
/** #1744: one entity's lifecycle replayed over this scope's outbox, where it lives. */
|
|
1194
|
+
lifecycleFlow(input) {
|
|
1195
|
+
return readLifecycleFlow({ sql: doScopedSql(this.sql) }, input);
|
|
1196
|
+
}
|
|
1193
1197
|
migrationBookmarks(limit = 20) {
|
|
1194
1198
|
return this.sql
|
|
1195
1199
|
.exec(`SELECT bookmark, taken_at, pending FROM _substrat_migration_bookmarks
|
|
@@ -1337,6 +1341,13 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
1337
1341
|
`the attachments of the entity its event names, and no others`);
|
|
1338
1342
|
}
|
|
1339
1343
|
}
|
|
1344
|
+
/** Does `subject` hold a role this scope can expand (#1665)? The #1659 lockout predicate
|
|
1345
|
+
* narrowed to one holder: a live role tuple, scope or tenant level, for a current role. */
|
|
1346
|
+
async hasEffectiveRoleGrantFor(tenantId, subject) {
|
|
1347
|
+
const q = effectiveRoleGrantQuery(tenantId, new Date().toISOString(), subject);
|
|
1348
|
+
const row = this.sql.exec(q.sql, ...q.params).toArray()[0];
|
|
1349
|
+
return row?.effective === 1;
|
|
1350
|
+
}
|
|
1340
1351
|
/** Tombstone a scope tuple (K-21) — the row stays, the walk skips it. Returns
|
|
1341
1352
|
* whether anything changed so a repeat revoke is a silent no-op. */
|
|
1342
1353
|
async revokeTuple(subject, relation, object, at) {
|
|
@@ -2917,6 +2928,9 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
2917
2928
|
.exec('SELECT 1 FROM _substrat_migrations WHERE module_id = ? AND version = ?', moduleId, migration.version)
|
|
2918
2929
|
.toArray()[0];
|
|
2919
2930
|
if (!already) {
|
|
2931
|
+
// #1898: a migration runs on this DO's own handle, not `ctx.sql`, so the
|
|
2932
|
+
// spine guard's REFERENCES rule is applied here.
|
|
2933
|
+
assertNoSpineReference(migration.sql, `migration ${key}`);
|
|
2920
2934
|
for (const stmt of splitSqlStatements(migration.sql)) {
|
|
2921
2935
|
this.sql.exec(stmt);
|
|
2922
2936
|
}
|
|
@@ -2929,6 +2943,10 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
2929
2943
|
// re-parsing the thrown message. The throw stays: `invoke` awaits
|
|
2930
2944
|
// `ensureMigrations` on every operation and relies on the rejection to
|
|
2931
2945
|
// fail closed, so resolving here would serve a half-migrated schema.
|
|
2946
|
+
// workerd logs this throw as `Uncaught (in promise)` when it leaves an RPC method,
|
|
2947
|
+
// as it does every exception an RPC call returns (a dump refusal, a denied check).
|
|
2948
|
+
// It is not an unhandled rejection: every caller awaits the memoised promise, and
|
|
2949
|
+
// the coordinator records the failure it receives (#1898 review).
|
|
2932
2950
|
this.lastFailure = { version: key, error: err.message };
|
|
2933
2951
|
throw new Error(`migration failed for ${key} — scope fails closed: ${err.message}`);
|
|
2934
2952
|
}
|
|
@@ -3286,7 +3304,7 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3286
3304
|
// carries the rows, never the index over them.
|
|
3287
3305
|
const defs = this.sql
|
|
3288
3306
|
.exec(`SELECT name, sql FROM sqlite_master
|
|
3289
|
-
WHERE type = 'table' AND name NOT
|
|
3307
|
+
WHERE type = 'table' AND name NOT GLOB 'sqlite_*' AND sql IS NOT NULL
|
|
3290
3308
|
ORDER BY name`)
|
|
3291
3309
|
.toArray().filter(({ name }) => !isSearchIndexTable(name));
|
|
3292
3310
|
return defs.map(({ name, sql }) => {
|
|
@@ -3300,8 +3318,9 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3300
3318
|
}
|
|
3301
3319
|
/**
|
|
3302
3320
|
* Load a dump into this (freshly-provisioned) scope — the write half of the fork
|
|
3303
|
-
* (host.ts importScope). Drop-then-replay: the
|
|
3304
|
-
*
|
|
3321
|
+
* (host.ts importScope). Drop-then-replay: the provisioning schema is wiped and rebuilt,
|
|
3322
|
+
* a vertical's tables from the dump's DDL and the `_substrat_*` spine from KERNEL_DDL
|
|
3323
|
+
* (#1883), then the dump's rows go back in.
|
|
3305
3324
|
*
|
|
3306
3325
|
* `destScopeId` is the scope being written INTO. A dump carries scope-level tuples
|
|
3307
3326
|
* naming the scope it was captured from, so restoring one anywhere else needs them
|
|
@@ -3312,14 +3331,18 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3312
3331
|
*/
|
|
3313
3332
|
/**
|
|
3314
3333
|
* The additive spine-column migrations. KERNEL_DDL is all IF NOT EXISTS, so a
|
|
3315
|
-
* scope DO created before a column keeps the old shape
|
|
3316
|
-
*
|
|
3317
|
-
*
|
|
3318
|
-
*
|
|
3334
|
+
* scope DO created before a column keeps the old shape until this runs on its next
|
|
3335
|
+
* wake. `importDump` runs it too, after rebuilding the spine from KERNEL_DDL: there every
|
|
3336
|
+
* ALTER is a duplicate, since a restore never brings a legacy spine table (#1883), but the
|
|
3337
|
+
* pass also creates `_substrat_outbox_invocation`, which KERNEL_DDL deliberately does not
|
|
3338
|
+
* and the restore's DROP of the outbox took with it. Attempt-and-tolerate: DO SQLite restricts PRAGMA, so there
|
|
3319
3339
|
* is no column probe, and a duplicate is the steady state after the first cold
|
|
3320
3340
|
* start (same argument as ControlPlaneDO.addColumn).
|
|
3321
3341
|
*/
|
|
3322
3342
|
applySpineColumnAdditions() {
|
|
3343
|
+
// Every column here is nullable with no DEFAULT (`attempts` is grandfathered): a restore may
|
|
3344
|
+
// have added it bare already, and then this ALTER is skipped as a duplicate (#1883).
|
|
3345
|
+
// `lint:spine-ddl` refuses one that is not.
|
|
3323
3346
|
for (const alter of [
|
|
3324
3347
|
'ALTER TABLE _substrat_tuples ADD COLUMN revoked_at TEXT',
|
|
3325
3348
|
// Executor retry state (#100). The defaults read as "terminal", which is
|
|
@@ -3440,7 +3463,8 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3440
3463
|
// cycles, and self-referencing rows within a single table.
|
|
3441
3464
|
// A dump written before indexes were excluded may still carry them; skipped
|
|
3442
3465
|
// rather than failing a restore over data about to be recomputed.
|
|
3443
|
-
|
|
3466
|
+
// Matched without case, as SQLite resolves a table name (#1883 review).
|
|
3467
|
+
const replayable = tables.filter((t) => !isSearchIndexTable(t.name.toLowerCase()));
|
|
3444
3468
|
// The dump is untrusted input (#1143). `SqlStorage.exec` runs every statement
|
|
3445
3469
|
// in the string it is given, so a `ddl` with anything appended to its CREATE
|
|
3446
3470
|
// TABLE executed that too — with entirely plain identifiers, which is why no
|
|
@@ -3456,53 +3480,59 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3456
3480
|
this.sql.exec('PRAGMA defer_foreign_keys = ON');
|
|
3457
3481
|
// Real tables only; `sqlite_*` internals are auto-managed and un-droppable.
|
|
3458
3482
|
const existing = this.sql
|
|
3459
|
-
.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT
|
|
3483
|
+
.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT GLOB 'sqlite_*'`)
|
|
3460
3484
|
.toArray();
|
|
3461
3485
|
// Search index tables are left alone here and rebuilt below (#827): dropping a
|
|
3462
3486
|
// shadow table directly is an error, and `sqlite_master` order would reach one
|
|
3463
3487
|
// before its virtual table.
|
|
3464
3488
|
for (const { name } of existing) {
|
|
3465
|
-
if (isSearchIndexTable(name))
|
|
3489
|
+
if (isSearchIndexTable(name.toLowerCase()))
|
|
3466
3490
|
continue;
|
|
3467
3491
|
this.sql.exec(`DROP TABLE IF EXISTS "${name}"`);
|
|
3468
3492
|
}
|
|
3493
|
+
// A vertical's tables take the dump's own DDL. The spine never does (#1883): a dump
|
|
3494
|
+
// that declared a column the checker compares on as COLLATE NOCASE would otherwise
|
|
3495
|
+
// decide how this DO's permission checks match. Every `_substrat_*` table is built
|
|
3496
|
+
// from KERNEL_DDL instead, and the dump contributes only rows, by column name
|
|
3497
|
+
// (`spineRowsInsert`), a missing column taking the kernel's default.
|
|
3469
3498
|
for (const t of replayable)
|
|
3470
|
-
|
|
3499
|
+
if (!isSpineTable(t.name))
|
|
3500
|
+
this.sql.exec(t.ddl);
|
|
3501
|
+
// KERNEL_DDL also builds what the dump did not carry (#321). A dump captured from a
|
|
3502
|
+
// WORLD that stores some `_substrat_*` tables ELSEWHERE carries only a subset — an
|
|
3503
|
+
// `@substrat-run/adapter-sqlite` scope file keeps `_substrat_roles` /
|
|
3504
|
+
// `_substrat_tenant_tuples` in its DIRECTORY database, so its per-scope dump omits
|
|
3505
|
+
// them, and without them the very next permission check would raise a bare `no such
|
|
3506
|
+
// table: _substrat_roles`. Roles land empty here and are re-projected by the
|
|
3507
|
+
// restore's repair leg (host.projectRolesLocal) — the spine's job is only to exist so
|
|
3508
|
+
// the checker can read it. The column pass follows, for the one outbox index KERNEL_DDL
|
|
3509
|
+
// leaves to it.
|
|
3510
|
+
for (const stmt of splitSqlStatements(KERNEL_DDL))
|
|
3511
|
+
this.sql.exec(stmt);
|
|
3512
|
+
this.applySpineColumnAdditions();
|
|
3513
|
+
const columnsOf = (name) => doSpineColumnsOf(this.sql, name);
|
|
3514
|
+
assertSpineTablesBuilt(replayable.map((t) => t.name), columnsOf);
|
|
3515
|
+
// A spine column this kernel does not know (a dump from a newer one) is kept, as a plain
|
|
3516
|
+
// untyped column the checker never reads.
|
|
3471
3517
|
for (const t of replayable) {
|
|
3472
|
-
if (t.
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3518
|
+
if (isSpineTable(t.name))
|
|
3519
|
+
for (const alter of spineColumnAdditions(t, columnsOf(t.name)))
|
|
3520
|
+
this.sql.exec(alter);
|
|
3521
|
+
}
|
|
3522
|
+
for (const t of replayable) {
|
|
3523
|
+
const insert = dumpRowsInsert(t, columnsOf);
|
|
3477
3524
|
for (const row of t.rows)
|
|
3478
3525
|
this.sql.exec(insert, ...row);
|
|
3479
3526
|
}
|
|
3480
|
-
// Re-assert the kernel spine (#321). A dump captured from a WORLD that stores some
|
|
3481
|
-
// `_substrat_*` tables ELSEWHERE carries only a subset — an `@substrat-run/adapter-
|
|
3482
|
-
// sqlite` scope file, for instance, keeps `_substrat_roles`/`_substrat_tenant_tuples`
|
|
3483
|
-
// in its DIRECTORY database, so its per-scope dump omits them. Replaying such a dump
|
|
3484
|
-
// verbatim would leave this DO missing those spine tables, and the very next
|
|
3485
|
-
// permission check would raise a bare `no such table: _substrat_roles`. KERNEL_DDL is
|
|
3486
|
-
// all IF NOT EXISTS, so this recreates only what the dump did not carry (empty), and
|
|
3487
|
-
// never disturbs a table the dump DID bring. Roles land empty here and are re-projected
|
|
3488
|
-
// by the restore's repair leg (host.projectRolesLocal) — the spine's job is only to
|
|
3489
|
-
// exist so the checker can read it.
|
|
3490
|
-
for (const stmt of splitSqlStatements(KERNEL_DDL))
|
|
3491
|
-
this.sql.exec(stmt);
|
|
3492
|
-
// …and the additive columns, for the same reason KERNEL_DDL is re-asserted: a
|
|
3493
|
-
// dump captured before a column existed replays DDL WITHOUT it, and IF NOT
|
|
3494
|
-
// EXISTS cannot widen a table the dump did bring. Without this, the next emit
|
|
3495
|
-
// in this instance fails with `no such column` until a cold start re-runs the
|
|
3496
|
-
// constructor's pass.
|
|
3497
|
-
this.applySpineColumnAdditions();
|
|
3498
3527
|
// #1738: a dump's `provisioned_for` names the scope (and tenant) it was captured from,
|
|
3499
3528
|
// not this one. Dropped, so a restore never carries a receipt over; the repair
|
|
3500
3529
|
// projection that follows writes this scope's own.
|
|
3501
3530
|
this.sql.exec(`DELETE FROM _substrat_meta WHERE key = 'provisioned_for'`);
|
|
3502
3531
|
// Re-point the restored grants at THIS scope (after the spine exists, so a dump
|
|
3503
3532
|
// that carried no tuples table still finds one here).
|
|
3504
|
-
if (destScopeId)
|
|
3505
|
-
this.rewriteScopeTuples(destScopeId, sourceScopeId && { scopeId: sourceScopeId, exact });
|
|
3533
|
+
if (destScopeId) {
|
|
3534
|
+
this.rewriteScopeTuples(destScopeId, sourceScopeId && { scopeId: sourceScopeId, exact }, new Date().toISOString());
|
|
3535
|
+
}
|
|
3506
3536
|
// #1742: the recorded-off modules go back off INSIDE the replay's transaction, with
|
|
3507
3537
|
// the spine and the re-point. A switch that throws rolls the whole restore back, so
|
|
3508
3538
|
// the dump's grants never commit live without the switch that should cover them.
|
|
@@ -3574,12 +3604,12 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3574
3604
|
*
|
|
3575
3605
|
* Which rows move is `repointScopeGrants`'s (#1869): the dump's source scope, exactly.
|
|
3576
3606
|
*
|
|
3577
|
-
*
|
|
3578
|
-
*
|
|
3579
|
-
*
|
|
3607
|
+
* (subject, relation, object) is the primary key, so a moved row can meet one the dump
|
|
3608
|
+
* already holds for the destination scope. Which survives is that function's rule too
|
|
3609
|
+
* (#1882), with expiry judged at `now`, as this DO's checker judges it.
|
|
3580
3610
|
*/
|
|
3581
|
-
rewriteScopeTuples(destScopeId, source) {
|
|
3582
|
-
repointScopeGrants(this.switchSql(), destScopeId, source);
|
|
3611
|
+
rewriteScopeTuples(destScopeId, source, now) {
|
|
3612
|
+
repointScopeGrants(this.switchSql(), destScopeId, source, now);
|
|
3583
3613
|
}
|
|
3584
3614
|
/**
|
|
3585
3615
|
* Wipe this scope's storage — the reap half of deleteSnapshot (preview-and-
|