import { ControllerSessionCreationObligationModel, type TControllerSessionCreationObligationState, } from '../ts/classes.managedsessionmodels.js'; import { controllerManagedSessionRecoveryEntryLimit, controllerManagedSessionRecoveryPageEntryLimit, type ControllerSessionIdentityService, } from '../ts/classes.sessionidentityservice.js'; import { controllerRuntimeIdKey, type TControllerSessionId } from '../ts_interfaces/index.js'; import { ControllerLegacyManagedSessionMigrationModel, ControllerLegacyManagedSessionV26Migration, controllerLegacyManagedSessionMigrationDocumentId, type IControllerLegacyManagedSessionMigrationInput, type IControllerLegacyManagedSessionMigrationResult, } from './v26_legacymanagedsessionmigration.js'; export interface IControllerLegacyManagedSessionMigrationRunnerOptions { controllerId: string; issuerId: string; sessionIdentityService: ControllerSessionIdentityService; } export type TControllerLegacyManagedSessionMigrationRunnerInput = Omit< IControllerLegacyManagedSessionMigrationInput, | 'controllerId' | 'issuerId' | 'creationObligationsComplete' | 'creationObligationRuntimeIds' >; const creationObligationStates: readonly TControllerSessionCreationObligationState[] = [ 'pending', 'admitted', 'retired', ]; export class ControllerLegacyManagedSessionMigrationRunner { private readonly migration: ControllerLegacyManagedSessionV26Migration; constructor(private readonly options: IControllerLegacyManagedSessionMigrationRunnerOptions) { this.migration = new ControllerLegacyManagedSessionV26Migration({ issuerId: options.issuerId, sessionIdentityService: options.sessionIdentityService, }); } private async markerExists( inputArg: TControllerLegacyManagedSessionMigrationRunnerInput, ): Promise { inputArg.signal.throwIfAborted(); const marker = await ControllerLegacyManagedSessionMigrationModel.exact.findStoredOne({ id: controllerLegacyManagedSessionMigrationDocumentId( this.options.controllerId, this.options.issuerId, inputArg.projectId, inputArg.harnessId, ), }, { signal: inputArg.signal }); inputArg.signal.throwIfAborted(); return marker !== null && marker !== undefined; } private async listCreationObligationRuntimeIds( inputArg: TControllerLegacyManagedSessionMigrationRunnerInput, ): Promise { const runtimeIds = new Map(); for (const state of creationObligationStates) { let lastId: string | undefined; while (true) { inputArg.signal.throwIfAborted(); const pageLimit = Math.min( controllerManagedSessionRecoveryPageEntryLimit, controllerManagedSessionRecoveryEntryLimit + 1 - runtimeIds.size, ); const page = await ControllerSessionCreationObligationModel.exact.findStored({ filter: { issuerId: this.options.issuerId, projectIdentityId: inputArg.projectId, 'runtimeId.harnessId': inputArg.harnessId, state, ...(lastId === undefined ? {} : { id: { $gt: lastId } }), }, sort: { id: 1 }, limit: pageLimit, signal: inputArg.signal, }); inputArg.signal.throwIfAborted(); if (runtimeIds.size + page.length > controllerManagedSessionRecoveryEntryLimit) { throw new Error('The legacy creation-obligation scope exceeds its recovery limit.'); } if (page.length > pageLimit) { throw new Error('A legacy creation-obligation page exceeds its requested limit.'); } if (page.length === 0) break; for (const stored of page) { const obligation = ControllerSessionCreationObligationModel.exact.toPersisted(stored); if ( (lastId !== undefined && obligation.id <= lastId) || obligation.issuerId !== this.options.issuerId || obligation.projectIdentityId !== inputArg.projectId || obligation.runtimeId.harnessId !== inputArg.harnessId || obligation.state !== state ) throw new Error('A creation obligation escaped its legacy migration scope.'); const key = controllerRuntimeIdKey(obligation.runtimeId); if (runtimeIds.has(key)) { throw new Error('The legacy migration found duplicate creation obligations.'); } runtimeIds.set(key, { ...obligation.runtimeId }); lastId = obligation.id; } if (page.length < pageLimit) break; } } return [...runtimeIds.values()]; } public async run( inputArg: TControllerLegacyManagedSessionMigrationRunnerInput, ): Promise { const creationObligationRuntimeIds = await this.markerExists(inputArg) ? [] : await this.listCreationObligationRuntimeIds(inputArg); return this.migration.run({ ...inputArg, controllerId: this.options.controllerId, issuerId: this.options.issuerId, creationObligationsComplete: true, creationObligationRuntimeIds, }); } }