@substrat-run/adapter-sqlite 0.107.0 → 0.109.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 +32 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +135 -5
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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;
|
|
@@ -344,8 +359,8 @@ export declare class SqliteScopeHost implements ScopeHost {
|
|
|
344
359
|
* The connection's CURRENT public sealing key, minted on first ask (#687).
|
|
345
360
|
*
|
|
346
361
|
* Mint-on-read rather than mint-only-at-connect, because the fleet already
|
|
347
|
-
* holds live connections older than this feature —
|
|
348
|
-
* carries years of real contracts, and "reconnect to acquire a keypair" is not
|
|
362
|
+
* holds live connections older than this feature — a Scrive credential in the
|
|
363
|
+
* fleet carries years of real contracts, and "reconnect to acquire a keypair" is not
|
|
349
364
|
* a migration anyone should have to run against production. Asking IS the
|
|
350
365
|
* back-fill, and it is idempotent: the partial-unique index makes a race
|
|
351
366
|
* between two callers resolve to one current key rather than two.
|
|
@@ -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.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAcL,KAAK,eAAe,EAuDpB,KAAK,YAAY,EAGjB,KAAK,QAAQ,EAWb,KAAK,WAAW,EAIhB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAI3B,KAAK,wBAAwB,EAW7B,KAAK,eAAe,EACpB,KAAK,WAAW,EAehB,KAAK,SAAS,EAEd,KAAK,OAAO,EAYZ,KAAK,sBAAsB,EAG3B,KAAK,QAAQ,EAGb,KAAK,iBAAiB,EAUvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAcL,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAc1B,KAAK,uBAAuB,EAG5B,KAAK,gBAAgB,EAGrB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EAoCxB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,SAAS,EAEd,KAAK,SAAS,EACd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAErB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EAExB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EAGtB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,gBAAgB,EAGrB,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,
|
|
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, } 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,
|
|
@@ -39,6 +59,15 @@ const KERNEL_DDL = `
|
|
|
39
59
|
-- #1232: the freshness evaluator's read — MAX(occurred_at) per type, every pass,
|
|
40
60
|
-- over a table that is never pruned. Unindexed, that is a full history scan.
|
|
41
61
|
CREATE INDEX IF NOT EXISTS _substrat_outbox_type_at ON _substrat_outbox (type, occurred_at);
|
|
62
|
+
-- #1334: the drain's read — WHERE drained_at IS NULL ORDER BY id. No existing index
|
|
63
|
+
-- starts with drained_at, so without this one SQLite walks the PRIMARY KEY from the
|
|
64
|
+
-- oldest event forward, stepping over every row a previous pass already shipped. A
|
|
65
|
+
-- drain RETAINS what it marks, so that prefix only grows: the cost of finding the next
|
|
66
|
+
-- batch would rise with the scope's lifetime event count rather than with how far
|
|
67
|
+
-- behind the drain is. Leading with drained_at makes the undrained rows a seekable
|
|
68
|
+
-- range, and the trailing id gives the ORDER BY for free. IF NOT EXISTS in
|
|
69
|
+
-- KERNEL_DDL, which every runtime() re-runs, so existing scopes get it on next wake.
|
|
70
|
+
CREATE INDEX IF NOT EXISTS _substrat_outbox_drained ON _substrat_outbox (drained_at, id);
|
|
42
71
|
-- platform-intents.md: durable intents a vertical enqueues (ctx.requestPlatform) for the platform
|
|
43
72
|
-- to drain and execute with HostAdmin authority — the sandbox-clean way a vertical asks for a
|
|
44
73
|
-- privileged action. Written by the kernel (spine), settled by the platform drain.
|
|
@@ -405,6 +434,21 @@ export class SqliteScopeHost {
|
|
|
405
434
|
* schema backwards and the next read of a newer column would fail with a bare `no such
|
|
406
435
|
* column`. Every statement is IF NOT EXISTS and `ensureDirectoryColumns` probes before
|
|
407
436
|
* it alters, so this only ever adds back what a copy did not carry.
|
|
437
|
+
*
|
|
438
|
+
* It is also one of two hand-written copies of the directory spine (#969). The other is
|
|
439
|
+
* `DIRECTORY_DDL` in `adapter-cloudflare/src/control-plane-do.ts`, and the two must
|
|
440
|
+
* describe the same schema: this side is dev, CI, self-host and escrow, that side is
|
|
441
|
+
* production. So a new directory table both adapters need, or a new column on a table
|
|
442
|
+
* they both build, is added HERE and THERE — and for a directory created before it, to
|
|
443
|
+
* both adapters' `ensureDirectoryColumns` as well.
|
|
444
|
+
* `pnpm lint:spine-ddl` (`tools/spine-ddl-drift.mjs`) refuses a divergence by executing
|
|
445
|
+
* each side's DDL plus those later ALTERs and comparing the schemas a query would
|
|
446
|
+
* actually meet — columns, indexes and foreign keys. What it does not judge: a table
|
|
447
|
+
* present on one side only, which it reports as a note because the adapters legitimately
|
|
448
|
+
* partition the spine differently, and triggers or CHECK constraints, which it does not
|
|
449
|
+
* compare at all (the spine has none).
|
|
450
|
+
* Keeping the copies and gating them, rather than moving the DDL into the kernel, is the
|
|
451
|
+
* recorded answer to #969; `docs/architecture/kernel-design.md` §8 says why.
|
|
408
452
|
*/
|
|
409
453
|
applyDirectorySchema() {
|
|
410
454
|
this.directory.exec(`
|
|
@@ -2986,8 +3030,8 @@ export class SqliteScopeHost {
|
|
|
2986
3030
|
* The connection's CURRENT public sealing key, minted on first ask (#687).
|
|
2987
3031
|
*
|
|
2988
3032
|
* Mint-on-read rather than mint-only-at-connect, because the fleet already
|
|
2989
|
-
* holds live connections older than this feature —
|
|
2990
|
-
* carries years of real contracts, and "reconnect to acquire a keypair" is not
|
|
3033
|
+
* holds live connections older than this feature — a Scrive credential in the
|
|
3034
|
+
* fleet carries years of real contracts, and "reconnect to acquire a keypair" is not
|
|
2991
3035
|
* a migration anyone should have to run against production. Asking IS the
|
|
2992
3036
|
* back-fill, and it is idempotent: the partial-unique index makes a race
|
|
2993
3037
|
* between two callers resolve to one current key rather than two.
|
|
@@ -4119,6 +4163,72 @@ export class SqliteScopeHost {
|
|
|
4119
4163
|
.run(expiresAt, scopeId);
|
|
4120
4164
|
this.recordAdmin(actor, 'setScopeExpiresAt', { tenantId, scopeId }, { expiresAt: scope.expires_at }, { expiresAt });
|
|
4121
4165
|
},
|
|
4166
|
+
readUndrainedEvents: async (actor, tenantId, scopeId, limit) => {
|
|
4167
|
+
const db = this.scopeDbFor(tenantId, scopeId);
|
|
4168
|
+
const rows = db
|
|
4169
|
+
.prepare(`SELECT * FROM _substrat_outbox WHERE drained_at IS NULL ORDER BY id LIMIT ?`)
|
|
4170
|
+
.all(Math.min(Math.max(limit ?? 200, 1), 1000));
|
|
4171
|
+
this.recordAccess(actor, 'readUndrainedEvents', { tenantId, scopeId }, { limit }, rows.length);
|
|
4172
|
+
return rows.map((r) => ({
|
|
4173
|
+
...this.parseOutboxRow(r),
|
|
4174
|
+
operation: r.operation ?? null,
|
|
4175
|
+
version: r.version ?? null,
|
|
4176
|
+
}));
|
|
4177
|
+
},
|
|
4178
|
+
markEventsDrained: async (actor, tenantId, scopeId, eventIds) => {
|
|
4179
|
+
// BEFORE the empty-batch shortcut, not after. "Nothing to mark" and "you may
|
|
4180
|
+
// not address this scope" are different answers, and returning 0 for an
|
|
4181
|
+
// unknown pair would make a caller's typo look like a successful no-op —
|
|
4182
|
+
// while the Cloudflare host, which resolves the record first, throws. An
|
|
4183
|
+
// empty batch must not open the scope file either, which is why this is the
|
|
4184
|
+
// directory-only check rather than `scopeDbFor`.
|
|
4185
|
+
this.assertScopeReachable(tenantId, scopeId);
|
|
4186
|
+
if (eventIds.length === 0)
|
|
4187
|
+
return 0;
|
|
4188
|
+
const db = this.scopeDbFor(tenantId, scopeId);
|
|
4189
|
+
const at = new Date().toISOString();
|
|
4190
|
+
// Only an UNDRAINED row is stamped, so a replayed batch cannot move an
|
|
4191
|
+
// earlier drain's timestamp forward and misreport when it shipped.
|
|
4192
|
+
const stmt = db.prepare(`UPDATE _substrat_outbox SET drained_at = ? WHERE id = ? AND drained_at IS NULL`);
|
|
4193
|
+
// All-or-nothing. A PARTIAL mark is the one failure the ship→stamp order
|
|
4194
|
+
// cannot absorb: the next read resumes from the middle of a batch already
|
|
4195
|
+
// in the lake, and the object it writes overlaps the one already there —
|
|
4196
|
+
// the duplication the first-id object key exists to prevent.
|
|
4197
|
+
const drained = db.transaction(() => {
|
|
4198
|
+
let n = 0;
|
|
4199
|
+
for (const id of eventIds)
|
|
4200
|
+
n += stmt.run(at, id).changes;
|
|
4201
|
+
return n;
|
|
4202
|
+
})();
|
|
4203
|
+
// K-24's rule, one tier down (#1334): declaring domain payloads shipped is an
|
|
4204
|
+
// EGRESS, and the admin log is where "these events left the platform, at this
|
|
4205
|
+
// time, on this actor's say-so" is recorded. `drainAccessLog` audits the same
|
|
4206
|
+
// act for the smaller class of data; this one carries the larger. Only a
|
|
4207
|
+
// NONZERO change is recorded, so a retried pass that re-marks a batch it
|
|
4208
|
+
// already shipped writes no row claiming an egress that never happened.
|
|
4209
|
+
if (drained > 0) {
|
|
4210
|
+
this.recordAdmin(actor, 'drainEvents', { tenantId, scopeId }, null, { drained, requested: eventIds.length, drainedAt: at });
|
|
4211
|
+
}
|
|
4212
|
+
return drained;
|
|
4213
|
+
},
|
|
4214
|
+
facetEvents: async (actor, tenantId, scopeId, input) => {
|
|
4215
|
+
// `facetEvents` is the sanctioned read: an erased payload yields the same
|
|
4216
|
+
// NULL a missing field does, and only the helper counts them apart.
|
|
4217
|
+
const db = this.scopeDbFor(tenantId, scopeId);
|
|
4218
|
+
const result = facetEvents({ sql: scopedSql(db) }, input);
|
|
4219
|
+
this.recordAccess(actor, 'facetEvents', { tenantId, scopeId }, input, result.buckets.length);
|
|
4220
|
+
return result;
|
|
4221
|
+
},
|
|
4222
|
+
entityHistory: async (actor, tenantId, scopeId, input) => {
|
|
4223
|
+
// `readHistory` over this scope's own outbox — the sanctioned read, and the
|
|
4224
|
+
// only one that decodes the envelope's nullable facts (an erased payload, an
|
|
4225
|
+
// unrecorded authorization chain, nobody impersonating) as facts rather than
|
|
4226
|
+
// as missing data.
|
|
4227
|
+
const db = this.scopeDbFor(tenantId, scopeId);
|
|
4228
|
+
const page = readHistory({ sql: scopedSql(db) }, { entityType: input.entityType, entityId: input.entityId }, { limit: input.limit, cursor: input.cursor });
|
|
4229
|
+
this.recordAccess(actor, 'entityHistory', { tenantId, scopeId }, { entityType: input.entityType, entityId: input.entityId }, page.entries.length);
|
|
4230
|
+
return page;
|
|
4231
|
+
},
|
|
4122
4232
|
scopeAppliedMigrations: async (actor, tenantId, scopeId) => {
|
|
4123
4233
|
const db = this.scopeDbFor(tenantId, scopeId);
|
|
4124
4234
|
const rows = db
|
|
@@ -6218,10 +6328,30 @@ export class SqliteScopeHost {
|
|
|
6218
6328
|
* DIFFERENT tenant, throws — the introspection reads never open another tenant's
|
|
6219
6329
|
* DB, and never CREATE one for an id that was never provisioned.
|
|
6220
6330
|
*/
|
|
6221
|
-
|
|
6222
|
-
|
|
6331
|
+
/**
|
|
6332
|
+
* Whether this (tenant, scope) pair may be reached at all — the DIRECTORY half of
|
|
6333
|
+
* `scopeDbFor`, split out so a call that ends up touching no rows still answers the
|
|
6334
|
+
* question. K-3 first: a scope under another tenant is indistinguishable from one
|
|
6335
|
+
* that does not exist. Then the reap.
|
|
6336
|
+
*
|
|
6337
|
+
* A reaped scope keeps its directory row as a tombstone while `reapScope` deletes
|
|
6338
|
+
* the file (§4.4). `runtime()` opens a database by CREATING it when absent, so
|
|
6339
|
+
* reading one would not merely answer emptily — it would put the file back,
|
|
6340
|
+
* resurrecting storage an irreversible reap destroyed, and every later read would
|
|
6341
|
+
* report a scope with no events rather than a scope that is gone. `archived` is
|
|
6342
|
+
* deliberately still reachable: its bytes exist, and that is the whole point of the
|
|
6343
|
+
* state.
|
|
6344
|
+
*/
|
|
6345
|
+
assertScopeReachable(tenantId, scopeId) {
|
|
6346
|
+
const r = this.directory.prepare('SELECT tenant_id, status FROM scopes WHERE scope_id = ?').get(scopeId);
|
|
6223
6347
|
if (!r || r.tenant_id !== tenantId)
|
|
6224
6348
|
throw new Error(`unknown scope for tenant: (${tenantId}, ${scopeId})`);
|
|
6349
|
+
if (r.status === 'reaped') {
|
|
6350
|
+
throw new Error(`scope ${scopeId} is reaped — its storage is gone and cannot be read`);
|
|
6351
|
+
}
|
|
6352
|
+
}
|
|
6353
|
+
scopeDbFor(tenantId, scopeId) {
|
|
6354
|
+
this.assertScopeReachable(tenantId, scopeId);
|
|
6225
6355
|
return this.runtime(tenantId, scopeId).db;
|
|
6226
6356
|
}
|
|
6227
6357
|
/**
|