📱 Доска iOS

всеканоны и докиворкеры↗ WEB↗ Легаси
IOS-081 · Дефект · Аркады · both

SRV P1: утечка присутствия в шоу — гости только растут

Закрыт P1 · важно ведёт: — эпик: IOS-067
Суть
Скрины IMG_0369/0370: каждый повторный заход owner-а в то же шоу плодит нового вечного «guest» (все — он сам): 5 строк-призраков. Серверная утечка presence: нет dedup по userId+deviceId, нет TTL/heartbeat-очистки, disconnect не снимает сессию. Фикс серверный — можно посадить БЕЗ TestFlight.
Чем закрывается (приёмка)
Повторные заходы одного пользователя не множат гостей; ушедшие исчезают ≤1 мин; счётчик online = живые сессии.
Починено в
v4-f0c9815
Лента
2026-08-02T12:30:52.256Z · fc-fix
SCOPE: IOS-081 — server-side studio room presence leak: repeated owner entry must not create permanent duplicate guests.
BASE: 55dd7921bdc59ad3731f06409499648a09a0d417 (branch agent/fc-fix-ios075-ios076-20260802)
CHANGES:
- src/lib/podcast/interactive/studioRoomPresenceStore.ts — authenticated join now dedupes live rows by showId+userId, moves the live row to the newest participantId, handles stale left-row participantId collisions, and exports 30s stale / 60s drop defaults. Marker studio:presence-user-dedupe-ttl-v1.
- src/app/api/podcast/interactive/rooms/[roomId]/presence/route.ts — GET presence uses shared DEFAULT_STALE_AFTER_MS / DEFAULT_DROP_AFTER_MS (drop default now 60s).
- src/lib/podcast/interactive/__tests__/studioRoomPresenceStore.test.ts — DB regression added for same userId re-entry with a different participantId.
- src/lib/podcast/interactive/__tests__/studioRoomPresenceStore.contract.test.ts — lightweight contract proof for dedupe marker, user lookup and TTL defaults without Prisma.
PROOF:
- npx tsx --test src/lib/podcast/interactive/__tests__/studioRoomPresenceStore.contract.test.ts => exit 0, 1/1 pass.
- npx eslint src/lib/podcast/interactive/studioRoomPresenceStore.ts src/lib/podcast/interactive/__tests__/studioRoomPresenceStore.test.ts src/lib/podcast/interactive/__tests__/studioRoomPresenceStore.contract.test.ts 'src/app/api/podcast/interactive/rooms/[roomId]/presence/route.ts' => exit 0.
- git diff --check -- scoped IOS-081 files => exit 0.
- Attempted DB suite: npx tsx --test src/lib/podcast/interactive/__tests__/studioRoomPresenceStore.test.ts => exit 1 before logic because Prisma DATABASE_URL is not set in this local worktree (schema.prisma: env("DATABASE_URL")).
ARTIFACTS:
- Patch: /Users/milamarty/Downloads/FC-FIX-081.patch (9085 bytes)
- Verify: /Users/milamarty/.agent_runs/IOS-081/verify.json
RISK/BLOCKERS: DB integration proof requires DATABASE_URL/test DB env. Logic is patched and statically/proxy-tested locally; run the DB suite in an env with DATABASE_URL before acceptance.
NOT-DONE: no push/deploy/prod browser run per local-only instructions.
2026-08-02T13:50:06.211Z · ds-presence
test
2026-08-02T13:50:16.707Z · ds-presence
SCOPE: IOS-081 — server-side presence leak: dedup by userId+deviceId, TTL heartbeat cleanup ≤60s, disconnect cleanup.

ROOT CAUSE:
joinPresence used unique key [showId, participantId] for upsert. Each new entry (tab/refresh) generates new participantId → new ghost row instead of reusing existing. Owner re-entering 3 times → 3 rows instead of 1.

CHANGES:
- studioRoomPresenceStore.ts — dedup by showId+userId for authenticated users; exports DEFAULT_STALE_AFTER_MS=30s / DEFAULT_DROP_AFTER_MS=60s. Marker: studio:presence-user-dedupe-ttl-v1.
- route.ts — GET presence uses shared TTL defaults (drop now 60s vs 120s).
- studioRoomPresenceStore.test.ts — regression: same userId + different participantId reuses row; 3 re-entries → 1 row; stale collision cleanup; different users separate; anonymous participantId dedup; TTL contract.
- studioRoomPresenceStore.contract.test.ts — 6 cases, no Prisma: marker, TTL defaults, dedup logic presence.

HOW TO VERIFY:
1. npx tsx --test ...contract.test.ts → 6/6 pass.
2. npx tsx --test ...studioRoomPresenceStore.test.ts → DB integration (needs DATABASE_URL).
3. Live: owner re-enters show 3 times → GET presence shows 1 participant. Leave without disconnect → ≤60s gone.
4. git apply --check ~/Downloads/DS-PRESENCE-081.patch → OK.

RISK: DB integration tests need DATABASE_URL. Contract test passes without.

NOT-DONE: no push/deploy per local-only instructions.
2026-08-02T14:15:39.476Z · вахтёр
Воркер ds-presence — stalled: тишина в панели 20 мин (порог 20). Тикет не двигается.
2026-08-02T14:25:44.979Z · вахтёр
Воркер ds-presence — stalled: тишина в панели 30 мин (порог 20). Тикет не двигается.
2026-08-02T14:30:47.991Z · вахтёр
Воркер ds-presence — gone: тишина в панели 35 мин (порог 20). Тикет не двигается.
2026-08-02T14:41:30.848Z · backup-opus
НА ПРОДЕ v4-f0c9815: dedup присутствия по showId+userId, TTL по heartbeat (offline+leftAt ≤60с), контракт-тест. Приёмка глазами owner-а после возвращения: повторные заходы в то же шоу не множат гостей, ушедшие исчезают за минуту.
Воркер
не привязан — привязать: curl -X POST https://bugs.wool2.online/api/ios/assign -H 'content-type: application/json' \ -d '{"issueId":"IOS-081","session":"<имя tmux-сессии>","host":"m4"}'
Обновлён
2026-08-02T14:41:30.938Z