@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.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +212 -163
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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
|
|
8097
|
-
// (
|
|
8098
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
8111
|
-
.run(parsed.ref, parsed.versionId, JSON.stringify(parsed.doClasses), parsed.migrationTag, verticalSlug
|
|
8112
|
-
|
|
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
|
-
|
|
8730
|
-
|
|
8731
|
-
this.
|
|
8732
|
-
|
|
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
|
-
|
|
8766
|
-
this.
|
|
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
|
-
|
|
8772
|
-
this.
|
|
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
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
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
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
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
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
this.
|
|
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
|
-
|
|
9504
|
-
|
|
9505
|
-
.
|
|
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
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
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
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
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
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
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.
|
|
9604
|
-
.
|
|
9634
|
+
this.directoryWriteAndAudit(() => {
|
|
9635
|
+
this.directory
|
|
9636
|
+
.prepare(`UPDATE _substrat_connection_secrets
|
|
9605
9637
|
SET key_id = ?, ciphertext = ?, updated_at = ? WHERE connection_id = ?`)
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
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
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
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.
|
|
9630
|
-
.
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
.
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
.
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
.
|
|
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
|
-
|
|
9649
|
-
|
|
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
|
-
|
|
9790
|
-
|
|
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
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9911
|
-
|
|
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
|
-
|
|
9914
|
-
|
|
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
|
|
9918
|
-
}
|
|
9919
|
-
return
|
|
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
|
-
|
|
9925
|
-
|
|
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
|
-
|
|
9929
|
-
|
|
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
|
|
9934
|
-
}
|
|
9935
|
-
return
|
|
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
|
-
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
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) => {
|