@substrat-run/adapter-sqlite 0.108.0 → 0.110.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
@@ -119,6 +119,21 @@ export declare class SqliteScopeHost implements ScopeHost {
119
119
  * schema backwards and the next read of a newer column would fail with a bare `no such
120
120
  * column`. Every statement is IF NOT EXISTS and `ensureDirectoryColumns` probes before
121
121
  * it alters, so this only ever adds back what a copy did not carry.
122
+ *
123
+ * It is also one of two hand-written copies of the directory spine (#969). The other is
124
+ * `DIRECTORY_DDL` in `adapter-cloudflare/src/control-plane-do.ts`, and the two must
125
+ * describe the same schema: this side is dev, CI, self-host and escrow, that side is
126
+ * production. So a new directory table both adapters need, or a new column on a table
127
+ * they both build, is added HERE and THERE — and for a directory created before it, to
128
+ * both adapters' `ensureDirectoryColumns` as well.
129
+ * `pnpm lint:spine-ddl` (`tools/spine-ddl-drift.mjs`) refuses a divergence by executing
130
+ * each side's DDL plus those later ALTERs and comparing the schemas a query would
131
+ * actually meet — columns, indexes and foreign keys. What it does not judge: a table
132
+ * present on one side only, which it reports as a note because the adapters legitimately
133
+ * partition the spine differently, and triggers or CHECK constraints, which it does not
134
+ * compare at all (the spine has none).
135
+ * Keeping the copies and gating them, rather than moving the DDL into the kernel, is the
136
+ * recorded answer to #969; `docs/architecture/kernel-design.md` §8 says why.
122
137
  */
123
138
  private applyDirectorySchema;
124
139
  registerExecutor(id: string, eventType: string, handler: ExecutorHandler, retry?: ExecutorRetryPolicy): void;
@@ -434,6 +449,21 @@ export declare class SqliteScopeHost implements ScopeHost {
434
449
  * DIFFERENT tenant, throws — the introspection reads never open another tenant's
435
450
  * DB, and never CREATE one for an id that was never provisioned.
436
451
  */
452
+ /**
453
+ * Whether this (tenant, scope) pair may be reached at all — the DIRECTORY half of
454
+ * `scopeDbFor`, split out so a call that ends up touching no rows still answers the
455
+ * question. K-3 first: a scope under another tenant is indistinguishable from one
456
+ * that does not exist. Then the reap.
457
+ *
458
+ * A reaped scope keeps its directory row as a tombstone while `reapScope` deletes
459
+ * the file (§4.4). `runtime()` opens a database by CREATING it when absent, so
460
+ * reading one would not merely answer emptily — it would put the file back,
461
+ * resurrecting storage an irreversible reap destroyed, and every later read would
462
+ * report a scope with no events rather than a scope that is gone. `archived` is
463
+ * deliberately still reachable: its bytes exist, and that is the whole point of the
464
+ * state.
465
+ */
466
+ private assertScopeReachable;
437
467
  private scopeDbFor;
438
468
  /**
439
469
  * The additive spine-column migrations, shared by `runtime()` and the dump replay.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,9 +2,29 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync
2
2
  import { dirname, join } from 'node:path';
3
3
  import Database from 'better-sqlite3';
4
4
  import { accessLogEntry, adminLogEntry, opsFailureEntry, opsFailureFingerprint, issueEntry, FRESHNESS_HEARTBEAT_MINUTES, sweepRunEntry, modelUsageEntry, ATTACHMENT_ADDED, ATTACHMENT_REMOVED, attachmentRecord, createTenantInput, domainEvent, domainEventInput, entitlementGrant, entitlementGrantInput, eventId, meterReading, platformRequestInput, platformRequestId, platformRequest, MAX_PENDING_PLATFORM_REQUESTS, identityLink, identityPool, instant, connection, capabilityGrant, connectionGrant, connectionGrantRecord, connectionSecret, systemGrant, subjectRef, requestFingerprint, createConnectionInput, projectedConnectionGrant, projectedConnectionKey, moduleManifest, createOrgInput, promotionAcknowledgement, bindHostnameInput, channelHistoryEntry, hostnameBinding, publishVersionInput, AUTO_ADMISSION_NOTE, routeTarget, registerVerticalInput, vertical as verticalSchema, verticalChannel, verticalVersion, objectRef, grantRefFromProof, org as orgSchema, orgMembership, principalId, resolvedIdentity, roleDefinition, scope as scopeSchema, tenant as tenantSchema, tenantRole, subjectShredReceipt, SCOPE_TABLE_PAGE_DEFAULT, SCOPE_TABLE_PAGE_MAX, SCOPE_QUERY_ROW_MAX, verticalServingState, outboundOfManifestJson, listLimitOf, substratError, assertReplayableDump, } from '@substrat-run/contracts';
5
- import { asPrincipal, assertAllowed, assertReadOnlyQuery, attachmentBlobKey, entitlementDenial, foldMeterReading, guardSpine, parseValidationRecords, resolveScopeRecord, ulid, ISSUE_RETENTION_DAYS, OPS_FAILURE_RETENTION_DAYS, SWEEP_RUN_RETENTION_DAYS, MODEL_USAGE_RETENTION_DAYS, foldModelUsage, backoffAt, platformRequestHistoryQuery, PLATFORM_REQUEST_COLUMNS, denialListQuery, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialBucketRow, IMPERSONATION_COLUMNS, IMPERSONATION_DDL, ImpersonationRefused, assertImpersonationWrites, assertSessionUsable, impersonationByIdQuery, impersonationListQuery, impersonationRowValues, impersonationStampOf, mapImpersonationRow, newImpersonationSession, resolveRetryPolicy, isSecretBoxConfigured, unconfiguredSecretBox, createSubjectKeys, generateSealingKeyPair, noSealingKeyMessage, openSealed, sealTo, ConnectionSealingKeyUnavailableError, PermissionDenied, createAtomic, NotSearchable, NotListable, listIndexMigrations, listIndexPlans, listQuery, cursorOf, isSearchIndexTable, searchIndexDdl, searchIndexMigrations, searchIndexPlans, searchLimit, searchMatchExpression, searchQuery, entityVersionQuery, entityVersionOf, assertIfMatch, OUTBOX_ENTITY_INDEX, IDEMPOTENCY_DDL, assertIdempotencyKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, globalFetch, createUlid, readHistory, } from '@substrat-run/kernel';
5
+ import { asPrincipal, assertAllowed, assertReadOnlyQuery, attachmentBlobKey, entitlementDenial, foldMeterReading, guardSpine, parseValidationRecords, resolveScopeRecord, ulid, ISSUE_RETENTION_DAYS, OPS_FAILURE_RETENTION_DAYS, SWEEP_RUN_RETENTION_DAYS, MODEL_USAGE_RETENTION_DAYS, foldModelUsage, backoffAt, platformRequestHistoryQuery, PLATFORM_REQUEST_COLUMNS, denialListQuery, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialBucketRow, IMPERSONATION_COLUMNS, IMPERSONATION_DDL, ImpersonationRefused, assertImpersonationWrites, assertSessionUsable, impersonationByIdQuery, impersonationListQuery, impersonationRowValues, impersonationStampOf, mapImpersonationRow, newImpersonationSession, resolveRetryPolicy, isSecretBoxConfigured, unconfiguredSecretBox, createSubjectKeys, generateSealingKeyPair, noSealingKeyMessage, openSealed, sealTo, ConnectionSealingKeyUnavailableError, PermissionDenied, createAtomic, NotSearchable, NotListable, listIndexMigrations, listIndexPlans, listQuery, cursorOf, isSearchIndexTable, searchIndexDdl, searchIndexMigrations, searchIndexPlans, searchLimit, searchMatchExpression, searchQuery, entityVersionQuery, entityVersionOf, assertIfMatch, OUTBOX_ENTITY_INDEX, IDEMPOTENCY_DDL, assertIdempotencyKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, globalFetch, createUlid, facetEvents, readHistory, } from '@substrat-run/kernel';
6
6
  import { ScopeActor } from './actor.js';
7
7
  import { createTupleChecker } from './checker.js';
8
+ /**
9
+ * The scope spine, as this adapter builds it — one of two hand-written copies (#969).
10
+ *
11
+ * The other is `KERNEL_DDL` in `adapter-cloudflare/src/scope-do.ts`, and the two must
12
+ * describe the same schema: this side is dev, CI, self-host and escrow, that side is
13
+ * production. So a new `_substrat_*` table both hosts need, or a new column on a table
14
+ * they both build, is added HERE and THERE — and for a store created before it, to both
15
+ * column-addition lists as well (`ensureSpineColumns` below, `applySpineColumnAdditions`
16
+ * on the DO side).
17
+ *
18
+ * `pnpm lint:spine-ddl` (`tools/spine-ddl-drift.mjs`) is what refuses a divergence: it
19
+ * executes each side's DDL plus those later ALTERs and compares the schemas a query would
20
+ * actually meet — columns, indexes (including the ones a UNIQUE creates) and foreign keys.
21
+ * Two things it does NOT judge. A table present on one side only is a note, not a failure,
22
+ * because the adapters legitimately partition the spine differently. And triggers and CHECK
23
+ * constraints are not compared at all; the spine has none today, so adding one here means
24
+ * adding it there with nothing to catch you. Keeping the copies and gating them, rather
25
+ * than moving the DDL into the kernel, is the recorded answer to #969;
26
+ * `docs/architecture/kernel-design.md` §8 says why.
27
+ */
8
28
  const KERNEL_DDL = `
9
29
  CREATE TABLE IF NOT EXISTS _substrat_outbox (
10
30
  id TEXT PRIMARY KEY,
@@ -34,11 +54,28 @@ const KERNEL_DDL = `
34
54
  -- event was emitted (the signals version dimension, #1231). NULL = the host was
35
55
  -- handed no version id, or the row predates the column.
36
56
  version TEXT,
57
+ -- #1237: the event this one was emitted in REACTION to — set whenever an emit
58
+ -- happens while this scope is delivering an event to a module consumer. The
59
+ -- spine already recorded what authority an operation held and what invocation
60
+ -- it ran under; neither is cause, and a consumer emit has no operation at all,
61
+ -- so a backwards walk used to stop dead at the first consumer hop. NULL =
62
+ -- nothing was being delivered (an operation emitted it directly), or the row
63
+ -- predates the column.
64
+ caused_by TEXT,
37
65
  drained_at TEXT
38
66
  );
39
67
  -- #1232: the freshness evaluator's read — MAX(occurred_at) per type, every pass,
40
68
  -- over a table that is never pruned. Unindexed, that is a full history scan.
41
69
  CREATE INDEX IF NOT EXISTS _substrat_outbox_type_at ON _substrat_outbox (type, occurred_at);
70
+ -- #1334: the drain's read — WHERE drained_at IS NULL ORDER BY id. No existing index
71
+ -- starts with drained_at, so without this one SQLite walks the PRIMARY KEY from the
72
+ -- oldest event forward, stepping over every row a previous pass already shipped. A
73
+ -- drain RETAINS what it marks, so that prefix only grows: the cost of finding the next
74
+ -- batch would rise with the scope's lifetime event count rather than with how far
75
+ -- behind the drain is. Leading with drained_at makes the undrained rows a seekable
76
+ -- range, and the trailing id gives the ORDER BY for free. IF NOT EXISTS in
77
+ -- KERNEL_DDL, which every runtime() re-runs, so existing scopes get it on next wake.
78
+ CREATE INDEX IF NOT EXISTS _substrat_outbox_drained ON _substrat_outbox (drained_at, id);
42
79
  -- platform-intents.md: durable intents a vertical enqueues (ctx.requestPlatform) for the platform
43
80
  -- to drain and execute with HostAdmin authority — the sandbox-clean way a vertical asks for a
44
81
  -- privileged action. Written by the kernel (spine), settled by the platform drain.
@@ -405,6 +442,21 @@ export class SqliteScopeHost {
405
442
  * schema backwards and the next read of a newer column would fail with a bare `no such
406
443
  * column`. Every statement is IF NOT EXISTS and `ensureDirectoryColumns` probes before
407
444
  * it alters, so this only ever adds back what a copy did not carry.
445
+ *
446
+ * It is also one of two hand-written copies of the directory spine (#969). The other is
447
+ * `DIRECTORY_DDL` in `adapter-cloudflare/src/control-plane-do.ts`, and the two must
448
+ * describe the same schema: this side is dev, CI, self-host and escrow, that side is
449
+ * production. So a new directory table both adapters need, or a new column on a table
450
+ * they both build, is added HERE and THERE — and for a directory created before it, to
451
+ * both adapters' `ensureDirectoryColumns` as well.
452
+ * `pnpm lint:spine-ddl` (`tools/spine-ddl-drift.mjs`) refuses a divergence by executing
453
+ * each side's DDL plus those later ALTERs and comparing the schemas a query would
454
+ * actually meet — columns, indexes and foreign keys. What it does not judge: a table
455
+ * present on one side only, which it reports as a note because the adapters legitimately
456
+ * partition the spine differently, and triggers or CHECK constraints, which it does not
457
+ * compare at all (the spine has none).
458
+ * Keeping the copies and gating them, rather than moving the DDL into the kernel, is the
459
+ * recorded answer to #969; `docs/architecture/kernel-design.md` §8 says why.
408
460
  */
409
461
  applyDirectorySchema() {
410
462
  this.directory.exec(`
@@ -2784,6 +2836,11 @@ export class SqliteScopeHost {
2784
2836
  const ctx = this.operationContext(rt, asPrincipal(this.systemPrincipal), {
2785
2837
  system: mod.id,
2786
2838
  });
2839
+ // #1237: anything this consumer emits was emitted BECAUSE of this event.
2840
+ // `dispatchExecutors` already did this for the admin log; the module
2841
+ // consumers never did, which is exactly where a backwards walk stopped.
2842
+ // On the runtime rather than the host — see `ScopeRuntime.causedBy`.
2843
+ rt.causedBy = event.id;
2787
2844
  rt.db.exec('BEGIN IMMEDIATE');
2788
2845
  try {
2789
2846
  await consumer.handler(ctx, event);
@@ -2803,6 +2860,13 @@ export class SqliteScopeHost {
2803
2860
  VALUES (?, ?, ?, ?)`)
2804
2861
  .run(event.id, mod.id, new Date().toISOString(), String(err));
2805
2862
  }
2863
+ finally {
2864
+ // Cleared on BOTH paths. Left set, the id would leak onto every later
2865
+ // emit in this scope — an operation's own event stamped as caused by
2866
+ // whatever happened to be delivered last, which is worse than no cause
2867
+ // at all, because it reads as a recorded fact.
2868
+ rt.causedBy = null;
2869
+ }
2806
2870
  }
2807
2871
  }
2808
2872
  }
@@ -4119,6 +4183,62 @@ export class SqliteScopeHost {
4119
4183
  .run(expiresAt, scopeId);
4120
4184
  this.recordAdmin(actor, 'setScopeExpiresAt', { tenantId, scopeId }, { expiresAt: scope.expires_at }, { expiresAt });
4121
4185
  },
4186
+ readUndrainedEvents: async (actor, tenantId, scopeId, limit) => {
4187
+ const db = this.scopeDbFor(tenantId, scopeId);
4188
+ const rows = db
4189
+ .prepare(`SELECT * FROM _substrat_outbox WHERE drained_at IS NULL ORDER BY id LIMIT ?`)
4190
+ .all(Math.min(Math.max(limit ?? 200, 1), 1000));
4191
+ this.recordAccess(actor, 'readUndrainedEvents', { tenantId, scopeId }, { limit }, rows.length);
4192
+ return rows.map((r) => ({
4193
+ ...this.parseOutboxRow(r),
4194
+ operation: r.operation ?? null,
4195
+ version: r.version ?? null,
4196
+ }));
4197
+ },
4198
+ markEventsDrained: async (actor, tenantId, scopeId, eventIds) => {
4199
+ // BEFORE the empty-batch shortcut, not after. "Nothing to mark" and "you may
4200
+ // not address this scope" are different answers, and returning 0 for an
4201
+ // unknown pair would make a caller's typo look like a successful no-op —
4202
+ // while the Cloudflare host, which resolves the record first, throws. An
4203
+ // empty batch must not open the scope file either, which is why this is the
4204
+ // directory-only check rather than `scopeDbFor`.
4205
+ this.assertScopeReachable(tenantId, scopeId);
4206
+ if (eventIds.length === 0)
4207
+ return 0;
4208
+ const db = this.scopeDbFor(tenantId, scopeId);
4209
+ const at = new Date().toISOString();
4210
+ // Only an UNDRAINED row is stamped, so a replayed batch cannot move an
4211
+ // earlier drain's timestamp forward and misreport when it shipped.
4212
+ const stmt = db.prepare(`UPDATE _substrat_outbox SET drained_at = ? WHERE id = ? AND drained_at IS NULL`);
4213
+ // All-or-nothing. A PARTIAL mark is the one failure the ship→stamp order
4214
+ // cannot absorb: the next read resumes from the middle of a batch already
4215
+ // in the lake, and the object it writes overlaps the one already there —
4216
+ // the duplication the first-id object key exists to prevent.
4217
+ const drained = db.transaction(() => {
4218
+ let n = 0;
4219
+ for (const id of eventIds)
4220
+ n += stmt.run(at, id).changes;
4221
+ return n;
4222
+ })();
4223
+ // K-24's rule, one tier down (#1334): declaring domain payloads shipped is an
4224
+ // EGRESS, and the admin log is where "these events left the platform, at this
4225
+ // time, on this actor's say-so" is recorded. `drainAccessLog` audits the same
4226
+ // act for the smaller class of data; this one carries the larger. Only a
4227
+ // NONZERO change is recorded, so a retried pass that re-marks a batch it
4228
+ // already shipped writes no row claiming an egress that never happened.
4229
+ if (drained > 0) {
4230
+ this.recordAdmin(actor, 'drainEvents', { tenantId, scopeId }, null, { drained, requested: eventIds.length, drainedAt: at });
4231
+ }
4232
+ return drained;
4233
+ },
4234
+ facetEvents: async (actor, tenantId, scopeId, input) => {
4235
+ // `facetEvents` is the sanctioned read: an erased payload yields the same
4236
+ // NULL a missing field does, and only the helper counts them apart.
4237
+ const db = this.scopeDbFor(tenantId, scopeId);
4238
+ const result = facetEvents({ sql: scopedSql(db) }, input);
4239
+ this.recordAccess(actor, 'facetEvents', { tenantId, scopeId }, input, result.buckets.length);
4240
+ return result;
4241
+ },
4122
4242
  entityHistory: async (actor, tenantId, scopeId, input) => {
4123
4243
  // `readHistory` over this scope's own outbox — the sanctioned read, and the
4124
4244
  // only one that decodes the envelope's nullable facts (an erased payload, an
@@ -5942,13 +6062,18 @@ export class SqliteScopeHost {
5942
6062
  .prepare(`INSERT INTO _substrat_outbox
5943
6063
  (id, type, schema_version, occurred_at, tenant_id, scope_id, actor,
5944
6064
  entity_type, entity_id, pii_class, subject_id, authorization,
5945
- impersonation, operation, version, payload)
5946
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
6065
+ impersonation, operation, version, caused_by, payload)
6066
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
5947
6067
  .run(full.id, full.type, full.schemaVersion, full.occurredAt, full.tenantId, full.scopeId, JSON.stringify(full.actor), full.entity.entityType, full.entity.entityId, full.piiClass, full.subjectId ?? null, full.authorization ? JSON.stringify(full.authorization) : null, full.impersonation ? JSON.stringify(full.impersonation) : null, full.operation ?? null,
5948
6068
  // #1242: host configuration, not envelope data — the version is a fact
5949
6069
  // about the process, so it never rides `DomainEvent` for module code to
5950
6070
  // branch on; it exists for the observability joins the column serves.
5951
- this.versionId, full.payload === undefined ? null : JSON.stringify(full.payload));
6071
+ this.versionId,
6072
+ // #1237: whatever delivery is in flight FOR THIS SCOPE, if any — read off
6073
+ // the runtime, not the host, because the host serves every scope at once
6074
+ // and this context outlives an `await`. A fact about the surrounding
6075
+ // dispatch, never envelope data module code could set or branch on.
6076
+ rt.causedBy, full.payload === undefined ? null : JSON.stringify(full.payload));
5952
6077
  },
5953
6078
  requestPlatform: (request) => {
5954
6079
  assertImpersonationWrites(impersonation, 'ctx.requestPlatform');
@@ -6228,10 +6353,30 @@ export class SqliteScopeHost {
6228
6353
  * DIFFERENT tenant, throws — the introspection reads never open another tenant's
6229
6354
  * DB, and never CREATE one for an id that was never provisioned.
6230
6355
  */
6231
- scopeDbFor(tenantId, scopeId) {
6232
- const r = this.directory.prepare('SELECT tenant_id FROM scopes WHERE scope_id = ?').get(scopeId);
6356
+ /**
6357
+ * Whether this (tenant, scope) pair may be reached at all — the DIRECTORY half of
6358
+ * `scopeDbFor`, split out so a call that ends up touching no rows still answers the
6359
+ * question. K-3 first: a scope under another tenant is indistinguishable from one
6360
+ * that does not exist. Then the reap.
6361
+ *
6362
+ * A reaped scope keeps its directory row as a tombstone while `reapScope` deletes
6363
+ * the file (§4.4). `runtime()` opens a database by CREATING it when absent, so
6364
+ * reading one would not merely answer emptily — it would put the file back,
6365
+ * resurrecting storage an irreversible reap destroyed, and every later read would
6366
+ * report a scope with no events rather than a scope that is gone. `archived` is
6367
+ * deliberately still reachable: its bytes exist, and that is the whole point of the
6368
+ * state.
6369
+ */
6370
+ assertScopeReachable(tenantId, scopeId) {
6371
+ const r = this.directory.prepare('SELECT tenant_id, status FROM scopes WHERE scope_id = ?').get(scopeId);
6233
6372
  if (!r || r.tenant_id !== tenantId)
6234
6373
  throw new Error(`unknown scope for tenant: (${tenantId}, ${scopeId})`);
6374
+ if (r.status === 'reaped') {
6375
+ throw new Error(`scope ${scopeId} is reaped — its storage is gone and cannot be read`);
6376
+ }
6377
+ }
6378
+ scopeDbFor(tenantId, scopeId) {
6379
+ this.assertScopeReachable(tenantId, scopeId);
6235
6380
  return this.runtime(tenantId, scopeId).db;
6236
6381
  }
6237
6382
  /**
@@ -6273,6 +6418,10 @@ export class SqliteScopeHost {
6273
6418
  // #1242: the signals `version` dimension on the outbox, for a scope DB created
6274
6419
  // before the column. NULL stays honest — unstamped, not a version nobody named.
6275
6420
  this.ensureColumn(db, '_substrat_outbox', 'version', 'version TEXT');
6421
+ // #1237: existing scopes get the cause column on their next wake, like the four
6422
+ // above. Rows already written keep NULL, which is honestly "unrecorded" — the
6423
+ // reason the column is nullable rather than defaulted.
6424
+ this.ensureColumn(db, '_substrat_outbox', 'caused_by', 'caused_by TEXT');
6276
6425
  }
6277
6426
  runtime(tenantId, scopeId) {
6278
6427
  const key = `${tenantId}/${scopeId}`;
@@ -6300,6 +6449,7 @@ export class SqliteScopeHost {
6300
6449
  actor: new ScopeActor(),
6301
6450
  appliedMigrations,
6302
6451
  mintEventId,
6452
+ causedBy: null,
6303
6453
  };
6304
6454
  this.scopes.set(key, created);
6305
6455
  this.scopesById.set(scopeId, created);