@substrat-run/adapter-sqlite 0.163.0 → 0.165.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.js CHANGED
@@ -2,8 +2,8 @@ import { createHash } from 'node:crypto';
2
2
  import { closeSync, existsSync, fstatSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, realpathSync, rmSync, writeFileSync, writeSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import Database from 'better-sqlite3';
5
- import { accessLogEntry, adminLogEntry, delegatedReadParams, operationSeriesCount, opsFailureEntry, opsFailureFingerprint, issueEntry, FRESHNESS_HEARTBEAT_MINUTES, sweepRunEntry, modelUsageEntry, ATTACHMENT_ADDED, ATTACHMENT_REMOVED, createTenantInput, domainEvent, domainEventInput, exportReadInput, importBatch, importCursorMove, exportsOfManifestJson, assertNoBindingShapeBreak, importsOfManifestJson, connectionLandingsOfManifestJson, entitlementGrant, entitlementGrantInput, eventId, meterReading, platformRequestInput, platformRequestId, identityLink, identityPool, instant, isSubstratError, connection, capabilityGrant, connectionGrant, connectionGrantRecord, connectionSecret, systemGrant, systemSwitch, systemSwitchRecord, peerSwitch, verticalCaller, verticalSlug as verticalSlugOf, subjectRef, requestFingerprint, createConnectionInput, connectLinkFilter, connectLinkKey, consumeConnectLinkInput, mintConnectLinkInput, projectedConnectionGrant, projectedConnectionKey, moduleManifest, createOrgInput, promotionAcknowledgement, bindAcknowledgement, bindHostnameInput, channelHistoryEntry, hostnameBinding, publishVersionInput, AUTO_ADMISSION_NOTE, routeTarget, ROUTE_SCOPE_HOSTNAMES_MAX, registerVerticalInput, vertical as verticalSchema, verticalChannel, verticalVersion, objectRef, entityObjectRef, assertModuleEmittableType, assertKernelAuthoredType, grantRefFromProof, org as orgSchema, orgMembership, principalId, resolvedIdentity, identityMembership, roleDefinition, scope as scopeSchema, tenant as tenantSchema, tenantRole, principalBecomeCapabilityInput, subjectShredReceipt, SCOPE_TABLE_PAGE_DEFAULT, SCOPE_TABLE_PAGE_MAX, SCOPE_QUERY_ROW_MAX, verticalServingState, deploymentWireAuth, callsOfManifestJson, outboundOfManifestJson, listLimitOf, pageOf, countedPageOf, SCOPE_GATE_REASONS, substratError, assertReplayableDump, delegatedReadRecord, ownerTransferAudit, memberChangeAudit, peerBindingAudit, connectionBindingAudit, copyMarkAudit, orphanSweepAudit, redrainEventsInput, REDRAIN_BATCH, errorCodeOf, } from '@substrat-run/contracts';
6
- import { actorOf, asPrincipal, assertAllowed, assertNoSecret, assertPermissionKey, CAPABILITY_DDL, UPLOAD_LINK_DDL, createAttachmentVerbs, inspectUploadLink, redeemUploadLinkInTransaction, redeemOrReplayUploadLink, COPY_ORIGIN_DDL, ENTITY_STATE_MOVES_DDL, CAPABILITY_EXCHANGE_OPERATION, CAPABILITY_BECOME_MINT_OPERATION, becomeMintCheck, mintBecomeCapabilityAsPrincipal, revokeBecomeCapabilityAsPrincipal, readBecomeLinkStates, ENTITY_UNBIND_OPERATION, allowedFor, unbindEntityHolderBounded, capabilityAttachmentWriteRefused, capabilityTokenHash, createCapabilityVerbs, createEntityEdgeVerbs, createEntityStateVerbs, createTrashedReads, CONTINUATION_POSITION_CAP, visibleContinuation, searchStateWhere, uncheckedView, addStatePlans, addUpdatedStampPlans, watchUpdatedStamps, entityStateMigrations, assertNoCallerPurge, assertPlatformRequestRoom, isUnreachableParent, heldPurgePass, purgeDueOf, purgeHeldBy, lifecycleRefusal, scopeNotHeldRefusal, purgeOnlyKeysOf, purgeReportOf, purgeStillDue, refuseTrashedTarget, assertBoundOperations, checkHeldOperationsOf, countChecks, noteUncheckedCompletion, registerTrashTargets, runPurgePass, withheldKeysFor, statefulTablesOf, afterMigration, afterRuntimeDdl, derivesAnything, repairDerivedObjects, StateColumnLost, exchangeCapability as exchangeCapabilitySecret, guardSecrets, mintBecomeCapability, plausibleSessionToken, readCapabilityPage, redactSecrets, redactSecretText, moduleLog, consoleLogSink, asyncInvocationId, asyncLinePass, consoleInvocationLineSink, resolveCapabilitySession, revokeCapabilityAsPlatform, assertReadOnlyQuery, attachmentBlobKey, attachmentSha256, isSizedByteStream, knownLengthStream, openAttachmentByteStream, putAttachmentBytes, entitlementDenial, requiredEntitlementFor, foldMeterReading, guardSpine, guardSqlLimits, DO_SQL_LIMITS, tooManyResultColumns, tooManyTableColumns, parseValidationRecords, readableScopes, resolveScopeRecord, ulid, ISSUE_RETENTION_DAYS, telemetryRetentionStatements, FINDINGS_DDL, ATTACHMENT_BYTES_SQL, SCOPE_STORAGE_DDL, forgetScopeStorage, listScopeAttachmentStorageRows, retireScopeStorage, listScopeStorageAttemptRows, listScopeStorageRows, listScopeStoreStorageRows, pruneScopeStorageRows, recordScopeStorageRows, REQUEST_ROLLUP_DDL, listRequestDayRows, listRequestRollupDayRows, pruneRequestDayRows, recordRequestDayRows, METER_DAYS_DDL, VERTICAL_OWNERS_BACKFILL, VERTICAL_OWNERS_DDL, closeVerticalOwnerRow, listVerticalOwnerRows, openVerticalOwnerRow, verticalOwnerNow, listMeterDayRows, listMeterDayTakeRows, pruneMeterDayRows, recordMeterDayRows, CONNECT_LINKS_DDL, consumeConnectLinkRow, insertConnectLink, listConnectLinks as listConnectLinkRows, readConnectLink, restoreConnectLinkRow, revokeConnectLinkRow, createFindingRule, findingOfOpsFailure, findingOfSweepRun, pruneFindings, listFindingRules, listFindings, observeFinding, revokeFindingRule, setFindingStatus, assertRowLimit, assertRowOffset, 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, eraseSubjectFromModules, moduleRowsErased, moduleErasurePlan, redactSubjectScopeText, redactSubjectDirectoryText, ISSUE_EXEMPLAR_OWNER_BACKFILL_SQL, issueExemplarOwner, 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_STEP_RECORD_SQL, JOB_LEASE_EXPIRED_NOTE, assertLeaseMs, DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, seatScopeTuple, applyScopeRoleChange, changeScopeRole, revokeScopeRoles, scopeRoleHolders, assertModuleDeclared, noteDeclaredKeys, noteReservedKeys, dumpRoleRefusal, reservedGrantRefusal, reservedRoleDefinitionRefusal, reservedRoleRefusal, reservedShapeRefusal, SCOPE_SEATED_ROLES_SQL, SEAT_HOLDING_SCOPE_STATUSES, TENANT_SEATED_ROLES_SQL, admitPeer, boundToPeerDeclarations, peerCallerAt, collectPeers, peerSeats, resolveVerticalInstanceFrom, resolvePeerInstanceFrom, peerVerticalSpellings, samePeerVertical, peerGrantsStatus, systemGrantsStatus, systemScheduleState, systemSwitchedOff, systemSwitchedOffMessage, tenantSystemSwitchedOffMessage, PEER_SWITCHES_DDL, PEER_BINDINGS_DDL, SWITCH_OWED_DDL, SWITCH_FENCES_DDL, TABLE_OWNERS_DDL, assertMigrationLeavesLedgerAlone, recordOwnershipSteps, runMigrationStatements, executableSqlStatements, blankSqlComments, recordWriteSuperseded, switchFencesOf, switchSupersededMessage, SWITCH_KINDS, SYSTEM_SWITCHES_DDL, forgetSwitchesOf, listSystemSwitchRecords, moveSwitch, reassertActionOf, reassertEntry, reassertOffRow, recordSwitchedOff, recordSwitchedOn, restoreSwitchRecord, scopesSwitchedOffFor, switchActionOf, switchAuditSubject, recordAuditOutcome, auditedCapabilityRevoke, auditWarningOf, switchNotFoundMessage, switchRecordsOf, switchedOffOf, switchesBackfillSqlOf, switchesTableExists, tenantHeldOf, tenantHoldsGrant, inUnitMovesToAudit, staleCarryRevertRow, staleCarryReverts, withRecorded, VERSION_MIGRATIONS_DDL, VERTICAL_SCOPE_STORES_DDL, forgetVerticalScopeStores, DEPLOYMENT_WIRE_AUTH_DDL, assertWireAuthFilter, deploymentWireAuthOf, forgetVerticalWireAuth, wireAuthAccessTarget, writeDeploymentWireAuth, indexVersionScopeStores, driverDeclarationsOf, verticalScopeStoresOf, STORE_DRIVER_RELEASES_DDL, legacyStoreDriverConflict, legacyStoreDriverRenames, beginStoreDriverRolloutIn, finishStoreDriverRolloutIn, holdStoreDriverIn, advanceStoreDriverRebuildIn, claimStoreDriverRebuildIn, storeDriverRebuildIn, recordStoreDriverReleaseIn, releaseStoreDriverHoldIn, storeDriverReleaseIn, storeDriverStateIn, storeDriverRolloutPendingIn, storeDriverRolloutWalkedIn, splitManifestMigrations, splitVersionMigrationsBatch, versionMigrationsOf, deriveVersionMigrationDigest, publishedMigrationDigest, migrationChangeRefusal, writeVersionMigrations, denialListQuery, refusalListQuery, mapRefusalRow, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialSummaryBuckets, IMPERSONATION_COLUMNS, IMPERSONATION_DDL, ImpersonationRefused, assertImpersonationWrites, assertModuleEnqueueableKind, assertSessionUsable, impersonationByIdQuery, impersonationListQuery, impersonationRowValues, impersonationStampOf, mapImpersonationRow, newImpersonationSession, resolveRetryPolicy, executorOutcomeOf, isDeliveryRefusal, refusalJournalText, membershipFencesBackfillSql, MEMBERSHIP_FENCES_DDL, membershipFencesTableExists, MEMBERSHIP_FENCE_SINCE_SQL, RAISE_MEMBERSHIP_FENCE_SQL, memberAddedAudit, orgChangeBound, tenantCoverage, isSecretBoxConfigured, unconfiguredSecretBox, createSubjectKeys, generateSealingKeyPair, noSealingKeyMessage, openSealed, sealTo, ConnectionSealingKeyUnavailableError, PermissionDenied, createAtomic, NotSearchable, NotListable, listIndexMigrations, listIndexPlans, listQuery, cursorOf, moduleMigrations, MIGRATION_DIGEST_FENCE_DDL, MIGRATION_DIGEST_MARK_LEGACY, assertJournalDumpCoherent, assertMigrationSql, migrationDivergence, migrationFailedError, migrationSetDivergence, migrationSteps, planMigrations, isSearchIndexTable, searchIndexMigrations, searchIndexPlans, searchLimit, searchMatchExpression, searchQuery, ATTACHMENT_TEXT_DDL, ATTACHMENT_CONDEMNED_DDL, ATTACHMENT_SWEEP_LICENCE_DDL, ATTACHMENT_SWEEP_LICENCE_TABLE, clearOrphanSweepLicence, condemnLicensedOrphanAttachments, consumeOrphanSweepLicence, licensedOrphanIds, recordOrphanSweepLicence, refuseUnlicensedOrphanSweep, fenceAttachmentSweep, refuseCondemnedAttachment, refuseCondemnedInDump, rowlessAttachmentIds, sweepOrphanAttachments, missingAttachmentStore, SETTLED_COPY_STATES, unsettledCopyRefusal, assertAttachmentExtractors, resolveAttachmentTextBounds, assertJobRegistrable, attachmentRecordOfRow, attachmentTextJob, enqueueAttachmentText, ATTACHMENT_TEXT_BACKFILL_JOB, ATTACHMENT_TEXT_JOB, kernelJobFor, queueAttachmentTextBackfill, startAttachmentTextBackfill, attachmentTextBackfillJob, reconcileAttachmentText, recordAttachmentText, searchAttachments, 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, emittedSinceQuery, emittedReportOf, EMITTED_REPORT_CAP, exportedSinceQuery, IMPORT_CURSOR_ADVANCE_SQL, IMPORT_RECORD_SQL, moveImportCursor, importCursorSourceOf, exportBreaksOf, exportBreakRefusal, assertPromoteReviewCurrent, promoteRaceRefusal, servingRaceRefusal, autoAdmittedProdRefusal, listedPromoteRefusal, rebindRaceRefusal, SERVES_NOW_CONDITION, SERVES_NOW_PREDICATE, servesNowParams, bindExportBreaksOf, bindExportBreakRefusal, scopeMoveOf, runningVersionOf, isPrimaryScope, PROVISIONED_VERSIONS_SQL, installsReconciledOf, strandJudgmentsOf, provisionedVersionsParams, boundShapesColumn, boundShapesOfColumn, installReconciled, CrossVerticalRegistry, exportReadPlan, exportReadQuery, planExportBatch, withheldNote, WIRE_NONCE_DDL, WIRE_NONCE_FULL, claimWireNonceIn, isWireNonceTable, JOB_RUN_DDL, JOB_RUN_DUE_AT, jobRunListLimit, jobRunOf, runDueJobRuns, startJobRun, IDEMPOTENCY_DDL, REFUSALS_TABLE_DDL, REFUSALS_INDEX, refusalInsert, refusalOf, markGuardRefusal, REFUSALS_REBUILD, refusalsAdmitGuards, assertIdempotencyKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, globalFetch, fetchLikeOf, deadlineFetch, createUlid, facetEvents, readHistory, walkEventCause, walkEventEffects, assertRedrainWindow, readInvocation, readDeadLetters, readLifecycleFlow, readOperationSeries, domainEventOf, readUndrainedOutbox, undrainedEventsOf, connectorCallRecord, noopConnectorCallRecorder, recordConnectorCall, settleConnectionUse, unknownRoleError, callReservedStoreOperation, callScopeStore, observeStoreCall, replayStoreErasures, scopeStoreLifecycleOf, scopeStoreRunnersOf, } from '@substrat-run/kernel';
5
+ import { accessLogEntry, adminLogEntry, delegatedReadParams, operationSeriesCount, opsFailureEntry, opsFailureFingerprint, issueEntry, FRESHNESS_HEARTBEAT_MINUTES, sweepRunEntry, modelUsageEntry, ATTACHMENT_ADDED, ATTACHMENT_REMOVED, createTenantInput, domainEvent, domainEventInput, exportReadInput, importBatch, importCursorMove, exportsOfManifestJson, assertNoBindingShapeBreak, importsOfManifestJson, connectionLandingsOfManifestJson, connectorsOfManifestJson, entitlementGrant, entitlementGrantInput, eventId, meterReading, platformRequestInput, platformRequestId, identityLink, identityPool, instant, isSubstratError, connection, capabilityGrant, connectionGrant, connectionGrantRecord, connectionSecret, systemGrant, systemSwitch, systemSwitchRecord, peerSwitch, verticalCaller, verticalSlug as verticalSlugOf, subjectRef, requestFingerprint, createConnectionInput, connectLinkFilter, connectLinkKey, consumeConnectLinkInput, mintConnectLinkInput, projectedConnectionGrant, projectedConnectionKey, moduleManifest, createOrgInput, promotionAcknowledgement, bindAcknowledgement, bindHostnameInput, channelHistoryEntry, hostnameBinding, publishVersionInput, AUTO_ADMISSION_NOTE, routeTarget, ROUTE_SCOPE_HOSTNAMES_MAX, registerVerticalInput, vertical as verticalSchema, verticalChannel, verticalVersion, objectRef, entityObjectRef, assertModuleEmittableType, assertKernelAuthoredType, grantRefFromProof, org as orgSchema, orgMembership, principalId, resolvedIdentity, identityMembership, roleDefinition, scope as scopeSchema, tenant as tenantSchema, tenantRole, principalBecomeCapabilityInput, subjectShredReceipt, SCOPE_TABLE_PAGE_DEFAULT, SCOPE_TABLE_PAGE_MAX, SCOPE_QUERY_ROW_MAX, verticalServingState, deploymentWireAuth, callsOfManifestJson, outboundOfManifestJson, listLimitOf, pageOf, countedPageOf, SCOPE_GATE_REASONS, substratError, assertReplayableDump, delegatedReadRecord, ownerTransferAudit, memberChangeAudit, peerBindingAudit, connectionBindingAudit, copyMarkAudit, orphanSweepAudit, redrainEventsInput, REDRAIN_BATCH, errorCodeOf, } from '@substrat-run/contracts';
6
+ import { actorOf, asPrincipal, assertAllowed, assertNoSecret, assertPermissionKey, CAPABILITY_DDL, UPLOAD_LINK_DDL, createAttachmentVerbs, inspectUploadLink, redeemUploadLinkInTransaction, redeemOrReplayUploadLink, COPY_ORIGIN_DDL, ENTITY_STATE_MOVES_DDL, CAPABILITY_EXCHANGE_OPERATION, CAPABILITY_BECOME_MINT_OPERATION, becomeMintCheck, mintBecomeCapabilityAsPrincipal, revokeBecomeCapabilityAsPrincipal, readBecomeLinkStates, ENTITY_UNBIND_OPERATION, allowedFor, unbindEntityHolderBounded, capabilityAttachmentWriteRefused, capabilityTokenHash, createCapabilityVerbs, createEntityEdgeVerbs, createEntityStateVerbs, createTrashedReads, CONTINUATION_POSITION_CAP, visibleContinuation, searchStateWhere, uncheckedView, addStatePlans, addUpdatedStampPlans, watchUpdatedStamps, entityStateMigrations, assertNoCallerPurge, assertPlatformRequestRoom, isUnreachableParent, heldPurgePass, purgeDueOf, purgeHeldBy, lifecycleRefusal, scopeNotHeldRefusal, purgeOnlyKeysOf, purgeReportOf, purgeStillDue, refuseTrashedTarget, assertBoundOperations, checkHeldOperationsOf, countChecks, noteUncheckedCompletion, registerTrashTargets, runPurgePass, withheldKeysFor, statefulTablesOf, afterMigration, afterRuntimeDdl, derivesAnything, repairDerivedObjects, StateColumnLost, exchangeCapability as exchangeCapabilitySecret, guardSecrets, mintBecomeCapability, plausibleSessionToken, readCapabilityPage, redactSecrets, redactSecretText, moduleLog, consoleLogSink, asyncInvocationId, asyncLinePass, consoleInvocationLineSink, resolveCapabilitySession, revokeCapabilityAsPlatform, assertReadOnlyQuery, attachmentBlobKey, attachmentSha256, isSizedByteStream, knownLengthStream, openAttachmentByteStream, putAttachmentBytes, entitlementDenial, requiredEntitlementFor, foldMeterReading, guardSpine, guardSqlLimits, DO_SQL_LIMITS, tooManyResultColumns, tooManyTableColumns, parseValidationRecords, readableScopes, resolveScopeRecord, ulid, ISSUE_RETENTION_DAYS, telemetryRetentionStatements, FINDINGS_DDL, ATTACHMENT_BYTES_SQL, SCOPE_STORAGE_DDL, forgetScopeStorage, listScopeAttachmentStorageRows, retireScopeStorage, listScopeStorageAttemptRows, listScopeStorageRows, listScopeStoreStorageRows, pruneScopeStorageRows, recordScopeStorageRows, REQUEST_ROLLUP_DDL, listRequestDayRows, listRequestRollupDayRows, pruneRequestDayRows, recordRequestDayRows, METER_DAYS_DDL, VERTICAL_OWNERS_BACKFILL, VERTICAL_OWNERS_DDL, closeVerticalOwnerRow, listVerticalOwnerRows, openVerticalOwnerRow, verticalOwnerNow, listMeterDayRows, listMeterDayTakeRows, pruneMeterDayRows, recordMeterDayRows, CONNECT_LINKS_DDL, consumeConnectLinkRow, insertConnectLink, listConnectLinks as listConnectLinkRows, readConnectLink, restoreConnectLinkRow, revokeConnectLinkRow, createFindingRule, findingOfOpsFailure, findingOfSweepRun, pruneFindings, listFindingRules, listFindings, observeFinding, revokeFindingRule, setFindingStatus, assertRowLimit, assertRowOffset, 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, eraseSubjectFromModules, moduleRowsErased, moduleErasurePlan, redactSubjectScopeText, redactSubjectDirectoryText, ISSUE_EXEMPLAR_OWNER_BACKFILL_SQL, issueExemplarOwner, 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_STEP_RECORD_SQL, JOB_LEASE_EXPIRED_NOTE, assertLeaseMs, DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, seatScopeTuple, applyScopeRoleChange, changeScopeRole, revokeScopeRoles, scopeRoleHolders, assertModuleDeclared, noteDeclaredKeys, noteReservedKeys, dumpRoleRefusal, reservedGrantRefusal, reservedRoleDefinitionRefusal, reservedRoleRefusal, reservedShapeRefusal, SCOPE_SEATED_ROLES_SQL, SEAT_HOLDING_SCOPE_STATUSES, TENANT_SEATED_ROLES_SQL, admitPeer, boundToPeerDeclarations, peerCallerAt, collectPeers, peerSeats, resolveVerticalInstanceFrom, resolvePeerInstanceFrom, peerVerticalSpellings, samePeerVertical, peerGrantsStatus, systemGrantsStatus, systemScheduleState, systemSwitchedOff, systemSwitchedOffMessage, tenantSystemSwitchedOffMessage, PEER_SWITCHES_DDL, PEER_BINDINGS_DDL, SWITCH_OWED_DDL, SWITCH_FENCES_DDL, TABLE_OWNERS_DDL, assertMigrationLeavesLedgerAlone, recordOwnershipSteps, runMigrationStatements, executableSqlStatements, blankSqlComments, recordWriteSuperseded, switchFencesOf, switchSupersededMessage, SWITCH_KINDS, SYSTEM_SWITCHES_DDL, forgetSwitchesOf, listSystemSwitchRecords, moveSwitch, reassertActionOf, reassertEntry, reassertOffRow, recordSwitchedOff, recordSwitchedOn, restoreSwitchRecord, scopesSwitchedOffFor, switchActionOf, switchAuditSubject, recordAuditOutcome, auditedCapabilityRevoke, auditWarningOf, switchNotFoundMessage, switchRecordsOf, switchedOffOf, switchesBackfillSqlOf, switchesTableExists, tenantHeldOf, tenantHoldsGrant, inUnitMovesToAudit, staleCarryRevertRow, staleCarryReverts, withRecorded, VERSION_MIGRATIONS_DDL, VERTICAL_SCOPE_STORES_DDL, forgetVerticalScopeStores, DEPLOYMENT_WIRE_AUTH_DDL, assertWireAuthFilter, deploymentWireAuthOf, forgetVerticalWireAuth, wireAuthAccessTarget, writeDeploymentWireAuth, indexVersionScopeStores, driverDeclarationsOf, verticalScopeStoresOf, STORE_DRIVER_RELEASES_DDL, legacyStoreDriverConflict, legacyStoreDriverRenames, beginStoreDriverRolloutIn, finishStoreDriverRolloutIn, holdStoreDriverIn, advanceStoreDriverRebuildIn, claimStoreDriverRebuildIn, storeDriverRebuildIn, recordStoreDriverReleaseIn, releaseStoreDriverHoldIn, storeDriverReleaseIn, storeDriverStateIn, storeDriverRolloutPendingIn, storeDriverRolloutWalkedIn, splitManifestMigrations, splitVersionMigrationsBatch, versionMigrationsOf, deriveVersionMigrationDigest, publishedMigrationDigest, migrationChangeRefusal, writeVersionMigrations, denialListQuery, refusalListQuery, mapRefusalRow, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialSummaryBuckets, IMPERSONATION_COLUMNS, IMPERSONATION_DDL, ImpersonationRefused, assertImpersonationWrites, assertModuleEnqueueableKind, assertSessionUsable, impersonationByIdQuery, impersonationListQuery, impersonationRowValues, impersonationStampOf, mapImpersonationRow, newImpersonationSession, resolveRetryPolicy, executorOutcomeOf, isDeliveryRefusal, refusalJournalText, membershipFencesBackfillSql, MEMBERSHIP_FENCES_DDL, membershipFencesTableExists, MEMBERSHIP_FENCE_SINCE_SQL, RAISE_MEMBERSHIP_FENCE_SQL, memberAddedAudit, orgChangeBound, tenantCoverage, isSecretBoxConfigured, unconfiguredSecretBox, createSubjectKeys, generateSealingKeyPair, noSealingKeyMessage, openSealed, sealTo, ConnectionSealingKeyUnavailableError, PermissionDenied, createAtomic, NotSearchable, NotListable, listIndexMigrations, listIndexPlans, listQuery, cursorOf, moduleMigrations, MIGRATION_DIGEST_FENCE_DDL, MIGRATION_DIGEST_MARK_LEGACY, assertJournalDumpCoherent, assertMigrationSql, migrationDivergence, migrationFailedError, migrationSetDivergence, migrationSteps, planMigrations, isSearchIndexTable, searchIndexMigrations, searchIndexPlans, searchLimit, searchMatchExpression, searchQuery, ATTACHMENT_TEXT_DDL, ATTACHMENT_CONDEMNED_DDL, ATTACHMENT_SWEEP_LICENCE_DDL, ATTACHMENT_SWEEP_LICENCE_TABLE, clearOrphanSweepLicence, condemnLicensedOrphanAttachments, consumeOrphanSweepLicence, licensedOrphanIds, recordOrphanSweepLicence, refuseUnlicensedOrphanSweep, fenceAttachmentSweep, refuseCondemnedAttachment, refuseCondemnedInDump, rowlessAttachmentIds, sweepOrphanAttachments, missingAttachmentStore, SETTLED_COPY_STATES, unsettledCopyRefusal, assertAttachmentExtractors, resolveAttachmentTextBounds, assertJobRegistrable, attachmentRecordOfRow, attachmentTextJob, enqueueAttachmentText, ATTACHMENT_TEXT_BACKFILL_JOB, ATTACHMENT_TEXT_JOB, kernelJobFor, queueAttachmentTextBackfill, startAttachmentTextBackfill, attachmentTextBackfillJob, reconcileAttachmentText, recordAttachmentText, searchAttachments, 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, emittedSinceQuery, emittedReportOf, EMITTED_REPORT_CAP, exportedSinceQuery, IMPORT_CURSOR_ADVANCE_SQL, IMPORT_RECORD_SQL, moveImportCursor, importCursorSourceOf, exportBreaksOf, exportBreakRefusal, assertPromoteReviewCurrent, promoteRaceRefusal, servingRaceRefusal, autoAdmittedProdRefusal, listedPromoteRefusal, rebindRaceRefusal, SERVES_NOW_CONDITION, SERVES_NOW_PREDICATE, servesNowParams, migratesFrom, servedFromRow, servesAsFrom, bindExportBreaksOf, bindExportBreakRefusal, scopeMoveOf, runningVersionOf, isPrimaryScope, PROVISIONED_VERSIONS_SQL, installsReconciledOf, strandJudgmentsOf, provisionedVersionsParams, boundShapesColumn, boundShapesOfColumn, installReconciled, CrossVerticalRegistry, exportReadPlan, exportReadQuery, planExportBatch, withheldNote, WIRE_NONCE_DDL, WIRE_NONCE_FULL, claimWireNonceIn, isWireNonceTable, JOB_RUN_DDL, JOB_RUN_DUE_AT, jobRunListLimit, jobRunOf, runDueJobRuns, startJobRun, IDEMPOTENCY_DDL, REFUSALS_TABLE_DDL, REFUSALS_INDEX, refusalInsert, refusalOf, markGuardRefusal, REFUSALS_REBUILD, refusalsAdmitGuards, assertIdempotencyKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, globalFetch, fetchLikeOf, deadlineFetch, createUlid, facetEvents, readHistory, walkEventCause, walkEventEffects, assertRedrainWindow, readInvocation, readDeadLetters, readLifecycleFlow, readOperationSeries, domainEventOf, readUndrainedOutbox, undrainedEventsOf, connectorCallRecord, noopConnectorCallRecorder, recordConnectorCall, settleConnectionUse, unknownRoleError, callReservedStoreOperation, callScopeStore, observeStoreCall, replayStoreErasures, scopeStoreLifecycleOf, scopeStoreRunnersOf, } from '@substrat-run/kernel';
7
7
  import { attributedView, delegatedRevokeSql, writeExplicitTupleIn, grantEntityShapeIn, shapeTopUpBatch, topUpEntityGrantShapes, settlePlatformRequestIn, } from '@substrat-run/kernel';
8
8
  import { ADMIN_LOG_INDEXES_SQL, SETTLE_INTENT_SQL, SETTLE_OUTCOME_SQL, settleOutcomeParamsOf, readAuditedOperations, unknownOutcomeOf, } from '@substrat-run/kernel';
9
9
  import { INERT_SCOPE_REASON, isPrimaryScopeRow, normalizeUploadMediaType } from '@substrat-run/kernel';
@@ -2234,11 +2234,10 @@ export class SqliteScopeHost {
2234
2234
  // Physically mint the database now, so a successful mint means the file exists (matching
2235
2235
  // "the platform mints the store"); the vertical then runs its OWN migrations against it.
2236
2236
  this.tenantStoreDb(ref);
2237
- this.directory
2237
+ this.directoryWriteAndAudit(() => this.directory
2238
2238
  .prepare(`INSERT INTO tenant_stores (tenant_id, vertical, binding, kind, ref, created_at)
2239
2239
  VALUES (?, ?, ?, 'relational', ?, ?)`)
2240
- .run(input.tenantId, input.vertical, input.binding, ref, new Date().toISOString());
2241
- this.recordAdmin(actor, 'provisionTenantStore', { tenantId: input.tenantId, vertical: input.vertical }, null, { binding: input.binding, kind: 'relational', ref });
2240
+ .run(input.tenantId, input.vertical, input.binding, ref, new Date().toISOString()).changes > 0, () => this.recordAdmin(actor, 'provisionTenantStore', { tenantId: input.tenantId, vertical: input.vertical }, null, { binding: input.binding, kind: 'relational', ref }));
2242
2241
  return { binding: input.binding, kind: 'relational', ref };
2243
2242
  }
2244
2243
  openTenantStore(handle) {
@@ -2295,11 +2294,10 @@ export class SqliteScopeHost {
2295
2294
  const safeVertical = input.vertical.replace(/[^A-Za-z0-9_-]+/g, '-');
2296
2295
  const ref = `blob__${input.tenantId}__${safeVertical}__${input.binding}__${ulid()}`;
2297
2296
  mkdirSync(join(this.dir, ref), { recursive: true });
2298
- this.directory
2297
+ this.directoryWriteAndAudit(() => this.directory
2299
2298
  .prepare(`INSERT INTO blob_stores (tenant_id, vertical, binding, kind, ref, created_at)
2300
2299
  VALUES (?, ?, ?, 'blob', ?, ?)`)
2301
- .run(input.tenantId, input.vertical, input.binding, ref, new Date().toISOString());
2302
- this.recordAdmin(actor, 'provisionBlobStore', { tenantId: input.tenantId, vertical: input.vertical }, null, { binding: input.binding, kind: 'blob', ref });
2300
+ .run(input.tenantId, input.vertical, input.binding, ref, new Date().toISOString()).changes > 0, () => this.recordAdmin(actor, 'provisionBlobStore', { tenantId: input.tenantId, vertical: input.vertical }, null, { binding: input.binding, kind: 'blob', ref }));
2303
2301
  return { binding: input.binding, kind: 'blob', ref };
2304
2302
  }
2305
2303
  /** A `TenantBlobStore` over the per-tenant directory a `ref` names (#473). Keys are
@@ -3749,6 +3747,13 @@ export class SqliteScopeHost {
3749
3747
  throw substratError('not_found', `unknown version ${versionId} for vertical '${slug}'`);
3750
3748
  return connectionLandingsOfManifestJson(manifest);
3751
3749
  }
3750
+ /** What one version of `slug` declares about vertical-defined connectors (vertical-connectors.md §6.4); `not_found` for a version it does not know. */
3751
+ async versionConnectors(slug, versionId) {
3752
+ const manifest = this.manifestJsonOf(versionId, slug);
3753
+ if (manifest === undefined)
3754
+ throw substratError('not_found', `unknown version ${versionId} for vertical '${slug}'`);
3755
+ return connectorsOfManifestJson(manifest);
3756
+ }
3752
3757
  /** #1705 PR 3: the promote gate's question, over two stored versions (`exportBreaksOf`). */
3753
3758
  exportBreaksBetween(actor, producer, outgoingId, incomingId) {
3754
3759
  return exportBreaksOf({
@@ -7425,6 +7430,9 @@ export class SqliteScopeHost {
7425
7430
  installSpec.provides = parsed.provides;
7426
7431
  if (parsed.requires)
7427
7432
  installSpec.requires = parsed.requires;
7433
+ // The declared vertical-defined connectors (vertical-connectors.md §7.2), the dashboard's door.
7434
+ if (parsed.connectors)
7435
+ installSpec.connectors = parsed.connectors;
7428
7436
  if (parsed.provisions)
7429
7437
  installSpec.provisions = parsed.provisions;
7430
7438
  if (parsed.sendsEmail)
@@ -7732,6 +7740,7 @@ export class SqliteScopeHost {
7732
7740
  ack,
7733
7741
  channel,
7734
7742
  baseId: outgoing?.id ?? null,
7743
+ permissionsChange: outgoing !== undefined && outgoing.permissionDigest !== incoming.permissionDigest,
7735
7744
  migrationsChange: outgoing !== undefined && outgoing.migrationDigest !== incoming.migrationDigest,
7736
7745
  read: async () => ({ base: outgoing ? reviewed(outgoing) : null, incoming: reviewed(incoming) }),
7737
7746
  });
@@ -8084,33 +8093,39 @@ export class SqliteScopeHost {
8084
8093
  migrationTag: r.serving_migration_tag,
8085
8094
  });
8086
8095
  },
8087
- setVerticalServing: async (actor, verticalSlug, state) => {
8096
+ setVerticalServing: async (actor, verticalSlug, state, expected) => {
8088
8097
  const parsed = verticalServingState.parse(state);
8089
8098
  const r = this.directory
8090
8099
  .prepare('SELECT * FROM verticals WHERE slug = ?')
8091
8100
  .get(verticalSlug);
8092
8101
  if (!r)
8093
8102
  throw substratError('not_found', `unknown vertical '${verticalSlug}'`);
8103
+ // #2577: the caller's upload diffed its DO-class migration from `expected`'s class set and tag.
8104
+ if (expected !== undefined && !migratesFrom(r, expected))
8105
+ throw servingRaceRefusal(verticalSlug);
8094
8106
  // #2178: every install on the serving script runs the new version from this moment.
8095
8107
  this.assertNoStrandFrom([r.serving_version_id], parsed.versionId, r.serving_ref ? this.provisionedVersionsOf(verticalSlug, r.serving_version_id ? { ref: r.serving_ref, versionId: r.serving_version_id } : null, null) : []);
8096
- // #2528: a compare-and-set on the serving ref and version the gate judged
8097
- // (`SERVES_NOW_CONDITION`), and the row's `before` read in the transaction that moves it,
8098
- // as the Cloudflare host does.
8108
+ // #2528/#2577: a compare-and-set on the serving state the gate judged, its ref, version,
8109
+ // class set and tag (a serve and a re-serve can bring the ref and version back over a grown
8110
+ // class set), and the row's `before` read in the transaction that moves it, as the
8111
+ // Cloudflare host does.
8099
8112
  let before = null;
8100
8113
  let raced = false;
8101
8114
  this.directoryWriteAndAudit(() => {
8102
8115
  const now = this.directory
8103
- .prepare('SELECT serving_ref, serving_version_id FROM verticals WHERE slug = ?')
8116
+ .prepare('SELECT * FROM verticals WHERE slug = ?')
8104
8117
  .get(verticalSlug);
8105
8118
  if (!now)
8106
8119
  throw substratError('not_found', `unknown vertical '${verticalSlug}'`);
8107
8120
  before = now.serving_ref ? { ref: now.serving_ref, versionId: now.serving_version_id } : null;
8108
- const moved = this.directory
8121
+ raced = !servesAsFrom(now, servedFromRow(r));
8122
+ if (raced)
8123
+ return false;
8124
+ this.directory
8109
8125
  .prepare(`UPDATE verticals SET serving_ref = ?, serving_version_id = ?,
8110
- serving_do_classes = ?, serving_migration_tag = ? WHERE ${SERVES_NOW_CONDITION}`)
8111
- .run(parsed.ref, parsed.versionId, JSON.stringify(parsed.doClasses), parsed.migrationTag, verticalSlug, r.serving_ref ?? null, r.serving_version_id ?? null).changes > 0;
8112
- raced = !moved;
8113
- return moved;
8126
+ serving_do_classes = ?, serving_migration_tag = ? WHERE slug = ?`)
8127
+ .run(parsed.ref, parsed.versionId, JSON.stringify(parsed.doClasses), parsed.migrationTag, verticalSlug);
8128
+ return true;
8114
8129
  }, () => this.recordAdmin(actor, 'setVerticalServing', { tenantId: null }, before, { vertical: verticalSlug, ref: parsed.ref, versionId: parsed.versionId }));
8115
8130
  if (raced)
8116
8131
  throw servingRaceRefusal(verticalSlug);
@@ -8726,10 +8741,12 @@ export class SqliteScopeHost {
8726
8741
  // this host's clock, so a test can let a lease lapse without waiting.
8727
8742
  recordStoreDriverRelease: async (actor, input) => {
8728
8743
  const at = this.clock();
8729
- const { record, created } = this.directory.transaction(() => recordStoreDriverReleaseIn(switchSqlOf(this.directory), { ...input, actor, at }))();
8730
- if (created)
8731
- this.recordAdmin(actor, 'uploadStoreDriverRelease', { tenantId: null }, null, record, at);
8732
- return record;
8744
+ let result;
8745
+ this.directoryWriteAndAudit(() => {
8746
+ result = recordStoreDriverReleaseIn(switchSqlOf(this.directory), { ...input, actor, at });
8747
+ return result.created;
8748
+ }, () => this.recordAdmin(actor, 'uploadStoreDriverRelease', { tenantId: null }, null, result.record, at));
8749
+ return result.record;
8733
8750
  },
8734
8751
  getStoreDriverRelease: async (actor, driver, sha256) => {
8735
8752
  const record = storeDriverReleaseIn(switchSqlOf(this.directory), driver, sha256);
@@ -8762,14 +8779,20 @@ export class SqliteScopeHost {
8762
8779
  },
8763
8780
  beginStoreDriverRollout: async (actor, input) => {
8764
8781
  const at = this.clock();
8765
- const row = this.directory.transaction(() => beginStoreDriverRolloutIn(switchSqlOf(this.directory), { ...input, actor, at, nowMs: Date.parse(at) }))();
8766
- this.recordAdmin(actor, input.action === 'rollback' ? 'rollBackStoreDriver' : 'rollOutStoreDriver', { tenantId: null }, null, row, at);
8782
+ let row;
8783
+ this.directoryWriteAndAudit(() => {
8784
+ row = beginStoreDriverRolloutIn(switchSqlOf(this.directory), { ...input, actor, at, nowMs: Date.parse(at) });
8785
+ return true;
8786
+ }, () => this.recordAdmin(actor, input.action === 'rollback' ? 'rollBackStoreDriver' : 'rollOutStoreDriver', { tenantId: null }, null, row, at));
8767
8787
  return row;
8768
8788
  },
8769
8789
  finishStoreDriverRollout: async (actor, input) => {
8770
8790
  const at = this.clock();
8771
- const row = this.directory.transaction(() => finishStoreDriverRolloutIn(switchSqlOf(this.directory), { ...input, at, nowMs: Date.parse(at) }))();
8772
- this.recordAdmin(actor, row.action === 'rollback' ? 'rollBackStoreDriver' : 'rollOutStoreDriver', { tenantId: null }, null, row, at);
8791
+ let row;
8792
+ this.directoryWriteAndAudit(() => {
8793
+ row = finishStoreDriverRolloutIn(switchSqlOf(this.directory), { ...input, at, nowMs: Date.parse(at) });
8794
+ return true;
8795
+ }, () => this.recordAdmin(actor, row.action === 'rollback' ? 'rollBackStoreDriver' : 'rollOutStoreDriver', { tenantId: null }, null, row, at));
8773
8796
  return row;
8774
8797
  },
8775
8798
  storeDriverRebuild: async (actor, driver) => {
@@ -8970,6 +8993,9 @@ export class SqliteScopeHost {
8970
8993
  insert.run(...row);
8971
8994
  },
8972
8995
  });
8996
+ // The dump's rows have landed in the rebuilt tables. Append this restore's
8997
+ // own row before commit, so a failed insert rolls the imported state back.
8998
+ this.recordAdmin(actor, 'restoreDirectory', { tenantId: null }, { tenants: before }, { capturedAt: dump.capturedAt, tables: dump.tables.length });
8973
8999
  })();
8974
9000
  // #1764's split, over any version the dump stored before it.
8975
9001
  this.splitVersionMigrations();
@@ -8977,7 +9003,6 @@ export class SqliteScopeHost {
8977
9003
  // table would leave every permission check reading the PRE-restore roles until
8978
9004
  // the process restarted — the exact silent-divergence a recovery must not have.
8979
9005
  this.loadRoles();
8980
- this.recordAdmin(actor, 'restoreDirectory', { tenantId: null }, { tenants: before }, { capturedAt: dump.capturedAt, tables: dump.tables.length });
8981
9006
  },
8982
9007
  activateScope: async (actor, tenantId, scopeId) => {
8983
9008
  // Idempotent on `active`, unaudited because nothing changed. Provisioning is
@@ -9210,15 +9235,15 @@ export class SqliteScopeHost {
9210
9235
  // records an access that could never have happened.
9211
9236
  this.assertScope(input.tenantId, input.scopeId);
9212
9237
  const session = newImpersonationSession(ulid(), actor, input, this.clock());
9213
- this.directory
9238
+ this.directoryWriteAndAudit(() => this.directory
9214
9239
  .prepare(`INSERT INTO _substrat_impersonations
9215
9240
  (${IMPERSONATION_COLUMNS}) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
9216
- .run(...impersonationRowValues(session));
9241
+ .run(...impersonationRowValues(session)).changes > 0,
9217
9242
  // BEFORE the session is returned, so the log entry precedes every row it
9218
9243
  // will ever touch. The reason rides in `after` — it is the field a review
9219
9244
  // reads first, and a log saying a session opened but not why is half a
9220
9245
  // record.
9221
- this.recordAdmin(actor, 'beginImpersonation', { tenantId: session.tenantId, scopeId: session.scopeId }, null, session);
9246
+ () => this.recordAdmin(actor, 'beginImpersonation', { tenantId: session.tenantId, scopeId: session.scopeId }, null, session));
9222
9247
  return session;
9223
9248
  },
9224
9249
  endImpersonation: async (actor, id) => {
@@ -9227,11 +9252,14 @@ export class SqliteScopeHost {
9227
9252
  // already stopped it, and it must not move the moment it stopped.
9228
9253
  if (before.endedAt !== null)
9229
9254
  return before;
9230
- this.directory
9231
- .prepare('UPDATE _substrat_impersonations SET ended_at = ? WHERE id = ?')
9232
- .run(this.clock(), id);
9233
- const after = this.readImpersonation(id);
9234
- this.recordAdmin(actor, 'endImpersonation', { tenantId: after.tenantId, scopeId: after.scopeId }, before, after);
9255
+ let after;
9256
+ this.directoryWriteAndAudit(() => {
9257
+ const changed = this.directory
9258
+ .prepare('UPDATE _substrat_impersonations SET ended_at = ? WHERE id = ?')
9259
+ .run(this.clock(), id).changes > 0;
9260
+ after = this.readImpersonation(id);
9261
+ return changed;
9262
+ }, () => this.recordAdmin(actor, 'endImpersonation', { tenantId: after.tenantId, scopeId: after.scopeId }, before, after));
9235
9263
  return after;
9236
9264
  },
9237
9265
  listImpersonations: async (actor, filter) => {
@@ -9404,24 +9432,23 @@ export class SqliteScopeHost {
9404
9432
  },
9405
9433
  registerIdentityPool: async (actor, input) => {
9406
9434
  const parsed = identityPool.parse(input);
9407
- const existing = readPool(parsed.provider);
9408
- if (existing) {
9409
- // Idempotent on an identical registration. A CONFLICTING one throws:
9410
- // flipping a live pool's topology silently reinterprets every row it owns
9411
- // — the same externalId across tenants would change from one human to two.
9412
- if (existing.topology === parsed.topology && existing.tenantId === parsed.tenantId) {
9413
- return;
9435
+ this.directoryWriteAndAudit(() => {
9436
+ const existing = readPool(parsed.provider);
9437
+ if (existing) {
9438
+ // Idempotent on an identical registration. A conflicting one is refused.
9439
+ if (existing.topology === parsed.topology && existing.tenantId === parsed.tenantId) {
9440
+ return false;
9441
+ }
9442
+ throw substratError('conflict', `identity pool '${parsed.provider}' is already registered as ${existing.topology}` +
9443
+ `${existing.tenantId ? ` for tenant ${existing.tenantId}` : ''}`);
9414
9444
  }
9415
- throw substratError('conflict', `identity pool '${parsed.provider}' is already registered as ${existing.topology}` +
9416
- `${existing.tenantId ? ` for tenant ${existing.tenantId}` : ''}`);
9417
- }
9418
- this.directory
9419
- .prepare('INSERT INTO _substrat_identity_pools (provider, topology, tenant_id, created_at) VALUES (?, ?, ?, ?)')
9420
- .run(parsed.provider, parsed.topology, parsed.tenantId, new Date().toISOString());
9421
- this.recordAdmin(actor, 'registerIdentityPool',
9445
+ return this.directory
9446
+ .prepare('INSERT INTO _substrat_identity_pools (provider, topology, tenant_id, created_at) VALUES (?, ?, ?, ?)')
9447
+ .run(parsed.provider, parsed.topology, parsed.tenantId, new Date().toISOString()).changes > 0;
9448
+ }, () => this.recordAdmin(actor, 'registerIdentityPool',
9422
9449
  // Null for a central pool: it belongs to no single tenant, which is what
9423
9450
  // made the admin log's tenantId nullable.
9424
- { tenantId: parsed.tenantId }, null, parsed);
9451
+ { tenantId: parsed.tenantId }, null, parsed));
9425
9452
  },
9426
9453
  getIdentityPool: async (actor, provider) => {
9427
9454
  const pool = readPool(provider);
@@ -9500,42 +9527,46 @@ export class SqliteScopeHost {
9500
9527
  const input = createConnectionInput.parse(raw);
9501
9528
  const sealed = await this.secretBox.seal(JSON.stringify(input.secret));
9502
9529
  const now = new Date().toISOString();
9503
- try {
9504
- this.directory
9505
- .prepare(`INSERT INTO _substrat_connections
9530
+ this.directoryWriteAndAudit(() => {
9531
+ try {
9532
+ this.directory
9533
+ .prepare(`INSERT INTO _substrat_connections
9506
9534
  (id, tenant_id, vertical, provider, label, status, external_account_ref,
9507
9535
  scopes, expires_at, last_ok_at, last_error, last_error_at,
9508
9536
  created_by, created_at, revoked_at)
9509
9537
  VALUES (?, ?, ?, ?, ?, 'active', ?, ?, ?, NULL, NULL, NULL, ?, ?, NULL)`)
9510
- .run(input.id, input.tenantId, input.vertical, input.provider, input.label, input.externalAccountRef ?? null, JSON.stringify(input.scopes), input.expiresAt ?? null,
9511
- // The authorizing principal when supplied (a self-serve connect), else the
9512
- // effecting platform actor. See connections.md §3.5.1 / createConnectionInput.
9513
- input.createdBy ?? actor, now);
9514
- }
9515
- catch (err) {
9516
- if (/UNIQUE constraint failed/i.test(err.message)) {
9517
- const account = input.externalAccountRef
9518
- ? ` under account '${input.externalAccountRef}'`
9519
- : '';
9520
- throw new Error(`tenant ${input.tenantId} already has a live '${input.provider}' connection ` +
9521
- `for vertical '${input.vertical}'${account} — revoke it before connecting another`);
9538
+ .run(input.id, input.tenantId, input.vertical, input.provider, input.label, input.externalAccountRef ?? null, JSON.stringify(input.scopes), input.expiresAt ?? null,
9539
+ // The authorizing principal when supplied (a self-serve connect), else the
9540
+ // effecting platform actor. See connections.md §3.5.1 / createConnectionInput.
9541
+ input.createdBy ?? actor, now);
9522
9542
  }
9523
- throw err;
9524
- }
9525
- this.directory
9526
- .prepare(`INSERT INTO _substrat_connection_secrets (connection_id, key_id, ciphertext, updated_at)
9543
+ catch (err) {
9544
+ if (/UNIQUE constraint failed/i.test(err.message)) {
9545
+ const account = input.externalAccountRef
9546
+ ? ` under account '${input.externalAccountRef}'`
9547
+ : '';
9548
+ throw new Error(`tenant ${input.tenantId} already has a live '${input.provider}' connection ` +
9549
+ `for vertical '${input.vertical}'${account} — revoke it before connecting another`);
9550
+ }
9551
+ throw err;
9552
+ }
9553
+ this.directory
9554
+ .prepare(`INSERT INTO _substrat_connection_secrets (connection_id, key_id, ciphertext, updated_at)
9527
9555
  VALUES (?, ?, ?, ?)`)
9528
- .run(input.id, sealed.keyId, sealed.ciphertext, now);
9529
- // METADATA ONLY. `_substrat_admin_log` is append-only, so a credential
9530
- // written here could never be removed — the redaction is the point, and
9531
- // it is structural rather than a rule someone has to remember.
9532
- this.recordAdmin(actor, 'createConnection', { tenantId: input.tenantId, vertical: input.vertical }, null, {
9533
- id: input.id,
9534
- provider: input.provider,
9535
- label: input.label,
9536
- scopes: input.scopes,
9537
- externalAccountRef: input.externalAccountRef ?? null,
9538
- createdBy: input.createdBy ?? actor,
9556
+ .run(input.id, sealed.keyId, sealed.ciphertext, now);
9557
+ return true;
9558
+ }, () => {
9559
+ // METADATA ONLY. `_substrat_admin_log` is append-only, so a credential
9560
+ // written here could never be removed — the redaction is the point, and
9561
+ // it is structural rather than a rule someone has to remember.
9562
+ this.recordAdmin(actor, 'createConnection', { tenantId: input.tenantId, vertical: input.vertical }, null, {
9563
+ id: input.id,
9564
+ provider: input.provider,
9565
+ label: input.label,
9566
+ scopes: input.scopes,
9567
+ externalAccountRef: input.externalAccountRef ?? null,
9568
+ createdBy: input.createdBy ?? actor,
9569
+ });
9539
9570
  });
9540
9571
  },
9541
9572
  connectionSealingKey: (id) => this.ensureSealingKey(id),
@@ -9600,25 +9631,29 @@ export class SqliteScopeHost {
9600
9631
  const row = this.connectionRow(id);
9601
9632
  const sealed = await this.secretBox.seal(JSON.stringify(connectionSecret.parse(secret)));
9602
9633
  const now = new Date().toISOString();
9603
- this.directory
9604
- .prepare(`UPDATE _substrat_connection_secrets
9634
+ this.directoryWriteAndAudit(() => {
9635
+ this.directory
9636
+ .prepare(`UPDATE _substrat_connection_secrets
9605
9637
  SET key_id = ?, ciphertext = ?, updated_at = ? WHERE connection_id = ?`)
9606
- .run(sealed.keyId, sealed.ciphertext, now, id);
9607
- // A refresh revives a connection that had lapsed or errored.
9608
- this.directory
9609
- .prepare(`UPDATE _substrat_connections
9638
+ .run(sealed.keyId, sealed.ciphertext, now, id);
9639
+ // A refresh revives a connection that had lapsed or errored.
9640
+ this.directory
9641
+ .prepare(`UPDATE _substrat_connections
9610
9642
  SET status = 'active', expires_at = ?, last_error = NULL, last_error_at = NULL
9611
9643
  WHERE id = ?`)
9612
- .run(expiresAt === undefined ? row.expires_at : expiresAt, id);
9613
- // The event, never the token. "Rotated at T" is the auditable fact — plus WHO
9614
- // authorized it when the rotation was a tenant admin's act (§3.5.1's attribution,
9615
- // rotate-side): the principal, never laundered into the actor column.
9616
- this.recordAdmin(actor, 'updateConnectionSecret', { tenantId: row.tenant_id, vertical: row.vertical }, null, {
9617
- id,
9618
- provider: row.provider,
9619
- rotatedAt: now,
9620
- expiresAt: expiresAt === undefined ? row.expires_at : expiresAt,
9621
- ...(opts?.rotatedBy ? { rotatedBy: opts.rotatedBy } : {}),
9644
+ .run(expiresAt === undefined ? row.expires_at : expiresAt, id);
9645
+ return true;
9646
+ }, () => {
9647
+ // The event, never the token. "Rotated at T" is the auditable fact — plus WHO
9648
+ // authorized it when the rotation was a tenant admin's act (§3.5.1's attribution,
9649
+ // rotate-side): the principal, never laundered into the actor column.
9650
+ this.recordAdmin(actor, 'updateConnectionSecret', { tenantId: row.tenant_id, vertical: row.vertical }, null, {
9651
+ id,
9652
+ provider: row.provider,
9653
+ rotatedAt: now,
9654
+ expiresAt: expiresAt === undefined ? row.expires_at : expiresAt,
9655
+ ...(opts?.rotatedBy ? { rotatedBy: opts.rotatedBy } : {}),
9656
+ });
9622
9657
  });
9623
9658
  },
9624
9659
  revokeConnection: async (actor, id) => {
@@ -9626,27 +9661,29 @@ export class SqliteScopeHost {
9626
9661
  if (row.revoked_at)
9627
9662
  return; // idempotent, and a no-op is not audited
9628
9663
  const now = new Date().toISOString();
9629
- this.directory
9630
- .prepare(`UPDATE _substrat_connections SET status = 'revoked', revoked_at = ? WHERE id = ?`)
9631
- .run(now, id);
9632
- // The sealed blob goes NOW. A tombstoned connection is evidence that a
9633
- // grant existed (K-21); keeping the usable credential would make it a
9634
- // liability instead. The row says what happened; the secret does not.
9635
- this.directory
9636
- .prepare('DELETE FROM _substrat_connection_secrets WHERE connection_id = ?')
9637
- .run(id);
9638
- // Connector state dies with the connection: it is that connection's
9639
- // private bookkeeping and means nothing once it can no longer act.
9640
- this.directory
9641
- .prepare('DELETE FROM _substrat_connector_state WHERE connection_id = ?')
9642
- .run(id);
9643
- // #592: its grants tombstone (K-21 — evidence, not roster). Absent from the
9644
- // next gather, so no later provision/reconcile delivers them again.
9645
- this.directory
9646
- .prepare(`UPDATE _substrat_connection_grants SET revoked_at = ?
9664
+ this.directoryWriteAndAudit(() => {
9665
+ this.directory
9666
+ .prepare(`UPDATE _substrat_connections SET status = 'revoked', revoked_at = ? WHERE id = ?`)
9667
+ .run(now, id);
9668
+ // The sealed blob goes NOW. A tombstoned connection is evidence that a
9669
+ // grant existed (K-21); keeping the usable credential would make it a
9670
+ // liability instead. The row says what happened; the secret does not.
9671
+ this.directory
9672
+ .prepare('DELETE FROM _substrat_connection_secrets WHERE connection_id = ?')
9673
+ .run(id);
9674
+ // Connector state dies with the connection: it is that connection's
9675
+ // private bookkeeping and means nothing once it can no longer act.
9676
+ this.directory
9677
+ .prepare('DELETE FROM _substrat_connector_state WHERE connection_id = ?')
9678
+ .run(id);
9679
+ // #592: its grants tombstone (K-21 — evidence, not roster). Absent from the
9680
+ // next gather, so no later provision/reconcile delivers them again.
9681
+ this.directory
9682
+ .prepare(`UPDATE _substrat_connection_grants SET revoked_at = ?
9647
9683
  WHERE connection_id = ? AND revoked_at IS NULL`)
9648
- .run(now, id);
9649
- this.recordAdmin(actor, 'revokeConnection', { tenantId: row.tenant_id, vertical: row.vertical }, { status: row.status }, { id, provider: row.provider, status: 'revoked', revokedAt: now });
9684
+ .run(now, id);
9685
+ return true;
9686
+ }, () => this.recordAdmin(actor, 'revokeConnection', { tenantId: row.tenant_id, vertical: row.vertical }, { status: row.status }, { id, provider: row.provider, status: 'revoked', revokedAt: now }));
9650
9687
  },
9651
9688
  // -- a vertical's mailed connect links (connections.md §3.5.4) --------------
9652
9689
  // The statements are the kernel's (`connect-links.ts`), shared with the hosted
@@ -9786,32 +9823,30 @@ export class SqliteScopeHost {
9786
9823
  // Read before write. `INSERT OR IGNORE` alone cannot tell "already bound to the
9787
9824
  // same principal" (idempotent) from "already bound to someone else" (a
9788
9825
  // collision), and silently ignoring the second resolves one person as another.
9789
- const existing = this.directory
9790
- .prepare(`SELECT principal_id FROM _substrat_identities
9826
+ this.directoryWriteAndAudit(() => {
9827
+ const existing = this.directory
9828
+ .prepare(`SELECT principal_id FROM _substrat_identities
9791
9829
  WHERE tenant_id = ? AND provider = ? AND external_id = ?`)
9792
- .get(parsed.tenantId, parsed.provider, parsed.externalId);
9793
- if (existing) {
9794
- if (existing.principal_id === parsed.principal)
9795
- return; // idempotent, unaudited
9796
- throw substratError('conflict', `identity ${parsed.provider}:${parsed.externalId} in tenant ${parsed.tenantId} ` +
9797
- `is already bound to ${existing.principal_id}`);
9798
- }
9799
- this.directory
9800
- .prepare(`INSERT INTO _substrat_identities
9830
+ .get(parsed.tenantId, parsed.provider, parsed.externalId);
9831
+ if (existing) {
9832
+ if (existing.principal_id === parsed.principal)
9833
+ return false; // idempotent, unaudited
9834
+ throw substratError('conflict', `identity ${parsed.provider}:${parsed.externalId} in tenant ${parsed.tenantId} ` +
9835
+ `is already bound to ${existing.principal_id}`);
9836
+ }
9837
+ return this.directory
9838
+ .prepare(`INSERT INTO _substrat_identities
9801
9839
  (provider, external_id, principal_id, tenant_id, scope_id, created_at)
9802
9840
  VALUES (?, ?, ?, ?, ?, ?)`)
9803
- .run(parsed.provider, parsed.externalId, parsed.principal, parsed.tenantId, parsed.scopeId ?? null, new Date().toISOString());
9804
- this.recordAdmin(actor, 'linkIdentity', { tenantId: parsed.tenantId, scopeId: parsed.scopeId }, null, { provider: parsed.provider, externalId: parsed.externalId, principal: parsed.principal });
9841
+ .run(parsed.provider, parsed.externalId, parsed.principal, parsed.tenantId, parsed.scopeId ?? null, new Date().toISOString()).changes > 0;
9842
+ }, () => this.recordAdmin(actor, 'linkIdentity', { tenantId: parsed.tenantId, scopeId: parsed.scopeId }, null, { provider: parsed.provider, externalId: parsed.externalId, principal: parsed.principal }));
9805
9843
  },
9806
9844
  unlinkIdentity: async (actor, tenantId, principal) => {
9807
9845
  // DELETE, not a tombstone — the identity map is current state (audit is the log),
9808
9846
  // and re-inviting must be able to re-link a fresh principal for the same person.
9809
- const info = this.directory
9847
+ this.directoryWriteAndAudit(() => this.directory
9810
9848
  .prepare(`DELETE FROM _substrat_identities WHERE tenant_id = ? AND principal_id = ?`)
9811
- .run(tenantId, principal);
9812
- if (info.changes === 0)
9813
- return; // no link — idempotent, unaudited
9814
- this.recordAdmin(actor, 'unlinkIdentity', { tenantId, scopeId: null }, { principal }, null);
9849
+ .run(tenantId, principal).changes > 0, () => this.recordAdmin(actor, 'unlinkIdentity', { tenantId, scopeId: null }, { principal }, null));
9815
9850
  },
9816
9851
  resolveIdentity: async (tenantId, provider, externalId) => {
9817
9852
  const row = this.directory
@@ -9907,32 +9942,40 @@ export class SqliteScopeHost {
9907
9942
  // `drained_at IS NULL` makes the stamp idempotent: a retried pass that ships
9908
9943
  // the same batch twice re-stamps nothing and reports 0, so the admin log does
9909
9944
  // not grow a row claiming an egress that already happened.
9910
- const info = this.directory
9911
- .prepare(`UPDATE _substrat_access_log SET drained_at = ?
9945
+ let drained = 0;
9946
+ this.directoryWriteAndAudit(() => {
9947
+ const info = this.directory
9948
+ .prepare(`UPDATE _substrat_access_log SET drained_at = ?
9912
9949
  WHERE id <= ? AND drained_at IS NULL`)
9913
- .run(drainedAt, upToId);
9914
- if (info.changes > 0) {
9950
+ .run(drainedAt, upToId);
9951
+ drained = info.changes;
9952
+ return drained > 0;
9953
+ }, () => {
9915
9954
  // The payload is the APPLIED state, so it belongs in `after` (contracts'
9916
9955
  // adminLogEntry: before = prior state, after = the applied payload).
9917
- this.recordAdmin(actor, 'drainAccessLog', { tenantId: null }, null, { drained: info.changes, upToId, drainedAt });
9918
- }
9919
- return info.changes;
9956
+ this.recordAdmin(actor, 'drainAccessLog', { tenantId: null }, null, { drained, upToId, drainedAt });
9957
+ });
9958
+ return drained;
9920
9959
  },
9921
9960
  pruneAccessLog: async (actor, limit) => {
9922
9961
  assertRowLimit('limit', limit);
9923
9962
  // ONLY drained rows. Age alone is not a licence to delete evidence.
9924
- const info = this.directory
9925
- .prepare(`DELETE FROM _substrat_access_log WHERE id IN (
9963
+ let pruned = 0;
9964
+ this.directoryWriteAndAudit(() => {
9965
+ const info = this.directory
9966
+ .prepare(`DELETE FROM _substrat_access_log WHERE id IN (
9926
9967
  SELECT id FROM _substrat_access_log WHERE drained_at IS NOT NULL ORDER BY id LIMIT ?
9927
9968
  )`)
9928
- .run(limit);
9929
- if (info.changes > 0) {
9969
+ .run(limit);
9970
+ pruned = info.changes;
9971
+ return pruned > 0;
9972
+ }, () => {
9930
9973
  // The payload is the APPLIED state, so it belongs in `after` (contracts'
9931
9974
  // adminLogEntry: before = prior state, after = the applied payload) — the
9932
9975
  // same shape as drainAccessLog's row above (#557).
9933
- this.recordAdmin(actor, 'pruneAccessLog', { tenantId: null }, null, { pruned: info.changes });
9934
- }
9935
- return info.changes;
9976
+ this.recordAdmin(actor, 'pruneAccessLog', { tenantId: null }, null, { pruned });
9977
+ });
9978
+ return pruned;
9936
9979
  },
9937
9980
  auditLog: async (actor, filter) => {
9938
9981
  const where = [];
@@ -10219,21 +10262,27 @@ export class SqliteScopeHost {
10219
10262
  return rows.map((r) => issueEntry.parse(issueOf(r)));
10220
10263
  },
10221
10264
  setIssueStatus: async (actor, fingerprint, status) => {
10222
- const existing = this.directory
10223
- .prepare('SELECT * FROM _substrat_issues WHERE fingerprint = ?')
10224
- .get(fingerprint);
10225
- if (!existing)
10226
- return undefined;
10227
- const resolvedAt = status === 'resolved' ? new Date().toISOString() : null;
10228
- // "Resolved under X": freeze the newest version at the verdict, so a later
10229
- // regression can name the pair. A reopen or ignore clears it with the timestamp.
10230
- const resolvedVersion = status === 'resolved' ? existing.last_version : null;
10231
- this.directory
10232
- .prepare('UPDATE _substrat_issues SET status = ?, resolved_at = ?, resolved_version = ? WHERE fingerprint = ?')
10233
- .run(status, resolvedAt, resolvedVersion, fingerprint);
10234
- const after = issueEntry.parse(issueOf({ ...existing, status, resolved_at: resolvedAt, resolved_version: resolvedVersion }));
10235
- // A lifecycle flip is a staff mutation — audited with the diff (K-33).
10236
- this.recordAdmin(actor, 'setIssueStatus', { tenantId: null, vertical: after.lastVertical }, { fingerprint, status: existing.status }, { fingerprint, status: after.status });
10265
+ let existing;
10266
+ let after;
10267
+ this.directoryWriteAndAudit(() => {
10268
+ existing = this.directory
10269
+ .prepare('SELECT * FROM _substrat_issues WHERE fingerprint = ?')
10270
+ .get(fingerprint);
10271
+ if (!existing)
10272
+ return false;
10273
+ const resolvedAt = status === 'resolved' ? new Date().toISOString() : null;
10274
+ // "Resolved under X": freeze the newest version at the verdict, so a later
10275
+ // regression can name the pair. A reopen or ignore clears it with the timestamp.
10276
+ const resolvedVersion = status === 'resolved' ? existing.last_version : null;
10277
+ this.directory
10278
+ .prepare('UPDATE _substrat_issues SET status = ?, resolved_at = ?, resolved_version = ? WHERE fingerprint = ?')
10279
+ .run(status, resolvedAt, resolvedVersion, fingerprint);
10280
+ after = issueEntry.parse(issueOf({ ...existing, status, resolved_at: resolvedAt, resolved_version: resolvedVersion }));
10281
+ return true;
10282
+ }, () => {
10283
+ // A lifecycle flip is a staff mutation — audited with the diff (K-33).
10284
+ this.recordAdmin(actor, 'setIssueStatus', { tenantId: null, vertical: after.lastVertical }, { fingerprint, status: existing.status }, { fingerprint, status: after.status });
10285
+ });
10237
10286
  return after;
10238
10287
  },
10239
10288
  listFindings: async (actor, filter) => {