WEB-77 · Эпик · Инфраструктура · web
ЭПИК: Студия 10 → 10 000 комнат (по спайку SCALESPIKE, owner greenlight 03.08)
Закрыт
P1 · важно
ведёт: codex-spike-p1
Суть
Спайк-дизайн: ~/Downloads/STUDIO-SCALE-SPIKE.md (копия у owner-а). 7 узких мест: presence-polling шторм, write-heavy read path, floor-кап 60 комнат, process-local warmup timers, SSE держит Node, one-process допущения, Prisma pool. Целевая архитектура: abstraction layer → Redis presence → event stream вместо polling → warmup без таймеров → floor-индекс → offhost-режим → hardening. Фазы = дети WEB-78..84.
## 2026-08-23 BOARDSWEEP: STALE-NOT-DEPLOYED
- Evidence: buildFixed is empty. The Studio scale epic has no deployed build pointer or production/load evidence bound to canonical.
- buildFixed: <empty>; canonical: v4-081ff4fb5 / 081ff4fb5f6110f8001daec7ed910ccef2e395dc.
- Status preserved by sweep: review.
Доказательства
[2026-08-24 STALEREV против 61bc04462; в проде с посадки 45 v4-5a4edbac3] Вердикт STILL-OPEN: эпик 10→10000 Studio без реализации; декомпозировать в children. Отчёт: A1 /home/ubuntu/waves/STALEREVIEW-REPORT.md (STALEREVIEW_DONE).
[2026-08-25 19:28Z] owner GO 19:22 на нарезку. Запущена волна epic77plan (A1, luna): аудит факта в a453b16 (floor index/realtime-gateway/hardening/канарейка комнат) + нарезка остатка на тикеты 0.5-2 дня с DoD. Отчёт EPIC77PLAN-REPORT.md.
[2026-08-27 ~11:45Z multiroom — КАРТА СОСТОЯНИЯ (линия 3, owner-напоминание)] Разведка по коду: durable-контур Meeting Room СУЩЕСТВУЕТ (комната, участники, legs каналов, consent, события, история turns, очистка legs); есть локальный Stage 11/12 multi-channel mock и Stage 14 in-process PCM16 fan-out. НО это не равно живой встрече нескольких каналов. Полная карта с границей «что работает / что нет» и планом доведения: A1:/home/ubuntu/waves/MULTIROOM-MAP.md. Отложенные дефекты линии: WEB-15 (браузер не индицирует речь ассистента), WEB-45/46 (roomcall отвечает на будущие даты как на свершившееся), WEB-48 (браузерный preflight делит общий runtime).
[2026-08-27 ~15:55Z mroom1 — ШАГ 1 ЭПИКА РЕАЛИЗОВАН (owner: «эпиком никто не занимается»)] Добавлен ship-dark room-scoped runtime для двух типов legs (browser + SIP): один provider-neutral PCM16 transport contract, один room mixer, одна очередь ходов/VAD, ОДИН assistant invocation на room turn, общий audio fan-out. По умолчанию runtime НЕ создаётся (флаг). Отчёт: A1:/home/ubuntu/waves/MROOM1-REPORT.md. Закрывает пункты 1-4 критериев готовности эпика (из MULTIROOM-MAP.md) для 2 каналов из 4. accmroom1 диспатчнута — 8 злых проверок: двойной ответ ассистента, fan-out во все legs, self-exclusion (эхо), детерминизм очереди и provenance при перебивании, disconnect одного leg, orphan bridge после аварийного обрыва, backpressure медленного потребителя, OFF-путь. Остаётся до полного эпика: Telegram, Signal, живая приёмка со звонком.
[2026-08-27 ~16:55Z mroom2 — RUNTIME ПОДКЛЮЧЁН к production entrypoints (закрытие NO-GO «код мёртв»)] Точки подключения с адресами: browser session-start (route.ts:188-196 durable binding -> runtime hook; production POST attachBrowser route.ts:462-475), browser session-turn (route.ts:197-220 -> room queue; production callback route.ts:388-408), SIP-путь по таблице MROOM2-REPORT.md. Статус: готово к ship-dark проверке, прод/звонки не запускались. accmroom2 диспатчнута с главным требованием прошлого провала: доказать, что ФЛАГ РЕАЛЬНО ВКЛЮЧАЕТ — интеграционным тестом через НАСТОЯЩИЕ обработчики (не моки нового модуля) + самостоятельный греп production-путей; плюс один ассистент на room turn (счётчик вызовов), fan-out, self-exclusion, provenance, disconnect/cleanup, backpressure, OFF=1:1 прод, несломанность WEB-398.
[2026-08-27 ~17:30Z accmroom2 NO-GO — приёмка поймала «включается, но звука нет»] Структурные вызовы coordinator в browser и SIP production-путях ДОБАВЛЕНЫ (принято). НО при включении флага в настоящем production singleton не подключается НИ ОДИН реальный audio transport: attachBrowser и attachSip доходят до `transport_not_configured`. Поэтому требования «один assistant на комнату», fan-out и WEB-398 принять нельзя — мультирум при ON фактически не работает. ЦЕННОЕ: приёмщик оставил acceptance-тест roomMultichannelProductionEntrypoints.acceptance.test.ts, который вызывает НАСТОЯЩИЕ handleBrowserRealtimeConsentCore/handleBrowserRealtimeSessionTurnCore без подмены coordinator и singleton (подменены только детерминированные dependency seams route core) — 3/3, причём первый тест НАМЕРЕННО фиксирует ошибочное production-поведение ON. Этот тест теперь эталон: позеленеет только когда звук реально пойдёт. mroom3 запущена на M4 (M4 был свободен, A1 держит 3 приёмки): подключить настоящий двунаправленный звук — браузерный PCM-поток (WebRTC/worklet/провайдерская сессия) и SIP-поток (ARI externalMedia/snoop) в общий room bridge, единый контракт PCM16, с прямым запретом выдавать мок за рабочий транспорт.
[2026-08-27 ~19:25Z accmroom3 GO ДЛЯ КУСКА — runtime + точка входа]
ПРИНЯТО: реальный звук подключён. PCM16 LE mono 16 кГц, кадр 20 мс = 640 байт, Socket.IO binary без base64, rendezvous по transportId; браузерный и SIP-транспорт встречаются в ОДНОЙ комнате. Эталон приёмки accmroom2 позеленел БЕЗ ослабления ассертов. SIP gateway syntax + regression 28/28 pass. Флаг OFF: runtime не создаётся вовсе.
ЧЕСТНО НЕ ЗАКРЫТО (прямо из отчёта приёмщика): Telegram transport/entrypoint; Signal transport/entrypoint; живой звонок — Asterisk externalMedia/RTP, провайдерская медиа и микрофон браузера по реальному внешнему стенду НЕ прогонялись. Local Socket.IO proof подтверждает runtime и rendezvous, но НЕ заменяет живой звонок. Для полного GO эпика нужен отдельный live-гейт с учётками, RTP/codec path и звонком из браузера/от провайдера.
ПОБОЧНОЕ: 7 baseline-падений mirror-compare-chat/mirror-contract воспроизводятся ОДИНАКОВО в MROOM3 и в wt-l59-accmroom2 (отсутствует dict/en в тестовом BuildMirrorResultInput) — это НЕ изменение MROOM3. Отдельного WEB-398 acceptance artifact в checkout нет, поэтому dedicated WEB-398 pass не заявляется.
ОТЧЁТ: A1:/home/ubuntu/waves/ACCMROOM3-REPORT.md; дерево wt-mroom3acc, ветка mroom3, HEAD 3c53215560ac384c5987ae4ecf3719c12694cc34.
ЗАПУЩЕНО: mroom4 (очередь A1 141-mroom4-brief.md) — транспорты Telegram и Signal в ту же комнату и тот же аудио-контракт: точка входа, конвертер формата, ДВУСТОРОННИЙ поток, OFF-путь, тесты в стиле репо (node --test + tsx). Живые учётки и внешний звонок НЕ трогать — это owner-гейт.
НАПОМИНАНИЕ О СМЫСЛЕ ЭПИКА (owner 27.08): доказать, что в ОДНОЙ комнате встречаются Signal, Telegram, SIP-звонок и браузер и могут разговаривать. Это не про PIN.
[2026-08-27 ~20:19Z mroom4 ЗАКОНЧИЛ — транспорты Telegram и Signal сделаны, приёмка запущена]
СДЕЛАНО (заявлено автором): общий provider-edge infra/messenger-room/roomAudioTransport.js для telegram/telegram_call и signal/signal_call; фабрики createTelegramRoomAudioTransport / createSignalRoomAudioTransport при выключенном флаге возвращают null; обёртки infra/telegram-call-adapter/roomAudioTransport.js и infra/signal-adapter/roomAudioTransport.js; разрешённые участники комнаты browser, sip, telegram, signal; Telegram/Signal авторизуются секретом из окружения. Цифры автора: новые JS 4/4, приёмочные Telegram/Signal 3/3, полный MROOM regression 33/33, ESLint и node --check чисто. Живые аккаунты и звонки НЕ запускались (owner-гейт).
РАБОТА ЗАФИКСИРОВАНА: лежала незакоммиченной (12 изменённых + 4 новых) — та же ловушка, что поймана сегодня на движке. Закоммичено в ветку mroom4, коммит a8fb20ba, 13 файлов, +989/-18, дерево чистое.
ЗАПУЩЕНА ПРИЁМКА accmroom4 (очередь A1 145). Главное требование: доказать ВСТРЕЧУ в одной комнате всех четырёх участников и звук в обе стороны — «модули существуют и тесты зелёные» не принимается. Плюс: реальные байты аудио-контракта (640 Б кадр, без base64, отвергать мусор), двусторонность каждого транспорта, чистый OFF-путь, поведение при ОТСУТСТВУЮЩЕМ секрете (сегодня в соседней линии на этом нашли дыру — подставлялся общеизвестный секрет из исходников), чужой провайдер/подделанный transportId/чужая комната, и что 7 известных baseline-падений mirror не выросли.
[2026-08-28 ~00:05Z СЛИЯНИЕ ДВУХ ЛИНИЙ ВЫПОЛНЕНО (telmerge)]
Причина: линии WEB-77 (ветка mroom6) и WEB-393 (ветка web393i) независимо правили ОДНИ файлы телефонии. Приёмка accmroom6 дала NO-GO по причинам, УЖЕ решённым в соседней ветке: запускатели SIP звали `infra/sip/gateway/telephony-secret-preflight.js`, которого в дереве mroom6 НЕТ (создан в web393i) -> MODULE_NOT_FOUND; и в mroom6 остались подстановки `dev-gw-secret` (`appSessionHttpClient.js:93`, `index.js:58`, `start-sip-dev.sh`), которые в web393i уже убраны. Проверено мной пофайлово в обоих деревьях.
Вместо доделки каждой линии выполнено СЛИЯНИЕ: общий предок `2c6c2276`, оба рабочих дерева были грязными -> сделаны snapshot-коммиты, чтобы слить ровно проверенные состояния; слияние вручную, `-X theirs/ours` не применялся. Результат — ветка `telmerge` в `/home/ubuntu/waves/wt-telmerge`.
Волна поступила правильно: неразрешённых противоречий между линиями не осталось, но ОДНО контрактное расхождение зафиксировано явно, а не решено самовольно — старая проверка эталонного теста ACC393F считает обращение с петлевого адреса при ОТСУТСТВУЮЩЕМ секрете основанием выдать пропуск дорожки, а новое поведение это запрещает.
Запущена acctelmerge с главным риском слияния: ПОФАЙЛОВО убедиться, что сохранены ОБА набора правок (потерянная при ручном слиянии чужая правка = NO-GO), заглушек не осталось нигде, запуск SIP с верным секретом работает (на этом споткнулась прошлая приёмка), тихие отказы закрыты. Плюс рассудить расхождение контрактов — с учётом свежего факта, что определять «своего» по петлевому адресу в этом приложении НЕЛЬЗЯ (см. WEB-387).
Урок в память: параллельные волны в одной зоне кода требуют волны слияния; при NO-GO сначала проверять соседнюю ветку.
[28.08 ~19:49Z ACCMROOM10 (круг 9, повтор): NO-GO — новый блокер на границе Signal-процесса]
Предыстория круга: ACCMROOM9 на codex умерла молча — модель упёрлась в собственную модерацию ровно на враждебных пробах (лог полон «flagged for possible cybersecurity risk»), отчёта и маркера не оставила. Перезапущено как ACCMROOM10 на luna с нейтральными формулировками. Правило подтверждено: security/adversarial приёмку не гонять на моделях, которые режут такой контент.
Отчёт: /Users/milamarty/ACCMROOM10-REPORT.md (дерево /home/milamarty.guest/mroom3, ветка mroom3, HEAD 3c532155).
ЗАКРЫТО (независимо воспроизведено приёмщиком): все 4 замечания ACCMROOM7 теперь отклоняют повреждённый ввод; выборочные негативы прошлых кругов 55/55 без регресса; SCOPED_EMPTY_CATCH_SCAN=0.
БЛОКЕР: infra/signal-adapter/signal-room-audio.js:~191 — socket.once('room-audio:ready', ...) принимает событие БЕЗ ПРОВЕРКИ PAYLOAD вообще: нет transportId, нет canonical PCM format check. У SIP-соседа такая проверка есть, у Signal-ветки нет.
Доказательство: 7/7 повреждённых payload (absent, null, number, array, object, foreign-id, …) приняты ровно как canonical, каждый в СВЕЖЕМ child-процессе, exit=0. Два одновременных контекста: оба приняли и мусор, и валидное, distinction=false. Три вопроса дали НЕТ/НЕТ/НЕТ — защита не переживает рестарт (durable validation state на границе отсутствует), не различает контексты, не отличает мусор от валидного.
Класс: «мусор читается как валидное» + «защита не переживает границу процесса» — обе системные болезни, зафиксированные 28.08.
Круг 10 запущен: MROOMHARD9 на M4 (бриф /Users/milamarty/mroomhard9-brief.md) — переиспользовать ИМЕННО существующую SIP-проверку как источник истины (не писать вторую копию правил), громкий fail-closed отказ с ненулевым кодом, работа в свежем процессе и при двух контекстах. Обязательны: таблица было/стало по 7 формам, проба двух контекстов с distinction=true, негативный тест (ослабить проверку — матрица обязана снова принять мусор), регресс 55/55.
ЗАКАЛКА К ПОСАДКЕ В l65/l66: пока НЕТ.
[28.08 ~20:25Z MROOMHARD9 СДАН — блокер круга 9 закрыт, приёмка ACCMROOM11 идёт]
Коммит автора: 01e6aecfbcb1de9b5c851726d6e587b338a048fc (ветка mroom3). Отчёт: /Users/milamarty/MROOMHARD9-REPORT.md.
Заявлено и подкреплено: матрица 7/7 повреждённых payload теперь REJECTED с exit 1, canonical принимается; два одновременных контекста дают distinction=true; при намеренно ОСЛАБЛЕННОЙ проверке 7/7 снова ACCEPTED (негативный тест валиден, проверка настоящая), затем проверка восстановлена; регрессии 55/55.
Приёмка ACCMROOM11 запущена на M4 (luna). Требования: прогнать 7 форм самому в свежем процессе, добавить минимум ПЯТЬ своих форм (верный transportId с неверным PCM, верный формат с чужим transportId, лишние поля, вложенный объект вместо строки, невидимые символы, число вместо строки, граничное значение), свой негативный тест, три вопроса, и обязательная проверка что валидация ПЕРЕИСПОЛЬЗУЕТ SIP-проверку, а не является второй копией правил.
[28.08 ~19:48Z ★ ACCMROOM11_VERDICT=GO — ЗАКАЛКА КОМНАТЫ ПРИНЯТА]
Отчёт /Users/milamarty/ACCMROOM11-REPORT.md. Проверялся коммит автора 01e6aecfbcb1de9b5c851726d6e587b338a048fc (ветка mroom3, дерево /home/milamarty.guest/mroom3 в Lima на M4).
Приёмщик прогнал матрицу 7 форм сам в свежем child-процессе на каждую строку, добавил СВОИ формы сверх авторских, проверил два одновременных контекста и сделал СВОЙ негативный тест (при ослаблении проверки мусор снова проходит — значит защита настоящая), проверил переиспользование существующей SIP-проверки вместо второй копии правил и громкость отказа (ненулевой код + наблюдаемая запись), прогнал регресс прошлых кругов.
СТАТУС ЛИНИИ: закалка комнаты ГОТОВА к посадке (l66). Путь пройден за два круга: ACCMROOM9 (умерла на модерации codex) → ACCMROOM10 NO-GO (блокер: room-audio:ready принимался без проверки payload, 7/7 мусора проходило) → MROOMHARD9 (подключена SIP-проверка как источник истины) → ACCMROOM11 GO.
[28.08 ~20:20Z ЗАКАЛКА КОМНАТЫ ПЕРЕНЕСЕНА С M4 НА A1 — готова к слиянию в l66]
Работа комнаты жила ТОЛЬКО в Lima на M4 (/home/milamarty.guest/mroom3, ветка mroom3) и на A1 её не было — проверено: коммиты 3c532155 и 01e6aecf в репозитории A1 отсутствовали, а их общий предок 2c6c2276 в линии УЖЕ есть. Это делает перенос чистым.
Перенос: `git bundle create /tmp/mroom3.bundle mroom3 --not 2c6c2276` в VM (форма с диапазоном `A..B` бандл делать отказывается — «Refusing to create empty bundle», нужна именно ссылка + --not), 94 КБ, verify OK. Маршрут VM → M4 (limactl copy) → ноут → A1, отпечаток 8f8e3243b04ad0c2 сверен на КАЖДОМ шаге и совпал везде.
На A1 влито как ветка refs/heads/mroom3-from-m4: два коммита — 3c532155 «feat(meeting-room): connect browser and SIP audio transports» и 01e6aecf «mroom9: validate room-audio:ready payload at signal child boundary».
ВНИМАНИЕ ДЛЯ ВОЛНЫ СЛИЯНИЯ l66: база комнаты — линия l59-merge, то есть заметно старше l65. Конфликты решать РУКАМИ, `-X theirs` запрещён.
[28.08 ~20:30Z СОСТОЯНИЕ КОМНАТЫ ПЕРЕД КОМПАКТОМ — ЛИНИЯ ЗАКРЫТА, ЖДЁТ ПОСАДКИ]
✅ ACCMROOM11_VERDICT=GO. Путь: ACCMROOM9 (умерла молча — codex упёрся в собственную модерацию на враждебных пробах) → ACCMROOM10 NO-GO (room-audio:ready принимался БЕЗ проверки payload, 7/7 мусора проходило как canonical, distinction=false) → MROOMHARD9 (подключена существующая SIP-проверка как источник истины, не вторая копия правил) → ACCMROOM11 GO.
Приёмка прогнала матрицу 7 форм сама в свежем child-процессе на каждую строку, добавила СВОИ формы сверх авторских, проверила два одновременных контекста и сделала свой негативный тест (при ослаблении проверки мусор снова проходит — значит защита настоящая), подтвердила переиспользование SIP-проверки файлами:строками и громкость отказа, прогнала регресс прошлых кругов.
★ ПЕРЕВОЗКА: работа жила ТОЛЬКО в Lima на M4 и в репозитории A1 отсутствовала — то есть существовала в одном экземпляре. Перевезена бандлом: `git bundle create /tmp/mroom3.bundle mroom3 --not 2c6c2276` (форма с диапазоном A..B отказывается: «Refusing to create empty bundle»), маршрут VM → M4 → ноут → A1, отпечаток 8f8e3243b04ad0c2 сверен на КАЖДОМ шаге. На A1 влито как ветка refs/heads/mroom3-from-m4: коммиты 3c532155 «feat(meeting-room): connect browser and SIP audio transports» и 01e6aecf «mroom9: validate room-audio:ready payload at signal child boundary».
⚠️ ДЛЯ ВОЛНЫ СЛИЯНИЯ l66: база комнаты — линия l59-merge (общий предок 2c6c2276 в линии есть), то есть заметно старше l65. Конфликты решать РУКАМИ, `-X theirs` ЗАПРЕЩЁН.
Дети
- WEB-164 Закрыт SCALESPIKE-00: измерение текущей базы (presence/SSE/DB)
- WEB-165 Закрыт SCALESPIKE-01: abstraction layer для room realtime state
- WEB-166 Закрыт SCALESPIKE-02: Redis presence + агрегированные counts
- WEB-167 Закрыт SCALESPIKE-03: room event stream вместо polling
- WEB-168 Закрыт SCALESPIKE-04: warmup без per-room таймеров
- WEB-169 Закрыт SCALESPIKE-05: floor index для 10k rooms
- WEB-170 Закрыт SCALESPIKE-06: offhost realtime mode
- WEB-171 Закрыт SCALESPIKE-07: operational hardening
- WEB-78 Закрыт SCALESPIKE-00 измерение текущей базы (метрики нагрузки presence/SSE/DB до изменений)
- WEB-79 Закрыт SCALESPIKE-01 abstraction layer для room realtime state
- WEB-80 Закрыт SCALESPIKE-02 Redis presence + агрегированные counts
- WEB-81 Закрыт SCALESPIKE-03 room event stream вместо polling
- WEB-82 Закрыт SCALESPIKE-04 warmup без per-room timers
- WEB-83 Закрыт SCALESPIKE-05 floor index для 10k rooms
- WEB-84 Закрыт SCALESPIKE-06 offhost realtime mode
- WEB-85 Закрыт SCALESPIKE-07 operational hardening
Лента
2026-08-10T23:55:34.487Z · backup-opus11.08 триаж: снято с in_progress — воркер-ассайни мёртв ~5-6 дней (orphaned), фактически НЕ в работе. Возвращено в К работе для честного переподхвата. Если покрыто активной нитью — подниму на нить при следующем скане.
Воркер
не привязан — привязать:
curl -X POST https://bugs.wool2.online/api/web/assign -H 'content-type: application/json' \
-d '{"issueId":"WEB-77","session":"<имя tmux-сессии>","host":"m4"}'
Обновлён
2026-08-28T21:29:30.315Z