@@ -278,6 +278,16 @@ const defaultDeps: SessionDeps = {
278278
279279const nowOf = ( deps : SessionDeps ) : Date => ( deps . now ?? ( ( ) => new Date ( ) ) ) ( )
280280
281+ function isSessionRowCompatibleWithAccessTier (
282+ row : InternalSessionRow ,
283+ accessTier : FreebuffAccessTier ,
284+ ) : boolean {
285+ if ( accessTier === 'limited' && ( row . access_tier ?? 'full' ) !== 'limited' ) {
286+ return false
287+ }
288+ return isFreebuffModelAllowedForAccessTier ( row . model , accessTier )
289+ }
290+
281291async function viewForRow (
282292 userId : string ,
283293 deps : SessionDeps ,
@@ -389,14 +399,7 @@ export async function requestSession(params: {
389399 // fresh admissions — blocking a reclaim here would strand a user with an
390400 // active 5th session unable to reconnect after a CLI restart.
391401 let existing = await deps . getSessionRow ( params . userId )
392- const existingAccessTier = existing ?. access_tier ?? 'full'
393- if (
394- existing &&
395- ( accessTier === 'limited'
396- ? existingAccessTier !== 'limited' ||
397- ! isFreebuffModelAllowedForAccessTier ( existing . model , accessTier )
398- : ! isFreebuffModelAllowedForAccessTier ( existing . model , accessTier ) )
399- ) {
402+ if ( existing && ! isSessionRowCompatibleWithAccessTier ( existing , accessTier ) ) {
400403 await deps . endSession ( {
401404 userId : params . userId ,
402405 now,
@@ -539,9 +542,11 @@ async function attachRateLimit(
539542}
540543
541544/**
542- * Read-only check of the caller's current state. Does not mutate or rotate
543- * `instance_id`. The CLI sends its currently-held `claimedInstanceId` so we
544- * can return `superseded` if a newer CLI on the same account took over.
545+ * Check of the caller's current state. Does not rotate `instance_id`. The CLI
546+ * sends its currently-held `claimedInstanceId` so we can return `superseded`
547+ * if a newer CLI on the same account took over. Mutates only to clear rows
548+ * that the current access tier can no longer use, so they don't leak queue or
549+ * active capacity after the CLI receives `none`.
545550 *
546551 * Returns:
547552 * - `disabled` when the waiting room is off
@@ -593,11 +598,12 @@ export async function getSessionState(params: {
593598
594599 if ( ! row ) return noneResponse ( )
595600
596- if (
597- accessTier === 'limited' &&
598- ( ( row . access_tier ?? 'full' ) !== 'limited' ||
599- ! isFreebuffModelAllowedForAccessTier ( row . model , accessTier ) )
600- ) {
601+ if ( ! isSessionRowCompatibleWithAccessTier ( row , accessTier ) ) {
602+ await deps . endSession ( {
603+ userId : params . userId ,
604+ now : nowOf ( deps ) ,
605+ sessionLengthMs : deps . sessionLengthMs ,
606+ } )
601607 return noneResponse ( )
602608 }
603609
@@ -747,11 +753,7 @@ export async function checkSessionAdmissible(params: {
747753 }
748754 }
749755
750- if (
751- accessTier === 'limited' &&
752- ( ( row . access_tier ?? 'full' ) !== 'limited' ||
753- ! isFreebuffModelAllowedForAccessTier ( row . model , accessTier ) )
754- ) {
756+ if ( ! isSessionRowCompatibleWithAccessTier ( row , accessTier ) ) {
755757 return {
756758 ok : false ,
757759 code : 'session_model_mismatch' ,
0 commit comments