Skip to content

Commit 2bbd3b1

Browse files
jahoomaclaude
andcommitted
Estimate waiting room wait as 1 minute per spot ahead
Decouples the user-facing wait estimate from the admission tick rate. The estimate is now a rough one-minute-per-spot rule of thumb, which reads more intuitively in the CLI than a tick-derived number that shifts with deployment cadence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 35021d8 commit 2bbd3b1

File tree

6 files changed

+20
-34
lines changed

6 files changed

+20
-34
lines changed

docs/freebuff-waiting-room.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,16 +246,16 @@ This is a **trust-the-client** design: the server still admits requests during t
246246

247247
## Estimated Wait Time
248248

249-
Computed in `session-view.ts` from the drip-admission rate:
249+
Computed in `session-view.ts` as a rough one-minute-per-spot-ahead estimate:
250250

251251
```
252-
waitMs = (position - 1) * admissionTickMs
252+
waitMs = (position - 1) * 60_000
253253
```
254254

255255
- Position 1 → 0 (next tick admits you)
256-
- Position 2 → one tick, and so on.
256+
- Position 2 → one minute, and so on.
257257

258-
This estimate **ignores health-gated pauses**: during a Fireworks incident admission halts entirely, so the actual wait can be longer. We choose to under-report here because showing "unknown" / "indefinite" is worse UX for the common case where the deployment is healthy.
258+
This estimate is intentionally decoupled from the admission tick — it's a human-friendly rule-of-thumb for the UI, not a precise projection. Actual wait depends on admission-tick cadence and health-gated pauses (during a Fireworks incident admission halts entirely), so the real wait can be longer or shorter.
259259

260260
## CLI Integration (frontend-side contract)
261261

web/src/app/api/v1/freebuff/session/__tests__/session.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ function makeSessionDeps(overrides: Partial<SessionDeps> = {}): SessionDeps & {
3333
return {
3434
rows,
3535
isWaitingRoomEnabled: () => true,
36-
admissionTickMs: 15_000,
3736
graceMs: 30 * 60 * 1000,
3837
now: () => now,
3938
getSessionRow: async (userId) => rows.get(userId) ?? null,

web/src/server/free-session/__tests__/public-api.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import type { SessionDeps } from '../public-api'
1111
import type { InternalSessionRow } from '../types'
1212

1313
const SESSION_LEN = 60 * 60 * 1000
14-
const TICK_MS = 15_000
1514
const GRACE_MS = 30 * 60 * 1000
1615

1716
function makeDeps(overrides: Partial<SessionDeps> = {}): SessionDeps & {
@@ -36,7 +35,6 @@ function makeDeps(overrides: Partial<SessionDeps> = {}): SessionDeps & {
3635
},
3736
_now: () => currentNow,
3837
isWaitingRoomEnabled: () => true,
39-
admissionTickMs: TICK_MS,
4038
graceMs: GRACE_MS,
4139
now: () => currentNow,
4240
getSessionRow: async (userId) => rows.get(userId) ?? null,

web/src/server/free-session/__tests__/session-view.test.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { estimateWaitMs, toSessionStateResponse } from '../session-view'
44

55
import type { InternalSessionRow } from '../types'
66

7-
const TICK_MS = 15_000
7+
const WAIT_PER_SPOT_MS = 60_000
88
const GRACE_MS = 30 * 60_000
99

1010
function row(overrides: Partial<InternalSessionRow> = {}): InternalSessionRow {
@@ -24,24 +24,22 @@ function row(overrides: Partial<InternalSessionRow> = {}): InternalSessionRow {
2424

2525
describe('estimateWaitMs', () => {
2626
test('position 1 → 0 wait (next tick picks you up)', () => {
27-
expect(estimateWaitMs({ position: 1, admissionTickMs: TICK_MS })).toBe(0)
27+
expect(estimateWaitMs({ position: 1 })).toBe(0)
2828
})
2929

30-
test('position N → (N-1) ticks ahead', () => {
31-
expect(estimateWaitMs({ position: 2, admissionTickMs: TICK_MS })).toBe(TICK_MS)
32-
expect(estimateWaitMs({ position: 10, admissionTickMs: TICK_MS })).toBe(9 * TICK_MS)
30+
test('position N → (N-1) minutes ahead', () => {
31+
expect(estimateWaitMs({ position: 2 })).toBe(WAIT_PER_SPOT_MS)
32+
expect(estimateWaitMs({ position: 10 })).toBe(9 * WAIT_PER_SPOT_MS)
3333
})
3434

3535
test('degenerate inputs return 0', () => {
36-
expect(estimateWaitMs({ position: 0, admissionTickMs: TICK_MS })).toBe(0)
37-
expect(estimateWaitMs({ position: 5, admissionTickMs: 0 })).toBe(0)
36+
expect(estimateWaitMs({ position: 0 })).toBe(0)
3837
})
3938
})
4039

4140
describe('toSessionStateResponse', () => {
4241
const now = new Date('2026-04-17T12:00:00Z')
4342
const baseArgs = {
44-
admissionTickMs: TICK_MS,
4543
graceMs: GRACE_MS,
4644
}
4745

@@ -69,7 +67,7 @@ describe('toSessionStateResponse', () => {
6967
instanceId: 'inst-1',
7068
position: 3,
7169
queueDepth: 10,
72-
estimatedWaitMs: 2 * TICK_MS,
70+
estimatedWaitMs: 2 * WAIT_PER_SPOT_MS,
7371
queuedAt: now.toISOString(),
7472
})
7573
})

web/src/server/free-session/public-api.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
ADMISSION_TICK_MS,
32
getSessionGraceMs,
43
isWaitingRoomEnabled,
54
} from './config'
@@ -25,7 +24,6 @@ export interface SessionDeps {
2524
/** Plain values, not getters: these never change at runtime. The deps
2625
* interface uses values rather than thunks so tests can pass numbers
2726
* inline without wrapping. */
28-
admissionTickMs: number
2927
graceMs: number
3028
now?: () => Date
3129
}
@@ -37,7 +35,6 @@ const defaultDeps: SessionDeps = {
3735
queueDepth,
3836
queuePositionFor,
3937
isWaitingRoomEnabled,
40-
admissionTickMs: ADMISSION_TICK_MS,
4138
get graceMs() {
4239
// Read-through getter so test overrides via env still work; the value
4340
// itself is materialized once per call. Cheaper than a thunk because
@@ -64,7 +61,6 @@ async function viewForRow(
6461
row,
6562
position,
6663
queueDepth: depth,
67-
admissionTickMs: deps.admissionTickMs,
6864
graceMs: deps.graceMs,
6965
now: nowOf(deps),
7066
})

web/src/server/free-session/session-view.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ export function toSessionStateResponse(params: {
1313
row: InternalSessionRow | null
1414
position: number
1515
queueDepth: number
16-
admissionTickMs: number
1716
graceMs: number
1817
now: Date
1918
}): SessionStateResponse | null {
20-
const { row, position, queueDepth, admissionTickMs, graceMs, now } = params
19+
const { row, position, queueDepth, graceMs, now } = params
2120
if (!row) return null
2221

2322
if (row.status === 'active' && row.expires_at) {
@@ -51,7 +50,7 @@ export function toSessionStateResponse(params: {
5150
instanceId: row.active_instance_id,
5251
position,
5352
queueDepth,
54-
estimatedWaitMs: estimateWaitMs({ position, admissionTickMs }),
53+
estimatedWaitMs: estimateWaitMs({ position }),
5554
queuedAt: row.queued_at.toISOString(),
5655
}
5756
}
@@ -60,18 +59,14 @@ export function toSessionStateResponse(params: {
6059
return null
6160
}
6261

62+
const WAIT_MS_PER_SPOT_AHEAD = 60_000
63+
6364
/**
64-
* Wait-time estimate under the drip-admission model: one user per
65-
* `admissionTickMs`, gated by Fireworks health. Ignoring health pauses, the
66-
* user at position P waits roughly `(P - 1) * admissionTickMs`.
67-
*
65+
* Rough wait-time estimate shown to queued users: one minute per spot ahead.
6866
* Position 1 → 0ms (next tick picks you up).
6967
*/
70-
export function estimateWaitMs(params: {
71-
position: number
72-
admissionTickMs: number
73-
}): number {
74-
const { position, admissionTickMs } = params
75-
if (position <= 1 || admissionTickMs <= 0) return 0
76-
return (position - 1) * admissionTickMs
68+
export function estimateWaitMs(params: { position: number }): number {
69+
const { position } = params
70+
if (position <= 1) return 0
71+
return (position - 1) * WAIT_MS_PER_SPOT_AHEAD
7772
}

0 commit comments

Comments
 (0)