@substrat-run/adapter-sqlite 0.162.0 → 0.164.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, 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, 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';
@@ -343,6 +343,10 @@ const KERNEL_DDL = `
343
343
  -- only) and its watermark per producer, plus the producer-side (type, id) outbox index the
344
344
  -- export read seeks on. Shared with the DO adapter from @substrat-run/kernel.
345
345
  ${VERTICAL_EVENTS_DDL}
346
+ -- RFC wire-signing §4: the nonces of signed calls this scope accepted, and their count.
347
+ -- Store-local, never dumped. Shared with the DO adapter from @substrat-run/kernel, where the
348
+ -- column comments are (its time is integer seconds, the envelope's own unit).
349
+ ${WIRE_NONCE_DDL}
346
350
  CREATE TABLE IF NOT EXISTS _substrat_migrations (
347
351
  module_id TEXT NOT NULL,
348
352
  version TEXT NOT NULL,
@@ -2889,7 +2893,9 @@ export class SqliteScopeHost {
2889
2893
  // A dump written before indexes were excluded may still carry them; skip those
2890
2894
  // too rather than failing a restore over data that is about to be recomputed.
2891
2895
  // Matched without case, as SQLite resolves a table name (#1883 review).
2892
- const replayable = dumped.filter((t) => !isSearchIndexTable(t.name.toLowerCase()));
2896
+ // The wire nonce store is this store's own (RFC wire-signing §4): a dump that carries it is not
2897
+ // replayed, so a load starts it empty, as it starts a vertical's search index.
2898
+ const replayable = dumped.filter((t) => !isSearchIndexTable(t.name.toLowerCase()) && !isWireNonceTable(t.name));
2893
2899
  // A Durable Object's dump carries spine tables this adapter keeps in its directory, or does
2894
2900
  // not keep at all; they are skipped by name, and any other unknown spine table is refused.
2895
2901
  const loadable = replayable.filter((t) => !DO_SCOPE_ONLY_SPINE_TABLES.has(t.name.toLowerCase()));
@@ -3743,6 +3749,13 @@ export class SqliteScopeHost {
3743
3749
  throw substratError('not_found', `unknown version ${versionId} for vertical '${slug}'`);
3744
3750
  return connectionLandingsOfManifestJson(manifest);
3745
3751
  }
3752
+ /** What one version of `slug` declares about vertical-defined connectors (vertical-connectors.md §6.4); `not_found` for a version it does not know. */
3753
+ async versionConnectors(slug, versionId) {
3754
+ const manifest = this.manifestJsonOf(versionId, slug);
3755
+ if (manifest === undefined)
3756
+ throw substratError('not_found', `unknown version ${versionId} for vertical '${slug}'`);
3757
+ return connectorsOfManifestJson(manifest);
3758
+ }
3746
3759
  /** #1705 PR 3: the promote gate's question, over two stored versions (`exportBreaksOf`). */
3747
3760
  exportBreaksBetween(actor, producer, outgoingId, incomingId) {
3748
3761
  return exportBreaksOf({
@@ -4225,6 +4238,20 @@ export class SqliteScopeHost {
4225
4238
  async redrainCountLocal(scopeId, drainedBefore) {
4226
4239
  return redrainCountOf(this.heldReadDb(scopeId), drainedBefore);
4227
4240
  }
4241
+ /**
4242
+ * RFC wire-signing §4: spend a signed call's nonce in this scope's own store, the hosted
4243
+ * object's `claimWireNonce` here: `true` once, `false` for a nonce already spent or a claim after
4244
+ * `until`, and a throw when the store is at its cap (after its purge committed). Judged against
4245
+ * this host's clock. No provisioned check, as on the hosted object: the call that provisions a
4246
+ * scope spends its nonce there too.
4247
+ */
4248
+ async claimWireNonceLocal(tenantId, scopeId, nonce, until) {
4249
+ const rt = this.runtime(tenantId, scopeId);
4250
+ const answer = await rt.actor.enqueue(() => rt.db.transaction(() => claimWireNonceIn(switchSqlOf(rt.db), nonce, until, Math.floor(Date.parse(this.clock()) / 1000)))());
4251
+ if (answer === 'full')
4252
+ throw substratError('unavailable', WIRE_NONCE_FULL);
4253
+ return answer;
4254
+ }
4228
4255
  /**
4229
4256
  * The read connection of a scope this host holds, for a read that names only the scope id (#2541),
4230
4257
  * with the directory read ONCE. Refused `conflict` before anything is opened: no row, a reaped one
@@ -7142,98 +7169,116 @@ export class SqliteScopeHost {
7142
7169
  // -- the hostname map (K-26) -------------------------------------------
7143
7170
  bindHostname: async (actor, input) => {
7144
7171
  const parsed = bindHostnameInput.parse(input);
7145
- const scope = this.directory
7146
- .prepare('SELECT tenant_id, vertical FROM scopes WHERE scope_id = ?')
7147
- .get(parsed.scopeId);
7148
- if (!scope || scope.tenant_id !== parsed.tenantId) {
7149
- throw substratError('not_found', `unknown scope ${parsed.scopeId} in tenant ${parsed.tenantId}`);
7150
- }
7151
- const existing = this.directory
7152
- .prepare(`SELECT h.scope_id AS scope_id, s.status AS scope_status
7153
- FROM hostnames h LEFT JOIN scopes s ON s.scope_id = h.scope_id
7154
- WHERE h.hostname = ?`)
7155
- .get(parsed.hostname);
7156
- const holderReleased = existing?.scope_status === 'archived' || existing?.scope_status === 'reaped';
7157
- if (existing && existing.scope_id !== parsed.scopeId && !holderReleased) {
7158
- // A hostname is globally unique and routes to exactly one place. Silently
7159
- // rebinding it would move another tenant's traffic. Exception: the holder is
7160
- // ARCHIVED or REAPED (a deleted app, storage since wiped) — it released the
7161
- // name, so the rebind reclaims it.
7162
- throw substratError('conflict', `hostname '${parsed.hostname}' is already bound to another scope`);
7163
- }
7164
- // Exactly one canonical per (scope, surface): "which one do certs and
7165
- // redirects use" has to have one answer, so a new canonical demotes the old.
7166
- if (parsed.canonical) {
7172
+ // #2526: the scope, the holder it judges, the demotion, the bind and the row are one
7173
+ // directory unit, so a name bound elsewhere meanwhile is refused rather than moved.
7174
+ let vertical = null;
7175
+ this.directoryWriteAndAudit(() => {
7176
+ const scope = this.directory
7177
+ .prepare('SELECT tenant_id, vertical FROM scopes WHERE scope_id = ?')
7178
+ .get(parsed.scopeId);
7179
+ if (!scope || scope.tenant_id !== parsed.tenantId) {
7180
+ throw substratError('not_found', `unknown scope ${parsed.scopeId} in tenant ${parsed.tenantId}`);
7181
+ }
7182
+ vertical = scope.vertical;
7183
+ const existing = this.directory
7184
+ .prepare(`SELECT h.scope_id AS scope_id, s.status AS scope_status
7185
+ FROM hostnames h LEFT JOIN scopes s ON s.scope_id = h.scope_id
7186
+ WHERE h.hostname = ?`)
7187
+ .get(parsed.hostname);
7188
+ const holderReleased = existing?.scope_status === 'archived' || existing?.scope_status === 'reaped';
7189
+ if (existing && existing.scope_id !== parsed.scopeId && !holderReleased) {
7190
+ // A hostname is globally unique and routes to exactly one place. Silently
7191
+ // rebinding it would move another tenant's traffic. Exception: the holder is
7192
+ // ARCHIVED or REAPED (a deleted app, storage since wiped) — it released the
7193
+ // name, so the rebind reclaims it.
7194
+ throw substratError('conflict', `hostname '${parsed.hostname}' is already bound to another scope`);
7195
+ }
7196
+ // Exactly one canonical per (scope, surface): "which one do certs and
7197
+ // redirects use" has to have one answer, so a new canonical demotes the old.
7198
+ if (parsed.canonical) {
7199
+ this.directory
7200
+ .prepare('UPDATE hostnames SET canonical = 0 WHERE scope_id = ? AND surface = ?')
7201
+ .run(parsed.scopeId, parsed.surface);
7202
+ }
7167
7203
  this.directory
7168
- .prepare('UPDATE hostnames SET canonical = 0 WHERE scope_id = ? AND surface = ?')
7169
- .run(parsed.scopeId, parsed.surface);
7170
- }
7171
- this.directory
7172
- .prepare(
7173
- // INSERT OR REPLACE resets issuance columns — a (re)bind starts a fresh
7174
- // lifecycle, so any prior CF hostname id / DNS records must not linger.
7175
- `INSERT OR REPLACE INTO hostnames
7176
- (hostname, tenant_id, scope_id, vertical_slug, surface, region,
7177
- status, status_note, canonical, created_at, custom_hostname_id, validation_records)
7178
- VALUES (?, ?, ?, ?, ?, ?, 'pending', NULL, ?, ?, NULL, NULL)`)
7179
- .run(parsed.hostname, parsed.tenantId, parsed.scopeId, scope.vertical, parsed.surface, parsed.region, parsed.canonical ? 1 : 0, new Date().toISOString());
7180
- this.recordAdmin(actor, 'bindHostname', { tenantId: parsed.tenantId, scopeId: parsed.scopeId, vertical: scope.vertical }, null, parsed);
7204
+ .prepare(
7205
+ // INSERT OR REPLACE resets issuance columns — a (re)bind starts a fresh
7206
+ // lifecycle, so any prior CF hostname id / DNS records must not linger.
7207
+ `INSERT OR REPLACE INTO hostnames
7208
+ (hostname, tenant_id, scope_id, vertical_slug, surface, region,
7209
+ status, status_note, canonical, created_at, custom_hostname_id, validation_records)
7210
+ VALUES (?, ?, ?, ?, ?, ?, 'pending', NULL, ?, ?, NULL, NULL)`)
7211
+ .run(parsed.hostname, parsed.tenantId, parsed.scopeId, scope.vertical, parsed.surface, parsed.region, parsed.canonical ? 1 : 0, new Date().toISOString());
7212
+ return true;
7213
+ }, () => this.recordAdmin(actor, 'bindHostname', { tenantId: parsed.tenantId, scopeId: parsed.scopeId, vertical }, null, parsed));
7181
7214
  },
7182
7215
  setHostnameStatus: async (actor, raw, status, note) => {
7183
7216
  const hostname = raw.toLowerCase(); // DNS is case-insensitive; the map is normalized
7184
- const row = this.directory
7185
- .prepare('SELECT tenant_id, scope_id, status FROM hostnames WHERE hostname = ?')
7186
- .get(hostname);
7187
- if (!row)
7188
- throw substratError('not_found', `unknown hostname '${hostname}'`);
7189
- if (row.status === status)
7190
- return; // idempotent, and a no-op is not audited
7191
- this.directory
7192
- .prepare('UPDATE hostnames SET status = ?, status_note = ? WHERE hostname = ?')
7193
- .run(status, note ?? null, hostname);
7194
- this.recordAdmin(actor, 'setHostnameStatus', { tenantId: row.tenant_id, scopeId: row.scope_id }, { status: row.status }, { status, note: note ?? null });
7217
+ // #2526: the row's read (its `before`, and the no-op judgement), the write and the
7218
+ // admin row are one directory unit.
7219
+ let row;
7220
+ this.directoryWriteAndAudit(() => {
7221
+ row = this.directory
7222
+ .prepare('SELECT tenant_id, scope_id, status FROM hostnames WHERE hostname = ?')
7223
+ .get(hostname);
7224
+ if (!row)
7225
+ throw substratError('not_found', `unknown hostname '${hostname}'`);
7226
+ if (row.status === status)
7227
+ return false; // idempotent, and a no-op is not audited
7228
+ this.directory
7229
+ .prepare('UPDATE hostnames SET status = ?, status_note = ? WHERE hostname = ?')
7230
+ .run(status, note ?? null, hostname);
7231
+ return true;
7232
+ }, () => this.recordAdmin(actor, 'setHostnameStatus', { tenantId: row.tenant_id, scopeId: row.scope_id }, { status: row.status }, { status, note: note ?? null }));
7195
7233
  },
7196
7234
  setHostnameIssuance: async (actor, raw, fields) => {
7197
7235
  const hostname = raw.toLowerCase(); // DNS is case-insensitive; the map is normalized
7198
- const row = this.directory
7199
- .prepare('SELECT tenant_id, scope_id, status, custom_hostname_id, validation_records FROM hostnames WHERE hostname = ?')
7200
- .get(hostname);
7201
- if (!row)
7202
- throw substratError('not_found', `unknown hostname '${hostname}'`);
7203
7236
  const recordsJson = fields.validationRecords.length
7204
7237
  ? JSON.stringify(fields.validationRecords)
7205
7238
  : null;
7206
- const idUnchanged = fields.customHostnameId === undefined || fields.customHostnameId === row.custom_hostname_id;
7207
- // A poll that changes nothing is not an event — skip it, so the reconcile sweep
7208
- // does not append a no-op admin-log row every interval.
7209
- if (row.status === fields.status &&
7210
- (row.validation_records ?? null) === recordsJson &&
7211
- idUnchanged) {
7212
- return;
7213
- }
7214
- if (fields.customHostnameId !== undefined) {
7215
- this.directory
7216
- .prepare('UPDATE hostnames SET status = ?, status_note = ?, custom_hostname_id = ?, validation_records = ? WHERE hostname = ?')
7217
- .run(fields.status, fields.note ?? null, fields.customHostnameId, recordsJson, hostname);
7218
- }
7219
- else {
7220
- this.directory
7221
- .prepare('UPDATE hostnames SET status = ?, status_note = ?, validation_records = ? WHERE hostname = ?')
7222
- .run(fields.status, fields.note ?? null, recordsJson, hostname);
7223
- }
7224
- this.recordAdmin(actor, 'setHostnameIssuance', { tenantId: row.tenant_id, scopeId: row.scope_id }, { status: row.status }, { status: fields.status, note: fields.note ?? null });
7239
+ // #2526: the row's read (its `before`, and the no-op judgement), the write and the
7240
+ // admin row are one directory unit.
7241
+ let row;
7242
+ this.directoryWriteAndAudit(() => {
7243
+ row = this.directory
7244
+ .prepare('SELECT tenant_id, scope_id, status, custom_hostname_id, validation_records FROM hostnames WHERE hostname = ?')
7245
+ .get(hostname);
7246
+ if (!row)
7247
+ throw substratError('not_found', `unknown hostname '${hostname}'`);
7248
+ const idUnchanged = fields.customHostnameId === undefined || fields.customHostnameId === row.custom_hostname_id;
7249
+ // A poll that changes nothing is not an event — skip it, so the reconcile sweep
7250
+ // does not append a no-op admin-log row every interval.
7251
+ if (row.status === fields.status &&
7252
+ (row.validation_records ?? null) === recordsJson &&
7253
+ idUnchanged) {
7254
+ return false;
7255
+ }
7256
+ if (fields.customHostnameId !== undefined) {
7257
+ this.directory
7258
+ .prepare('UPDATE hostnames SET status = ?, status_note = ?, custom_hostname_id = ?, validation_records = ? WHERE hostname = ?')
7259
+ .run(fields.status, fields.note ?? null, fields.customHostnameId, recordsJson, hostname);
7260
+ }
7261
+ else {
7262
+ this.directory
7263
+ .prepare('UPDATE hostnames SET status = ?, status_note = ?, validation_records = ? WHERE hostname = ?')
7264
+ .run(fields.status, fields.note ?? null, recordsJson, hostname);
7265
+ }
7266
+ return true;
7267
+ }, () => this.recordAdmin(actor, 'setHostnameIssuance', { tenantId: row.tenant_id, scopeId: row.scope_id }, { status: row.status }, { status: fields.status, note: fields.note ?? null }));
7225
7268
  },
7226
7269
  unbindHostname: async (actor, raw, owner) => {
7227
7270
  const hostname = raw.toLowerCase(); // DNS is case-insensitive; the map is normalized
7228
7271
  // One statement: the row it removes is the row it audits, and an owner is judged by the
7229
7272
  // delete itself, so a name rebound to another scope in between is left alone (#2285).
7230
- const row = this.directory
7231
- .prepare(`DELETE FROM hostnames WHERE hostname = ?${owner ? ' AND tenant_id = ? AND scope_id = ?' : ''}
7232
- RETURNING tenant_id, scope_id, status`)
7233
- .get(hostname, ...(owner ? [owner.tenantId, owner.scopeId] : []));
7234
- if (!row)
7235
- return; // idempotent, and a no-op is not audited
7236
- this.recordAdmin(actor, 'unbindHostname', { tenantId: row.tenant_id, scopeId: row.scope_id }, { hostname, status: row.status }, null);
7273
+ // #2526: the delete and its admin row are one directory unit.
7274
+ let row;
7275
+ this.directoryWriteAndAudit(() => {
7276
+ row = this.directory
7277
+ .prepare(`DELETE FROM hostnames WHERE hostname = ?${owner ? ' AND tenant_id = ? AND scope_id = ?' : ''}
7278
+ RETURNING tenant_id, scope_id, status`)
7279
+ .get(hostname, ...(owner ? [owner.tenantId, owner.scopeId] : []));
7280
+ return row !== undefined; // idempotent, and a no-op is not audited
7281
+ }, () => this.recordAdmin(actor, 'unbindHostname', { tenantId: row.tenant_id, scopeId: row.scope_id }, { hostname, status: row.status }, null));
7237
7282
  },
7238
7283
  listHostnames: async (actor, filter) => {
7239
7284
  const where = [];
@@ -7387,6 +7432,9 @@ export class SqliteScopeHost {
7387
7432
  installSpec.provides = parsed.provides;
7388
7433
  if (parsed.requires)
7389
7434
  installSpec.requires = parsed.requires;
7435
+ // The declared vertical-defined connectors (vertical-connectors.md §7.2), the dashboard's door.
7436
+ if (parsed.connectors)
7437
+ installSpec.connectors = parsed.connectors;
7390
7438
  if (parsed.provisions)
7391
7439
  installSpec.provisions = parsed.provisions;
7392
7440
  if (parsed.sendsEmail)
@@ -7537,8 +7585,7 @@ export class SqliteScopeHost {
7537
7585
  WHERE c.vertical_slug = ? AND c.channel = 'prod'`)
7538
7586
  .get(slug);
7539
7587
  if (prod?.admission_note === AUTO_ADMISSION_NOTE) {
7540
- throw substratError('conflict', `vertical '${slug}' prod version ${prod.id} is auto-admitted (private self-serve) — ` +
7541
- `a staff admit must vouch for it before listing`);
7588
+ throw autoAdmittedProdRefusal(slug, prod.id);
7542
7589
  }
7543
7590
  }
7544
7591
  return { listed: existing.listed };
@@ -7650,7 +7697,7 @@ export class SqliteScopeHost {
7650
7697
  this.recordAccess(actor, 'promotionImpact', { tenantId: null }, { verticalSlug, channel, versionId }, breaks.length);
7651
7698
  return breaks;
7652
7699
  },
7653
- promoteVersion: async (actor, verticalSlug, channel, versionId, acknowledge) => {
7700
+ promoteVersion: async (actor, verticalSlug, channel, versionId, acknowledge, options) => {
7654
7701
  const incoming = await readVersion(versionId);
7655
7702
  if (!incoming)
7656
7703
  throw substratError('not_found', `unknown version ${versionId}`);
@@ -7695,6 +7742,7 @@ export class SqliteScopeHost {
7695
7742
  ack,
7696
7743
  channel,
7697
7744
  baseId: outgoing?.id ?? null,
7745
+ permissionsChange: outgoing !== undefined && outgoing.permissionDigest !== incoming.permissionDigest,
7698
7746
  migrationsChange: outgoing !== undefined && outgoing.migrationDigest !== incoming.migrationDigest,
7699
7747
  read: async () => ({ base: outgoing ? reviewed(outgoing) : null, incoming: reviewed(incoming) }),
7700
7748
  });
@@ -7728,6 +7776,17 @@ export class SqliteScopeHost {
7728
7776
  .get(verticalSlug, channel);
7729
7777
  if ((now?.version_id ?? null) !== (outgoing?.id ?? null))
7730
7778
  return false;
7779
+ // #2565: the listing's rule and the builder's door, read where the listing could land.
7780
+ const listed = readVertical(verticalSlug)?.listed;
7781
+ if (listed && options?.unlistedOnly)
7782
+ throw listedPromoteRefusal();
7783
+ // A re-promote of what prod already names, and what serves (if anything does), moves
7784
+ // nothing, so it is not judged again. One whose serve would change what runs is.
7785
+ const serving = this.servingPointerOfVertical(verticalSlug)?.versionId ?? versionId;
7786
+ if (listed && channel === 'prod' && (outgoing?.id !== versionId || serving !== versionId) &&
7787
+ versionAdmission(versionId).admission_note === AUTO_ADMISSION_NOTE) {
7788
+ throw autoAdmittedProdRefusal(verticalSlug, versionId);
7789
+ }
7731
7790
  this.directory
7732
7791
  .prepare(`INSERT INTO vertical_channels (vertical_slug, channel, version_id, updated_at)
7733
7792
  VALUES (?, ?, ?, ?)
@@ -8036,33 +8095,39 @@ export class SqliteScopeHost {
8036
8095
  migrationTag: r.serving_migration_tag,
8037
8096
  });
8038
8097
  },
8039
- setVerticalServing: async (actor, verticalSlug, state) => {
8098
+ setVerticalServing: async (actor, verticalSlug, state, expected) => {
8040
8099
  const parsed = verticalServingState.parse(state);
8041
8100
  const r = this.directory
8042
8101
  .prepare('SELECT * FROM verticals WHERE slug = ?')
8043
8102
  .get(verticalSlug);
8044
8103
  if (!r)
8045
8104
  throw substratError('not_found', `unknown vertical '${verticalSlug}'`);
8105
+ // #2577: the caller's upload diffed its DO-class migration from `expected`'s class set and tag.
8106
+ if (expected !== undefined && !migratesFrom(r, expected))
8107
+ throw servingRaceRefusal(verticalSlug);
8046
8108
  // #2178: every install on the serving script runs the new version from this moment.
8047
8109
  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) : []);
8048
- // #2528: a compare-and-set on the serving ref and version the gate judged
8049
- // (`SERVES_NOW_CONDITION`), and the row's `before` read in the transaction that moves it,
8050
- // as the Cloudflare host does.
8110
+ // #2528/#2577: a compare-and-set on the serving state the gate judged, its ref, version,
8111
+ // class set and tag (a serve and a re-serve can bring the ref and version back over a grown
8112
+ // class set), and the row's `before` read in the transaction that moves it, as the
8113
+ // Cloudflare host does.
8051
8114
  let before = null;
8052
8115
  let raced = false;
8053
8116
  this.directoryWriteAndAudit(() => {
8054
8117
  const now = this.directory
8055
- .prepare('SELECT serving_ref, serving_version_id FROM verticals WHERE slug = ?')
8118
+ .prepare('SELECT * FROM verticals WHERE slug = ?')
8056
8119
  .get(verticalSlug);
8057
8120
  if (!now)
8058
8121
  throw substratError('not_found', `unknown vertical '${verticalSlug}'`);
8059
8122
  before = now.serving_ref ? { ref: now.serving_ref, versionId: now.serving_version_id } : null;
8060
- const moved = this.directory
8123
+ raced = !servesAsFrom(now, servedFromRow(r));
8124
+ if (raced)
8125
+ return false;
8126
+ this.directory
8061
8127
  .prepare(`UPDATE verticals SET serving_ref = ?, serving_version_id = ?,
8062
- serving_do_classes = ?, serving_migration_tag = ? WHERE ${SERVES_NOW_CONDITION}`)
8063
- .run(parsed.ref, parsed.versionId, JSON.stringify(parsed.doClasses), parsed.migrationTag, verticalSlug, r.serving_ref ?? null, r.serving_version_id ?? null).changes > 0;
8064
- raced = !moved;
8065
- return moved;
8128
+ serving_do_classes = ?, serving_migration_tag = ? WHERE slug = ?`)
8129
+ .run(parsed.ref, parsed.versionId, JSON.stringify(parsed.doClasses), parsed.migrationTag, verticalSlug);
8130
+ return true;
8066
8131
  }, () => this.recordAdmin(actor, 'setVerticalServing', { tenantId: null }, before, { vertical: verticalSlug, ref: parsed.ref, versionId: parsed.versionId }));
8067
8132
  if (raced)
8068
8133
  throw servingRaceRefusal(verticalSlug);
@@ -8572,6 +8637,8 @@ export class SqliteScopeHost {
8572
8637
  before = readTenant(tenantId);
8573
8638
  if (!before)
8574
8639
  throw substratError('not_found', `unknown tenant: ${tenantId}`);
8640
+ if (before.status === 'reaped')
8641
+ throw substratError('conflict', `tenant ${tenantId} is reaped and cannot change status`);
8575
8642
  return (status === 'deleting'
8576
8643
  ? this.directory
8577
8644
  .prepare('UPDATE tenants SET status = ?, deleting_at = ? WHERE tenant_id = ?')
@@ -8827,11 +8894,14 @@ export class SqliteScopeHost {
8827
8894
  // reason: its shadow tables cannot be replayed (the DO host rejects the name
8828
8895
  // outright), and it is recomputable — `loadDump` rebuilds it from the content
8829
8896
  // tables it just loaded. A dump carries the rows, never the index over them.
8897
+ //
8898
+ // The wire nonce store is dropped as well (RFC wire-signing §4): it describes calls this
8899
+ // store accepted, not the scope's data, so a load builds it empty.
8830
8900
  const defs = db
8831
8901
  .prepare(`SELECT name, sql FROM sqlite_master
8832
8902
  WHERE type = 'table' AND name NOT GLOB 'sqlite_*' AND sql IS NOT NULL
8833
8903
  ORDER BY name`)
8834
- .all().filter(({ name }) => !isSearchIndexTable(name));
8904
+ .all().filter(({ name }) => !isSearchIndexTable(name) && !isWireNonceTable(name));
8835
8905
  const tables = defs.map(({ name, sql }) => {
8836
8906
  // `.raw(true)` gives positional rows; cells stay as-is (blobs are Buffers, not
8837
8907
  // nulled as in a UI read) so the dump is byte-faithful. The name is from the live
@@ -9245,52 +9315,59 @@ export class SqliteScopeHost {
9245
9315
  },
9246
9316
  grantEntitlement: async (actor, tenantId, entitlementKey, plan) => {
9247
9317
  const input = entitlementGrantInput.parse(plan ?? {});
9248
- const existing = this.directory
9249
- .prepare('SELECT expires_at, quota, plan FROM _substrat_entitlements WHERE tenant_id = ? AND entitlement_key = ?')
9250
- .get(tenantId, entitlementKey);
9251
- // PATCH semantics (entitlementGrantInput): omitted preserves, null clears —
9252
- // a bare re-grant on an idempotent provisioning path must not erase a
9253
- // trial's expiry and quietly turn it perpetual.
9254
- const next = {
9255
- expiresAt: input.expiresAt !== undefined ? input.expiresAt : (existing?.expires_at ?? null),
9256
- quota: input.quota !== undefined ? input.quota : (existing?.quota ?? null),
9257
- plan: input.plan !== undefined ? input.plan : (existing?.plan ?? null),
9258
- };
9259
- // Idempotent: a grant that changes nothing is not audited.
9260
- if (existing &&
9261
- existing.expires_at === next.expiresAt &&
9262
- existing.quota === next.quota &&
9263
- existing.plan === next.plan) {
9264
- return;
9265
- }
9266
- const now = new Date().toISOString();
9267
- // granted_at/granted_by restamp on every effective change: a renewal is a
9268
- // new grant act, and the full history is the admin log below.
9269
- this.directory
9270
- .prepare(`INSERT INTO _substrat_entitlements
9271
- (tenant_id, entitlement_key, expires_at, quota, plan, granted_at, granted_by)
9272
- VALUES (?, ?, ?, ?, ?, ?, ?)
9273
- ON CONFLICT (tenant_id, entitlement_key) DO UPDATE SET
9274
- expires_at = excluded.expires_at,
9275
- quota = excluded.quota,
9276
- plan = excluded.plan,
9277
- granted_at = excluded.granted_at,
9278
- granted_by = excluded.granted_by`)
9279
- .run(tenantId, entitlementKey, next.expiresAt, next.quota, next.plan, now, actor);
9280
- this.recordAdmin(actor, 'grantEntitlement', { tenantId }, existing
9318
+ // #2526: the held grant (its `before`, and the no-op judgement), the write and the admin
9319
+ // row are one directory unit.
9320
+ let existing;
9321
+ let next = { expiresAt: null, quota: null, plan: null };
9322
+ this.directoryWriteAndAudit(() => {
9323
+ existing = this.directory
9324
+ .prepare('SELECT expires_at, quota, plan FROM _substrat_entitlements WHERE tenant_id = ? AND entitlement_key = ?')
9325
+ .get(tenantId, entitlementKey);
9326
+ // PATCH semantics (entitlementGrantInput): omitted preserves, null clears —
9327
+ // a bare re-grant on an idempotent provisioning path must not erase a
9328
+ // trial's expiry and quietly turn it perpetual.
9329
+ next = {
9330
+ expiresAt: input.expiresAt !== undefined ? input.expiresAt : (existing?.expires_at ?? null),
9331
+ quota: input.quota !== undefined ? input.quota : (existing?.quota ?? null),
9332
+ plan: input.plan !== undefined ? input.plan : (existing?.plan ?? null),
9333
+ };
9334
+ // Idempotent: a grant that changes nothing is not audited.
9335
+ if (existing &&
9336
+ existing.expires_at === next.expiresAt &&
9337
+ existing.quota === next.quota &&
9338
+ existing.plan === next.plan) {
9339
+ return false;
9340
+ }
9341
+ const now = new Date().toISOString();
9342
+ // granted_at/granted_by restamp on every effective change: a renewal is a
9343
+ // new grant act, and the full history is the admin log.
9344
+ this.directory
9345
+ .prepare(`INSERT INTO _substrat_entitlements
9346
+ (tenant_id, entitlement_key, expires_at, quota, plan, granted_at, granted_by)
9347
+ VALUES (?, ?, ?, ?, ?, ?, ?)
9348
+ ON CONFLICT (tenant_id, entitlement_key) DO UPDATE SET
9349
+ expires_at = excluded.expires_at,
9350
+ quota = excluded.quota,
9351
+ plan = excluded.plan,
9352
+ granted_at = excluded.granted_at,
9353
+ granted_by = excluded.granted_by`)
9354
+ .run(tenantId, entitlementKey, next.expiresAt, next.quota, next.plan, now, actor);
9355
+ return true;
9356
+ }, () => this.recordAdmin(actor, 'grantEntitlement', { tenantId }, existing
9281
9357
  ? { entitlementKey, expiresAt: existing.expires_at, quota: existing.quota, plan: existing.plan }
9282
- : null, { entitlementKey, ...next });
9358
+ : null, { entitlementKey, ...next }));
9283
9359
  },
9284
9360
  revokeEntitlement: async (actor, tenantId, entitlementKey) => {
9285
- const existing = this.directory
9286
- .prepare('SELECT expires_at, quota, plan FROM _substrat_entitlements WHERE tenant_id = ? AND entitlement_key = ?')
9287
- .get(tenantId, entitlementKey);
9288
- if (!existing)
9289
- return; // nothing held, nothing changed
9290
- this.directory
9291
- .prepare('DELETE FROM _substrat_entitlements WHERE tenant_id = ? AND entitlement_key = ?')
9292
- .run(tenantId, entitlementKey);
9293
- this.recordAdmin(actor, 'revokeEntitlement', { tenantId }, { entitlementKey, expiresAt: existing.expires_at, quota: existing.quota, plan: existing.plan }, null);
9361
+ // #2526: one statement removes the grant and answers what it held, which is the admin
9362
+ // row's `before`; the row commits with it.
9363
+ let removed;
9364
+ this.directoryWriteAndAudit(() => {
9365
+ removed = this.directory
9366
+ .prepare(`DELETE FROM _substrat_entitlements WHERE tenant_id = ? AND entitlement_key = ?
9367
+ RETURNING expires_at, quota, plan`)
9368
+ .get(tenantId, entitlementKey);
9369
+ return removed !== undefined; // nothing held, nothing changed
9370
+ }, () => this.recordAdmin(actor, 'revokeEntitlement', { tenantId }, { entitlementKey, expiresAt: removed.expires_at, quota: removed.quota, plan: removed.plan }, null));
9294
9371
  },
9295
9372
  listEntitlements: async (actor, tenantId) => {
9296
9373
  const grants = this.directory