@substrat-run/adapter-sqlite 0.120.0 → 0.121.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 +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +258 -36
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -182,6 +182,18 @@ export declare class SqliteScopeHost implements ScopeHost {
|
|
|
182
182
|
* recorded answer to #969; `docs/architecture/kernel-design.md` §8 says why.
|
|
183
183
|
*/
|
|
184
184
|
private applyDirectorySchema;
|
|
185
|
+
/**
|
|
186
|
+
* `applyDirectorySchema`, plus the backfill of the schedule switch's record (#1674) from
|
|
187
|
+
* the admin log. Whether the table exists is asked BEFORE the DDL creates it, so the
|
|
188
|
+
* backfill runs only on an application that creates the table: once on a directory that
|
|
189
|
+
* never had it, and again after a directory restore whose dump predates it (from that
|
|
190
|
+
* dump's own log). A directory that already holds the table is never backfilled over.
|
|
191
|
+
*
|
|
192
|
+
* One transaction (Copilot review, #1674): the table and its backfill commit together, so
|
|
193
|
+
* a backfill that fails leaves no table behind, and the next start tries again rather than
|
|
194
|
+
* reading an empty table as already migrated.
|
|
195
|
+
*/
|
|
196
|
+
private ensureDirectorySchema;
|
|
185
197
|
registerExecutor(id: string, eventType: string, handler: ExecutorHandler, retry?: ExecutorRetryPolicy): void;
|
|
186
198
|
registerConnector(id: string, eventType: string, handler: ConnectorHandler, options?: ConnectorOptions): void;
|
|
187
199
|
/**
|
|
@@ -201,6 +213,13 @@ export declare class SqliteScopeHost implements ScopeHost {
|
|
|
201
213
|
registerModule(registration: ModuleRegistration): void;
|
|
202
214
|
defineOperation<I, O>(name: string, handler: OperationHandler<I, O>): void;
|
|
203
215
|
provisionScope(actor: PlatformActorId, input: ProvisionScopeInput): Promise<void>;
|
|
216
|
+
/**
|
|
217
|
+
* `HostAdmin.reassertSystemSwitches`' body (#1674), run inside a turn the caller already
|
|
218
|
+
* holds on the scope's actor: every module the directory records OFF on the scope is
|
|
219
|
+
* switched off again. Already off answers `changed: false` and writes nothing, and a
|
|
220
|
+
* module the scope does not hold is left alone. Audited only when something moved.
|
|
221
|
+
*/
|
|
222
|
+
private reassertSwitchesInTurn;
|
|
204
223
|
provisionTenantStore(actor: PlatformActorId, input: TenantStoreProvisionInput): Promise<TenantStoreHandle>;
|
|
205
224
|
openTenantStore(handle: TenantStoreHandle): TenantRelationalStore;
|
|
206
225
|
/** Open (and cache) a per-tenant relational store file by its bare-filename `ref`. */
|
|
@@ -281,6 +300,13 @@ export declare class SqliteScopeHost implements ScopeHost {
|
|
|
281
300
|
/** #1705: this scope's watermark per producer, oldest source first. */
|
|
282
301
|
private importCursors;
|
|
283
302
|
deliverToPeer(tenantId: TenantId, scopeId: ScopeId, raw: ImportBatch): Promise<ImportResult>;
|
|
303
|
+
/**
|
|
304
|
+
* A stored version's manifest, unaudited: the platform reading its own code metadata (#1705).
|
|
305
|
+
* `undefined` when the registry has no such version (under `slug`, when one is named).
|
|
306
|
+
*/
|
|
307
|
+
private manifestJsonOf;
|
|
308
|
+
/** #1705 PR 3: the promote gate's question, over two stored versions (`exportBreaksOf`). */
|
|
309
|
+
private exportBreaksBetween;
|
|
284
310
|
/**
|
|
285
311
|
* The lifecycle gate the capability doors share (#1672): the scope must exist in this
|
|
286
312
|
* tenant, the tenant must be active, the scope must be active — K-3's fail-closed pair
|
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,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import Database from 'better-sqlite3';
|
|
4
|
-
import { accessLogEntry, adminLogEntry, opsFailureEntry, opsFailureFingerprint, issueEntry, FRESHNESS_HEARTBEAT_MINUTES, sweepRunEntry, modelUsageEntry, ATTACHMENT_ADDED, ATTACHMENT_REMOVED, attachmentRecord, createTenantInput, domainEvent, domainEventInput, exportReadInput, importBatch, entitlementGrant, entitlementGrantInput, eventId, meterReading, platformRequestInput, platformRequestId, MAX_PENDING_PLATFORM_REQUESTS, identityLink, identityPool, instant, connection, capabilityGrant, connectionGrant, connectionGrantRecord, connectionSecret, systemGrant, systemSwitch, peerSwitch, verticalCaller, verticalSlug as verticalSlugOf, 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, identityMembership, roleDefinition, scope as scopeSchema, tenant as tenantSchema, tenantRole, subjectShredReceipt, SCOPE_TABLE_PAGE_DEFAULT, SCOPE_TABLE_PAGE_MAX, SCOPE_QUERY_ROW_MAX, verticalServingState, callsOfManifestJson, outboundOfManifestJson, listLimitOf, substratError, assertReplayableDump, delegatedReadRecord, redrainEventsInput, REDRAIN_BATCH, } from '@substrat-run/contracts';
|
|
5
|
-
import { actorOf, asPrincipal, assertAllowed, assertNoSecret, assertPermissionKey, CAPABILITY_DDL, CAPABILITY_EXCHANGE_OPERATION, capabilityAttachmentWriteRefused, capabilityTokenHash, createCapabilityVerbs, exchangeCapability as exchangeCapabilitySecret, guardSecrets, mintBecomeCapability, plausibleSessionToken, redactSecrets, resolveCapabilitySession, revokeCapabilityAsPlatform, assertReadOnlyQuery, attachmentBlobKey, entitlementDenial, foldMeterReading, guardSpine, parseValidationRecords, resolveScopeRecord, ulid, ISSUE_RETENTION_DAYS, OPS_FAILURE_RETENTION_DAYS, SWEEP_RUN_RETENTION_DAYS, SWEEP_RUNS_INTENT_INDEX, sweepRunsIntentHasKind, MODEL_USAGE_RETENTION_DAYS, foldModelUsage, backoffAt, platformRequestHistoryQuery, platformRequestOf, PLATFORM_REQUEST_COLUMNS, PLATFORM_REQUEST_REDACTION_SQL, platformRequestRedactionParams, platformRequestRedactionQuery, intentPayloadCarriesSubject, redactSubjectJobRuns, JOB_RUN_PATCH_SQL, JOB_STEP_RECORD_SQL, DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, seatScopeTuple, switchSystemSchedules, admitPeer, collectPeers, peerSeats, resolveVerticalInstanceFrom, switchPeer, peerGrantsStatus, systemGrantsStatus, systemScheduleState, systemSwitchedOff, systemSwitchedOffMessage, denialListQuery, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialSummaryBuckets, IMPERSONATION_COLUMNS, IMPERSONATION_DDL, ImpersonationRefused, assertImpersonationWrites, assertModuleEnqueueableKind, 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, SCHEDULE_STATE_DDL, SCHEDULE_STATE_REBUILD, scheduleStateHasKind, VERTICAL_EVENTS_DDL, EXPORT_HOPS_SQL, IMPORT_CURSORS_SQL, IMPORT_CURSOR_OF_SQL, IMPORT_CURSOR_ADVANCE_SQL, IMPORT_RECORD_SQL, CrossVerticalRegistry, exportReadPlan, exportReadQuery, planExportBatch, withheldNote, JOB_RUN_DDL, jobRunListLimit, jobRunOf, runDueJobRuns, startJobRun, IDEMPOTENCY_DDL, assertIdempotencyKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, globalFetch, createUlid, facetEvents, readHistory, walkEventCause, walkEventEffects, assertRedrainWindow, readInvocation, readDeadLetters, domainEventOf, readUndrainedOutbox, undrainedEventsOf, connectorCallRecord, noopConnectorCallRecorder, recordConnectorCall, settleConnectionUse, } from '@substrat-run/kernel';
|
|
4
|
+
import { accessLogEntry, adminLogEntry, opsFailureEntry, opsFailureFingerprint, issueEntry, FRESHNESS_HEARTBEAT_MINUTES, sweepRunEntry, modelUsageEntry, ATTACHMENT_ADDED, ATTACHMENT_REMOVED, attachmentRecord, createTenantInput, domainEvent, domainEventInput, exportReadInput, importBatch, importCursorMove, exportsOfManifestJson, importsOfManifestJson, entitlementGrant, entitlementGrantInput, eventId, meterReading, platformRequestInput, platformRequestId, MAX_PENDING_PLATFORM_REQUESTS, identityLink, identityPool, instant, connection, capabilityGrant, connectionGrant, connectionGrantRecord, connectionSecret, systemGrant, systemSwitch, systemSwitchRecord, peerSwitch, verticalCaller, verticalSlug as verticalSlugOf, 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, identityMembership, roleDefinition, scope as scopeSchema, tenant as tenantSchema, tenantRole, subjectShredReceipt, SCOPE_TABLE_PAGE_DEFAULT, SCOPE_TABLE_PAGE_MAX, SCOPE_QUERY_ROW_MAX, verticalServingState, callsOfManifestJson, outboundOfManifestJson, listLimitOf, substratError, assertReplayableDump, delegatedReadRecord, redrainEventsInput, REDRAIN_BATCH, } from '@substrat-run/contracts';
|
|
5
|
+
import { actorOf, asPrincipal, assertAllowed, assertNoSecret, assertPermissionKey, CAPABILITY_DDL, CAPABILITY_EXCHANGE_OPERATION, capabilityAttachmentWriteRefused, capabilityTokenHash, createCapabilityVerbs, exchangeCapability as exchangeCapabilitySecret, guardSecrets, mintBecomeCapability, plausibleSessionToken, redactSecrets, resolveCapabilitySession, revokeCapabilityAsPlatform, assertReadOnlyQuery, attachmentBlobKey, entitlementDenial, foldMeterReading, guardSpine, guardSqlLimits, parseValidationRecords, resolveScopeRecord, ulid, ISSUE_RETENTION_DAYS, OPS_FAILURE_RETENTION_DAYS, SWEEP_RUN_RETENTION_DAYS, SWEEP_RUNS_INTENT_INDEX, sweepRunsIntentHasKind, MODEL_USAGE_RETENTION_DAYS, foldModelUsage, backoffAt, platformRequestHistoryQuery, platformRequestOf, PLATFORM_REQUEST_COLUMNS, PLATFORM_REQUEST_REDACTION_SQL, platformRequestRedactionParams, platformRequestRedactionQuery, intentPayloadCarriesSubject, redactSubjectJobRuns, JOB_RUN_PATCH_SQL, JOB_STEP_RECORD_SQL, DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, seatScopeTuple, switchSystemSchedules, admitPeer, collectPeers, peerSeats, resolveVerticalInstanceFrom, switchPeer, peerGrantsStatus, systemGrantsStatus, systemScheduleState, systemSwitchedOff, systemSwitchedOffMessage, SYSTEM_SWITCHES_BACKFILL_SQL, SYSTEM_SWITCHES_DDL, forgetSystemSwitchesOf, listSystemSwitchRecords, recordSystemSwitchedOff, recordSystemSwitchedOn, restoreSystemSwitchRecord, switchedOffModulesOf, systemSwitchRecordsOf, systemSwitchesTableExists, withRecorded, denialListQuery, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialSummaryBuckets, IMPERSONATION_COLUMNS, IMPERSONATION_DDL, ImpersonationRefused, assertImpersonationWrites, assertModuleEnqueueableKind, assertSessionUsable, impersonationByIdQuery, impersonationListQuery, impersonationRowValues, impersonationStampOf, mapImpersonationRow, newImpersonationSession, resolveRetryPolicy, isSecretBoxConfigured, unconfiguredSecretBox, createSubjectKeys, generateSealingKeyPair, noSealingKeyMessage, openSealed, sealTo, ConnectionSealingKeyUnavailableError, PermissionDenied, createAtomic, NotSearchable, NotListable, listIndexMigrations, listIndexPlans, listQuery, cursorOf, moduleMigrations, isSearchIndexTable, searchIndexDdl, searchIndexMigrations, searchIndexPlans, searchLimit, searchMatchExpression, searchQuery, entityVersionQuery, entityVersionOf, assertIfMatch, OUTBOX_ENTITY_INDEX, SCHEDULE_STATE_DDL, SCHEDULE_STATE_REBUILD, scheduleStateHasKind, VERTICAL_EVENTS_DDL, EXPORT_HOPS_SQL, IMPORT_CURSORS_SQL, IMPORT_CURSOR_OF_SQL, OUTBOX_MARK_SQL, exportedSinceQuery, IMPORT_CURSOR_ADVANCE_SQL, IMPORT_RECORD_SQL, moveImportCursor, importCursorSourceOf, exportBreaksOf, exportBreakRefusal, CrossVerticalRegistry, exportReadPlan, exportReadQuery, planExportBatch, withheldNote, JOB_RUN_DDL, jobRunListLimit, jobRunOf, runDueJobRuns, startJobRun, IDEMPOTENCY_DDL, assertIdempotencyKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, globalFetch, createUlid, facetEvents, readHistory, walkEventCause, walkEventEffects, assertRedrainWindow, readInvocation, readDeadLetters, domainEventOf, readUndrainedOutbox, undrainedEventsOf, connectorCallRecord, noopConnectorCallRecorder, recordConnectorCall, settleConnectionUse, } from '@substrat-run/kernel';
|
|
6
6
|
import { ScopeActor } from './actor.js';
|
|
7
7
|
import { createTupleChecker } from './checker.js';
|
|
8
8
|
/** The kernel's schedule-switch SQL (#1666), over one scope's database handle. */
|
|
@@ -512,7 +512,7 @@ export class SqliteScopeHost {
|
|
|
512
512
|
mkdirSync(this.dir, { recursive: true });
|
|
513
513
|
this.directory = new Database(join(this.dir, '_directory.sqlite'));
|
|
514
514
|
this.directory.pragma('journal_mode = WAL');
|
|
515
|
-
this.
|
|
515
|
+
this.ensureDirectorySchema();
|
|
516
516
|
this.loadRoles();
|
|
517
517
|
this.checker =
|
|
518
518
|
options.checker ??
|
|
@@ -968,6 +968,7 @@ export class SqliteScopeHost {
|
|
|
968
968
|
PRIMARY KEY (scope_id, subject_id)
|
|
969
969
|
);
|
|
970
970
|
${IMPERSONATION_DDL}
|
|
971
|
+
${SYSTEM_SWITCHES_DDL}
|
|
971
972
|
CREATE TABLE IF NOT EXISTS _substrat_admin_log (
|
|
972
973
|
id TEXT PRIMARY KEY,
|
|
973
974
|
actor TEXT NOT NULL,
|
|
@@ -1101,6 +1102,25 @@ export class SqliteScopeHost {
|
|
|
1101
1102
|
`);
|
|
1102
1103
|
this.ensureDirectoryColumns();
|
|
1103
1104
|
}
|
|
1105
|
+
/**
|
|
1106
|
+
* `applyDirectorySchema`, plus the backfill of the schedule switch's record (#1674) from
|
|
1107
|
+
* the admin log. Whether the table exists is asked BEFORE the DDL creates it, so the
|
|
1108
|
+
* backfill runs only on an application that creates the table: once on a directory that
|
|
1109
|
+
* never had it, and again after a directory restore whose dump predates it (from that
|
|
1110
|
+
* dump's own log). A directory that already holds the table is never backfilled over.
|
|
1111
|
+
*
|
|
1112
|
+
* One transaction (Copilot review, #1674): the table and its backfill commit together, so
|
|
1113
|
+
* a backfill that fails leaves no table behind, and the next start tries again rather than
|
|
1114
|
+
* reading an empty table as already migrated.
|
|
1115
|
+
*/
|
|
1116
|
+
ensureDirectorySchema() {
|
|
1117
|
+
this.directory.transaction(() => {
|
|
1118
|
+
const switchRecordIsNew = !systemSwitchesTableExists(switchSqlOf(this.directory));
|
|
1119
|
+
this.applyDirectorySchema();
|
|
1120
|
+
if (switchRecordIsNew)
|
|
1121
|
+
this.directory.exec(SYSTEM_SWITCHES_BACKFILL_SQL);
|
|
1122
|
+
})();
|
|
1123
|
+
}
|
|
1104
1124
|
registerExecutor(id, eventType, handler, retry) {
|
|
1105
1125
|
if (this.executors.has(id))
|
|
1106
1126
|
throw new Error(`executor '${id}' is already registered`);
|
|
@@ -1303,7 +1323,9 @@ export class SqliteScopeHost {
|
|
|
1303
1323
|
this.crossVertical.register(manifest, registration.imports);
|
|
1304
1324
|
this.modules.set(manifest.id, {
|
|
1305
1325
|
id: manifest.id,
|
|
1306
|
-
|
|
1326
|
+
// The order the kernel writes once (#1677): authored, then search, then list indexes.
|
|
1327
|
+
// The two derived sets are still computed above, for the duplicate-version refusals.
|
|
1328
|
+
migrations: moduleMigrations(registration),
|
|
1307
1329
|
consumers,
|
|
1308
1330
|
schedules: manifest.schedules ?? [],
|
|
1309
1331
|
freshness: manifest.freshness ?? [],
|
|
@@ -1484,12 +1506,38 @@ export class SqliteScopeHost {
|
|
|
1484
1506
|
const seat = seatScopeTuple(peer.subject, peer.relation, peer.object, null);
|
|
1485
1507
|
rt.db.prepare(seat.sql).run(...seat.params);
|
|
1486
1508
|
}
|
|
1509
|
+
// #1674: re-assert the recorded OFF positions after the seat, in the same turn
|
|
1510
|
+
// (`system-switch-record.ts`).
|
|
1511
|
+
this.reassertSwitchesInTurn(rt, actor, input.tenantId, input.scopeId);
|
|
1487
1512
|
});
|
|
1488
1513
|
// Audit a real provision only; an idempotent re-provision changed nothing.
|
|
1489
1514
|
if (!existing) {
|
|
1490
1515
|
this.recordAdmin(actor, 'provisionScope', { tenantId: input.tenantId, scopeId: input.scopeId, vertical: record.vertical }, null, record);
|
|
1491
1516
|
}
|
|
1492
1517
|
}
|
|
1518
|
+
/**
|
|
1519
|
+
* `HostAdmin.reassertSystemSwitches`' body (#1674), run inside a turn the caller already
|
|
1520
|
+
* holds on the scope's actor: every module the directory records OFF on the scope is
|
|
1521
|
+
* switched off again. Already off answers `changed: false` and writes nothing, and a
|
|
1522
|
+
* module the scope does not hold is left alone. Audited only when something moved.
|
|
1523
|
+
*/
|
|
1524
|
+
reassertSwitchesInTurn(rt, actor, tenantId, scopeId) {
|
|
1525
|
+
const at = new Date().toISOString();
|
|
1526
|
+
return switchedOffModulesOf(switchSqlOf(this.directory), tenantId, scopeId).map((moduleId) => {
|
|
1527
|
+
const outcome = rt.db.transaction(() => switchSystemSchedules(switchSqlOf(rt.db), { moduleId, scopeId, to: 'off', at }))();
|
|
1528
|
+
if (outcome.changed) {
|
|
1529
|
+
this.recordAdmin(actor, 'reassertSystemSwitch', { tenantId, scopeId }, null, {
|
|
1530
|
+
operationId: ulid(),
|
|
1531
|
+
moduleId,
|
|
1532
|
+
schedules: 'off',
|
|
1533
|
+
phase: 'applied',
|
|
1534
|
+
changed: true,
|
|
1535
|
+
permissions: outcome.permissions,
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
return { moduleId, held: outcome.held, changed: outcome.changed };
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1493
1541
|
async provisionTenantStore(actor, input) {
|
|
1494
1542
|
// Fail closed on an unknown/inactive tenant, exactly as provisionScope does: a store
|
|
1495
1543
|
// for a tenant that does not exist is the same "FK string" hole (§4.1). A store is
|
|
@@ -2078,6 +2126,7 @@ export class SqliteScopeHost {
|
|
|
2078
2126
|
// orphaned bytes with no record (the §9 hazard).
|
|
2079
2127
|
this.directory.prepare('DELETE FROM hostnames WHERE scope_id = ?').run(scopeId);
|
|
2080
2128
|
rmSync(join(this.dir, `${tenantId}__${scopeId}.sqlite`), { force: true });
|
|
2129
|
+
forgetSystemSwitchesOf(switchSqlOf(this.directory), scopeId);
|
|
2081
2130
|
this.directory.prepare('DELETE FROM scopes WHERE scope_id = ?').run(scopeId);
|
|
2082
2131
|
this.recordAdmin(actor, 'deleteSnapshot', { tenantId, scopeId }, null, {
|
|
2083
2132
|
forkedFrom: rec.forkedFrom,
|
|
@@ -2411,6 +2460,34 @@ export class SqliteScopeHost {
|
|
|
2411
2460
|
return result;
|
|
2412
2461
|
});
|
|
2413
2462
|
}
|
|
2463
|
+
/**
|
|
2464
|
+
* A stored version's manifest, unaudited: the platform reading its own code metadata (#1705).
|
|
2465
|
+
* `undefined` when the registry has no such version (under `slug`, when one is named).
|
|
2466
|
+
*/
|
|
2467
|
+
manifestJsonOf(versionId, slug) {
|
|
2468
|
+
const row = (slug === undefined
|
|
2469
|
+
? this.directory.prepare('SELECT manifest_json FROM vertical_versions WHERE id = ?').get(versionId)
|
|
2470
|
+
: this.directory
|
|
2471
|
+
.prepare('SELECT manifest_json FROM vertical_versions WHERE id = ? AND vertical_slug = ?')
|
|
2472
|
+
.get(versionId, slug));
|
|
2473
|
+
return row ? row.manifest_json : undefined;
|
|
2474
|
+
}
|
|
2475
|
+
/** #1705 PR 3: the promote gate's question, over two stored versions (`exportBreaksOf`). */
|
|
2476
|
+
exportBreaksBetween(actor, producer, outgoingId, incomingId) {
|
|
2477
|
+
return exportBreaksOf({
|
|
2478
|
+
admin: this.admin,
|
|
2479
|
+
actor,
|
|
2480
|
+
producer,
|
|
2481
|
+
outgoing: exportsOfManifestJson(this.manifestJsonOf(outgoingId)),
|
|
2482
|
+
incoming: exportsOfManifestJson(this.manifestJsonOf(incomingId)),
|
|
2483
|
+
readImports: async (slug, versionId) => {
|
|
2484
|
+
const manifest = this.manifestJsonOf(versionId, slug);
|
|
2485
|
+
if (manifest === undefined)
|
|
2486
|
+
throw substratError('not_found', `unknown version ${versionId} for vertical '${slug}'`);
|
|
2487
|
+
return importsOfManifestJson(manifest);
|
|
2488
|
+
},
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2414
2491
|
/**
|
|
2415
2492
|
* The lifecycle gate the capability doors share (#1672): the scope must exist in this
|
|
2416
2493
|
* tenant, the tenant must be active, the scope must be active — K-3's fail-closed pair
|
|
@@ -2826,6 +2903,11 @@ export class SqliteScopeHost {
|
|
|
2826
2903
|
// harness only after the enqueue task resolves — i.e. after COMMIT — so a
|
|
2827
2904
|
// rolled-back intent never signals.
|
|
2828
2905
|
const signals = { platformRequests: 0 };
|
|
2906
|
+
// #1705 PR 2: exported-type rows this invoke (and its consumers' tail) committed, for
|
|
2907
|
+
// `onExportedEvents`. Counted only in a deployment that exports something, and never
|
|
2908
|
+
// for a read-only session, which commits nothing.
|
|
2909
|
+
const exportTypes = session?.mode === 'read-only' ? [] : this.crossVertical.exportTypes();
|
|
2910
|
+
let exported = 0;
|
|
2829
2911
|
// #129: read inside the transaction, reported after it commits — a tag
|
|
2830
2912
|
// handed out for writes that were rolled back is a tag nobody may hold.
|
|
2831
2913
|
let committedVersion;
|
|
@@ -2839,6 +2921,9 @@ export class SqliteScopeHost {
|
|
|
2839
2921
|
// `finally` so a later consumer carries none rather than the last caller's,
|
|
2840
2922
|
// which is the leak `causedBy` had to be moved off the host to avoid.
|
|
2841
2923
|
rt.invocationId = invokeOptions?.invocationId ?? null;
|
|
2924
|
+
// The outbox's insertion mark, inside the actor task so nothing else writes between it
|
|
2925
|
+
// and this invoke (`OUTBOX_MARK_SQL` says why rowid and not the id).
|
|
2926
|
+
const exportMark = exportTypes.length > 0 ? Number(rt.db.prepare(OUTBOX_MARK_SQL).get().mark) : null;
|
|
2842
2927
|
try {
|
|
2843
2928
|
// #1672: the capability door's session, re-resolved on EVERY invoke and inside the
|
|
2844
2929
|
// actor task — so nothing can revoke between this read and the transaction below.
|
|
@@ -2986,6 +3071,10 @@ export class SqliteScopeHost {
|
|
|
2986
3071
|
await this.dispatch(rt, rt.invocationId);
|
|
2987
3072
|
await this.dispatchExecutors(rt, rt.invocationId);
|
|
2988
3073
|
}
|
|
3074
|
+
if (exportMark !== null && !replayed) {
|
|
3075
|
+
const q = exportedSinceQuery(exportTypes, exportMark);
|
|
3076
|
+
exported = Number(rt.db.prepare(q.sql).get(...q.params).n);
|
|
3077
|
+
}
|
|
2989
3078
|
return structuredClone(result);
|
|
2990
3079
|
}
|
|
2991
3080
|
finally {
|
|
@@ -2999,6 +3088,8 @@ export class SqliteScopeHost {
|
|
|
2999
3088
|
});
|
|
3000
3089
|
if (signals.platformRequests > 0)
|
|
3001
3090
|
options?.onPlatformRequests?.(signals.platformRequests);
|
|
3091
|
+
if (exported > 0)
|
|
3092
|
+
options?.onExportedEvents?.(exported);
|
|
3002
3093
|
if (committedVersion !== undefined)
|
|
3003
3094
|
invokeOptions?.onEntityVersion?.(committedVersion);
|
|
3004
3095
|
if (replayed)
|
|
@@ -4015,27 +4106,34 @@ export class SqliteScopeHost {
|
|
|
4015
4106
|
throw new Error(`illegal scope transition for ${action}: ${row.status} → ${to} ` +
|
|
4016
4107
|
`(allowed from: ${from.join('|')})`);
|
|
4017
4108
|
}
|
|
4018
|
-
//
|
|
4019
|
-
//
|
|
4020
|
-
//
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4109
|
+
// One directory transaction: the status, the audit row and — on a reap — the scope's
|
|
4110
|
+
// switch records (#1674). Reaped is terminal, so a cleanup that failed after the flip
|
|
4111
|
+
// could never be retried; together, a failure leaves the scope where it was.
|
|
4112
|
+
this.directory.transaction(() => {
|
|
4113
|
+
// Stamp/clear archived_at so the reap sweep can age scopes (§4.4). Entering
|
|
4114
|
+
// `archived` records when; `unarchive` (→ active, a restore) clears it so a later
|
|
4115
|
+
// re-archive dates from the new event; `reaped` keeps it as terminal history.
|
|
4116
|
+
if (to === 'archived') {
|
|
4117
|
+
this.directory
|
|
4118
|
+
.prepare('UPDATE scopes SET status = ?, archived_at = ? WHERE scope_id = ?')
|
|
4119
|
+
.run(to, new Date().toISOString(), scopeId);
|
|
4120
|
+
}
|
|
4121
|
+
else if (to === 'active') {
|
|
4122
|
+
this.directory
|
|
4123
|
+
.prepare('UPDATE scopes SET status = ?, archived_at = NULL WHERE scope_id = ?')
|
|
4124
|
+
.run(to, scopeId);
|
|
4125
|
+
}
|
|
4126
|
+
else {
|
|
4127
|
+
this.directory.prepare('UPDATE scopes SET status = ? WHERE scope_id = ?').run(to, scopeId);
|
|
4128
|
+
}
|
|
4129
|
+
if (to === 'reaped')
|
|
4130
|
+
forgetSystemSwitchesOf(switchSqlOf(this.directory), scopeId);
|
|
4131
|
+
// The audit target carries the scope's vertical (control-plane.md §4.4:
|
|
4132
|
+
// "vertical stays null until §4.2 lifecycle actions that name one"). It is
|
|
4133
|
+
// read from the scope rather than passed in, so the trail cannot disagree
|
|
4134
|
+
// with the directory about which deployment the action touched.
|
|
4135
|
+
this.recordAdmin(actor, action, { tenantId, scopeId, vertical: row.vertical }, { status: row.status }, { status: to, ...afterExtra });
|
|
4136
|
+
})();
|
|
4039
4137
|
};
|
|
4040
4138
|
const writeTenantTuple = (tenantId, subject, relation, object, expiresAt) => this.directory
|
|
4041
4139
|
.prepare(`INSERT OR REPLACE INTO _substrat_tenant_tuples
|
|
@@ -4095,6 +4193,44 @@ export class SqliteScopeHost {
|
|
|
4095
4193
|
const target = { tenantId, scopeId };
|
|
4096
4194
|
const base = { operationId, moduleId: input.moduleId, schedules: to };
|
|
4097
4195
|
this.recordAdmin(actor, action, target, null, { ...base, phase: 'intent', reason: input.reason });
|
|
4196
|
+
// The directory's record (#1674): ON before the scope moves, OFF after it held — see
|
|
4197
|
+
// `recordSystemSwitchedOn` for why that order is the safe one.
|
|
4198
|
+
const at = new Date().toISOString();
|
|
4199
|
+
const directorySql = switchSqlOf(this.directory);
|
|
4200
|
+
const record = { tenantId, scopeId, moduleId: input.moduleId, actor, reason: input.reason, operationId, at };
|
|
4201
|
+
const errorOf = (err) => (err instanceof Error ? err.message : String(err));
|
|
4202
|
+
let prior = null;
|
|
4203
|
+
if (to === 'on') {
|
|
4204
|
+
try {
|
|
4205
|
+
prior = recordSystemSwitchedOn(directorySql, record);
|
|
4206
|
+
}
|
|
4207
|
+
catch (err) {
|
|
4208
|
+
// Nothing has moved: fail the call here, audited — the Cloudflare adapter's posture.
|
|
4209
|
+
try {
|
|
4210
|
+
this.recordAdmin(actor, action, target, null, { ...base, phase: 'failed', error: errorOf(err) });
|
|
4211
|
+
}
|
|
4212
|
+
catch {
|
|
4213
|
+
// Best effort: the original error is what the caller must see.
|
|
4214
|
+
}
|
|
4215
|
+
throw err;
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
/** A record write after the scope moved (review #5): retried once, never swallowed. */
|
|
4219
|
+
const recordWrite = (write) => {
|
|
4220
|
+
try {
|
|
4221
|
+
write();
|
|
4222
|
+
return null;
|
|
4223
|
+
}
|
|
4224
|
+
catch {
|
|
4225
|
+
try {
|
|
4226
|
+
write();
|
|
4227
|
+
return null;
|
|
4228
|
+
}
|
|
4229
|
+
catch (err) {
|
|
4230
|
+
return errorOf(err);
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
};
|
|
4098
4234
|
let outcome;
|
|
4099
4235
|
try {
|
|
4100
4236
|
// A turn on the scope actor (#1666 review), exactly as the job store (#1577): `invoke`
|
|
@@ -4103,19 +4239,16 @@ export class SqliteScopeHost {
|
|
|
4103
4239
|
// with it — after this verb had already audited `applied`. `turn` is re-entrant, so a
|
|
4104
4240
|
// caller already inside one of this scope's tasks joins it rather than deadlocking.
|
|
4105
4241
|
const rt = this.runtime(tenantId, scopeId);
|
|
4106
|
-
outcome = await rt.actor.turn(() => rt.db.transaction(() => switchSystemSchedules(switchSqlOf(rt.db), {
|
|
4107
|
-
moduleId: input.moduleId,
|
|
4108
|
-
scopeId,
|
|
4109
|
-
to,
|
|
4110
|
-
at: new Date().toISOString(),
|
|
4111
|
-
}))());
|
|
4242
|
+
outcome = await rt.actor.turn(() => rt.db.transaction(() => switchSystemSchedules(switchSqlOf(rt.db), { moduleId: input.moduleId, scopeId, to, at }))());
|
|
4112
4243
|
}
|
|
4113
4244
|
catch (err) {
|
|
4245
|
+
const recordError = recordWrite(() => restoreSystemSwitchRecord(directorySql, record, prior));
|
|
4114
4246
|
try {
|
|
4115
4247
|
this.recordAdmin(actor, action, target, null, {
|
|
4116
4248
|
...base,
|
|
4117
4249
|
phase: 'failed',
|
|
4118
|
-
error:
|
|
4250
|
+
error: errorOf(err),
|
|
4251
|
+
...(recordError ? { recordError } : {}),
|
|
4119
4252
|
});
|
|
4120
4253
|
}
|
|
4121
4254
|
catch {
|
|
@@ -4123,15 +4256,30 @@ export class SqliteScopeHost {
|
|
|
4123
4256
|
}
|
|
4124
4257
|
throw err;
|
|
4125
4258
|
}
|
|
4259
|
+
// OFF is recorded once the scope held it. A refused ON moved nothing, so its record
|
|
4260
|
+
// write is undone: left `on`, the next reconcile of a wiped scope would leave the
|
|
4261
|
+
// module running.
|
|
4262
|
+
const recordError = to === 'off' && outcome.held
|
|
4263
|
+
? recordWrite(() => recordSystemSwitchedOff(directorySql, record))
|
|
4264
|
+
: to === 'on' && !outcome.held
|
|
4265
|
+
? recordWrite(() => restoreSystemSwitchRecord(directorySql, record, prior))
|
|
4266
|
+
: null;
|
|
4126
4267
|
this.recordAdmin(actor, action, target, null, {
|
|
4127
4268
|
...base,
|
|
4128
4269
|
phase: outcome.held ? 'applied' : 'refused',
|
|
4129
4270
|
changed: outcome.changed,
|
|
4130
4271
|
permissions: outcome.permissions,
|
|
4272
|
+
...(recordError ? { recordError } : {}),
|
|
4131
4273
|
});
|
|
4132
4274
|
if (!outcome.held) {
|
|
4133
4275
|
throw substratError('not_found', `scope ${scopeId} holds no system grant for module '${input.moduleId}' — nothing to switch ${to} ` +
|
|
4134
|
-
`(check the module id: it is the module's manifest id, e.g. '@substrat-run/engine-absence')`
|
|
4276
|
+
`(check the module id: it is the module's manifest id, e.g. '@substrat-run/engine-absence')` +
|
|
4277
|
+
(recordError ? `; and its directory record could not be put back (${recordError})` : ''));
|
|
4278
|
+
}
|
|
4279
|
+
if (recordError) {
|
|
4280
|
+
throw substratError('unavailable', `module '${input.moduleId}' is switched ${to} on scope ${scopeId}, but the directory's record of it ` +
|
|
4281
|
+
`could not be written (${recordError}) — repeat the call to record it; a wipe of this scope would ` +
|
|
4282
|
+
`otherwise lose the switch`);
|
|
4135
4283
|
}
|
|
4136
4284
|
return {
|
|
4137
4285
|
operationId,
|
|
@@ -4267,10 +4415,12 @@ export class SqliteScopeHost {
|
|
|
4267
4415
|
const states = await rt.actor.turn(() => systemGrantsStatus(switchSqlOf(rt.db), new Date().toISOString()));
|
|
4268
4416
|
const offModules = new Set(states.filter((s) => s.schedules === 'off').map((s) => s.moduleId));
|
|
4269
4417
|
const explanations = offModules.size > 0 ? lastSwitchedOff(tenantId, scopeId, offModules) : new Map();
|
|
4270
|
-
const
|
|
4418
|
+
const recorded = systemSwitchRecordsOf(switchSqlOf(this.directory), tenantId, scopeId);
|
|
4419
|
+
const result = withRecorded(states, recorded).map((s) => ({
|
|
4271
4420
|
moduleId: s.moduleId,
|
|
4272
4421
|
schedules: s.schedules,
|
|
4273
4422
|
switchedOff: explanations.get(s.moduleId) ?? null,
|
|
4423
|
+
recorded: s.recorded,
|
|
4274
4424
|
}));
|
|
4275
4425
|
// K-24: reading the switch's position and any live incident reason is itself
|
|
4276
4426
|
// access-logged, the same as every other HostAdmin read.
|
|
@@ -4501,6 +4651,18 @@ export class SqliteScopeHost {
|
|
|
4501
4651
|
// #1674: the switch's status read — same gate, the admin log to explain an `off`
|
|
4502
4652
|
// entry. Nothing to delegate here: the pure adapter's scope storage IS the store.
|
|
4503
4653
|
systemGrantsStatus: systemGrantsStatusOf,
|
|
4654
|
+
// #1674: the switch's directory record — the fleet read, and the re-assert (whose body
|
|
4655
|
+
// is `reassertSwitchesInTurn`; this is the scope check and the turn around it).
|
|
4656
|
+
listSystemSwitches: async (actor, filter) => {
|
|
4657
|
+
const rows = listSystemSwitchRecords(switchSqlOf(this.directory), filter);
|
|
4658
|
+
this.recordAccess(actor, 'listSystemSwitches', { tenantId: filter?.tenantId ?? null }, filter ?? null, rows.length);
|
|
4659
|
+
return rows.map((r) => systemSwitchRecord.parse(r));
|
|
4660
|
+
},
|
|
4661
|
+
reassertSystemSwitches: async (actor, node) => {
|
|
4662
|
+
this.assertScope(node.tenantId, node.scopeId);
|
|
4663
|
+
const rt = this.runtime(node.tenantId, node.scopeId);
|
|
4664
|
+
return rt.actor.turn(() => this.reassertSwitchesInTurn(rt, actor, node.tenantId, node.scopeId));
|
|
4665
|
+
},
|
|
4504
4666
|
peerGrantsStatus: peerGrantsStatusOf,
|
|
4505
4667
|
// #1672 — the platform's two capability verbs, both a turn on the scope actor
|
|
4506
4668
|
// (#1678: every scope-level admin write takes one, so none lands mid-invoke inside a
|
|
@@ -4978,6 +5140,18 @@ export class SqliteScopeHost {
|
|
|
4978
5140
|
note,
|
|
4979
5141
|
});
|
|
4980
5142
|
},
|
|
5143
|
+
promotionImpact: async (actor, verticalSlug, channel, versionId) => {
|
|
5144
|
+
const incoming = readVersion(versionId);
|
|
5145
|
+
if (!incoming || incoming.verticalSlug !== verticalSlug) {
|
|
5146
|
+
throw substratError('not_found', `unknown version ${versionId} for vertical '${verticalSlug}'`);
|
|
5147
|
+
}
|
|
5148
|
+
const current = this.directory
|
|
5149
|
+
.prepare('SELECT version_id FROM vertical_channels WHERE vertical_slug = ? AND channel = ?')
|
|
5150
|
+
.get(verticalSlug, channel);
|
|
5151
|
+
const breaks = current ? await this.exportBreaksBetween(actor, verticalSlug, current.version_id, versionId) : [];
|
|
5152
|
+
this.recordAccess(actor, 'promotionImpact', { tenantId: null }, { verticalSlug, channel, versionId }, breaks.length);
|
|
5153
|
+
return breaks;
|
|
5154
|
+
},
|
|
4981
5155
|
promoteVersion: async (actor, verticalSlug, channel, versionId, acknowledge) => {
|
|
4982
5156
|
const incoming = readVersion(versionId);
|
|
4983
5157
|
if (!incoming)
|
|
@@ -5005,6 +5179,12 @@ export class SqliteScopeHost {
|
|
|
5005
5179
|
throw new Error(`promotion changes migrations (${outgoing.migrationDigest} → ` +
|
|
5006
5180
|
`${incoming.migrationDigest}) — acknowledge it explicitly to promote`);
|
|
5007
5181
|
}
|
|
5182
|
+
// #1705 PR 3: an export an installed consumer imports, dropped or re-versioned.
|
|
5183
|
+
if (!ack.exportBreak) {
|
|
5184
|
+
const breaks = await this.exportBreaksBetween(actor, verticalSlug, outgoing.id, incoming.id);
|
|
5185
|
+
if (breaks.length > 0)
|
|
5186
|
+
throw substratError('precondition_failed', exportBreakRefusal(breaks));
|
|
5187
|
+
}
|
|
5008
5188
|
}
|
|
5009
5189
|
const promotedAt = new Date().toISOString();
|
|
5010
5190
|
this.directory
|
|
@@ -5299,6 +5479,47 @@ export class SqliteScopeHost {
|
|
|
5299
5479
|
this.recordAccess(actor, 'importState', { tenantId, scopeId }, null, cursors.length);
|
|
5300
5480
|
return { consumes, cursors };
|
|
5301
5481
|
},
|
|
5482
|
+
moveImportCursor: async (actor, tenantId, scopeId, raw) => {
|
|
5483
|
+
// #1705 PR 3: the replay lever. Parsed at the door: the acknowledgement literal is part
|
|
5484
|
+
// of the input, and a move without it never reaches the store.
|
|
5485
|
+
const move = importCursorMove.parse(raw);
|
|
5486
|
+
this.assertScopeReachable(tenantId, scopeId);
|
|
5487
|
+
const rec = this.directory.prepare('SELECT vertical FROM scopes WHERE scope_id = ?').get(scopeId);
|
|
5488
|
+
const source = await importCursorSourceOf((t, v) => this.admin.resolveVerticalInstance(t, v), { tenantId, scopeId, vertical: rec.vertical }, move.from);
|
|
5489
|
+
const replayId = ulid();
|
|
5490
|
+
const target = { tenantId, scopeId };
|
|
5491
|
+
const base = { replayId, mode: move.mode, from: move.from, source: source.scopeId };
|
|
5492
|
+
this.recordAdmin(actor, 'moveImportCursor', target, null, { ...base, phase: 'intent', reason: move.reason });
|
|
5493
|
+
let moved;
|
|
5494
|
+
try {
|
|
5495
|
+
const rt = this.runtime(tenantId, scopeId);
|
|
5496
|
+
const now = Date.parse(this.clock());
|
|
5497
|
+
// One turn on the scope actor, which is where `deliverToPeer` runs: a delivery on this
|
|
5498
|
+
// edge is either wholly before the move or refused by its compare-and-set after it.
|
|
5499
|
+
moved = await rt.actor.turn(() => rt.db.transaction(() => moveImportCursor(switchSqlOf(rt.db), { move, source, replayId, now, imports: this.crossVertical.consumes() }))());
|
|
5500
|
+
}
|
|
5501
|
+
catch (err) {
|
|
5502
|
+
try {
|
|
5503
|
+
this.recordAdmin(actor, 'moveImportCursor', target, null, {
|
|
5504
|
+
...base,
|
|
5505
|
+
phase: 'failed',
|
|
5506
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5507
|
+
});
|
|
5508
|
+
}
|
|
5509
|
+
catch {
|
|
5510
|
+
// Best effort: the original error is what the caller must see.
|
|
5511
|
+
}
|
|
5512
|
+
throw err;
|
|
5513
|
+
}
|
|
5514
|
+
this.recordAdmin(actor, 'moveImportCursor', target, null, {
|
|
5515
|
+
...base,
|
|
5516
|
+
phase: 'applied',
|
|
5517
|
+
previous: moved.previous,
|
|
5518
|
+
cursor: moved.cursor,
|
|
5519
|
+
archived: moved.archived,
|
|
5520
|
+
});
|
|
5521
|
+
return moved;
|
|
5522
|
+
},
|
|
5302
5523
|
redrainEvents: async (actor, tenantId, scopeId, input) => {
|
|
5303
5524
|
// Directory check first, on mark's reasoning: "nothing to reopen" and "you may not
|
|
5304
5525
|
// address this scope" are different answers.
|
|
@@ -5854,7 +6075,7 @@ export class SqliteScopeHost {
|
|
|
5854
6075
|
})();
|
|
5855
6076
|
// Carry a copy taken before a directory migration forward to the running
|
|
5856
6077
|
// code's shape — the same assertion a cold start makes.
|
|
5857
|
-
this.
|
|
6078
|
+
this.ensureDirectorySchema();
|
|
5858
6079
|
// Roles are held in memory (`loadRoles`), so a restore that only rewrote the
|
|
5859
6080
|
// table would leave every permission check reading the PRE-restore roles until
|
|
5860
6081
|
// the process restarted — the exact silent-divergence a recovery must not have.
|
|
@@ -6076,6 +6297,7 @@ export class SqliteScopeHost {
|
|
|
6076
6297
|
'_substrat_roles', // operator-defined roles
|
|
6077
6298
|
'_substrat_entitlements', // per-tenant SKU flags
|
|
6078
6299
|
'orgs', // K-22 org records
|
|
6300
|
+
'_substrat_system_switches', // #1674: the schedule switch's record, per scope
|
|
6079
6301
|
];
|
|
6080
6302
|
const clear = this.directory.transaction(() => {
|
|
6081
6303
|
for (const table of tables) {
|
|
@@ -7379,7 +7601,7 @@ export class SqliteScopeHost {
|
|
|
7379
7601
|
tenantId: rt.tenantId,
|
|
7380
7602
|
scopeId: rt.scopeId,
|
|
7381
7603
|
principal,
|
|
7382
|
-
sql: guardSecrets(scopedSql(rt.db), minted),
|
|
7604
|
+
sql: guardSecrets(guardSqlLimits(scopedSql(rt.db)), minted),
|
|
7383
7605
|
now: () => at,
|
|
7384
7606
|
emit: (event) => {
|
|
7385
7607
|
assertImpersonationWrites(impersonation, 'ctx.emit');
|