@substrat-run/adapter-cloudflare 0.175.0 → 0.176.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.
@@ -1 +1 @@
1
- {"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"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"}
package/dist/scope-do.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { REWIND_REFUSED } from './rewind-refusal.js';
2
2
  import { SYSTEM_DOOR_MOVED } from './system-door.js';
3
3
  import { DurableObject } from 'cloudflare:workers';
4
- import { ATTACHMENT_ADDED, ATTACHMENT_REMOVED, CAPTURE_REFUSALS, attachmentRecord, domainEvent, domainEventInput, eventId, instant, objectRef, entityObjectRef, assertModuleEmittableType, assertKernelAuthoredType, toWireFailure, grantRefFromProof, principalId, principalBecomeCapabilityInput, scopeId as scopeIdOf, tenantId as tenantIdOf, platformRequestInput, platformRequestId, MAX_PENDING_PLATFORM_REQUESTS, MAX_PENDING_SWEEP_RUNS, SWEEP_RUNS_KIND, SCOPE_DUMP_PAGE_MAX_BYTES, SCOPE_TABLE_PAGE_MAX, SCOPE_QUERY_ROW_MAX, listLimitOf, pageOf, countedPageOf, requestFingerprint, substratError, errorCodeOf, assertReplayableDump, REDRAIN_BATCH, } from '@substrat-run/contracts';
5
- import { ulid, DO_SQL_LIMITS, unknownRoleError, createUlid, assertAllowed, ConnectionSealingKeyUnavailableError, noSealingKeyMessage, sealTo, assertReadOnlyQuery, entitlementDenial, platformRequestHistoryQuery, platformRequestOf, PLATFORM_REQUEST_COLUMNS, PLATFORM_REQUEST_REDACTION_SQL, platformRequestRedactionParams, platformRequestRedactionQuery, intentPayloadCarriesSubject, redactSubjectJobRuns, eraseSubjectFromModules, moduleErasurePlan, redactSubjectScopeText, assertRowLimit, assertRowOffset, JOB_RUN_PATCH_SQL, JOB_RUN_CLAIM_SQL, JOB_RUN_RENEW_SQL, JOB_RUN_BEGIN_SQL, JOB_RUN_MISS_SQL, JOB_RUN_MISS_SETTLE_SQL, admissionMissOutcome, JOB_LEASE_EXPIRED_NOTE, JOB_STEP_RECORD_SQL, DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, seatScopeTuple, delegatedRevokeSql, writeExplicitTupleIn, grantEntityShapeIn, topUpEntityGrantShapes, applyScopeRoleChange, changeScopeRole, revokeScopeRoles, scopeRoleHolders, assertModuleDeclared, noteDeclaredKeys, noteReservedKeys, SCOPE_SEATED_ROLES_SQL, inUnitRoleRefusal, loadedRoleSetRefusedRole, reservedRoleSetRefusedRole, reservedGrantRefusal, reservedRoleRefusal, reservedShapeRefusal, effectiveRoleGrantQuery, switchRecordedOff, switchSystemSchedules, peerGrantsStatus, systemGrantsStatus, systemScheduleState, systemSwitchedOff, denialListQuery, refusalListQuery, mapRefusalRow, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialSummaryBuckets, PermissionDenied, assertImpersonationWrites, assertModuleEnqueueableKind, impersonationStampOf, createAtomic, NotSearchable, isSearchIndexTable, searchIndexPlans, NotListable, listIndexPlans, moduleMigrations, MIGRATION_DIGEST_FENCE_DDL, MIGRATION_DIGEST_MARK_LEGACY, assertJournalDumpCoherent, assertMigrationSql, migrationDivergence, migrationFailedError, migrationSetDivergence, migrationSteps, planMigrations, listQuery, cursorOf, searchLimit, searchMatchExpression, searchQuery, ATTACHMENT_TEXT_DDL, ATTACHMENT_BYTES_SQL, ATTACHMENT_CONDEMNED_DDL, ATTACHMENT_SWEEP_LICENCE_DDL, ATTACHMENT_SWEEP_LICENCE_TABLE, WIRE_NONCE_DDL, WIRE_NONCE_FULL, claimWireNonceIn, isWireNonceTable, isWireNonceWrite, clearOrphanSweepLicence, condemnLicensedOrphanAttachments, consumeOrphanSweepLicence, licensedOrphanIds, recordOrphanSweepLicence, refuseUnlicensedOrphanSweep, refuseCondemnedAttachment, refuseCondemnedInDump, rowlessAttachmentIds, attachmentRecordOfRow, enqueueAttachmentText, reconcileAttachmentText, queueAttachmentTextBackfill, startAttachmentTextBackfill, recordAttachmentText, searchAttachments, IDEMPOTENCY_DDL, REFUSALS_TABLE_DDL, REFUSALS_INDEX, refusalInsert, refusalOf, markGuardRefusal, REFUSALS_REBUILD, refusalsAdmitGuards, 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, emptyImportResult, IMPORT_CURSORS_SQL, IMPORT_CURSOR_OF_SQL, OUTBOX_MARK_SQL, exportedSinceQuery, emittedSinceQuery, emittedReportOf, EMITTED_REPORT_CAP, noteTransitionInto, withTransitions, IMPORT_CURSOR_ADVANCE_SQL, IMPORT_RECORD_SQL, moveImportCursor, CrossVerticalRegistry, exportReadPlan, exportReadQuery, planExportBatch, withheldNote, JOB_RUN_DDL, JOB_RUN_DUE_AT, jobRunListLimit, SYSTEM_DOOR_WAIT, ancestorsWithin, } from '@substrat-run/kernel';
4
+ import { ATTACHMENT_ADDED, ATTACHMENT_REMOVED, CAPTURE_REFUSALS, attachmentRecord, domainEvent, domainEventInput, eventId, instant, objectRef, entityObjectRef, assertModuleEmittableType, assertKernelAuthoredType, toWireFailure, grantRefFromProof, principalId, principalBecomeCapabilityInput, scopeId as scopeIdOf, tenantId as tenantIdOf, platformRequestInput, platformRequestId, MAX_PENDING_PLATFORM_REQUESTS, MAX_PENDING_SWEEP_RUNS, SWEEP_RUNS_KIND, SCOPE_DUMP_PAGE_MAX_BYTES, SCOPE_TABLE_PAGE_MAX, SCOPE_QUERY_ROW_MAX, listLimitOf, pageOf, countedPageOf, requestFingerprint, substratError, errorCodeOf, assertReplayableDump, DUMP_OBJECTS_QUERY, dumpObjectsOf, replayDumpObjects, dumpObjectsAudit, REDRAIN_BATCH, } from '@substrat-run/contracts';
5
+ import { ulid, DO_SQL_LIMITS, unknownRoleError, createUlid, assertAllowed, ConnectionSealingKeyUnavailableError, noSealingKeyMessage, sealTo, assertReadOnlyQuery, entitlementDenial, platformRequestHistoryQuery, platformRequestOf, PLATFORM_REQUEST_COLUMNS, PLATFORM_REQUEST_REDACTION_SQL, platformRequestRedactionParams, platformRequestRedactionQuery, intentPayloadCarriesSubject, redactSubjectJobRuns, eraseSubjectFromModules, moduleErasurePlan, redactSubjectScopeText, assertRowLimit, assertRowOffset, JOB_RUN_PATCH_SQL, JOB_RUN_CLAIM_SQL, JOB_RUN_RENEW_SQL, JOB_RUN_BEGIN_SQL, JOB_RUN_MISS_SQL, JOB_RUN_MISS_SETTLE_SQL, admissionMissOutcome, JOB_LEASE_EXPIRED_NOTE, JOB_STEP_RECORD_SQL, DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, seatScopeTuple, delegatedRevokeSql, writeExplicitTupleIn, grantEntityShapeIn, topUpEntityGrantShapes, applyScopeRoleChange, changeScopeRole, revokeScopeRoles, scopeRoleHolders, assertModuleDeclared, noteDeclaredKeys, noteReservedKeys, SCOPE_SEATED_ROLES_SQL, inUnitRoleRefusal, loadedRoleSetRefusedRole, reservedRoleSetRefusedRole, reservedGrantRefusal, reservedRoleRefusal, reservedShapeRefusal, effectiveRoleGrantQuery, switchRecordedOff, switchSystemSchedules, peerGrantsStatus, systemGrantsStatus, systemScheduleState, systemSwitchedOff, denialListQuery, refusalListQuery, mapRefusalRow, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialSummaryBuckets, PermissionDenied, assertImpersonationAdmitted, assertImpersonationWrites, assertModuleEnqueueableKind, impersonationStampOf, createAtomic, NotSearchable, isSearchIndexTable, searchIndexPlans, NotListable, listIndexPlans, moduleMigrations, MIGRATION_DIGEST_FENCE_DDL, MIGRATION_DIGEST_MARK_LEGACY, assertJournalDumpCoherent, assertMigrationSql, migrationDivergence, migrationFailedError, migrationSetDivergence, migrationSteps, planMigrations, listQuery, cursorOf, searchLimit, searchMatchExpression, searchQuery, ATTACHMENT_TEXT_DDL, ATTACHMENT_BYTES_SQL, ATTACHMENT_CONDEMNED_DDL, ATTACHMENT_SWEEP_LICENCE_DDL, ATTACHMENT_SWEEP_LICENCE_TABLE, WIRE_NONCE_DDL, WIRE_NONCE_FULL, claimWireNonceIn, isWireNonceTable, isWireNonceWrite, clearOrphanSweepLicence, condemnLicensedOrphanAttachments, consumeOrphanSweepLicence, licensedOrphanIds, recordOrphanSweepLicence, refuseUnlicensedOrphanSweep, refuseCondemnedAttachment, refuseCondemnedInDump, rowlessAttachmentIds, attachmentRecordOfRow, enqueueAttachmentText, reconcileAttachmentText, queueAttachmentTextBackfill, startAttachmentTextBackfill, recordAttachmentText, searchAttachments, IDEMPOTENCY_DDL, REFUSALS_TABLE_DDL, REFUSALS_INDEX, refusalInsert, refusalOf, markGuardRefusal, REFUSALS_REBUILD, refusalsAdmitGuards, 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, emptyImportResult, IMPORT_CURSORS_SQL, IMPORT_CURSOR_OF_SQL, OUTBOX_MARK_SQL, exportedSinceQuery, emittedSinceQuery, emittedReportOf, EMITTED_REPORT_CAP, noteTransitionInto, withTransitions, IMPORT_CURSOR_ADVANCE_SQL, IMPORT_RECORD_SQL, moveImportCursor, CrossVerticalRegistry, exportReadPlan, exportReadQuery, planExportBatch, withheldNote, JOB_RUN_DDL, JOB_RUN_DUE_AT, jobRunListLimit, SYSTEM_DOOR_WAIT, ancestorsWithin, } from '@substrat-run/kernel';
6
6
  import { isUpgradeRequest, readSubscription, LIVE_DECIDE_ATTEMPTS, LIVE_FANOUT_LIMIT, LIVE_PRINCIPAL_HEADER, LIVE_SCOPE_HEADER, LIVE_SUBSCRIBE_PATH, LIVE_TENANT_HEADER, LIVE_WITHIN_HEADER, LIVE_EXPIRES_HEADER, LIVE_CLOSE, LIVE_SOCKETS_PER_PRINCIPAL, decodeLiveWithin, liveInstant, liveRefusal, liveRefusalStatus, } from './live-reads.js';
7
7
  import { replyOf } from './do-reply.js';
8
8
  import { OperationQueue } from './serialization.js';
@@ -442,16 +442,22 @@ const STAGE_IN_PROGRESS = 'a different staged load is in progress; retry after i
442
442
  const STAGE_MAX_AGE_MS = 6 * 3_600_000 + 3 * SCOPE_COPY_LEASE_MS;
443
443
  /**
444
444
  * A staged load's own tables (#2450): `_substrat_stage` (which load, and the next page it takes),
445
- * `_substrat_stage_tables` (each staged table's name, DDL and columns) and one
446
- * `_substrat_stage_rows_<n>` per staged table. They hold a carry's pages until the load commits
447
- * from them, so they are this store's, never its data: no dump carries them, and a load's drops
448
- * leave them to the commit that reads them.
445
+ * `_substrat_stage_tables` (each staged table's name, DDL and columns), `_substrat_stage_objects`
446
+ * (below) and one `_substrat_stage_rows_<n>` per staged table. They hold a carry's pages until the
447
+ * load commits from them, so they are this store's, never its data: no dump carries them, and a
448
+ * load's drops leave them to the commit that reads them.
449
449
  */
450
450
  const STAGE_GLOB = '_substrat_stage*';
451
451
  const STAGE_TABLES_DDL = [
452
452
  `CREATE TABLE _substrat_stage (stage_id TEXT NOT NULL, next_seq INTEGER NOT NULL)`,
453
453
  `CREATE TABLE _substrat_stage_tables (ord INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, ddl TEXT NOT NULL, columns TEXT NOT NULL)`,
454
454
  ];
455
+ /**
456
+ * #2820: each staged table's own indexes and triggers, for a table whose pages carried them. Created
457
+ * by every page if missing, so a stage opened before it existed still takes pages, and commits with
458
+ * none: as a dump written before objects were carried.
459
+ */
460
+ const STAGE_OBJECTS_DDL = `CREATE TABLE IF NOT EXISTS _substrat_stage_objects (name TEXT PRIMARY KEY, objects TEXT NOT NULL)`;
455
461
  const stageRowsTable = (ord) => `_substrat_stage_rows_${ord}`;
456
462
  /** How many staged rows a commit reads at a time. */
457
463
  const STAGE_READ_BATCH = 500;
@@ -944,6 +950,8 @@ options = {}) {
944
950
  purgeSchedules = new Map();
945
951
  /** #116: the operations that declared `idempotency: false` — refusals, not participants. */
946
952
  operationIdempotencyOptOut = new Set();
953
+ /** #2821: the operations that declared `impersonation: 'refused'` — no session may invoke them. */
954
+ operationImpersonationRefused = new Set();
947
955
  /** #2387: the operations held to a `ctx.check` — the pure adapter's set, from the same kernel read. */
948
956
  operationCheckHeld = new Set();
949
957
  modules = new Map();
@@ -1222,6 +1230,7 @@ options = {}) {
1222
1230
  // #116: same rule again for an opt-out — one on an unbound name reads as a
1223
1231
  // deliberate exclusion of an operation that is not there.
1224
1232
  const declaredOptOuts = registration.operations?.idempotencyOptOuts ?? [];
1233
+ const impersonationRefused = registration.operations?.impersonationRefused ?? [];
1225
1234
  const unboundOptOuts = declaredOptOuts.filter((name) => !ownOps.has(name));
1226
1235
  if (unboundOptOuts.length > 0) {
1227
1236
  throw new Error(`${manifest.id} declares idempotency: false for unbound operation(s): ` +
@@ -1244,6 +1253,9 @@ options = {}) {
1244
1253
  if (declaredOptOuts.includes(name) && this.operations.has(name)) {
1245
1254
  this.operationIdempotencyOptOut.add(name);
1246
1255
  }
1256
+ if (impersonationRefused.includes(name) && this.operations.has(name)) {
1257
+ this.operationImpersonationRefused.add(name);
1258
+ }
1247
1259
  const target = trashTargets.get(name);
1248
1260
  if (target && this.operations.has(name))
1249
1261
  this.operationTarget.set(name, target);
@@ -2266,6 +2278,8 @@ options = {}) {
2266
2278
  // else. Naming the code once here is what lets those patterns go.
2267
2279
  if (!handler)
2268
2280
  throw substratError('not_found', `unknown operation: ${operation}`);
2281
+ // #2821: before the parse, the guards and the handler — and under read-only too.
2282
+ assertImpersonationAdmitted(impersonation, operation, this.operationImpersonationRefused);
2269
2283
  // #304 entitlement gate, scope-local path: fail closed on the projected view exactly as
2270
2284
  // the coordinator fails closed against the CP. Only active once entitlements have been
2271
2285
  // projected (the `entitlements_enforced` marker) — before that the scope trusts upstream,
@@ -4364,11 +4378,12 @@ options = {}) {
4364
4378
  return await this.queue.enqueue(async () => {
4365
4379
  const liveSince = this.liveHighWaterMark();
4366
4380
  const now = instant.parse(new Date().toISOString());
4367
- let answer = { ok: true, unbound: [] };
4381
+ let answer = { ok: true, unbound: [], revokedLinks: [] };
4368
4382
  await this.revision.transaction(async () => {
4369
4383
  answer = await unbindEntityHolderBounded({
4370
4384
  sql: doSpineSql(this.sql),
4371
4385
  now,
4386
+ caller: by,
4372
4387
  allowed: allowedFor(this.checker, by, { tenantId, scopeId }),
4373
4388
  emit: (event) => kernelEmit(this.operationContext(by, tenantId, scopeId, undefined, undefined, undefined, undefined, undefined, ENTITY_UNBIND_OPERATION, undefined, [], now), event),
4374
4389
  }, entity);
@@ -5488,6 +5503,7 @@ options = {}) {
5488
5503
  * with the scope's identity after the K-3 check (host.ts admin.exportScope).
5489
5504
  */
5490
5505
  exportDump() {
5506
+ const objects = this.dumpObjects();
5491
5507
  return this.dumpTableDefs().map(({ name, sql }) => {
5492
5508
  // Raw positional rows, cells as-is (blobs kept as bytes, not nulled like a UI
5493
5509
  // read) so the dump reloads faithfully. The name is from the live schema.
@@ -5495,9 +5511,13 @@ options = {}) {
5495
5511
  const cursor = this.sql.exec(`SELECT * FROM "${name}" WHERE ${where}`, ...bindings);
5496
5512
  const columns = cursor.columnNames;
5497
5513
  const rows = Array.from(cursor.raw(), (row) => row);
5498
- return { name, ddl: sql, columns, rows };
5514
+ return { name, ddl: sql, columns, rows, ...objects(name) };
5499
5515
  });
5500
5516
  }
5517
+ /** #2820: each module table's own indexes and triggers, as a dump carries them beside the table. */
5518
+ dumpObjects() {
5519
+ return dumpObjectsOf(this.sql.exec(DUMP_OBJECTS_QUERY).toArray());
5520
+ }
5501
5521
  /**
5502
5522
  * The tables a dump carries, by name. The derived search index (#827) is excluded: its shadow
5503
5523
  * tables cannot be replayed — this host answers "object name reserved for internal use" — and
@@ -5537,6 +5557,7 @@ options = {}) {
5537
5557
  this.sql.exec(`INSERT INTO _substrat_meta (key, value) VALUES (?, ?)`, LOAD_STAMP_KEY, loadStamp);
5538
5558
  }
5539
5559
  const defs = this.dumpTableDefs();
5560
+ const objects = this.dumpObjects();
5540
5561
  if (capture !== undefined) {
5541
5562
  if (cursor === null)
5542
5563
  this.openCapture(capture, defs);
@@ -5584,7 +5605,7 @@ options = {}) {
5584
5605
  if (full || read.length < DUMP_READ_BATCH)
5585
5606
  break;
5586
5607
  }
5587
- tables.push({ name, ddl: sql, columns, rows, ...(capture !== undefined && byRowid ? { rowids } : {}) });
5608
+ tables.push({ name, ddl: sql, columns, rows, ...objects(name), ...(capture !== undefined && byRowid ? { rowids } : {}) });
5588
5609
  if (full) {
5589
5610
  next = encodeDumpCursor({ table: name, after: last ?? 0 });
5590
5611
  break;
@@ -5677,11 +5698,21 @@ options = {}) {
5677
5698
  return key.map((c) => ({ column: c.name, collation: c.coll }));
5678
5699
  });
5679
5700
  }
5680
- /** What a capture's pages read the schema as (#2450): the dumped tables and their explicit indexes. */
5701
+ /**
5702
+ * What a capture's pages read the schema as (#2450): the dumped tables, their explicit indexes and
5703
+ * (#2820) their triggers, which the pages carry as the tables' own objects. The capture's own
5704
+ * triggers are not part of it: they come and go with captures.
5705
+ */
5681
5706
  captureSchema(defs) {
5682
5707
  const names = new Set(defs.map((d) => d.name));
5683
- const indexes = this.sql.exec(`SELECT tbl_name, name, sql FROM sqlite_master WHERE type = 'index' AND sql IS NOT NULL ORDER BY name`).toArray().filter((i) => names.has(i.tbl_name));
5684
- return JSON.stringify({ tables: defs, indexes: indexes.map((i) => [i.name, i.sql]) });
5708
+ const objects = this.sql
5709
+ .exec(`SELECT type, tbl_name, name, sql FROM sqlite_master
5710
+ WHERE (type = 'index' OR (type = 'trigger' AND name NOT GLOB '${CAPTURE_GLOB}')) AND sql IS NOT NULL ORDER BY name`)
5711
+ .toArray().filter((o) => names.has(o.tbl_name));
5712
+ const of = (type) => objects.filter((o) => o.type === type).map((o) => [o.name, o.sql]);
5713
+ // Named only when there are some, so a capture opened before triggers were part of it still reads alike.
5714
+ const triggers = of('trigger');
5715
+ return JSON.stringify({ tables: defs, indexes: of('index'), ...(triggers.length ? { triggers } : {}) });
5685
5716
  }
5686
5717
  /**
5687
5718
  * The capture `copyId`, renewed, as its pages read the schema (#2450). Refused when it is gone.
@@ -6076,7 +6107,7 @@ options = {}) {
6076
6107
  });
6077
6108
  }
6078
6109
  async importDump(tables, destScopeId, opts = {}) {
6079
- return this.loadTables(tables.map((t) => ({ name: t.name, ddl: t.ddl, columns: t.columns, rows: () => t.rows })), destScopeId, opts);
6110
+ return this.loadTables(tables.map((t) => ({ name: t.name, ddl: t.ddl, columns: t.columns, objects: t.objects, rows: () => t.rows })), destScopeId, opts);
6080
6111
  }
6081
6112
  /**
6082
6113
  * The load `importDump` runs, over tables whose rows are read lazily (#2450): from the dump's
@@ -6084,7 +6115,7 @@ options = {}) {
6084
6115
  * in memory at once. `stageId` names the staged load the rows come from: checked inside the
6085
6116
  * load's transaction, and its tables dropped in it once the rows are in.
6086
6117
  */
6087
- async loadTables(tables, destScopeId, { switchOff, sourceScopeId, exact, loadStamp, expect, resolveKept, markCopy, provisionedFor, fence, roleRepair, onRolesProjected, projection, keepPermissionFlags, } = {}, stageId) {
6118
+ async loadTables(tables, destScopeId, { switchOff, sourceScopeId, exact, loadStamp, expect, resolveKept, markCopy, provisionedFor, fence, roleRepair, onRolesProjected, projection, keepPermissionFlags, onObjects, } = {}, stageId) {
6088
6119
  // The WHOLE drop-then-replay runs under deferred foreign keys, in one transaction.
6089
6120
  //
6090
6121
  // Two distinct FK hazards, and both need the deferral — the second is why it cannot
@@ -6118,7 +6149,8 @@ options = {}) {
6118
6149
  // amount of name checking reaches it. There is no prepare step here to compile
6119
6150
  // only the first statement, so the text itself has to be the one statement.
6120
6151
  // Pure input validation, so it runs before the first DROP: a refused dump touches nothing.
6121
- assertReplayableDump(replayable, { maxColumns: DO_SQL_LIMITS.columns });
6152
+ const objects = assertReplayableDump(replayable, { maxColumns: DO_SQL_LIMITS.columns });
6153
+ onObjects?.(objects);
6122
6154
  // #1869: `exact` vouches for a named source; without one it would silently fall back.
6123
6155
  if (exact && !sourceScopeId) {
6124
6156
  throw substratError('validation_failed', 'restore refused: `exact` needs the scope the dump came from');
@@ -6306,6 +6338,10 @@ options = {}) {
6306
6338
  // derived (#2090). Inside the replay's transaction: a dump whose journal owes a table a
6307
6339
  // state column it does not carry rolls the whole load back. A plan whose table this dump
6308
6340
  // did not carry is skipped: a restore must not invent one.
6341
+ // #2820: a module's own indexes and triggers went with the dropped tables, and its journal
6342
+ // says the migrations that made them ran. Put back from the dump, after the rows they judge
6343
+ // and before the kernel derives its own.
6344
+ replayDumpObjects(objects, (ddl) => this.sql.exec(blankSqlComments(ddl)));
6309
6345
  repairDerivedObjects(doSpineSql(this.sql), (ddl) => this.runScript(ddl), this.derivedPlans(), {
6310
6346
  after: 'the restored dump',
6311
6347
  absentTable: 'skip',
@@ -6405,7 +6441,7 @@ options = {}) {
6405
6441
  * (`changed`, 412) and "this store is a kept copy" (`kept`, 409) from a failure.
6406
6442
  */
6407
6443
  async importDumpChecked(tables, destScopeId, opts) {
6408
- return this.checkedLoad(() => this.importDump(tables, destScopeId, opts));
6444
+ return this.checkedLoad((onObjects) => this.importDump(tables, destScopeId, { ...opts, onObjects }));
6409
6445
  }
6410
6446
  /**
6411
6447
  * #2531: `importDumpChecked` with the role repair judged and projected in the load's own
@@ -6415,7 +6451,7 @@ options = {}) {
6415
6451
  * rather than loading with the repair silently dropped.
6416
6452
  */
6417
6453
  async importDumpJudged(tables, destScopeId, opts, roleRepair) {
6418
- return this.judgedLoad((onRolesProjected) => this.checkedLoad(() => this.importDump(tables, destScopeId, { ...opts, roleRepair, onRolesProjected })));
6454
+ return this.judgedLoad((onRolesProjected) => this.checkedLoad((onObjects) => this.importDump(tables, destScopeId, { ...opts, roleRepair, onRolesProjected, onObjects })));
6419
6455
  }
6420
6456
  /**
6421
6457
  * #2555: a control-plane-full restore or import. `importDump`, with this store's permission flags
@@ -6429,15 +6465,16 @@ options = {}) {
6429
6465
  importDumpProjected(tables, destScopeId, opts, projection) {
6430
6466
  return replyOf(async () => {
6431
6467
  try {
6432
- return {
6433
- switchedOff: await this.importDump(tables, destScopeId, {
6434
- switchOff: opts.switchOff,
6435
- sourceScopeId: opts.sourceScopeId,
6436
- exact: opts.exact,
6437
- keepPermissionFlags: true,
6438
- ...(projection ? { projection } : {}),
6439
- }),
6440
- };
6468
+ let plan;
6469
+ const switchedOff = await this.importDump(tables, destScopeId, {
6470
+ switchOff: opts.switchOff,
6471
+ sourceScopeId: opts.sourceScopeId,
6472
+ exact: opts.exact,
6473
+ keepPermissionFlags: true,
6474
+ ...(projection ? { projection } : {}),
6475
+ onObjects: (judged) => (plan = judged),
6476
+ });
6477
+ return { switchedOff, ...(plan ? { moduleObjects: dumpObjectsAudit(plan) } : {}) };
6441
6478
  }
6442
6479
  catch (e) {
6443
6480
  if (e instanceof Error && roleRefusals.has(e))
@@ -6508,19 +6545,31 @@ options = {}) {
6508
6545
  if (seq > head.nextSeq) {
6509
6546
  throw substratError('precondition_failed', `stage page ${seq} arrived before page ${head.nextSeq}`);
6510
6547
  }
6548
+ // A stage opened before objects were staged takes their table now: its earlier pages
6549
+ // staged none, so a later page that carries them reads as the table having changed.
6550
+ this.sql.exec(STAGE_OBJECTS_DDL);
6511
6551
  for (const t of tables) {
6512
6552
  const columns = JSON.stringify(t.columns);
6553
+ const objects = t.objects === undefined ? null : JSON.stringify(t.objects);
6513
6554
  let held = this.sql
6514
- .exec(`SELECT ord, ddl, columns FROM _substrat_stage_tables WHERE name = ?`, t.name)
6555
+ .exec(`SELECT s.ord, s.ddl, s.columns, o.objects FROM _substrat_stage_tables s
6556
+ LEFT JOIN _substrat_stage_objects o ON o.name = s.name WHERE s.name = ?`, t.name)
6515
6557
  .toArray()[0];
6516
6558
  if (!held) {
6559
+ // #2820: an export carries objects for every module table or for none. A page that
6560
+ // disagrees with the tables already staged was read across a change of version.
6561
+ if (!isSpineTable(t.name) && this.stagedModuleObjects().some((has) => has !== (objects !== null))) {
6562
+ throw substratError('precondition_failed', `table ${t.name} changed between dump pages; nothing more was staged`);
6563
+ }
6517
6564
  const ord = Number(this.sql.exec(`SELECT COUNT(*) AS n FROM _substrat_stage_tables`).toArray()[0].n);
6518
6565
  this.sql.exec(`INSERT INTO _substrat_stage_tables (ord, name, ddl, columns) VALUES (?, ?, ?, ?)`, ord, t.name, t.ddl, columns);
6566
+ if (objects !== null)
6567
+ this.sql.exec(`INSERT INTO _substrat_stage_objects (name, objects) VALUES (?, ?)`, t.name, objects);
6519
6568
  // Untyped columns: no affinity, so a cell is stored exactly as it came.
6520
6569
  this.sql.exec(`CREATE TABLE "${stageRowsTable(ord)}" (${t.columns.map((_, i) => `c${i}`).join(', ')})`);
6521
- held = { ord, ddl: t.ddl, columns };
6570
+ held = { ord, ddl: t.ddl, columns, objects };
6522
6571
  }
6523
- else if (held.ddl !== t.ddl || held.columns !== columns) {
6572
+ else if (held.ddl !== t.ddl || held.columns !== columns || held.objects !== objects) {
6524
6573
  throw substratError('precondition_failed', `table ${t.name} changed between dump pages; nothing more was staged`);
6525
6574
  }
6526
6575
  this.stageRows(held.ord, t);
@@ -6614,21 +6663,27 @@ options = {}) {
6614
6663
  */
6615
6664
  async commitStagedDumpChecked(stageId, destScopeId, opts) {
6616
6665
  let count = 0;
6617
- const out = await this.checkedLoad(() => {
6666
+ const out = await this.checkedLoad((onObjects) => {
6618
6667
  const head = this.stageHead();
6619
6668
  if (head?.stageId !== stageId)
6620
6669
  throw substratError('precondition_failed', STAGE_GONE);
6670
+ // Read only: a stage opened before objects were staged has no table for them, and loads none.
6671
+ const withObjects = this.hasTable('_substrat_stage_objects');
6621
6672
  const staged = this.sql
6622
- .exec(`SELECT ord, name, ddl, columns FROM _substrat_stage_tables ORDER BY name`)
6673
+ .exec(withObjects
6674
+ ? `SELECT s.ord, s.name, s.ddl, s.columns, o.objects FROM _substrat_stage_tables s
6675
+ LEFT JOIN _substrat_stage_objects o ON o.name = s.name ORDER BY s.name`
6676
+ : `SELECT ord, name, ddl, columns, NULL AS objects FROM _substrat_stage_tables ORDER BY name`)
6623
6677
  .toArray();
6624
6678
  const tables = staged.map((t) => ({
6625
6679
  name: t.name,
6626
6680
  ddl: t.ddl,
6627
6681
  columns: JSON.parse(t.columns),
6682
+ ...(t.objects === null ? {} : { objects: JSON.parse(t.objects) }),
6628
6683
  rows: () => this.stagedRows(t.ord),
6629
6684
  }));
6630
6685
  count = tables.length;
6631
- return this.loadTables(tables, destScopeId, opts, stageId);
6686
+ return this.loadTables(tables, destScopeId, { ...opts, onObjects }, stageId);
6632
6687
  });
6633
6688
  return out.refused === false ? { ...out, tables: count } : out;
6634
6689
  }
@@ -6664,6 +6719,14 @@ options = {}) {
6664
6719
  after = Number(batch[batch.length - 1][0]);
6665
6720
  }
6666
6721
  }
6722
+ /** #2820: for each module table staged so far, whether its pages carried its objects. */
6723
+ stagedModuleObjects() {
6724
+ return this.sql
6725
+ .exec(`SELECT s.name, o.name IS NOT NULL AS has FROM _substrat_stage_tables s LEFT JOIN _substrat_stage_objects o ON o.name = s.name`)
6726
+ .toArray()
6727
+ .filter((r) => !isSpineTable(r.name))
6728
+ .map((r) => Boolean(r.has));
6729
+ }
6667
6730
  /** The staged load this store holds (#2450), or null. */
6668
6731
  stageHead() {
6669
6732
  const present = this.sql.exec(`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '_substrat_stage'`).toArray();
@@ -6683,7 +6746,9 @@ options = {}) {
6683
6746
  /** A load's refusals answered as values (#1722): across the RPC a throw carries only its message. */
6684
6747
  async checkedLoad(run) {
6685
6748
  try {
6686
- return { refused: false, switchedOff: await run() };
6749
+ let plan;
6750
+ const switchedOff = await run((judged) => (plan = judged));
6751
+ return { refused: false, switchedOff, ...(plan ? { moduleObjects: dumpObjectsAudit(plan) } : {}) };
6687
6752
  }
6688
6753
  catch (e) {
6689
6754
  if (e instanceof Error && fenceRefusals.has(e))