WEB board

всеканоны и докиворкеры↗ iOS↗ Легаси
WEB-393 · Задача · — · web

[ТЕЛЕФОНИЯ] Голосовые мосты: 8011/8013 не подняты, 8014 рвётся внутри spike-шлюза — довести контур до живого звонка-диалога

Закрыт P1 · важно ведёт: — эпик: WEB-395
Доказательства
[2026-08-27 00:35Z] Диспатчнут web393bridges на A1 (luna xhigh) — разбор lane-ов 8010/8011/8013/8014, фиксы в копии, deploy-план координатору. База — свежая живая копия ПОСЛЕ эхо-деплоя.
[2026-08-27 01:05Z] Боевой звонок owner 23:21: местами обрывы связи, глотание слов, хрип («качество так себе»). В логе шлюза во время звонка: audiosocket lifecycle 403 origin_boundary_rejected на realtime-tool-defs + live-turns persist — ТРЕТИЙ непропатченный s2s-коллер origin-lock (upstreamLifecycleClient.js buildHeaders). ПОПРАВЛЕНО на проде: Origin/Referer 127.0.0.1:3010 (бэкап .bak-web075-origin), node --check OK, сервис перезапущен. Обрывы/качество аудио — предмет бегущей волны web393bridges (+ проверить не рвал ли 403 tool-defs сам диалог).
[2026-08-27 01:50Z] ДОКУМЕНТЫ (путь эволюции): диагностическая база: живая копия A1:/home/ubuntu/waves/pi-gateway-live/ (после эхо-деплоя); бриф queue/043; сдача WEB393-REPORT.md; смежное: origin-lock трейс звонка 23:21 (третий коллер upstreamLifecycleClient.js, бэкап .bak-web075-origin на Pi).
[2026-08-27 02:55Z] ЗАДЕПЛОЕНО на Pi: бэкап /home/pi/WEB393-backup-20260826T234937Z; файлы (SHA байт-в-байт): snoop 515bdf2a (bounded recovery 8014, эхо-фикс сохранён — дифф против задеплоенного 143 строки recovery-скоуп, bargeInDetector нетронут f908d1be), externalMedia 206757ec, index f31805 (readiness), start-experimental 2961d2. Волна путала пути (корень vs infra/sip/gateway) — смаплено корректно, рантайм один. Оба юнита active; ЭКСПЕРИМЕНТАЛЬНЫЙ ЛЕЙН 8013 ПОДНЯТ (свой Asterisk 20.19 :8089); readiness :4080 = ready, snoop connected. Тесты волны 9/9. ОСТАЛОСЬ: матрица звонков 8010/8011/8013/8014 боем — утром с owner. -> review.
[2026-08-27 20:30Z] МАТРИЦА ЗВОНКОВ owner (боевые): 8010 PIN->room OK, 8014 PIN->room OK, 8011 БЕЗ PIN сразу ассистент+документы = УЯЗВИМОСТЬ (lane открывал сессию без identity-гейта), 8013 invalid extension. ДЫРА 8011 ЗАГЛУШЕНА на проде немедленно (runtime-диалплан /tmp/ag-asterisk-native/extensions.conf, весь 8011-блок в WEB393-HOLD, dialplan reload — 8011 теперь падает в _X. invalid; бэкап .bak-8011hold; 8010/8014 целы). web393b в очереди: гейт для 8011 (PIN/allowlist) + маршрут 8013 на experimental lane + контрольная матрица с негативом. Зеркало A1 обновлено (runtime диалплан + прод-шлюз).
[2026-08-27 20:50Z] КОМПАКТ-ЧЕКПОИНТ (хендофф Downloads/HANDOFF-COMPACT-2026-08-27b.md): 8011 security-hole заглушена в проде (runtime-диалплан WEB393-HOLD, бэкап .bak-8011hold). web393b: гейт 8011 (PIN/allowlist) + 8013 experimental-lane маршрут + контрольная матрица с негативом. После готовности — деплой с бэкапом + звонок owner.

[2026-08-27 07:30Z web393b DONE] Гейт 8011 + мост 8013 сданы (A1, luna xhigh, 483k tokens): КОРЕНЬ дыры 8011 — Stasis передавал targetExtension=8011, endpoint-поиск по caller-id включён только для 8010 → в dev_single_user trust mode отсутствие endpoint НЕ отказ → unbound unscoped session → retrieval без source-фильтра = виден общий индекс. ФИКС: alias 8011→canonical 8010 (dialedExtension=8011 для audit), fail-closed caller_id гейт ДО room/snoop/ExternalMedia (web393bSecurity.js), отказ без raw caller number в trace. 8013: static PJSIP endpoint → experimental SIP 127.0.0.1:5076, ARI 8089. Тесты 4/4. Прод НЕ тронут (8011 остаётся заглушенным). Отчёт A1:/home/ubuntu/waves/WEB393B-REPORT.md. ВАЖНО деплой-инвариант: пустой callerAllowlist в app-коде = allow-all — обязательный DB-чек перед включением. Следом: адверсарная приёмка acc393b (очередь A1, luna, негативы + ловушка пустого allowlist) → при GO деплой с бэкапом + контрольный звонок owner.

[2026-08-27 ~07:51Z acc393b NO-GO] Адверсарная приёмка (luna ≠ автор) нашла 5 fail-open: (1) гейт не валидирует формат caller-id при положительном snapshot (спецсимволы проходят); (2) пустой/битый allowlist наследует allow-all из app-кода — snapshot.callerAllowed=true достаточно; (3) buildSessionSecuritySnapshot игнорирует top-level preconditionsPassed=false/blockedReason; (4) 8013 создаёт provider bridge/ExternalMedia после startSession() без sessionId и БЕЗ identity gate; (5) raw caller number течёт в rejection trace. Отчёт A1:ACC393B-REPORT.md. Деплой ОТМЕНЁН (8011 остаётся заглушенным на проде). web393c (fix всех 5, негативы обяз.) в очереди A1. Мельница приёмки поймала реальное — второй раз подряд по телефонии.

[2026-08-27 08:43Z acc393c NO-GO — 2-й независимый круг] web393c закрыл 5 исходных дыр (подтверждено 12-тестовой независимой сюитой ACC393C-independent.test.js: caller-id спецсимволы/unicode/fullwidth/zero-width, exact-vs-suffix коллизия, пустой/битый allowlist, typed deny, callerVerified=false, 8013 без sessionId, raw number в trace — ВСЕ PASS). НО 5 тестов упали, 3 НОВЫХ класса fail-open: (1) MALFORMED BOOLEAN — Boolean("false")===true в upstreamLifecycleClient.js:147-154: строковые 'false' в callerAllowed/callerVerified/preconditionsPassed дают положительный snapshot; (2) APP-SIDE ALLOW-ALL — identity.ts всё ещё allow при пустом allowlist и не передаёт allowlist в handoff; (3) ГОНКИ — duplicate StasisStart с одним event-id даёт startCalls=2, два concurrent startSession() дают 2 upstream POST. Деплой снова отложен (8011 остаётся заглушенным = безопасно). web393d диспатчнута (Luna xhigh): строгий разбор булевых (malformed=отказ), fail-closed в app за флагом с проверкой 8010/8014, идемпотентность по event-id + single-flight POST; цель ACC393C-independent 12/12. Третий круг мельницы — каждый находил РЕАЛЬНОЕ.

[2026-08-27 ~13:20Z acc393d GO + прод-инвариант НЕ ВЫПОЛНЕН] Гейт 8011 ПРИНЯТ: регрессия по всем восьми накопленным классам PASS (caller-id инъекции, пустой/битый allowlist, top-level deny, 8013 без sessionId, маскировка номера, malformed boolean, app-side allow-all, гонки duplicate/concurrent), плюс новые классы приёмщика: exact '1234' != '51234', fullwidth/Arabic/zero-width/NBSP/BOM/RTL отклонены, allowlist из пробела отклонён, снятый/возвращённый PIN, /sessions/verify-pin строка 'true' отклонена. Требование к проду: TELEPHONY_8011_CALLER_ALLOWLIST_FAIL_CLOSED=1. НО прод-инвариант НЕ ВЫПОЛНИМ как есть: в TelephonyEndpoint всего 2 строки (7016, 7999), обе authMethod='sip_digest', callerAllowlist=NULL. Owner звонит с SIP 7016. ep8011 (аудит по коду) показал: resolver ищет endpoint по provider+extension, и ОДНА строка 7016 обслуживает ВСЕ лейны — 8010, 8014 и 8011 (после alias все идут targetExtension=8010). Значит UPDATE 7016 на caller_id СНЯЛ БЫ PIN и с митинг-рума 8010/8014 — ослабление, которого owner не просил. Одной строкой БД задача не решается. РЕШЕНИЕ: web393e диспатчнута — lane-aware авторизация (решение об auth-контракте по dialedExtension: 8011 -> caller_id-контракт с allowlist, 8010/8014 -> прежний PIN), с сохранением fail-closed гейта, additive-миграцией, тестами (7016->8011 проходит; чужой номер -> отказ; 8010/8014 PIN работает; SIP-регистрация 7016 цела) и runbook отката.

[2026-08-27 ~15:00Z acc393e NO-GO — КРИТИЧЕСКИЙ PIN-BYPASS] Приёмка нашла ровно тот класс, который я просил проверить: для запроса на canonical extension=8010 клиентский/upstream payload с metadata.dialedExtension='8011' выбирает lane 'caller_id' и pinRequired=false => ЛЮБОЙ, кто влияет на payload, обходит ПИН митинг-рума, просто объявив «я звоню на 8011». Доказательства, что поле доверено только gateway и связано с фактическим Asterisk-маршрутом, НЕТ. Применять WEB393E нельзя. web393f диспатчнута: сделать lane НЕПОДДЕЛЬНЫМ (подписанный gateway lane-claim с nonce/timestamp против replay ЛИБО вывод lane из доверенного канала, payload игнорируется), fail-closed на самый строгий контракт (PIN) при отсутствии/просрочке/несовпадении claim, шесть обязательных негативов (подделка, replay, чужой секрет, отсутствие claim, легальный 8011, 8010/8014), сохранение SIP-регистрации 7016 и сюиты acc393d.

[2026-08-28 ~00:11Z web393j — немые отказы закрыты]
Находка ACC393I: в запасном транспорте обратного вызова Telegram (native) неверный bearer давал 401 БЕЗ записи в лог; плюс две немые ветви в настройке.
Закрыто: native-путь пишет машиночитаемый отказ ДО каждого ответа — неизвестный маршрут/метод, неверный bearer, неверная длина тела, ошибки разбора и проверки тела, ошибка и отказ рабочего процесса. В лог НЕ передаются путь, заголовки, тело и текст исключения. Две ветви в TypeScript переведены на существующую общую функцию `logTelephonyAuthorizationRefusal` (отсутствие безопасного секрета перед native-обратным вызовом Telegram и перед исходящим вызовом Signal).
Запущена acc393j: пройти ВСЕ пути отказа самому и составить свою таблицу; главное — проверить, что новая запись НЕ создала утечку (секрет и чувствительные данные в разных недоверенных местах: заголовок авторизации, произвольные заголовки, тело, параметры адреса, путь; текст исключения не должен утаскивать содержимое запроса); оценить ограничение объёма записи; свой злой тест (обрыв соединения на середине тела, неверная кодировка, гонка).

[29.08 КРУГ J ЗАКОММИЧЕН КООРДИНАТОРОМ (f3d7ddfa), приёмка ACC393J нашла утечку — круг K заряжен (очередь 477)]
⚠️ Волна J свою работу НЕ закоммитила: в дереве /home/ubuntu/waves/wt-l59-w393e висели 64 изменённых/новых файла (src/lib 14, infra/sip 14, tests/unit 12, scripts/telegram_call_adapter_native 5, docs/reports 5, infra/signal-adapter 3). Закоммичено как f3d7ddfa8833433f9bdffbdc725022fcadc31e51 — иначе работа терялась при первой же чистке деревьев.
ПРИНЯТО: структурированная запись отказов работает, в самом логгере текста исключения нет (проверено на битом UTF-8 и на исключении из worker), прежние свойства сохранены.
БЛОКЕРЫ:
1. ⚠️ УТЕЧКА НАРУЖУ: callback_server.py:132 — при исключении worker с содержимым запроса ответ 500 включает repr(exc). В пробе приёмки подставленный маркер оказался в HTTP-ответе; логгер остался чистым, но наружу утекло.
2. Чтение тела и постановка в очередь не обёрнуты в безопасный путь отказа — нет обработки aborted/error при обрыве соединения на середине тела.
3. Результат worker не проверяется перед .get() — падение вместо внятного отказа.
4. Тело запроса не закрывается и не ограничено по объёму.
5. Не определено, должны ли основные отказы (тело/маршрут/схема) идти через тот же структурированный логгер.

[29.08 ПРИНЯТО — ACC393L_VERDICT=GO, коммит 99f1b12476760e82c0ddbb6f679013fe551372fa, круг L]
Утечка содержимого запроса закрыта ПО ВСЕМ поверхностям. Путь линии: круг J дал структурированную запись отказов, но приёмка нашла repr(exc) в HTTP-ответе; круг K убрал его из ответа, но приёмка нашла, что маркер переехал в канонический маячок и загружаемый debug_log, а диагностика при этом потерялась (reason строкой, без exc_info и stack); круг L закрыл обе стороны — наружу не уходит ничего, внутрь сохраняется объект ошибки со stack.
Также закрыты: echo пользовательского path в generic-маршруте, echo providerCallId в provider ingress, границы размера тела.
Кандидат в следующую линию после l70.


[BOARDTRIAGE] Приёмщик: независимый telephony-QA, owner даёт контрольный звонок. Нужна post-l70 матрица 8010/8011/8013/8014: 8011 fail-closed, 8013 identity/session, негативы и один успешный голосовой диалог без утечек.
Починено в
web393@A1-wt
Лента
2026-09-02T00:18:47.197Z · Fable
[02.09 00:25Z Фабл, закрытие хвостов эпиков] ACC393L GO (99f1b124): коммит НЕ найден в A2/nc-build — кандидат посадки мостов 8011/8013/8014 мог погибнуть с деревьями 01.09 (см. WEB-320 о потере c4/c5/c6/c2/web428). Проверяю A1 mirrors/bundles; если нет — повторная волна на базе l86-candidate.
2026-09-02T00:20:37.009Z · Fable
[02.09 00:30Z Фабл] Коммит 99f1b124 НАЙДЕН в A1 mirrors/rescued/wt-web393l-99f1b124.bundle (и acc393l). База линии — l59-merge (2c6c2276), 103 файла; пробное слияние на l86-candidate даёт 19 конфликтов (signal-adapter, extensions.conf, sip gateway, telegram_call_adapter, signal-call routes) — конфликт с посаженными siproom2/telspend. Поставлена волна ручного слияния 1097-web393merge (Luna) на базе l86-candidate; после неё — приёмка и посадка в l86.
2026-09-02T01:58:41.477Z · Fable
[02.09 02:00Z Фабл] Слияние web393merge (131aefea) — приёмка 1102 NO-GO: функциональные пробы линии 393, SIP/siproom2, telspend, tel1/tel2 ПРОШЛИ; красные только 2 тест-сьюты Signal (dispatch-failed: фикстура с 14-символьным секретом против слитой политики 24; legacy ENDED source-contract 1/2). Доработка 1104-web393merge2 поставлена. РЕШЕНИЕ: l86 собирается БЕЗ линии 393 (не задерживать 26 коммитов); 393 → l87 после доработки и ре-приёмки. Отчёт: A1 /home/ubuntu/waves/ACCWEB393MERGE-REPORT.md.
2026-09-02T02:12:48.794Z · Fable
[02.09 02:15Z Фабл] Доработка web393merge2 сдана (31974408, тест-only 2 файла: 24-символьный секрет в фикстуре, ENDED-контракт приведён к WEB393L; политика не ослаблена). Ре-приёмка 1106 поставлена. Цель — l87.
2026-09-02T02:27:13.507Z · Fable
[02.09 02:55Z Фабл] Ре-приёмка 1106-accweb393merge2: GO (31974408) — все обязательные Signal/SIP/TEL сьюты + scoped gate WEB393 зелёные, политика minimumSecretLength=24 сохранена, 14-символьный секрет отвергается, сырой provider reason не утекает; единственная диагностика scoped tsc — pre-existing groundedAnswer.ts:2081. Слияние в l87-candidate (A2). Сядет с l87 — тогда done. Отчёт: A1 /home/ubuntu/waves/ACCWEB393MERGE2-REPORT.md.
2026-09-02T04:14:59.546Z · Fable
[02.09 05:55Z Фабл] 🛬 ПОСАЖЕН В l87 (A1, commit e94b2121c819781365626093f9f83f8680fe8bac, артефакт me2-standalone-linux-arm64-e94b2121c-20260902T040954Z sha 003301cf…, аттестация fable-a2-l87, сухой прогон :3011 paidReady TRUE после синхронизации SPEND_MIGRATION_SET_SHA256, миграции web393e/web393f применены, флип nc-a1 + nc-a1-indexing, paid 200 публично). Мосты 8011/8013/8014 (web393merge2 31974408, ACCWEB393MERGE2 GO) на проде; таблицы TelephonyEndpointLane/TelephonyLaneClaimNonce созданы. Закрываю; пост-посадочная матрица звонков 8010/8011/8013/8014 — при следующем контрольном звонке owner.
Воркер
не привязан — привязать: curl -X POST https://bugs.wool2.online/api/web/assign -H 'content-type: application/json' \ -d '{"issueId":"WEB-393","session":"<имя tmux-сессии>","host":"m4"}'
Обновлён
2026-09-02T04:14:59.568Z