@substrat-run/adapter-cloudflare 0.155.0 → 0.158.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/index.d.ts CHANGED
@@ -23,6 +23,7 @@ export { createDoTupleChecker } from './checker.js';
23
23
  export { definePlatformSweeperDO, PLATFORM_SWEEPER_NAME } from './platform-sweeper-do.js';
24
24
  export { defineKickCoalescerDO, kickCoalescerName, CROSS_VERTICAL_KICK_WINDOW_MS, type KickCoalescerConfig, type KickCoalescerDo, type KickOutcome, type KickProducer, } from './kick-coalescer-do.js';
25
25
  export type { PlatformSweeperDoConfig, PlatformSweepOutcome } from './platform-sweeper-do.js';
26
+ export { defineCopyJobDO, type CopyJobDo, type CopyJobDoConfig, type CopyJobDoRecord, type CopyJobStep } from './copy-job-do.js';
26
27
  export { defineScopeSweeperDO, SCOPE_SWEEPER_NAME } from './scope-sweeper-do.js';
27
28
  export type { ScopeSweeperDo, ScopeSweeperDoConfig, ScopeSweepHost, ScopeSweepOutcome, ScopeSweepReport, } from './scope-sweeper-do.js';
28
29
  export { createD1TenantStores, d1TenantRelationalStore, tenantStoreDatabaseName, } from './d1.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,YAAY,EACV,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAE1F,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,6BAA6B,EAC7B,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAC9F,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACjF,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7E,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EACL,oCAAoC,EACpC,gCAAgC,EAChC,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,YAAY,EACV,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAE1F,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,6BAA6B,EAC7B,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAE9F,OAAO,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACjI,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACjF,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7E,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EACL,oCAAoC,EACpC,gCAAgC,EAChC,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -22,6 +22,8 @@ export { createDoTupleChecker } from './checker.js';
22
22
  export { definePlatformSweeperDO, PLATFORM_SWEEPER_NAME } from './platform-sweeper-do.js';
23
23
  // #1705 PR 2: the global bound on the router's cross-vertical kick, one object per producer.
24
24
  export { defineKickCoalescerDO, kickCoalescerName, CROSS_VERTICAL_KICK_WINDOW_MS, } from './kick-coalescer-do.js';
25
+ // #2450 step 2: one object per background scope copy, stepped by alarm.
26
+ export { defineCopyJobDO } from './copy-job-do.js';
25
27
  export { defineScopeSweeperDO, SCOPE_SWEEPER_NAME } from './scope-sweeper-do.js';
26
28
  export { createD1TenantStores, d1TenantRelationalStore, tenantStoreDatabaseName, } from './d1.js';
27
29
  export { blobStoreBucketName, createR2BlobStores, r2TenantBlobStore, } from './r2.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAYhD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAC1F,6FAA6F;AAC7F,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,6BAA6B,GAK9B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAQjF,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE7E,0EAA0E;AAC1E,OAAO,EACL,oCAAoC,EACpC,gCAAgC,EAChC,sBAAsB,GACvB,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAYhD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAC1F,6FAA6F;AAC7F,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,6BAA6B,GAK9B,MAAM,wBAAwB,CAAC;AAEhC,wEAAwE;AACxE,OAAO,EAAE,eAAe,EAAgF,MAAM,kBAAkB,CAAC;AACjI,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAQjF,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE7E,0EAA0E;AAC1E,OAAO,EACL,oCAAoC,EACpC,gCAAgC,EAChC,sBAAsB,GACvB,MAAM,sBAAsB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAgEnD,OAAO,EAwDL,KAAK,mBAAmB,EASxB,KAAK,SAAS,EAsBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAkHtB,MAAM,sBAAsB,CAAC;AAoD9B,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAwBL,kBAAkB,EAgFnB,MAAM,sBAAsB,CAAC;AA2B9B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAoFD,oFAAoF;AACpF,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAmrB5E;;;GAGG;AACH,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;AAmBtD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,SAAS,CAOxD;AAED,gHAAgH;AAChH,OAAO,EAAE,kBAAkB,EAAE,CAAC;AA2B9B,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAAE,EAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AACzD;;;;GAIG;AACH,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,mBAAmB,CAAA;CAAO,GAClD,KAAK,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,UAAU,KAAK,aAAa,CAAC,UAAU,CAAC,CAqoP7E"}
1
+ {"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAgEnD,OAAO,EAwDL,KAAK,mBAAmB,EASxB,KAAK,SAAS,EAsBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAkHtB,MAAM,sBAAsB,CAAC;AAoD9B,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAwBL,kBAAkB,EAgFnB,MAAM,sBAAsB,CAAC;AA2B9B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAoFD,oFAAoF;AACpF,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AA2rB5E;;;GAGG;AACH,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;AAmBtD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,SAAS,CAOxD;AAED,gHAAgH;AAChH,OAAO,EAAE,kBAAkB,EAAE,CAAC;AA2B9B,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAAE,EAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AACzD;;;;GAIG;AACH,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,mBAAmB,CAAA;CAAO,GAClD,KAAK,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,UAAU,KAAK,aAAa,CAAC,UAAU,CAAC,CAwpP7E"}
package/dist/scope-do.js CHANGED
@@ -11,7 +11,7 @@ import { actorOf, admitPeer, boundToPeerDeclarations, collectPeers, switchPeer,
11
11
  import { COPY_RESTORE_FENCE_LAPSED, copyRestoreFenceLapsed, orphanSweepVersionRefusal } from '@substrat-run/kernel';
12
12
  import { createDoTupleChecker, createLocalControlPlaneReader, scopeTupleReader } from './checker.js';
13
13
  import { settlePlatformRequestIn } from '@substrat-run/kernel';
14
- import { loadDropsRowsOf } from '@substrat-run/kernel';
14
+ import { loadDropsRowsOf, SCOPE_COPY_LEASE_MS, ulidTime } from '@substrat-run/kernel';
15
15
  import { CARRIED_AWAY_KEY, COPY_MARK_CLEARED_KEY, KEPT_COPY_REFUSAL, KEPT_DIVERGENT_KEY, LOAD_STAMP_KEY, STORE_LOCAL_META_KEYS, WRITE_REVISION_KEY, carriedAwayDump, isCopyMarkInsert, isWriteStatement, assertSpineTablesBuilt, clearCopyMarker, dumpRowsInsert, isSpineTable, markCopyOrigin, repointScopeGrants, settleCopiedWork, emittedHere, IS_COPY_SQL, isCopyLoad, isLifecycleWrite, readLifecycle, settleLifecycleAfterLoad, writeLifecycle, spineColumnAdditions } from '@substrat-run/kernel';
16
16
  import { GRANT_CHILDREN_INDEX_DDL, SHAPE_MARKER_INDEX_DDL, grantedEntitiesForContext } from '@substrat-run/kernel';
17
17
  /** A scope's migration journal: `module@version` → the SQL digest it recorded (#2066). */
@@ -268,6 +268,7 @@ const KERNEL_DDL = `
268
268
  role_key TEXT NOT NULL,
269
269
  permissions TEXT NOT NULL,
270
270
  source TEXT NOT NULL,
271
+ holder TEXT,
271
272
  -- A removed role tombstones rather than deletes — a tombstoned role reads as
272
273
  -- absent (grants nothing) but stays as evidence.
273
274
  revoked_at TEXT,
@@ -418,6 +419,13 @@ function kernelEmit(ctx, event) {
418
419
  }
419
420
  /** #2450: a commit or a page naming a stage this store no longer holds. */
420
421
  const STAGE_GONE = 'the staged load is not here any more (another carry replaced it, or it was already loaded); nothing was loaded';
422
+ /** A competing page zero must leave the stage a live copy owns intact. */
423
+ const STAGE_IN_PROGRESS = 'a different staged load is in progress; retry after its copy finishes';
424
+ // Failed, cancelled and superseded jobs normally discard their stages. This is only a backstop
425
+ // for a lost job record: six hours of copying plus one 30-minute lease while done and a possible
426
+ // two-lease finish claim. At the default lease, the stage is reclaimable after 7.5 hours, when
427
+ // no job can still load it.
428
+ const STAGE_MAX_AGE_MS = 6 * 3_600_000 + 3 * SCOPE_COPY_LEASE_MS;
421
429
  /**
422
430
  * A staged load's own tables (#2450): `_substrat_stage` (which load, and the next page it takes),
423
431
  * `_substrat_stage_tables` (each staged table's name, DDL and columns) and one
@@ -5239,6 +5247,8 @@ options = {}) {
5239
5247
  // #2066: the rows already there get the legacy mark below, never a digest.
5240
5248
  'ALTER TABLE _substrat_migrations ADD COLUMN sql_digest TEXT',
5241
5249
  'ALTER TABLE _substrat_tuples ADD COLUMN revoked_at TEXT',
5250
+ // #2444: old projected rows keep NULL, retaining the old role-door decision.
5251
+ 'ALTER TABLE _substrat_roles ADD COLUMN holder TEXT',
5242
5252
  // #2126: NULL keeps every existing capability's attachment behavior unchanged.
5243
5253
  'ALTER TABLE _substrat_capabilities ADD COLUMN attachments TEXT',
5244
5254
  // #1686: NULL on every row already there — none is a principal-minted become.
@@ -5664,8 +5674,9 @@ options = {}) {
5664
5674
  * Stage one page of a dump for a later `commitStagedDump` (#2450). The rows go into this
5665
5675
  * store's own staging tables, untyped, so every cell is kept as it arrived; nothing of the
5666
5676
  * scope's data is touched, and the write revision does not move, so the marker a carry read
5667
- * here before staging still holds at the commit. Page `0` of a new stage replaces any
5668
- * other; the following pages must arrive in order. A page already staged (page `0` included)
5677
+ * here before staging still holds at the commit. Page `0` of a different stage is refused
5678
+ * while the current stage can still be loaded; an expired stage can be replaced. The
5679
+ * following pages must arrive in order. A page already staged (page `0` included)
5669
5680
  * answers `staged: false` and changes nothing, so a retried page is harmless.
5670
5681
  */
5671
5682
  stageDumpPage(stageId, seq, tables) {
@@ -5680,8 +5691,22 @@ options = {}) {
5680
5691
  this.revisionSuspended = true;
5681
5692
  try {
5682
5693
  return this.revision.transactionSync(() => {
5694
+ const held = this.stageHead();
5683
5695
  // A page 0 that already opened this stage is a retry, answered below as one.
5684
- if (seq === 0 && this.stageHead()?.stageId !== stageId) {
5696
+ if (seq === 0 && held?.stageId !== stageId) {
5697
+ if (held) {
5698
+ // beginTransfer mints stage IDs as ULIDs. A stage from an older caller with a
5699
+ // non-ULID ID cannot prove it has expired, so only its owner may discard it.
5700
+ let openedAt;
5701
+ try {
5702
+ openedAt = ulidTime(held.stageId);
5703
+ }
5704
+ catch {
5705
+ throw substratError('conflict', STAGE_IN_PROGRESS);
5706
+ }
5707
+ if (Date.now() - openedAt <= STAGE_MAX_AGE_MS)
5708
+ throw substratError('conflict', STAGE_IN_PROGRESS);
5709
+ }
5685
5710
  this.dropStage();
5686
5711
  for (const ddl of STAGE_TABLES_DDL)
5687
5712
  this.sql.exec(ddl);
@@ -5756,16 +5781,18 @@ options = {}) {
5756
5781
  }
5757
5782
  /** Drop a staged load (#2450), if it is still `stageId`'s. A carry that gave up calls it. */
5758
5783
  discardStagedDump(stageId) {
5759
- if (this.stageHead()?.stageId !== stageId)
5760
- return { discarded: false };
5761
5784
  this.revisionSuspended = true;
5762
5785
  try {
5763
- this.revision.transactionSync(() => this.dropStage());
5786
+ return this.revision.transactionSync(() => {
5787
+ if (this.stageHead()?.stageId !== stageId)
5788
+ return { discarded: false };
5789
+ this.dropStage();
5790
+ return { discarded: true };
5791
+ });
5764
5792
  }
5765
5793
  finally {
5766
5794
  this.revisionSuspended = false;
5767
5795
  }
5768
- return { discarded: true };
5769
5796
  }
5770
5797
  /** One staged table's rows, in the order they were staged, a batch at a time. */
5771
5798
  *stagedRows(ord) {
@@ -6814,8 +6841,8 @@ options = {}) {
6814
6841
  /** Project (upsert) a role definition into this scope. */
6815
6842
  async projectRole(tenantId, role) {
6816
6843
  await this.queue.enqueue(() => {
6817
- this.sql.exec(`INSERT OR REPLACE INTO _substrat_roles (tenant_id, role_key, permissions, source, revoked_at)
6818
- VALUES (?, ?, ?, ?, NULL)`, tenantId, role.key, JSON.stringify(role.permissions), role.source);
6844
+ this.sql.exec(`INSERT OR REPLACE INTO _substrat_roles (tenant_id, role_key, permissions, source, holder, revoked_at)
6845
+ VALUES (?, ?, ?, ?, ?, NULL)`, tenantId, role.key, JSON.stringify(role.permissions), role.source, role.holder ?? null);
6819
6846
  });
6820
6847
  }
6821
6848
  /** Tombstone a projected role — it stops granting but stays as evidence (K-21). */
@@ -6915,8 +6942,8 @@ options = {}) {
6915
6942
  this.sql.exec(`INSERT OR REPLACE INTO _substrat_meta (key, value) VALUES (?, ?)`, PROVISIONED_FOR_KEY, tenantId);
6916
6943
  this.sql.exec(`DELETE FROM _substrat_roles WHERE tenant_id = ?`, tenantId);
6917
6944
  for (const r of roles) {
6918
- this.sql.exec(`INSERT OR REPLACE INTO _substrat_roles (tenant_id, role_key, permissions, source, revoked_at)
6919
- VALUES (?, ?, ?, ?, NULL)`, tenantId, r.role_key, r.permissions, r.source);
6945
+ this.sql.exec(`INSERT OR REPLACE INTO _substrat_roles (tenant_id, role_key, permissions, source, holder, revoked_at)
6946
+ VALUES (?, ?, ?, ?, ?, NULL)`, tenantId, r.role_key, r.permissions, r.source, r.holder === 'system' ? 'system' : r.holder == null ? null : 'person');
6920
6947
  }
6921
6948
  this.sql.exec(`DELETE FROM _substrat_tenant_tuples WHERE tenant_id = ?`, tenantId);
6922
6949
  for (const t of tuples) {