WEB board

всеканоны и докиворкеры↗ iOS↗ Легаси
WEB-580 · Дефект · — · web

P1 [голос]: голос не знает тетрадь в фокусе и не берёт адрес из профиля, а текстовый чат берёт — контекст собирается в двух местах

На проверке P1 · важно ведёт: —
Суть
# WEB-580 — [голос] голос не знает тетрадь в фокусе и не берёт адрес из профиля, а текстовый чат берёт — контекст собирается в двух местах

## 1. Суть
Голосовой ассистент должен видеть ту же тетрадь в фокусе и брать адрес из профиля, что и текстовый чат, — контекст должен собираться в одном месте.

## 2. Где мы сейчас (13.09.2026)
Статус review, P1, bug. Половина «тетрадь в фокусе» — ГОТОВА: распознаватель NOTEBOOK_FOCUS_QUERY_RE сужен (волна 3269), разговорная речь добита (волна 3287, conversationContext.ts +70/-5, тест 135 строк), посажено l114z; владелец подтвердил на живом звонке 10.09. Половина «адрес из профиля» — в тексте/голос-виджете/мессенджере ГОТОВА: паттерны nearby («пабы рядом»/around me) и route («скинь маршрут»/directions) + подстановка адреса профиля при отсутствии явного origin в общем чат-пути (волна 3585 GO, HEAD 6482f640, refs/waves/3585), посажено l115i (13.09 06:37Z, PROD-FACTS). НЕ покрыто: телефонный/SIP realtime-канал (8000) — этот код не разделяет (KNOWN 3585); живого подтверждения владельцем голосового адреса нет.

## 3. Хронология
- 09.09: два круга приёмки голосового контекста — NO-GO (3204 общий загрузчик; 3220 realtime focus); круг 3 (3269) сужает матчер.
- 10.09: l114x — сужение распознавателя; волна 3287 добивает разговорную речь; посадка l114z; владелец подтвердил «тетрадь» живьём.
- 10.09: адрес разобран на два дефекта (узкий образец «в» в DISTANCE_PATTERNS; origin не доезжает из профиля); owner-регрессия 3379 («скинь маршрут», «пабы рядом»).
- 12.09: МОЙКА 3567 — «тетрадь готова, адрес наполовину», review → in_progress.
- 12.09 23:17Z: волна 3585-voice-address-nearby-route (Claude sonnet, A1) — nearby/route + адрес профиля.
- 12.09 23:40Z: 3585 GO (HEAD 6482f640), статус → review (кандидат l115i).
- 13.09 06:37Z: 3585 посажен в l115i (PROD-FACTS: «голос: nearby/route + адрес из профиля (волна 3585)»).

## 4. Карта документов и кода
- Тетрадь: src/lib/ai/conversationContext.ts:170 (NOTEBOOK_FOCUS_QUERY_RE), тест 3287-notebook-focus-conversational.test.ts.
- Адрес: src/lib/chat/directChatTool.ts (DISTANCE_PATTERNS), src/lib/ai/tools/locationSearch.ts:92-105 (profileFallback), detectDirectChatToolIntent.
- Связано: WEB-479 (живое плечо 8000), WEB-584 (маскировка причины), WEB-597 (шлюз, без него ответ не доезжал).

## 5. Остаток
1. Адрес в телефонном/SIP realtime-канале (8000) — отдельная работа (волна 3585 этот канал не разделяет).
2. Живое подтверждение владельцем голосового адреса (текст/виджет уже есть, голос — нет).

## 6. Критерий закрытия
На живом звонке 8000 ассистент называет тетрадь в фокусе И берёт адрес из профиля для расстояний/маршрутов (либо: адрес доказан в общем голосовом пути + заведён отдельный тикет на SIP-realtime канал); владелец подтверждает.

---

## История (прежнее тело, сохранено по канону WEB-449)



---
## Эволюция 09.09 (вечер) — полный след для нулевого агента

**Живой прод:** l114u `54243a5cff19ef2bacf2531a0951b7bb82a71633`, посажен 09.09 ~20:0xZ. Откат = l114t `3b1a451cf6cf5b300bd32ce75c9304098448a308`.
**Расписки линий:** `L114S-RELEASE-RECEIPT.md`, `L114T-RELEASE-RECEIPT.md` — лежат в очередях волн на A1/A2/M4 и у security lead в `~/audit/inbox` на M1.
**Обзорный документ эпика энтерпрайза:** https://bugs.wool2.online/labs/enterprise-ent093-20260909.html
**Паспорт, топология парка (добавлен Нео, исправлена строка M4):** https://bugs.wool2.online/passport/03-TOPOLOGY-INFRA.md
**Живой журнал координатора:** `/Users/annakorin/Downloads/OPS-STATUS-LIVE.md` на ноуте владельца.
**Линии, посаженные 09.09:** l114m → l114u, каждая с браузерной пробой открытия документа на проде.

### Голос: какую тетрадь я вижу — ДВА круга приёмки, оба NO-GO. Контрольный звонок владельцу НЕ разрешён.
- **Симптом владельца (13:00Z):** позвонил на 8000, ассистент не смог сказать, какую тетрадку видит, хотя список тетрадей видел; геолокацию из профиля тоже не увидел.
- **Круг 1 — волна 3204** сделала общий загрузчик контекста. **Приёмка ACCVOICECONTEXTL114Q на M4 → NO-GO:** загрузчик работает и передаёт название тетради и геолокацию в текст, пошаговый голос и realtime bootstrap, НО `buildNotebookFocusAnswer` подключён только к тексту и пошаговому голосу, в обработчиках realtime его нет. То есть именно вопрос владельца в трубке остался бы без детерминированного ответа.
- **Круг 2 — волна 3220**, коммит `e89e76f539454785c6249daa76c61ce78a7902f7`, бандл `3220-realtime-notebook-focus-answer.bundle` (sha256 `befe86e978e16c26803d868af38a52734a48ed9824dbfe36fafdb53edfa6a9a6`). Подключила `buildNotebookFocusAnswer` ко всем четырём боевым петлям realtime, включая линию 8000; добавила английские альтернативы (`Which notebook can you see?` на базе НЕ распознавался вовсе).
- **🔴 Приёмка ACC3220REALTIMEFOCUS на Нео (Клод, НЕ модель OpenAI) → NO-GO с воспроизводимым тестом.** Причина — пункт 4 брифа, негативная проверка:
  - `NOTEBOOK_FOCUS_QUERY_RE` (`src/lib/ai/conversationContext.ts:170`) содержит альтернативы `(?:тетрад|ноутбук).{0,35}(?:работ|фокус|назва)` и украинский аналог, то есть срабатывает на ЛЮБОЙ близости слов «тетрадь» и «работ»;
  - пять воспроизводимых ложных срабатываний, ВСЕ на языках владельца: «Что в тетради написано про работу?», «Расскажи из тетради про работу над проектом», «В тетради есть план работ?», «Прочитай тетрадь и назови самые важные работы», «Що в зошиті про працю?»;
  - английские негативы, которые проверял автор, проходят — автор выбрал фразы мимо матчера;
  - **атрибуция честная:** матчер УНАСЛЕДОВАН с базы, автор его не портил. Но 3220 переводит его из совещательного в принудительный и выполняет на КАЖДОМ принятом ходе без классификации (`stasis:8342`), подавляя остальные 12 preflight и grounded-контекст. Раньше ложное срабатывание доходило до 8000 только через `resolveGroundedContextForTurn` (`stasis:9794`, вызов `:9864`) и приходило как совещательная улика — модель могла ответить по существу.
- **Ответ приёмки на вопрос координатора про живой шлюз:** конфликтов слияния не будет, `land-lane8000.sh` не патчит, а перезаписывает файлы манифеста целиком (`cp -a "$STAGING/." "$PI_DIR/"`); обратная сторона — живой дрейф в этих файлах затрётся. **Раздельно включать нельзя:** только шлюз при старом приложении даёт лишний оплачиваемый `generateGroundedAnswer` на каждом ходе, только приложение без шлюза не чинит ничего.
- **Круг 3 — волна 3269** (сузить матчер, сохранив всю полезную часть 3220). Тест приёмки уже написан и краснеет — он и есть доказательство.
- **Живой шлюз:** `/home/pi/ag-voice/infra/sip/gateway` на A1, юнит `ag-sip-native.service`, разошёлся с git в обе стороны (~6700 строк; в живых файлах 14796/1329/1881/5816 строк). Снимок ветки: `gateway-live-20260909`.
- **Перебивание на 8000 починено раньше** (волна 3195 на живом шлюзе): `providerAppendCount` 0 → 2704, владелец подтвердил.


---
## Хронология 10.09 (утро) — обновление координатора

**Текущий ПРОД: l114x `1ee8deb775e5d8da168be8bb47090b3cacd34539`.** Откат: l114w `b6817e28bfbe8eff3b0713a92f96fe109cd05e62`.
Расписка: `L114X-RELEASE-RELEASE-RECEIPT.md` разложена в очереди волн A1/A2/M4/Нео и в `~/audit/inbox` на M1.

**Три линии подряд за утро:**
| Линия | Коммит | Что внутри |
|---|---|---|
| l114v | `76f02b486` | читалка источника по цитате, состояние вида при автосохранении, ПУСТОЙ фикс книги |
| l114w | `b6817e28b` | настоящий фикс генерации книги + параметр temperature на модели gpt-5 |
| l114x | `1ee8deb77` | сужение распознавателя вопроса о тетради в голосе realtime |

**Документы:** ранбук координатора `~/Downloads/COORDINATOR-RUNBOOK.md` (копии на M1), документ эпика https://bugs.wool2.online/labs/enterprise-ent093-20260909.html, паспорт https://bugs.wool2.online/passport/03-TOPOLOGY-INFRA.md

### Голос: фикс В ПРИЛОЖЕНИИ на проде, в шлюзе НЕТ

**Круг 3 ПРИНЯТ** (`VERDICT=GO`, приёмка ACCVOICEMATCHER на Клоде): пять ложных срабатываний ушли, положительные формулировки сохранены, вес распознавателя на промахе обнулён. Коммит `1ee8deb77`, посажен в l114x.

🟡 **Оговорка приёмки (Находка 1):** автор закрыл блокер якорением на всю реплику (`^…$` в `conversationContext.ts:170`), из-за чего появились ложные ПРОПУСКИ на разговорной речи — 25 проб дали 12 попаданий и 13 промахов. Не распознаются: «Слушай, какую тетрадь ты видишь?», «А какую тетрадь…», «Какую тетрадь ты видишь сейчас?», «Ты в какой тетради работаешь?».
**Работают ровно пять:** «Какую тетрадь ты видишь?», «В какой тетради ты работаешь?», «Как называется тетрадь?», «Назови текущую тетрадь.», «Какая тетрадь сейчас в фокусе?»
Волна 3282 (разговорная речь) — на A1.

🔴 **ЗВОНОК ВЛАДЕЛЬЦА ПОКА НЕВОЗМОЖЕН.** Коммит трогает и приложение, и пять файлов шлюза. В приложении посажен, в живом шлюзе нет. Попытка посадки шлюза 10.09 ~10:05Z уронила телефонию (номер 8000 был недоступен несколько минут), откат отработал. Причина и правила — WEB-595.


OWNER-MAPS-REGRESSION-20260910-3379
Owner live chat regression прочитан координатором в Message по timestamp: «> сколько ехать от меня в дублин\n\n», «скинь маршрут от мня  до Дублина », «покажи пабы рядом  со мной» получили «I cannot verify that claim from the delivered sources.». User identity связана с известным входящим MessageLog 20:51:04.872; city/label/coordinates присутствуют (значения не публикуются). В exact l115c 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4 normalizeDirectQuestion не снимает blockquote, ROUTE_PATTERNS не покрывает скинь/от мня, DirectChatToolIntent не покрывает nearby/search. Это до вызова provider, не доказательство отсутствующего Maps key. 3379 поставлена A2 после brief guard/preflight; исправление трёх исходных фраз, соседних conversational форм и executable caller/context proof с отрицательными документными примерами. Бриф Intel nc-ops-scripts/fill-slots-20260910-2046/3379-maps-chat-language-brief.md. Независимая приёмка, сборка/посадка и полный live origin→provider→result остаются. Прежняя l115c live-проба проверяла только no-origin clarification; voice parity этим не доказана. Новых посадок нет.

DISPATCH-20260910-3400
На 2026-09-10T21:38:23.260126+00:00: active; M4, волна 3400 acc-maps-language. Exact BASE 525c6aa0e8700074e99d2a1c6115f1a99272f11a. Бриф прошёл brief-guard; входы SHA256 и наличие BASE проверены до постановки. Входы /Users/milamarty/waves/inputs/3400-acc-maps-language. Отчёт ожидается /Users/milamarty/waves/3400ACCMAPSLANGUAGE-REPORT.md. Живой процесс модели подтверждён; возраст журнала 0 с. Source/подготовка и независимая проверка — в объёме брифа; это не посадка, не live/capacity GO и не окончательное закрытие security.

---

## история (тело до 13.09.2026)



---
## Эволюция 09.09 (вечер) — полный след для нулевого агента

**Живой прод:** l114u `54243a5cff19ef2bacf2531a0951b7bb82a71633`, посажен 09.09 ~20:0xZ. Откат = l114t `3b1a451cf6cf5b300bd32ce75c9304098448a308`.
**Расписки линий:** `L114S-RELEASE-RECEIPT.md`, `L114T-RELEASE-RECEIPT.md` — лежат в очередях волн на A1/A2/M4 и у security lead в `~/audit/inbox` на M1.
**Обзорный документ эпика энтерпрайза:** https://bugs.wool2.online/labs/enterprise-ent093-20260909.html
**Паспорт, топология парка (добавлен Нео, исправлена строка M4):** https://bugs.wool2.online/passport/03-TOPOLOGY-INFRA.md
**Живой журнал координатора:** `/Users/annakorin/Downloads/OPS-STATUS-LIVE.md` на ноуте владельца.
**Линии, посаженные 09.09:** l114m → l114u, каждая с браузерной пробой открытия документа на проде.

### Голос: какую тетрадь я вижу — ДВА круга приёмки, оба NO-GO. Контрольный звонок владельцу НЕ разрешён.
- **Симптом владельца (13:00Z):** позвонил на 8000, ассистент не смог сказать, какую тетрадку видит, хотя список тетрадей видел; геолокацию из профиля тоже не увидел.
- **Круг 1 — волна 3204** сделала общий загрузчик контекста. **Приёмка ACCVOICECONTEXTL114Q на M4 → NO-GO:** загрузчик работает и передаёт название тетради и геолокацию в текст, пошаговый голос и realtime bootstrap, НО `buildNotebookFocusAnswer` подключён только к тексту и пошаговому голосу, в обработчиках realtime его нет. То есть именно вопрос владельца в трубке остался бы без детерминированного ответа.
- **Круг 2 — волна 3220**, коммит `e89e76f539454785c6249daa76c61ce78a7902f7`, бандл `3220-realtime-notebook-focus-answer.bundle` (sha256 `befe86e978e16c26803d868af38a52734a48ed9824dbfe36fafdb53edfa6a9a6`). Подключила `buildNotebookFocusAnswer` ко всем четырём боевым петлям realtime, включая линию 8000; добавила английские альтернативы (`Which notebook can you see?` на базе НЕ распознавался вовсе).
- **🔴 Приёмка ACC3220REALTIMEFOCUS на Нео (Клод, НЕ модель OpenAI) → NO-GO с воспроизводимым тестом.** Причина — пункт 4 брифа, негативная проверка:
  - `NOTEBOOK_FOCUS_QUERY_RE` (`src/lib/ai/conversationContext.ts:170`) содержит альтернативы `(?:тетрад|ноутбук).{0,35}(?:работ|фокус|назва)` и украинский аналог, то есть срабатывает на ЛЮБОЙ близости слов «тетрадь» и «работ»;
  - пять воспроизводимых ложных срабатываний, ВСЕ на языках владельца: «Что в тетради написано про работу?», «Расскажи из тетради про работу над проектом», «В тетради есть план работ?», «Прочитай тетрадь и назови самые важные работы», «Що в зошиті про працю?»;
  - английские негативы, которые проверял автор, проходят — автор выбрал фразы мимо матчера;
  - **атрибуция честная:** матчер УНАСЛЕДОВАН с базы, автор его не портил. Но 3220 переводит его из совещательного в принудительный и выполняет на КАЖДОМ принятом ходе без классификации (`stasis:8342`), подавляя остальные 12 preflight и grounded-контекст. Раньше ложное срабатывание доходило до 8000 только через `resolveGroundedContextForTurn` (`stasis:9794`, вызов `:9864`) и приходило как совещательная улика — модель могла ответить по существу.
- **Ответ приёмки на вопрос координатора про живой шлюз:** конфликтов слияния не будет, `land-lane8000.sh` не патчит, а перезаписывает файлы манифеста целиком (`cp -a "$STAGING/." "$PI_DIR/"`); обратная сторона — живой дрейф в этих файлах затрётся. **Раздельно включать нельзя:** только шлюз при старом приложении даёт лишний оплачиваемый `generateGroundedAnswer` на каждом ходе, только приложение без шлюза не чинит ничего.
- **Круг 3 — волна 3269** (сузить матчер, сохранив всю полезную часть 3220). Тест приёмки уже написан и краснеет — он и есть доказательство.
- **Живой шлюз:** `/home/pi/ag-voice/infra/sip/gateway` на A1, юнит `ag-sip-native.service`, разошёлся с git в обе стороны (~6700 строк; в живых файлах 14796/1329/1881/5816 строк). Снимок ветки: `gateway-live-20260909`.
- **Перебивание на 8000 починено раньше** (волна 3195 на живом шлюзе): `providerAppendCount` 0 → 2704, владелец подтвердил.


---
## Хронология 10.09 (утро) — обновление координатора

**Текущий ПРОД: l114x `1ee8deb775e5d8da168be8bb47090b3cacd34539`.** Откат: l114w `b6817e28bfbe8eff3b0713a92f96fe109cd05e62`.
Расписка: `L114X-RELEASE-RELEASE-RECEIPT.md` разложена в очереди волн A1/A2/M4/Нео и в `~/audit/inbox` на M1.

**Три линии подряд за утро:**
| Линия | Коммит | Что внутри |
|---|---|---|
| l114v | `76f02b486` | читалка источника по цитате, состояние вида при автосохранении, ПУСТОЙ фикс книги |
| l114w | `b6817e28b` | настоящий фикс генерации книги + параметр temperature на модели gpt-5 |
| l114x | `1ee8deb77` | сужение распознавателя вопроса о тетради в голосе realtime |

**Документы:** ранбук координатора `~/Downloads/COORDINATOR-RUNBOOK.md` (копии на M1), документ эпика https://bugs.wool2.online/labs/enterprise-ent093-20260909.html, паспорт https://bugs.wool2.online/passport/03-TOPOLOGY-INFRA.md

### Голос: фикс В ПРИЛОЖЕНИИ на проде, в шлюзе НЕТ

**Круг 3 ПРИНЯТ** (`VERDICT=GO`, приёмка ACCVOICEMATCHER на Клоде): пять ложных срабатываний ушли, положительные формулировки сохранены, вес распознавателя на промахе обнулён. Коммит `1ee8deb77`, посажен в l114x.

🟡 **Оговорка приёмки (Находка 1):** автор закрыл блокер якорением на всю реплику (`^…$` в `conversationContext.ts:170`), из-за чего появились ложные ПРОПУСКИ на разговорной речи — 25 проб дали 12 попаданий и 13 промахов. Не распознаются: «Слушай, какую тетрадь ты видишь?», «А какую тетрадь…», «Какую тетрадь ты видишь сейчас?», «Ты в какой тетради работаешь?».
**Работают ровно пять:** «Какую тетрадь ты видишь?», «В какой тетради ты работаешь?», «Как называется тетрадь?», «Назови текущую тетрадь.», «Какая тетрадь сейчас в фокусе?»
Волна 3282 (разговорная речь) — на A1.

🔴 **ЗВОНОК ВЛАДЕЛЬЦА ПОКА НЕВОЗМОЖЕН.** Коммит трогает и приложение, и пять файлов шлюза. В приложении посажен, в живом шлюзе нет. Попытка посадки шлюза 10.09 ~10:05Z уронила телефонию (номер 8000 был недоступен несколько минут), откат отработал. Причина и правила — WEB-595.


OWNER-MAPS-REGRESSION-20260910-3379
Owner live chat regression прочитан координатором в Message по timestamp: «> сколько ехать от меня в дублин\n\n», «скинь маршрут от мня  до Дублина », «покажи пабы рядом  со мной» получили «I cannot verify that claim from the delivered sources.». User identity связана с известным входящим MessageLog 20:51:04.872; city/label/coordinates присутствуют (значения не публикуются). В exact l115c 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4 normalizeDirectQuestion не снимает blockquote, ROUTE_PATTERNS не покрывает скинь/от мня, DirectChatToolIntent не покрывает nearby/search. Это до вызова provider, не доказательство отсутствующего Maps key. 3379 поставлена A2 после brief guard/preflight; исправление трёх исходных фраз, соседних conversational форм и executable caller/context proof с отрицательными документными примерами. Бриф Intel nc-ops-scripts/fill-slots-20260910-2046/3379-maps-chat-language-brief.md. Независимая приёмка, сборка/посадка и полный live origin→provider→result остаются. Прежняя l115c live-проба проверяла только no-origin clarification; voice parity этим не доказана. Новых посадок нет.

DISPATCH-20260910-3400
На 2026-09-10T21:38:23.260126+00:00: active; M4, волна 3400 acc-maps-language. Exact BASE 525c6aa0e8700074e99d2a1c6115f1a99272f11a. Бриф прошёл brief-guard; входы SHA256 и наличие BASE проверены до постановки. Входы /Users/milamarty/waves/inputs/3400-acc-maps-language. Отчёт ожидается /Users/milamarty/waves/3400ACCMAPSLANGUAGE-REPORT.md. Живой процесс модели подтверждён; возраст журнала 0 с. Source/подготовка и независимая проверка — в объёме брифа; это не посадка, не live/capacity GO и не окончательное закрытие security.
Доказательства

OWNER-MAPS-REGRESSION-20260910-3379
Owner live chat regression прочитан координатором в Message по timestamp: «> сколько ехать от меня в дублин\n\n», «скинь маршрут от мня  до Дублина », «покажи пабы рядом  со мной» получили «I cannot verify that claim from the delivered sources.». User identity связана с известным входящим MessageLog 20:51:04.872; city/label/coordinates присутствуют (значения не публикуются). В exact l115c 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4 normalizeDirectQuestion не снимает blockquote, ROUTE_PATTERNS не покрывает скинь/от мня, DirectChatToolIntent не покрывает nearby/search. Это до вызова provider, не доказательство отсутствующего Maps key. 3379 поставлена A2 после brief guard/preflight; исправление трёх исходных фраз, соседних conversational форм и executable caller/context proof с отрицательными документными примерами. Бриф Intel nc-ops-scripts/fill-slots-20260910-2046/3379-maps-chat-language-brief.md. Независимая приёмка, сборка/посадка и полный live origin→provider→result остаются. Прежняя l115c live-проба проверяла только no-origin clarification; voice parity этим не доказана. Новых посадок нет.

DISPATCH-20260910-3400
На 2026-09-10T21:38:23.260126+00:00: active; M4, волна 3400 acc-maps-language. Exact BASE 525c6aa0e8700074e99d2a1c6115f1a99272f11a. Бриф прошёл brief-guard; входы SHA256 и наличие BASE проверены до постановки. Входы /Users/milamarty/waves/inputs/3400-acc-maps-language. Отчёт ожидается /Users/milamarty/waves/3400ACCMAPSLANGUAGE-REPORT.md. Живой процесс модели подтверждён; возраст журнала 0 с. Source/подготовка и независимая проверка — в объёме брифа; это не посадка, не live/capacity GO и не окончательное закрытие security.
Лента
2026-09-10T13:14:03.713Z · coordinator
ЭВОЛЮЦИЯ 10.09 — владелец проверил живьём в 12:56Z: телефония отвечает, НАЗВАНИЕ ТЕТРАДИ НЕ ЗНАЕТ.
Он прав, и причина известна: починка ещё не в проде.

История трёх кругов:
- l114q (34c85e956, 09.09) — сведён единый контекст разговора для текста и голоса. Помогло частично.
- Волна 3269 (10.09) зацепила распознаватель NOTEBOOK_FOCUS_QUERY_RE в src/lib/ai/conversationContext.ts:170.
  ДО неё выражение срабатывало на любом соседстве «тетрад»+«работ» — ложные срабатывания.
  ПОСЛЕ якорения ложные срабатывания ушли, но появились ложные ПРОПУСКИ: 13 из 25 на разговорной речи.
  То есть на пяти контрольных фразах владельца работает, а на живой речи — нет.
- Волна 3287 (A1, крутится) добивает именно разговорную речь. Это повтор: первая попытка (3282)
  умерла молча, и умерла она по моей вине — я поставила её с базой b6817e28b, которой на A1 не было.

Контрольные фразы для звонка на 8000:
«Какую тетрадь ты видишь?», «В какой тетради ты работаешь?», «Как называется тетрадь?»,
«Назови текущую тетрадь.», «Какая тетрадь сейчас в фокусе?»

Связано: WEB-479 (живое плечо 8000), WEB-584 (инструменты не вызываются — соседний путь того же контекста).
2026-09-10T13:19:38.197Z · coordinator
ВЕРДИКТ 10.09 13:20Z — ФИКС СДАН, GO. Статус in_progress -> review (ждёт посадки).

Волна 3287 (A1) отдала два коммита поверх боевой головы 16e1ddaa1:
- e58d2a985 test(telephony): reproduce conversational notebook focus misses
- 7d4837274 fix(telephony): recognize conversational notebook focus questions
Тронуто: src/lib/ai/conversationContext.ts (+75) и новый тест
src/**/3287-notebook-focus-conversational.test.ts (135 строк). Итого 205 вставок, 5 удалений.

ПОПРАВКА К МОЕМУ ЖЕ ПРЕДЫДУЩЕМУ КОММЕНТАРИЮ: я написала, что волна умерла молча. Это неверно.
Волна доработала до конца и положила бандл в /home/wave/waves/queue/3287-voice-intent-conversational.bundle —
то есть НЕ в корень waves, куда я смотрела. Причина: я пересобрала бриф из старого (3282) и не сверила
блок сдачи — в нём не было строк «write report to PATH» и «create bundle at PATH» в нынешнем формате.
Диспетчер закрыл волну правильно: она сама выставила маркер DONE, отдав бандл.

УРОК ДЛЯ НУЛЕВОГО АГЕНТА: при перевыпуске старого брифа сверять ХВОСТ (пути отчёта, бандла и маркера
DONE), а не только BASE. Иначе работа делается, но «пропадает» — лежит там, куда её попросили положить.
Проверять сдачу так: find <waves> -name '*.bundle' -mmin -120 ПО ВСЕМ подкаталогам, включая queue/.

Дальше: собираю линию с этим фиксом. До посадки владелец при звонке на 8000 название тетради
слышать не будет — он это подтвердил живьём в 12:56Z.
2026-09-10T14:16:08.191Z · coordinator
ПОСАДКА 10.09 14:00Z — фикс НА ПРОДЕ, но владелец по телефону его НЕ ПОЛУЧАЕТ. Остаюсь в review.

Линия l114z, коммит 557d5889ce297c2e3e6b353f87b348f70494f7ed. Волна 3287, два коммита:
e58d2a985 (тест на пропуски разговорной речи) и 7d4837274 (сама починка),
src/lib/ai/conversationContext.ts +70/-5 и новый тест на 135 строк.

ДОКАЗАТЕЛЬСТВО, что фикс именно в выкаченном коде, а не только собрался.
Посчитала токены новых образцов в самом релизе против предыдущего:
  'работаешь'  6 -> 12
  'тетрадка'  31 -> 34
  'ноутбуке'   9 -> 12
Прирост ровно на новые шаблоны («в какой тетради ты работаешь», «как называется тетрадь»,
«какая у меня сейчас тетрадь»).
ЛОВУШКА, в которую я чуть не попала: файл .next/server/app/(full)/voice-query/page.js между
линиями ОТЛИЧАЕТСЯ, но фрагменты про тетрадь в нём ИДЕНТИЧНЫ — отличие косметическое,
фикс живёт в другом чанке. Сравнение одного «ожидаемого» файла дало бы ложный вывод в обе стороны.

ПОЧЕМУ НЕ done. Владелец позвонил на 8000 в 14:06Z и ответа не получил. Разбор звонка
(callId=144a4bfb-1eb2-4ae2-9752-4379a72c5687) показал, что причина в ШЛЮЗЕ, а не в распознавателе:
приветствие заблокировано тройкой missing_live_bridge / external_channel_not_ready / already_requested,
и 1.8 МБ его речи выброшено. Заведён отдельный тикет WEB-597.

Пока WEB-597 жив, владелец по телефону этот фикс не увидит. Текстовый и голосовой путь
в приложении — увидит. Переведу в done, когда владелец подтвердит на живом звонке.
2026-09-10T16:23:05.856Z · coordinator
ПОЛОВИНА ЗАКРЫТА, ПОЛОВИНА НЕТ. Возвращаю в работу с точным разбором.

✅ ТЕТРАДЬ В ФОКУСЕ — РАБОТАЕТ. Владелец подтвердил на живом звонке 10.09: спросил про тетрадь,
ассистент её назвал. Путь: волна 3269 заякорила выражение (убрала ложные срабатывания, но внесла
13 ложных пропусков из 25 на разговорной речи) -> волна 3287 добила разговорную речь
(`src/lib/ai/conversationContext.ts`, +70/-5, тест на 135 строк) -> посажено в l114z.
Плюс правка шлюза (волна 3296, WEB-597), без которой ответ до владельца физически не доезжал.

❌ АДРЕС ИЗ ПРОФИЛЯ — НЕ РАБОТАЕТ, и это ДВА разных дефекта. Оба воспроизведены на боевом.

ДЕФЕКТ 1 — ОБРАЗЕЦ РАСПОЗНАВАНИЯ СЛИШКОМ УЗКИЙ.
`src/lib/chat/directChatTool.ts:39`, первый из `DISTANCE_PATTERNS`, требует предлог «до» или «к»:
    (?:сколько\s+(?:ехать|идти)|как\s+далеко|время\s+в\s+пути)\s+(?:от\s+(?:меня|нас|дома)\s+)?(?:до|к)\s+(TARGET)
Владелец пишет «в». Пробы на боевом чате:
  «сколько ехать от меня В дублин»   -> инструмент НЕ вызван, ответ «I cannot verify that claim
                                        from the delivered sources» (ответ про ДОКУМЕНТЫ);
  «сколько ехать от меня ДО дублина» -> routed={"model":"direct-chat-tool","intent":"distance"}, 200.
Это ТОТ ЖЕ КЛАСС БОЛЕЗНИ, что был у вопроса про тетрадь: выражение привязано к формулировкам,
которыми владелец не пользуется. Голосовой вариант («Сколько от меня я хочу аэропорт») промахивается
тем более.

ДЕФЕКТ 2 — ТОЧКА ОТСЧЁТА НЕ ДОЕЗЖАЕТ ИЗ ПРОФИЛЯ.
Когда образец совпал, инструмент ответил:
  «I need an origin (shared geolocation or address) to compute distance.»
У владельца геолокация в профиле заполнена. Механизм в коде ЕСТЬ:
`src/lib/ai/tools/locationSearch.ts:92-105` принимает `profileFallback` и падает
на `profileFallback.lat/lng`, затем на `profileFallback.city`.
Но `directChatTool.ts:88-96` собирает аргументы как
`{ action:'distance', destination, mode:'driving', language }` — без `origin` и без прокидывания
`profileFallback` по этому пути.

Волна 3298 поставлена на A2 с шестью требованиями, включая негативный набор (чтобы расширение
образцов не начало срабатывать на обычных вопросах про документы) и отдельный вопрос: почему
владелец помнит, что РАНЬШЕ адрес брался — регрессия это или другой путь.

⚠️ ЛОЖНАЯ ТРЕВОГА, чтобы следующий не побежал чинить не то: моя проба этого вопроса под
фикстурной учёткой отдаёт 500 `spend_state_unavailable`. Это про КОШЕЛЁК ФИКСТУРЫ, а не про
владельца — у него те же вопросы возвращают 200 с ответом про документы. `paidReady=True
enforce_ready []` на обоих бэкендах, соединений с базой 35 idle / 1 active.
В журнале рядом висят `article28.path.closed` в режиме `observed` — это тоже НЕ причина отказа.

Связано: WEB-584 (маскировка причины под «не подтверждено источниками» — не вернуть её),
WEB-597 (шлюз, без него ответ не доезжал), WEB-575/576 (Article 28 в журнале).
2026-09-10T18:07:11.266Z · coordinator
10.09: l115b da5deea8b собран, подписан, доставлен и распакован на A1; SHA256 187a394f09e3abbeff56cfb7c2acdd2905f605dd513b75448ff91d0c1176b0da. ПОСАДКА ОСТАНОВЛЕНА сухим прогоном. Boot contract допускает запуск, paidReady=true/enforce_ready, но реальный чат с P11=1 даёт 500 spend_state_unavailable. Та же проба воспроизводится на проде l115a. Первоначальный 400 был ошибкой старого probe: отсутствовал обязательный Idempotency-Key, это исправлено в пробах. Причина 500: withPaidApiPerimeter берёт admission lease до создания PaidOperationRecord; вложенный withExpensiveOperationGuard видит активную policy и возвращается до admitCurrentPaidOperationRecord. Метка остаётся reserved, meteredVendorCall отказывает перед транспортом. Фикс 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4 переносит reentrant-путь через общую проверку записи без второго lease. Четыре новых сценария на исходной базе 0/4, с исправлением полный выбранный набор 43/43. Проверены callback/no-callback, lost claim, store failure, удержание внешнего lease и stream lifetime. Это результат тестов, не приёмка на проде. На A2 запущена l115c (включает l115b); текущий прод l115a. Артефакты и логи: /Users/annakorin/nc-ops-scripts/run-l115b-luna/. P11 и защиту расходов не выключали.
2026-09-10T18:40:47.779Z · coordinator
LANDING-L115C-1ad52e16b-POSTQA
2026-09-10 18:37Z: l115c посажен на A1. Source 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4; artifact me2-standalone-linux-arm64-1ad52e16b-20260910T183145Z; SHA256 3b2256904d1cbeb199c2d48b562732e55c175754e3c9ca2475d491704b1b29c9. Подпись и отрицательная проверка пройдены. Обе ready-пробы подтвердили новый SHA, paidReady=true/enforce_ready; четыре службы прошли гейт путей. После посадки реальный чат с включённым P11 вернул 200; distance на втором бэкенде: 200/direct-chat-tool/distance. У фикстуры нет адреса: проверен запрос точки отправления, расчёт от сохранённого адреса не проверен. Браузер: 58 документов, один открыт, поверхности редактора присутствуют, новых ошибок после клика 0. При первоначальной загрузке записаны ERR_ABORTED для cookies RSC и HTTP403/console для admin/agents; чистую консоль не заявляем. SIP readiness 200, LEASE_LOST 0, SIP не перезапускали. Миграций относительно l115a нет. Откат: l115a arm64-l115a-20260910T155308Z, копии юнитов prod/shared/pre-l115c-units. Логи: /Users/annakorin/nc-ops-scripts/run-l115b-luna/l115c-live-postqa.log и l115c-editor-postqa.log.
2026-09-10T18:55:03.561Z · coordinator
BOARD-WASH-20260910:WAVE-3334
По поручению владельца 18:43Z назначена исполнительская волна 3334 (sip), Codex gpt-5.6-luna xhigh, A1. Полная история карточки и база l115c 1ad52e16b доставлены, brief-guard и проверка Git-базы пройдены. Задача: проверить существующую сдачу, устранить остатки, передать бандл и доказательства. Финальная приёмка и посадка остаются за координатором. Запуск группы подтверждён в журнале диспетчера.
КАРТА ДОКУМЕНТОВ: ноут /Users/annakorin/nc-ops-scripts/board-wash-20260910/3334-wash-sip-brief.md; A1 /home/wave/waves/inputs/board-wash-20260910/sip-tickets.json; ожидаемый отчёт /home/wave/waves/3334WASHSIP-REPORT.md. Правила обогащения: WEB-449.
2026-09-10T20:57:56.759Z · coordinator
OWNER-MAPS-REGRESSION-20260910-3379
Owner live chat regression прочитан координатором в Message по timestamp: «> сколько ехать от меня в дублин\n\n», «скинь маршрут от мня  до Дублина », «покажи пабы рядом  со мной» получили «I cannot verify that claim from the delivered sources.». User identity связана с известным входящим MessageLog 20:51:04.872; city/label/coordinates присутствуют (значения не публикуются). В exact l115c 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4 normalizeDirectQuestion не снимает blockquote, ROUTE_PATTERNS не покрывает скинь/от мня, DirectChatToolIntent не покрывает nearby/search. Это до вызова provider, не доказательство отсутствующего Maps key. 3379 поставлена A2 после brief guard/preflight; исправление трёх исходных фраз, соседних conversational форм и executable caller/context proof с отрицательными документными примерами. Бриф Intel nc-ops-scripts/fill-slots-20260910-2046/3379-maps-chat-language-brief.md. Независимая приёмка, сборка/посадка и полный live origin→provider→result остаются. Прежняя l115c live-проба проверяла только no-origin clarification; voice parity этим не доказана. Новых посадок нет.
2026-09-10T21:39:01.224Z · coordinator
DISPATCH-20260910-3400
На 2026-09-10T21:38:23.260126+00:00: active; M4, волна 3400 acc-maps-language. Exact BASE 525c6aa0e8700074e99d2a1c6115f1a99272f11a. Бриф прошёл brief-guard; входы SHA256 и наличие BASE проверены до постановки. Входы /Users/milamarty/waves/inputs/3400-acc-maps-language. Отчёт ожидается /Users/milamarty/waves/3400ACCMAPSLANGUAGE-REPORT.md. Живой процесс модели подтверждён; возраст журнала 0 с. Source/подготовка и независимая проверка — в объёме брифа; это не посадка, не live/capacity GO и не окончательное закрытие security.
2026-09-12T22:17:55.112Z · coordinator
[12.09 22:17Z координатор] МОЙКА 12.09 (3567-wash-g2-telephony-billing): тетрадь в фокусе — готово, владелец подтвердил живьём. Адрес из профиля — наполовину: паттерн «в» и origin из профиля посажены, но «скинь маршрут» и «пабы рядом» ещё не распознаются (волна 3400 в работе). Статус review → вернуть в in_progress до закрытия адресной половины.
Остаток: Адрес: nearby/search («пабы рядом») и «скинь маршрут» не покрыты паттернами — owner-регрессия 3379 жива (волна 3400).; Voice parity для адреса не доказана (l115c live-проба только no-origin clarification).; Полное живое подтверждение владельцем по голосовому адресу.
Отчёт: /Users/milamarty/waves/3567WASH*-REPORT.md (M4), копия nc-ops-scripts/shift-20260912-resume/wash-20260912/collected/. Проверка по исходнику прода l115g (9c8a9762).
Статус: review → in_progress (по вердикту мойки KEEP).
2026-09-12T23:18:31.551Z · coordinator
[12.09 23:18Z координатор] ВЗЯТО В РАБОТУ 12.09 23:17Z (владелец: «мойка = брать тикеты в работу»): волна 3585-voice-address-nearby-route (Claude sonnet, A1): паттерны «пабы рядом»/«скинь маршрут» + адрес профиля, тесты red→green. Сдача — бандл + отчёт, комментарий сюда по вердикту.
2026-09-12T23:40:13.145Z · coordinator
[12.09 23:40Z координатор] Волна 3585 (Claude sonnet, A1) — GO: паттерны nearby («пабы рядом», around me) и route («скинь маршрут», directions) + подстановка адреса профиля при отсутствии явного origin в общем чат-пути (detectDirectChatToolIntent: текст/голос-виджет/мессенджер), тесты red→green + негатив (явный origin приоритетнее профиля). HEAD 6482f640 на l115g, refs/waves/3585. KNOWN: телефонный/SIP realtime-канал этот код не разделяет — там отдельная работа. Статус → review (в кандидат следующей линии l115i).
2026-09-13T10:37:39.861Z · coordinator
[13.09 10:37Z координатор] Мойка доски, волна 3652. RETURN: тетрадь в фокусе готова (владелец подтвердил живьём), адрес в тексте/виджете посажен l115i (волна 3585 GO, HEAD 6482f640). Не хватает адреса в телефонном SIP-канале 8000 и живого подтверждения голосом. Статус review.
2026-09-14T14:38:14.675Z · coordinator
[14.09 14:38Z координатор] VERDICT=NO-GO

# WEB-580 «голос не знает тетрадь в фокусе и не берёт адрес из контекста» — wave 3862

Дата: 2026-09-14. Ворктри: `wave/3862-web580-voice-focus`.

Это отчёт для «нулевого» агента — человека, который ничего не знает про этот тикет и этот код. Я объясняю всё с нуля и простыми словами, без предположений. Всё, что я называю «доказанным», я проверил сам; всё, что не могу проверить без живого звонка, я так и называю.

---

## 1. Что жаловалось (симптом словами пользователя)

Пользователь открывает тетрадь (notebook) в веб-приложении, нажимает «говорить голосом» и задаёт вопрос. Ассистент отвечает так, будто **не видит, с чем человек работает** (не знает, в какой тетради он находится), и **не берёт сохранённый адрес из профиля**, когда человек спрашивает «что рядом со мной».

То есть в карточке на самом деле **две разные жалобы**:
1. «не знает тетрадь в фокусе»;
2. «не берёт адрес из контекста».

Их надо разбирать отдельно, потому что у них разная судьба (одна уже починена, вторая — частично нет).

---

## 2. Что важно понять про «голос»

В этом продукте голос — это не одна кнопка, а **три разных входа**:

| Вход | Где живёт код | Что делает |
|---|---|---|
| **Веб-голос** (кнопка в веб-апке) | `src/hooks/useVoiceQuery.ts` → `chatWithSources` в `src/app/actions.ts` | Прямой путь: клиент знает открытую тетрадь, потому что она открыта в том же окне браузера. |
| **Телефонный голос** (SIP 8010, Signal, Telegram) | `src/lib/telephony/groundedAnswer.ts`, `src/lib/telephony/runtime/lifecycle.ts`, `personalSourceScope.ts` | Звонок приходит «со стороны». Сервер **не знает**, какая тетрадь открыта в браузере пользователя. |
| **Legacy голос** (старый Whisper→RAG→TTS) | `infra/sip/gateway/phoneVoiceEngine.js` | По проектной инструкции `CLAUDE.md` — устарел, для новых фич не используется. Я его не рассматривал как продуктовый. |

Слово «фокус» (какая тетрадь сейчас открыта) — это **чисто веб-клиентское** понятие. Оно хранится в переменной `activeNotebookId` в Zustand-хранилище браузера.

---

## 3. Ключевой факт: фокус не синхронизируется на сервер

`activeNotebookId` (открытая тетрадь) сохраняется только в **localStorage** браузера:

- `src/store/useStore.ts:95` — ключ `note-clone-last-active-notebook-id`;
- `src/store/useStore.ts:220,230,232` — читается/пишется только через `window.localStorage`.

Я проверил поиском по всему `src/`: этот ключ **нигде не отправляется на сервер**. Никакого API «я сейчас открыл тетрадь X» не существует.

Отсюда сразу вытекает главный вывод всей волны:

> **Веб-голос** знает фокус, потому что тетрадь открыта в том же браузере. **Телефонный голос** физически не может знать фокус — этого сигнала на сервере нет. Единственное, что ему остаётся, — угадывать.

---

## 4. Разбор случая №1: «не знает тетрадь в фокусе»

### 4а. Веб-голос — фокус ПЕРЕДАЁТСЯ и ИСПОЛЬЗУЕТСЯ (симптом НЕ воспроизводится)

Цепочка, которую я проследил по коду (каждая строка проверена):

1. `src/hooks/useVoiceQuery.ts:56` — `const activeNotebookId = useStore((state) => state.activeNotebookId);` — берёт открытую тетрадь из браузерного хранилища.
2. `src/hooks/useVoiceQuery.ts:200` — `notebookId: activeNotebookId` кладётся в `runtimeScope`, который уходит в `chatWithSources`.
3. `src/app/actions.ts:3461–3464` — `loadConversationContext({ userId, notebookId: runtimeScope.notebookId })` — сервер по этому id достаёт название тетради и готовит блок «ACTIVE NOTEBOOK» для промпта.
4. `src/app/actions.ts:4291` — `buildNotebookFocusAnswer(effectiveQuery, conversationContext, responseLanguage)` — детерминированный (без модели) ответ на вопрос «в какой я тетради?».
5. `src/lib/chat/effectiveScope.ts:139` — когда `notebookId` задан, scope становится `kind: 'notebook'`, то есть поиск по документам реально ограничивается этой тетрадью, а не всеми документами пользователя.

Вывод: в веб-голосе фокус **и передан, и использован**. Этот сценарий «открыл тетрадь → говоришь голосом» сейчас работает правильно. Это подтверждено кодом и тестами (см. раздел 6).

### 4б. Телефонный голос — фокус НЕ передаётся и теряется (симптом ВОСПРОИЗВОДИТСЯ)

Здесь две проблемы, и это ядро волны:

**Проблема 1 — сигнала нет.** Телефонная сессия (`VoiceSession`) не получает от веб-клиента id открытой тетради, потому что клиент его никуда не шлёт (раздел 3).

**Проблема 2 — эвристика «ровно одна тетрадь».** Единственное, что умеет телефонный путь — собрать все активные источники пользователя и «угадать» тетрадь:

- `src/lib/telephony/runtime/personalSourceScope.ts:41` — функция `queryPersonalSourceScope(userId)`;
- `src/lib/telephony/runtime/personalSourceScope.ts:59` — **вот строка, где фокус теряется:**

```
primaryNotebookId: notebookIds.length === 1 ? notebookIds[0] : null,
```

Что это значит простыми словами: если у пользователя источники лежат **ровно в одной** тетради — телефонный голос «знает», что это она. Если тетрадей **две или больше** — он честно отвечает `null`, то есть «тетрадь не выбрана», **даже если в браузере у человека открыта одна из них**.

Это ровно тот симптом из карточки, только он проявляется на **телефонном** входе и только при 2+ тетрадях. Я воспроизвёл это кодом и зафиксировал отрицательным контролем (см. раздел 6).

---

## 5. Разбор случая №2: «не берёт адрес из контекста»

### 5а. Веб/чат — адрес ПЕРЕДАЁТСЯ и ИСПОЛЬЗУЕТСЯ детерминированно

- `src/lib/chat/directChatTool.ts:177` — `detectDirectChatToolIntent(message)` — детерминированно (без модели) ловит «что рядом / рядом со мной».
- `src/lib/chat/directChatTool.ts:233` — `withProfileOrigin(...)` — достаёт сохранённые `context.userLat` / `context.userLng` / `context.userCity` из профиля.
- `src/app/actions.ts:4311` — этот детектор вызывается на сервере до обращения к модели.

Вывод: на веб/чат входе «адрес из контекста» работает на уровне детерминированного кода. Подтверждено тестами `WEB-580/3585` (16/16, см. раздел 6).

### 5б. Телефонный голос — адрес ПЕРЕДАН, но НЕ использован детерминированно (это случай «передан, но не использован»)

- `src/lib/telephony/groundedAnswer.ts` **не содержит** `detectDirectChatToolIntent` (я проверил — такого вызова нет, и это зафиксировано отрицательным контролем). То есть детерминированного перехвата «что рядом» в телефонном пути нет.
- НО адрес всё же **передаётся**: `src/lib/telephony/groundedAnswer.ts:1879` грузит `loadConversationContext(...)`, профиль с координатами попадает в промпт. Инструкция передавать `lat/lng` в тул `location_search` прописана в `src/lib/ai/userProfile.ts:218`.

Вывод: на телефонном входе «взятие адреса» зависит от **модели** (промпт + tool-calls), а не от гарантированного кода. Работает это «обычно», но не детерминированно. Это и есть вторая половина карточки в ослабленном виде.

---

## 6. Что я сделал и чем это доказано

### 6а. Новый тест с отрицательным контролем (главный результат)

Создан файл `tests/unit/web580-voice-focus-negative.test.ts` (в ворктри, не закоммичен). Он доказывает разделение, которое просила карточка:

| Тест | Что доказывает |
|---|---|
| 1 тетрадь с активными источниками → `primaryNotebookId` резолвится | эвристика работает, когда тетрадь одна |
| **Отрицательный контроль:** 2 тетради → `primaryNotebookId === null` | **фокус теряется** — ровно симптом карточки на телефонном входе |
| Строка-источник `notebookIds.length === 1 ? … : null` существует | фиксация точного места потери фокуса |
| `withProfileOrigin` + `detectDirectChatToolIntent` есть в веб/чат | адрес из контекста работает детерминированно на вебе |
| **Отрицательный контроль:** в `groundedAnswer.ts` нет `detectDirectChatToolIntent`, но есть `loadConversationContext` | на телефоне адрес передан, но не детерминирован |
| `useVoiceQuery` передаёт `notebookId` + сервер его использует | веб-голос передаёт И использует фокус |

Запуск (та же команда, что у серверных тестов репозитория):

```
node --conditions=react-server --import tsx --test tests/unit/web580-voice-focus-negative.test.ts
```

Результат, который я получил сам: **6/6 pass, 0 fail** (`RC=0`).

### 6б. Перепрогон уже существующих тестов (чтобы не верить на слово)

Все три набора я прогнал сам в этом ворктри:

- `src/lib/chat/__tests__/directChatTool.test.ts` — **16/16 pass** (внутри тесты WEB-580/3585 «nearby берёт город из профиля», «без адреса — уточняющий вопрос», «явные координаты приоритетнее профиля»).
- `src/lib/ai/__tests__/conversationContext.test.ts` + `conversationContextParity.contract.test.ts` + `tests/acceptance/3287-notebook-focus-conversational.test.ts` + `tests/acceptance/3269-notebook-focus-matcher-narrow.test.ts` — **69/69 pass**.

Вывод по тестам: детерминированные механизмы (матчер «в какой я тетради», nearby-перехват) на месте и зелёные. Отрицательный контроль показывает ровно ту дыру, которая осталась.

---

## 7. Почему это не поймали раньше (эволюция, включая мои тупики)

1. **Карточка старая, часть уже починена прошлыми волнами.** Фокус тетради для веб/чата закрыт коммитами `557d5889c`, `1ee8deb77`, `34c85e956`; адрес/«рядом» — коммитом `6482f640d` (WEB-580/3379), влит через `e48aa59f6` (wave/3599). Поэтому главный пользовательский сценарий (веб-голос) уже не воспроизводится — но карточка лежала «в ревью» и никто не перепроверил, что осталось.
2. **Оставшаяся дыра — узкая.** Она проявляется только на телефонном входе и только при 2+ тетрадях. Одиночный пользователь с одной тетрадью (а в dev-среде пользователь часто один) её никогда не увидит. Поэтому эвристика `notebookIds.length === 1` долго выглядела «достаточной».
3. **Мой тупик с окружением тестов.** Я пробовал прогнать `realtime-grounding.test.ts`, но он падает не по логике, а по среде: сначала `server-only`, потом `_react.default.createContext is not a function` даже с `--conditions=react-server`. Это тяжёлый Next/react-граф, который не поднимается в изолированном `node --test`. Это **не** дефект кода — я это не выдаю за доказательство. Для моих выводов этот тест не нужен.
4. **Мой тупик с node_modules.** В свежем ворктри не было `node_modules` — TS-тесты не запускались. Я сделал symlink `node_modules → ~/waves/.worktree-clientapikeys-3292/node_modules` (мутация только МОЕГО ворктри, чтение соседнего — только для запуска тестов). После этого всё заработало.

---

## 8. Что доказано и ГДЕ ГРАНИЦА

**Доказано кодом + отрицательным контролем (уверенно):**
- Телефонный голос теряет фокус тетради при 2+ тетрадях — `src/lib/telephony/runtime/personalSourceScope.ts:59`.
- Веб-голос передаёт и использует фокус — `src/hooks/useVoiceQuery.ts:56,200` → `src/app/actions.ts:3463,4291` → `src/lib/chat/effectiveScope.ts:139`.
- Веб/чат берёт адрес детерминированно — `src/lib/chat/directChatTool.ts:177,233`, `src/app/actions.ts:4311` (16/16 тестов).
- Телефония НЕ имеет детерминированного nearby-перехвата, но адрес в промпт передаёт — `src/lib/telephony/groundedAnswer.ts:1879` (без `detectDirectChatToolIntent`).

**ГРАНИЦА (чего статикой доказать нельзя):**
Всё, что происходит **после** реального распознавания живой речи:
- реальный STT-текст с шумом/ошибками → сработает ли матчер;
- инъекция инструкций в живую realtime-сессию (Google/OpenAI) и то, как модель распорядится `query_knowledge_base`;
- что ассистент **фактически произнесёт в трубку** и возьмёт ли координаты из промпта.

Статические тесты доказывают проводку (данные идут по нужному маршруту), но не «что сказали в трубку». Это можно проверить только живым звонком.

---

## 9. Что осталось открытым (и что именно нужно)

1. **Телефония: нет серверного сигнала «какая тетрадь открыта».** Чтобы починить настоящую причину (а не эвристику), нужен один из двух путей, и это решение принимает человек, а не я:
   - добавить на сервер сигнал «последняя открытая тетрадь» (например, поле `lastOpenedAt` на `Notebook` + синк из веба при смене `activeNotebookId`), ИЛИ
   - при исходящем звонке из веба передавать `notebookId` в метаданные `VoiceSession` (для входящего звонка это всё равно не поможет — там открытой тетради в момент звонка может не быть).

   Ни один из этих путей я **не делал**: это схема БД / новый клиентский вызов, требует `build`, миграции и живой проверки — всё это вне рамок волны и под запретами (без прод-БД, без deployment, без `next build`).

2. **Живой голос не проверен** — пошаговый сценарий ниже.

---

## 10. Troubleshooter: пошаговая проверка живым голосом

Это то, что надо сделать человеку, чтобы закрыть «границу» из раздела 8.

### Сценарий А — веб-голос (ожидаем: ВСЁ РАБОТАЕТ)
1. Создать **две** тетради: «Работа» и «Личное», в каждую положить по 1–2 документа с разным содержанием.
2. Открыть «Работа».
3. Нажать кнопку голоса и спросить: «В какой тетради я сейчас?» → ожидаем ответ «Работа».
4. Спросить: «Что у меня в документах?» → ожидаем, что ответ опирается на документы «Работы», а не «Личного».
5. Открыть «Личное», повторить шаги 3–4 → ожидаем, что ответ сменился на «Личное».
6. В профиле сохранить адрес/город. Спросить голосом: «Что рядом со мной?» → ожидаем результат, привязанный к сохранённому адресу.

### Сценарий Б — телефонный голос (ожидаем: ВОСПРОИЗВЕДЁТСЯ ДЫРА)
1. Тот же пользователь с двумя тетрадями «Работа» и «Личное» (обе с активными источниками).
2. В браузере открыть «Работа».
3. Позвонить на голосовой номер (8010 / Signal / Telegram) и спросить: «В какой я тетради?» → **ожидаем, что голос скажет «тетрадь не выбрана»**, хотя в браузере открыта «Работа». Это и есть баг из карточки.
4. Спросить: «Что рядом со мной?» → поведение зависит от модели: ответ может быть привязан к сохранённому адресу, а может уйти в уточняющий вопрос. Это надо записать фактически, не предполагая.

### Сценарий В — контроль эвристики
1. Удалить все источники из «Личное» так, чтобы активные источники остались только в «Работа».
2. Повторить Сценарий Б, шаг 3 → ожидаем, что теперь голос **знает** «Работа» (эвристика «ровно одна тетрадь» сработала). Это доказывает, что поведение зависит именно от числа тетрадей.

Записать фактические ответы каждого шага. Без этих записей вывод про живую речь остаётся «заявлено, не доказано».

---

## KNOWN ISSUES

1. **node_modules — symlink на соседний ворктри.** В `wave/3862-web580-voice-focus` `node_modules` — это symlink на `~/waves/.worktree-clientapikeys-3292/node_modules`. Сделан только в моём ворктри для запуска тестов; соседний ворктри только читается. После завершения волны symlink можно удалить (`rm node_modules`).
2. **`realtime-grounding.test.ts` не запускается в среде** (`_react.default.createContext is not a function` даже с `--conditions=react-server`). Это ограничение тестового окружения (тяжёлый Next/react-граф), не дефект проверяемой логики. Для выводов этой волны не требуется.
3. **Предупреждение `unable to access '/Users/milamarty/.config/git/attributes': Permission denied`** — безвредное предупреждение git на этой машине, на работу ворктри не влияет.
4. **Файл теста не закоммичен** — оставлен в ворктри как `??` (untracked), решение о вливе за человеком. В бандл вошёл как evidence.
2026-09-14T15:27:55.671Z · coordinator
[14.09 15:27Z координатор] VERDICT=GO

# WEB-580 «телефонный голос не знает тетрадь в фокусе» — wave 3874 (починка находки 3862)

Дата: 2026-09-14. Ворктри: `wave/3874-web580-focus-fix` (база `refs/waves/l115n`), коммит `bc4851dd4`.

Это отчёт для «нулевого» агента — человека, который ничего не знает ни про тикет, ни про код. Пишу с нуля и детским языком. Всё, что называю «доказанным», я проверил сам; всё, что нельзя проверить без живой БД и живого звонка, так и называю и расписываю по шагам.

---

## 1. Симптом словами пользователя

Человек открывает тетрадь в вебе, а потом звонит голосовому ассистенту по телефону (номер 8010, Signal или Telegram) и спрашивает «в какой я тетради?» или «что у меня в документах?». Ассистент отвечает так, будто не знает, с чем человек работает: «тетрадь не выбрана». Хотя в браузере тетрадь открыта.

Важно: в веб-голосе (кнопка в самом окне) это работало и работает — там клиент и открытая тетрадь в одном месте. Ломалось именно на **телефонном** входе, потому что звонок приходит «со стороны».

---

## 2. Как нашли и почему не поймали раньше (коротко, от 3862 + моё подтверждение)

Волна 3862 разобрала тикет и вынесла NO-GO. Её находка (я **подтвердил её в коде**, не переоткрывая с нуля):

> «Фокус» (какая тетрадь открыта) — чисто браузерное понятие. Оно хранится в `activeNotebookId` и пишется **только в localStorage** браузера, на сервер не уходит. Веб-голос знает фокус, потому что тетрадь в том же окне. Телефонный голос физически не может его знать — сигнала на сервере нет.

Что я сам проверил и где (шаг 1 задания):

- `src/store/useStore.ts:95` — ключ `note-clone-last-active-notebook-id`; функции чтения/записи `src/store/useStore.ts:218-235` трогают **только** `window.localStorage` (`getItem`/`setItem`/`removeItem`), ни одного `fetch` на сервер нет.
- `src/lib/telephony/runtime/personalSourceScope.ts` (до моей правки, строка 59) — единственная догадка телефонного пути:

  ```
  primaryNotebookId: notebookIds.length === 1 ? notebookIds[0] : null
  ```

  Простыми словами: если активные источники лежат **ровно в одной** тетради — телефонный голос её «знает». Если тетрадей **две и больше** — честно отвечает `null` = «тетрадь не выбрана», даже если в браузере у человека открыта одна из них. Это ровно симптом карточки на телефонном входе.

Вывод: находка 3862 **верна**, фокус действительно нигде не уходит на сервер. Чинить есть что.

Почему не поймали раньше (эволюция): дыра узкая — только телефонный вход и только при 2+ тетрадях. Один пользователь с одной тетрадью её не видит, поэтому эвристика «ровно одна тетрадь» долго выглядела достаточной. Часть карточки (веб-голос, адрес «рядом со мной») была закрыта прошлыми волнами, и никто не перепроверил, что осталось на телефонном входе.

---

## 3. Два устройства починки и выбор (шаг 2 задания)

**(а) Сохранять фокус на сервере при смене тетради.**
Чем плохо: сервер начинает *помнить* «что человек сейчас читает» — это расширение приватного следа (дурно); нужна новая таблица/колонка и миграция БД (в волне запрещены применение, build, deploy); нужен новый клиентский вызов при каждой смене тетради.

**(б) Телефонный путь спрашивает у пользователя, какая тетрадь.**
Чем плохо: трение на каждый звонок («в какой тетради?») — в живом realtime-голосе это целый диалог; если тетрадей много, зачитывать список мучительно; для входящего звонка, когда человек как раз сидит с открытой тетрадью, спрашивать — просто неправильно, он ждёт, что ассистент уже знает.

**Выбрал (а)** с обязательным «предохранителем»: фокус хранится на сервере с **сроком годности (TTL)** и читается только в контексте самого владельца. Когда свежего фокуса **нет** (его не писали, он протух, тетрадь удалили) — телефонный путь **не угадывает** и не берёт «первую попавшуюся» тетрадь, а ведёт себя как раньше: «тетрадь не выбрана» (единственная безопасная двусмысленность — ровно одна активная тетрадь — остаётся). Это ровно то поведение «как задумано» из отрицательного контроля задания: угадывать нельзя.

Почему не (б): оно не чинит причину («сервер не знает фокуса»), а добавляет трение. (а) чинит причину, а (б)-поведение остаётся естественным fallback'ом модели, когда фокуса нет.

---

## 4. Что сделано (шаг 3 задания) — со ссылками на файл и строку

Четыре куска, плюс миграция:

**4.1. Чистая логика свежести фокуса (ядро, проверяется тестами).**
Новый файл `src/lib/telephony/runtime/notebookFocus.ts`:
- `:29` — `NOTEBOOK_FOCUS_TTL_MS = 24 * 60 * 60 * 1000` (срок годности, см. раздел 6);
- `:50` — `resolveFreshNotebookFocus(focus, candidateNotebookIds, nowMs, ttlMs)` — чистая функция: возвращает тетрадь, только если фокус есть, записан не в будущем и не старше TTL, и указывает на тетрадь, которая **всё ещё есть** среди кандидатов (активных источников). Иначе `null`.

**4.2. Фокус стал частью scope телефонного голоса.**
`src/lib/telephony/runtime/personalSourceScope.ts`:
- `:50` — у `queryPersonalSourceScope` появился параметр `focusLookup` (по умолчанию `readUserNotebookFocus`);
- `:69-75` — `primaryNotebookId` теперь решается так: свежий фокус → если нет, ровно одна активная тетрадь → иначе `null`. **Никогда** не «первая из списка».

Это меняет поведение сразу всех входов, которые строят scope через `queryPersonalSourceScope`: SIP 8010 (`src/lib/telephony/runtime/sessionStart.ts:1263`), Signal, Telegram, WhatsApp.

**4.3. Запись фокуса на сервер.**
- `src/lib/telephony/runtime/notebookFocus.ts:112` — `writeUserNotebookFocus` (raw SQL, upsert/delete).
- Новый эндпоинт `src/app/api/user/notebook-focus/route.ts` — `PUT { notebookId: string | null }`, только владелец (`auth()`), и проверка, что тетрадь принадлежит звонящему (чужой id не запишешь).

**4.4. Клиент шлёт фокус при смене тетради.**
`src/store/useStore.ts:230-253` — `syncNotebookFocusToServer` вызывается из `writeLastActiveNotebookId` (fire-and-forget, с дедупликацией: повтор той же тетради не шлётся). Тот же путь срабатывает и при переоткрытии приложения (реhydrate), значит фокус освежается при каждом открытии, а не только при переключении.

**4.5. Миграция (raw SQL, не в schema.prisma).**
`prisma/migrations/20260914170000_web580_notebook_focus/migration.sql` — таблица `UserNotebookFocus(userId PK, notebookId, updatedAt)`. Намеренно **не** добавлена в `schema.prisma`, чтобы не требовался `prisma generate` (в волне запрещён). Если таблицы ещё нет — чтение возвращает «нет фокуса» (безопасное направление отказа), запись отдаёт `503 focus_store_not_ready` (клиент это игнорирует).

---

## 5. Приватность — кто может прочитать фокус после правки и почему доступ не расширен (шаг 3 задания)

Фокус — это сведения о том, что человек сейчас читает. После правки сервер начинает **помнить** это (раньше — только браузер). Надо честно разделить две вещи: **кто** может прочитать и **сколько** хранится.

Кто может прочитать запись `UserNotebookFocus`:
1. Сам владелец — запись читается только внутри его собственного телефонного scope (`readUserNotebookFocus(userId)`, ключ — userId звонящего).
2. Серверный код — тот же сервер, который и так хранит **все** тетради и **все** документы пользователя (содержимое чувствительнее одного указателя «какая открыта»).
3. Оператор с прямым доступом к БД — который и так читает всю БД целиком.

Почему это **не расширяет доступ**: таблица не отдаётся ни в один list/admin/team/шаринг-эндпоинт; чужой userId в неё не записать (эндпоинт проверяет принадлежность тетради владельцу); кросс-пользовательского чтения нет по построению. Тот же набор читателей, что и у самих тетрадей.

Что честно **расширяется** (и это осознанная цена): *долговечность* факта — «что я читал» теперь живёт на сервере. Поэтому два смягчения: (1) пишется только по явному действию пользователя (открыл/переключил тетрадь), не фоново; (2) срок годности 24 часа — сервер «помнит» максимум сутки.

---

## 6. Устаревание — срок годности числом (шаг 4 задания)

**TTL = 24 часа (86 400 000 мс)** — `src/lib/telephony/runtime/notebookFocus.ts:29`.

Обоснование числа:
- Требование карточки: фокус недельной давности не должен командовать сегодняшним звонком. 24 часа дают **6-кратный запас** против «неделя».
- 24 часа покрывают реальный сценарий «открыл тетрадь → отложил ноутбук → взял трубку» (минуты–часы, включая «оставил на ночь»).
- Фокус старше суток — это уже не «текущий рабочий контекст», а история: человек почти наверняка переключился/поспал. Держать дольше — значит отвечать на сегодняшний вопрос вчерашней тетрадью, что и есть баг.

Протухший фокус ведёт себя **как отсутствующий** (проверено тестом, раздел 7): не «первая тетрадь», а «тетрадь не выбрана».

---

## 7. Тесты с отрицательным контролем (шаг 5 задания) — что я запустил сам

Новый тест `tests/unit/web580-notebook-focus-fix.test.ts` (команда как у серверных тестов репо):

```
node --conditions=react-server --import tsx --test \
  tests/unit/web580-notebook-focus-fix.test.ts \
  src/lib/telephony/runtime/__tests__/personalSourceScope.test.ts
```

Результат, полученный мной: **25/25 pass, 0 fail, RC=0** (16 новых + 9 существующих).

Что доказывают отрицательные контроли (ровно то, что просила карточка):

| Тест | Что доказывает |
|---|---|
| два notebooks + свежий фокус → фокус побеждает | **сама починка**: телефонный голос узнаёт открытую тетрадь при 2+ тетрадях |
| свежий фокус на «не первой» тетради → берётся фокус | не «первая из списка», а именно открытая |
| **отрицательный контроль: фокуса нет** + 2 тетради → `null` | не берёт первую тетрадь молча |
| **отрицательный контроль: фокус недельной давности** + 2 тетради → `null` | протухший фокус не командует и не подменяется первой тетрадью |
| протухший фокус на одной из двух → `null` | не «откатывается» на другую тетрадь |
| свежий фокус на удалённую/пустую тетрадь → `null` | фокус на несуществующую тетрадь игнорируется, не угадывается |
| будущая метка времени / битая дата / пустой id → `null` | защита от clock-skew и мусора |
| одна тетрадь без фокуса → она же | старая «однозначная» эвристика сохранена |

Кроме того, прогнал, чтобы не сломать соседнее:
- `src/lib/telephony/runtime/__tests__/*` (6 файлов) — **73/73, RC=0**;
- `realtimeKnowledgeIdentity.test.ts` — **3/3, RC=0**;
- `channels/signalCall/__tests__/bindingNotebookId.test.ts` (структурный) — **5/5, RC=0**.

`personalSourceScope.test.ts` я не «чинил под себя» — только добавил в 9 вызовов инъекцию `focusLookup: async () => null`, чтобы старый тест не полез в БД (тесты и раньше инъецировали `queryFn` по той же причине). Все 9 проверок те же.

---

## 8. Чем доказано и ГДЕ ГРАНИЦА

**Доказано (запускал сам, число видел сам):**
- Чистая логика TTL + «не угадывать» — 16/16 тестов с отрицательными контролями.
- Что телефонный scope теперь читает фокус и отдаёт его в `primaryNotebookId` — интеграционные тесты `queryPersonalSourceScope` (8/8).
- Что соседние телефонные тесты не сломаны — 73/73 + 3/3 + 5/5.

**ГРАНИЦА (без живой БД, билда, деплоя и живого звонка доказать нельзя):**
1. **Запись фокуса на сервер** — клиентский `fetch` из `useStore.ts` + эндпоинт + миграция. Я написал код и SQL, но не применял миграцию (запрет: прод-БД/миграции/деплой) и не гонял приложение (запрет: `next build`). Что код синтаксически валиден и импорты на месте — проверил (esbuild-разбор всех изменённых файлов прошёл; сам эндпоинт в голом `node` не импортируется — это известное ограничение Next/react-графа, не дефект логики).
2. **Реальное поведение голоса** — что ассистент **произнесёт в трубку** при свежем/протухшем фокусе. Это проверяется только живым звонком (сценарии ниже).
3. **Что модель делает при `primaryNotebookId = null`** — скажет «тетрадь не выбрана» или сама переспросит. Это поведение модели, не гарантированного кода.

---

## 9. Что не проверяется без живого звонка + точные шаги (шаг 6 задания)

3862 уже написала сценарии А/Б/В. Сверяюсь с ними и **дописываю** шаги под мою починку (нужна применённая миграция и деплой ветки).

Предусловие для всех телефонных сценариев: миграция `20260914170000_web580_notebook_focus` применена, ветка задеплоена, пользователь тот же, что и в вебе.

**Сценарий А (веб-голос) — ожидаем: как было, всё работает.** Повторить ровно как у 3862 (две тетради «Работа»/«Личное», голос «в какой я тетради?» → «Работа», переключить → «Личное», «что рядом» → по адресу из профиля). Моя правка этот путь не трогает, но проверить, что не сломала.

**Сценарий Б (телефонный голос, 2 тетради, браузер открыт) — ЭТО НОВОЕ, ключевое для моей починки:**
1. Создать две тетради «Работа» и «Личное», в каждую положить по 1–2 документа с разным содержанием (обе с активными источниками).
2. В браузере открыть «Работа» (и дождаться, чтобы сработал `fetch` фокуса — см. «что смотреть», п.4).
3. Позвонить (8010 / Signal / Telegram) и спросить: «В какой я тетради?» → **ожидаем «Работа»** (раньше было «тетрадь не выбрана»).
4. Спросить: «Что у меня в документах?» → **ожидаем ответ по документам «Работы», а не «Личного»**.
5. Открыть «Личное», подождать, позвонить снова → **ожидаем «Личное»**.

**Сценарий В (контроль протухания — новое, проверяет TTL 24ч):**
1. Открыть «Работа», позвонить → «Работа» (убедились, что фокус записан).
2. **Не трогать веб 25+ часов** (либо в БД вручную выставить `"updatedAt"` на 25 часов назад — это честный способ без ожидания суток).
3. Позвонить → **ожидаем «тетрадь не выбрана»**, а НЕ «Работа» и НЕ «Личное» (протухший фокус не командует).
4. Вернуться в веб, открыть «Работа» заново (освежить фокус), позвонить → снова «Работа».

**Сценарий Г (контроль «не угадывает первую») — новое:**
1. Две тетради, в браузере **ни одна не открыта** (или очистить фокус: в БД удалить строку пользователя).
2. Позвонить, спросить «в какой я тетради?» → **ожидаем «тетрадь не выбрана»**, а НЕ автоматически «Работа» (первая по алфавиту/созданию).

**Что смотреть руками, чтобы не гадать (записать фактические ответы каждого шага):**
1. В браузерной консоли/сети: при смене тетради уходит `PUT /api/user/notebook-focus` с телом `{"notebookId":"…"}` (а при закрытии/очистке — `{"notebookId":null}`).
2. В БД: `SELECT "notebookId", "updatedAt" FROM "UserNotebookFocus" WHERE "userId"='…';` — запись появилась и `updatedAt` свежий.
3. Записать дословно, что ассистент сказал в трубку на каждом шаге Б/В/Г. Без этих записей вывод про живую речь остаётся «заявлено, не доказано».

---

## 10. Что осталось открытым

1. **Применение миграции и деплой** — вне рамок волны (запреты). Готово к прогону: `prisma/migrations/20260914170000_web580_notebook_focus/migration.sql`.
2. **Живая проверка** — сценарии Б/В/Г из раздела 9 (телефонный голос). Статические тесты доказывают проводку, но не «что сказали в трубку».
3. **Fallback «спросить, какая тетрадь»** — не выбран как основной путь (см. раздел 3). При `primaryNotebookId = null` голос сейчас говорит «тетрадь не выбрана»; хочет ли продукт, чтобы он ещё и активно переспрашивал со списком тетрадей — отдельное решение, не делал.

---

## KNOWN ISSUES

1. **`node_modules` — symlink** на `~/waves/.worktree-clientapikeys-3292/node_modules` (как в 3862), сделан только в моём ворктри для запуска тестов; соседний ворктри только читается. После волны можно `rm node_modules`.
2. **Эндпоинт/роут не импортируется в голом `node --test`** — `_react.default.createContext is not a function` (тяжёлый Next/react-граф, то же ограничение, что задокументировала 3862). Это ограничение среды, не дефект логики. Синтаксис всех изменённых файлов проверен esbuild-разбором (6/6 OK).
3. **Полный `tsc`/`next build` не гонял** — запрещены в волне. Типы сверял глазами по соседним идиомам (`shareAudienceStore.ts` для raw SQL, `profile/route.ts` для эндпоинта).
4. **Таблица не в `schema.prisma`** — намеренно (raw SQL, как `EmbedShareLinkAudience`), чтобы не требовался `prisma generate`. Пока миграция не применена, запись отдаёт `503`, чтение — «нет фокуса» (безопасно).
5. **Предупреждения git** `unable to access '/Users/milamarty/.config/git/attributes|ignore': Permission denied` — безвредные, на работу ворктри не влияют.
2026-09-14T15:51:17.670Z · coordinator
[14.09 15:51Z координатор] VERDICT=NO-GO

# Независимая приёмка WEB-580, волна 3874

## Для тикета, детским языком

Я открыл именно ветку автора `refs/waves/3874/wave/3874-web580-focus-fix` и создал свою ветку приёмки на коммите `bc4851dd4ed863e4f2eb8738e0504947e73c772a`.

Проверял так:

- запустил объявленный автором набор `node --test`;
- запустил все тесты `src/lib/telephony/runtime/__tests__`;
- проверил привязку тетради в Signal;
- отдельно проверил приватность кода;
- сделал красный тест для Telegram: свежая тетрадь открыта в вебе, но Telegram сам не выбрал тетрадь.

Что устояло:

- свежий фокус выбирает правильную тетрадь, даже если она не первая;
- без фокуса две тетради дают `null`, а не первую тетрадь;
- фокус старше срока не командует звонком;
- будущая дата, плохая дата и удалённая тетрадь не выбираются;
- одна тетрадь без фокуса сохраняет старое однозначное поведение;
- SIP/общий scope и Signal-проводка проходят проверенные тесты;
- пользовательский PUT требует авторизацию и проверяет, что тетрадь принадлежит этому пользователю; отдельного GET для чтения фокуса нет.

Что не устояло:

Telegram после вызова нового scope безусловно пишет `primaryNotebookId: notebookId`. Это `notebookId` из Telegram metadata. Если Telegram не выбрал тетрадь, его resolver уходит в Inbox. Поэтому свежий фокус, записанный из веба, там не становится выбранной тетрадью. Мой негативный `node --test` получил фактический `null` вместо ожидаемой свежей веб-тетради и завершился `RC=1`.

Это прямое противоречие заявлению автора, что новая логика сразу действует для SIP, Signal, Telegram и WhatsApp. Существующий Telegram-тест зелёный, но он проверяет только наличие записи `sourceScope.primaryNotebookId`; он не проверяет, что веб-фокус переживает Telegram-перезапись.

## Что я получил сам

Объявленная автором команда:

`node --conditions=react-server --import tsx --test tests/unit/web580-notebook-focus-fix.test.ts src/lib/telephony/runtime/__tests__/personalSourceScope.test.ts`

Результат: 25 passed, 0 failed, `RC=0`.

Все runtime-тесты по glob: 76 passed, 0 failed, `RC=0`.

Signal binding: 5 passed, 0 failed, `RC=0`.

Приватностный статический тест: 3 passed, 0 failed, `RC=0`.

Негативный Telegram-тест: 0 passed, 1 failed, `RC=1`. Именно это падение и открывает дефект.

На машине нет команды `timeout`, поэтому длинные запуски выполнялись синхронно через локальный alarm-wrapper вокруг `node --test`. Сначала попытка с отсутствующим `timeout` дала `RC=127`; это не результат тестов. В первом запуске с другим локальным набором зависимостей Prisma-клиент не был сгенерирован и оба файла упали на импорте; после подключения другого уже существующего локального набора тесты выполнились. Авторское дерево не менял.

## Приватность

Проверено по коду и отдельным тестом:

- route вызывает `auth()` до записи;
- ненулевой `notebookId` проверяется вместе с `session.user.id`;
- writer получает только `session.user.id` из сессии;
- raw SQL читает строку через `WHERE "userId" = $1`, а не через склеивание идентификатора;
- в route экспортирован PUT, пользовательского GET нет;
- в миграции `userId` — первичный ключ, то есть строка не является общим списком фокусов.

Я не обнаружил application-level чтения чужого фокуса или нового list/admin/team endpoint. В этом узком смысле доступ между пользователями не расширен.

Но граница важна: раньше фокус жил только в browser localStorage, а теперь копия появляется на сервере. Её может читать серверный runtime и оператор с прямым доступом к БД. Это новая серверная видимость, даже если такой оператор уже имел доступ к самим тетрадям. Если политика приватности означает «фокус никогда не должен покидать браузер», этот дизайн ей не соответствует.

Также TTL — это только правило выбора, не удаление данных. Код удаляет строку при последующем PUT с `null`; автоматически старую строку он не удаляет. Поэтому доказано «протухший фокус не управляет звонком», но не доказано «сервер хранит его не дольше TTL».

## Устаревание и запрет угадывать

В коде срок указан числом: `24 * 60 * 60 * 1000` миллисекунд.

Проверки подтверждают: свежий фокус принимается, фокус старше срока и недельный фокус отвергаются, фокус с будущей датой отвергается, фокус на тетрадь вне списка кандидатов отвергается. Если фокуса нет или он отвергнут, при двух тетрадях результат `null`; первой тетради молча не берут.

Это доказано чистой функцией и тестовым scope, без живой БД.

## Граница живой проверки

Без живой БД нельзя доказать, что миграция реально применена, PUT действительно записывает строку, а чтение в запущенном сервисе видит её. Я не применял миграцию.

Без живого звонка нельзя доказать, какие слова реально произнесёт ассистент, попадёт ли `primaryNotebookId` в конкретную VoiceSession, и что модель ответит при `null`. Я не звонил и не использовал deployment, production или платного провайдера.

Шаги автора для SIP/Signal/WhatsApp в целом пригодны человеку после применения миграции и запуска именно этой ветки: открыть две тетради, проверить PUT в браузере, позвонить, записать ответ, затем проверить протухший фокус. Но для Telegram шаги недостаточны: ожидаемый результат противоречит текущему коду. Для срока нужно либо ждать больше 24 часов, либо безопасно выставить старый `updatedAt` в тестовой БД; это нельзя делать в рамках этой приёмки.

## Что осталось открытым

1. Исправить Telegram: выбрать единый контракт между Telegram messaging scope и свежим веб-фокусом, затем добавить исполняемый тест этого случая.
2. Решить, допустима ли новая серверная видимость фокуса с точки зрения приватности.
3. Решить, нужно ли удалять истёкшие строки, а не только переставать ими пользоваться.
4. После исправления провести живой call-пакет для SIP, Signal, Telegram и WhatsApp; отдельно проверить речь при `primaryNotebookId = null`.
5. Применить миграцию только в разрешённом окружении и проверить реальную запись/чтение.

## KNOWN ISSUES

- В текущей среде нет `timeout`; использован синхронный alarm-wrapper, сам тестовый процесс — `node --test`.
- В новом worktree не было `node_modules`; для своей ветки использован существующий локальный набор зависимостей с Prisma-клиентом. Авторское дерево не затронуто.
- Полный `tsc`, `next build`, release build и deployment не выполнялись.
- Живой звонок и живая БД не использовались.
- Старая отрицательная проверка 3862 по файлу `tests/unit/web580-voice-focus-negative.test.ts` удалена коммитом автора; новая проверка фокуса есть, но Telegram-перезапись ею не покрыта.

Подробные receipts и исходники моих дополнительных тестов находятся в [3881ACC580-evidence](</Users/limamarty/waves/3881ACC580-evidence/README.md>).
2026-09-14T16:09:43.680Z · coordinator
[14.09 16:09Z координатор] VERDICT=GO

# WEB-580 round 2 — Telegram затирал свежий веб-фокус. Починка и приёмка

Волна 3885. Ворктри `~/waves/wt-3885-web580-tg`, ветка `wave/3885-web580-telegram-overwrite`,
коммит `061bfebac` (база `bc4851dd4` = ветка автора 3874). Дата 2026-09-14.

Артефакты: `~/waves/3885WEB580TG.bundle` (+ `.bundle.sha256`, 2 коммита от `refs/waves/l115n`),
evidence `~/waves/3885WEB580TG-evidence/` (с `SHA256SUMS`).

---

## Для тикета, детским языком (след для нулевого агента)

### Симптом словами пользователя

Человек открывает тетрадь «Работа» в вебе, а потом звонит голосовому ассистенту в Telegram и
спрашивает «в какой я тетради?». Ассистент отвечает так, будто не знает: показывает не «Работу»,
а Inbox (или «тетрадь не выбрана»). При этом в самом вебе тетрадь открыта, и по SIP/Signal/WhatsApp
ассистент её уже знает.

### Как нашли

Волна 3874 починила фокус тетради для телефонного голоса и заявила, что новая логика «сразу
действует для SIP, Signal, Telegram и WhatsApp». Независимая приёмка 3881 это опровергла красным
тестом: **Telegram после вызова нового scope безусловно писал `primaryNotebookId: notebookId`**, где
`notebookId` — это тетрадь из метаданных Telegram. Когда Telegram сам тетрадь не выбрал, его
resolver уходит в Inbox, поэтому свежий веб-фокус там выбранной тетрадью не становился. Красный
`node --test` приёмки получил фактический `null` вместо свежей веб-тетради, `RC=1`.

### Почему не поймали раньше

Существующий Telegram-тест был зелёный, но он проверял только наличие записи
`sourceScope.primaryNotebookId`, а не то, переживает ли веб-фокус перезапись Telegram. Зелёный тест
не был доказательством. Дефект сидел в двух местах сразу, и оба в `telegramCall/binding.ts`:

1. **фильтр до Inbox**: `buildUserSourceScope` вызывал `queryPersonalSourceScope` с `queryFn`,
   который фильтровал источники по `notebookId` (Inbox-фолбэк). Поэтому фокус, указывающий на
   «Работу», отсекался ещё на этапе кандидатов — даже без перезаписи он бы не победил;
2. **безусловная перезапись**: возврат `{ ...resolvedSourceScope, primaryNotebookId: notebookId }`
   затирал всё, что насчитал scope.

### Что сделали

Ввели явное правило старшинства для `primaryNotebookId` Telegram-звонка:

> **явный выбор внутри Telegram > свежий веб-фокус > одна-единственная тетрадь > `null`.**

То есть «канал сам ничего не выбрал» больше НЕ перебивает свежий веб-фокус, а явный выбор
пользователя внутри Telegram — перебивает. Это ровно то, что просила карточка, и то же самое, что
уже делают Signal и WhatsApp.

Правки (файл : строка):

- `src/lib/messaging/telegramScope.ts:50` — в `ResolvedTelegramScope` добавлено поле
  `notebookSelectedExplicitly: boolean`. `:234` — `true` в ветке явного выбора; `:255` — `false` в
  Inbox-фолбэке.
- `src/lib/telephony/channels/telegramCall/scopePrecedence.ts:30` — новый чистый помощник
  `resolveTelegramScopePrimaryNotebookId(selection, base)`: явный выбор → `notebookId`, иначе →
  `base.primaryNotebookId` (то, что насчитал `queryPersonalSourceScope`: фокус / одна тетрадь / null).
- `src/lib/telephony/channels/telegramCall/binding.ts:243` — читаем `notebookSelectedExplicitly`.
  `:248-261` — когда явного выбора нет, `queryPersonalSourceScope` вызывается с `undefined` (полный
  персональный scope, как у Signal/WhatsApp), а не с фильтром до Inbox. `:269-272` — финальный
  `primaryNotebookId` берётся из помощника, а не безусловно из `notebookId`.
- `tests/unit/web580-telegram-focus-negative.test.ts` — негативный тест: поведенческий (правило
  старшинства через помощника + полный scope с фокусом) и структурный (старый паттерн перезаписи
  исчез, guard на месте), плюс аудит-гарды, что SIP/Signal/WhatsApp не перезаписывают.

### Аудит всех четырёх каналов (перезаписывает ли веб-фокус)

| Канал | Перезаписывает? | Где (файл : строка) |
|-------|-----------------|---------------------|
| SIP (8010) | **Нет** | `sessionStart.ts:1267` — фолбэк берёт `personalScope.primaryNotebookId` (значение уже с фокусом). Явная настройка endpoint-а (`identity.ts:258` `primaryNotebookId: endpoint.notebookId`) — это явный выбор канала, она и должна побеждать. |
| Signal | **Нет** | `signalCall/binding.ts:101-120` — `buildSignalSourceScope` фильтрует `queryFn` только когда `extractSignalNotebookId` вернул тетрадь; результат — `...base` без перезаписи `primaryNotebookId`. |
| WhatsApp | **Нет** | `whatsappCall/binding.ts:188-199` — `buildUserSourceScope` просто `queryPersonalSourceScope(userId)` + `...base`, без канальной тетради и без перезаписи. |
| Telegram | **Было — да, теперь нет** | `telegramCall/binding.ts:263` (старый код — безусловная `primaryNotebookId: notebookId`) → исправлено на `:269-272`. |

Вывод шага 2: такая перезапись была **только у Telegram**. Поэтому шаг 5 (добавить такие же
негативные тесты для остальных каналов) — пустой по существу; вместо этого добавлены
аудит-гарды, фиксирующие, что три остальных канала не перезаписывают (см. тест, секция
«Other channels do not overwrite…»).

### Чем доказано (числа получены мной)

Красный тест приёмки 3881, адаптированный под путь моего ворктри, на **старом** коде:
`0 passed, 1 failed, RC=1`, фактический `null`, ожидаемый `web-focus-notebook` — дефект воспроизведён.

Примечание: вторая проверка оригинального красного теста — `assert.match(source,
/…primaryNotebookId:\s*notebookId/)` — буквально утверждает, что дефект В КОДЕ ЕСТЬ, поэтому «зелёной»
она не станет по построению. Поэтому «довести до зелёного» я делаю заменой на негативный тест,
который утверждает правильное поведение (перезапись исчезла + guard на месте) и проходит на новом коде.

Мой новый негативный тест на **старом** коде: `RC=1` — `Error: Cannot find module
'@/lib/telephony/channels/telegramCall/scopePrecedence'` (модуль-помощник ещё не существует, значит
тест реально различает старый и новый код).

Мой новый негативный тест на **новом** коде: `14 passed, 0 failed, RC=0`.

Регрессия (все на новом коде):

- авторский фокусный набор (`tests/unit/web580-notebook-focus-fix.test.ts` +
  `personalSourceScope.test.ts`): `25 passed, 0 failed, RC=0`;
- `src/lib/telephony/runtime/__tests__/*.test.ts`: `76 passed, 0 failed, RC=0` (совпадает с 76 у приёмки);
- `src/lib/telephony/channels/telegramCall/__tests__/*.test.ts`: `13 passed, 0 failed, RC=0`;
- `src/lib/telephony/channels/signalCall/__tests__/*.test.ts`: `24 passed, 0 failed, RC=0`;
- `src/lib/telephony/channels/whatsappCall/__tests__/*.test.ts`: `19 passed, 0 failed, RC=0`;
- esbuild-разбор всех 4 изменённых файлов — OK.

### ГРАНИЦА (без живой БД, билда и живого звонка доказать нельзя)

1. **Реальная запись/чтение фокуса** — я не применял миграцию и не гонял приложение (запреты
   волны). Что PUT реально пишет строку, а чтение в запущенном сервисе её видит, — не доказано.
2. **Живая речь** — что ассистент произнесёт в трубку при свежем/протухшем фокусе и при
   `primaryNotebookId = null`, проверяется только живым звонком.
3. **Что модель делает при `null`** — «тетрадь не выбрана» или переспросит, — поведение модели, не кода.
4. Изменение носит поведение только в одной узкой точке: когда Telegram тетрадь НЕ выбрал, звонок
   теперь берёт полный персональный scope (владельческий, отфильтрованный по видимости), а не
   «только Inbox». Это намеренное выравнивание с Signal/WhatsApp; кросс-пользовательского доступа
   не добавляет.

### Что осталось открытым (вне рамок этой волны)

1. Живой call-пакет для SIP, Signal, Telegram и WhatsApp; отдельно речь при `null` (сценарии Б/В/Г
   из отчёта 3874).
2. Применение миграции и проверка реальной записи/чтения фокуса.
3. Решение по приватности (новая серверная видимость фокуса) и по удалению истёкших строк (TTL —
   правило выбора, не удаление) — уже поднято приёмкой 3881, не входило в задание этой волны.

### Troubleshooter

- «Тесты зелёные, но в Telegram всё равно Inbox» → проверить, что в `resolveTelegramMessagingScope`
  при отсутствии выбора возвращается `notebookSelectedExplicitly: false` (`telegramScope.ts:255`), и
  что ветка реально задеплоена (этот commit, не старый).
- «Ассистент говорит не ту тетрадь» → смотреть в БД строку `UserNotebookFocus` пользователя: свежий ли
  `updatedAt`, тот ли `notebookId`. Если `updatedAt` старый — сработал TTL 24 ч (ожидаемо).
- «По SIP по-прежнему endpoint-тетрадь» → это явный выбор канала, он и должен побеждать; фокус
  включается только когда endpoint не даёт corpus (`sessionStart.ts:1261-1267`).

---

## KNOWN ISSUES

1. `node_modules` — symlink на `~/waves/.worktree-clientapikeys-3292/node_modules` (как в 3874/3881),
   только в моём ворктри, для запуска тестов. В git не попал (игнорируется).
2. Полный `tsc`, `next build`, release build и deployment не выполнялись (запреты волны).
3. `src/lib/messaging/__tests__/*.test.ts` в этой среде даёт `311 passed / 15 failed` — это
   **предсуществующие** падения (react-граф `createContext`, отсутствующий флаг
   `--experimental-test-module-mocks`, сетевые тесты `getaddrinfo ENOTFOUND`). На базовом коммите
   `bc4851dd4` тот же glob даёт ровно те же `311 passed / 15 failed` — моя правка их не меняет.
   В задание волны этот glob не входил; профильные наборы (runtime 76, каналы) — все зелёные.
4. Живой звонок и живая БД не использовались. Миграция не применялась.
5. Правки приёмкой признанного устоявшим поведения не трогали: TTL фокуса, «две тетради без фокуса →
   null», отказ от будущей/плохой даты и удалённой тетради, проверка владельца в PUT — без изменений
   (зафиксировано тестами 3874, все зелёные).
2026-09-14T16:35:41.023Z · coordinator
[14.09 16:35Z координатор] VERDICT=GO

# Независимая приёмка WEB-580, круг 2

## Для тикета, детским языком

Я проверил новую ветку автора `refs/waves/3885/wave/3885-web580-telegram-overwrite`
(`061bfebac7428e69111a7ca815e1edef98f52cc4`) и старую ветку круга 1
(`bc4851dd4ed863e4f2eb8738e0504947e73c772a`). Оба объекта и красный тест 3881
существуют.

Простыми словами: если в вебе открыта тетрадь, а канал сам ничего не выбрал, все четыре
канала сохраняют веб-фокус. Если в Telegram человек явно выбрал другую тетрадь, побеждает
его выбор. Если фокус старый, неправильный или тетрадь исчезла, код не угадывает.

Итог по коду: `GO`. Доказательство — независимые тесты ниже; production, БД, миграции,
живые звонки и сборки не использовались.

## Красный тест 3881

Буквальный файл `author-reports/3881ACC580-evidence/telegram-focus-negative.test.ts`
на старом worktree дал `0 passed, 1 failed, RC=1`: фактический `null`, ожидаемый
`web-focus-notebook`. Дефект круга 1 воспроизведён.

У файла есть дефект приёмочного материала: он жёстко читает `wt-3881-acc-web580`, а его
вторая проверка требует старую строку `primaryNotebookId: notebookId`. Поэтому его нельзя
честно запустить «на новом коде» без изменения самого теста, и после изменения пути его
поведенческая модель всё равно безусловно создаёт старый `null`. Я не выдаю это за зелёный
результат.

Для проверки именно поведения я добавил исправленный retest с тем же сценарием и guard-ом:

- новая ревизия: `1 passed, 0 failed, RC=0`;
- старая ревизия: `0 passed, 1 failed, RC=1`, `actual: null`, `expected: web-focus-notebook`.

## Моя независимая проверка четырёх каналов

Файл `tests/acceptance/3890-web580-r2-independent.test.ts` содержит отдельный тест для
каждого канала. В каждом случае есть две тетради, свежий focus у второй и отсутствие
явного выбора канала.

- SIP: fallback в `sessionStart.ts` переносит `personalScope.primaryNotebookId`.
- Signal: без notebook metadata берётся полный personal scope и возвращается `...base`.
- Telegram: Inbox fallback с `notebookSelectedExplicitly=false` передаёт решение helper-у;
  свежий веб-фокус сохраняется, старой безусловной перезаписи нет.
- WhatsApp: берётся `queryPersonalSourceScope(userId)` и возвращается `...base`.

Отдельный тест проверяет обратное правило Telegram: явный выбор внутри канала побеждает
свежий веб-фокус. Мой пакет: `5 passed, 0 failed, RC=0`.

## Что проверяет тест автора

В авторском `tests/unit/web580-telegram-focus-negative.test.ts` всего `14 passed,
0 failed, RC=0`. Он проверяет:

1. чистый Telegram precedence helper: явный выбор, свежий focus, `null` без focus и
   single-notebook fallback;
2. `queryPersonalSourceScope`: свежий focus, stale focus и отсутствие угадывания при
   двух тетрадях;
3. статически, по тексту binding: убрана безусловная Telegram-перезапись, подключён
   helper, при отсутствии выбора передаётся `undefined`, resolver сообщает explicit-флаг;
4. статически, по тексту каналов: Signal и WhatsApp сохраняют `...base`, SIP использует
   `personalScope.primaryNotebookId`.

Self-comparison вида `assert.equal(X, X)` и аналогов в этом тесте нет: равнения используют
литералы сценария (`web-focus-notebook`, `null`, `tg-notebook` и т.п.). Но статические
guards сами по себе не вызывают закрытые `buildUserSourceScope` с Prisma; поэтому мой
независимый тест добавляет поведенческую проверку чистого scope и отдельные channel guards,
но не заменяет живой DB/call сценарий.

## Регрессия того, что уже устояло

Мой regression-тест `tests/acceptance/3890-web580-regression.test.ts` отдельно проверил:

- истёкший focus — отвергнут;
- две тетради без focus — `null`, не первая тетрадь;
- будущая и плохая дата — отвергнуты;
- удалённая/вне scope тетрадь — отвергнута;
- PUT сначала проверяет auth, затем принадлежность notebook тому же пользователю;
  userId передаётся writer-у, GET в route нет.

Результат: `5 passed, 0 failed, RC=0`.

Профильные наборы на новой ревизии также зелёные:

- focus fix + personal source scope: `25 passed, 0 failed, RC=0`;
- Telegram channel tests: `13 passed, 0 failed, RC=0`;
- Signal channel tests: `24 passed, 0 failed, RC=0`;
- WhatsApp channel tests: `19 passed, 0 failed, RC=0`;
- авторский WEB-580 Telegram test: `14 passed, 0 failed, RC=0`.

## ГРАНИЦА доказательства

Доказан кодовый контракт: resolver использует свежий веб-фокус, Telegram не затирает его
при отсутствии явного выбора, явный Telegram выбор сильнее, а устоявшиеся правила не
сломались. Не доказано, что реальный сервер после миграции действительно записывает и
читает строку focus, и не доказана живая речь ассистента.

Для полного живого подтверждения в разрешённой тестовой среде нужны шаги:

1. применить миграцию только в тестовой БД;
2. войти пользователем A, имеющим две тетради, и PUT-нуть focus на вторую;
3. начать SIP, Signal, Telegram и WhatsApp звонок, каждый раз не выбирая тетрадь в
   канале, и проверить `VoiceSession.primaryNotebookId`/scope на второй тетради;
4. в Telegram явно выбрать первую тетрадь и проверить, что она перебила веб-фокус;
5. повторить с истёкшим, будущим, плохим и удалённым focus, а также с двумя тетрадями без
   focus — ожидается отказ от угадывания;
6. проверить PUT без сессии и PUT с чужой тетрадью: ожидаются `401` и `404`.

Эти шаги не выполнялись из-за заданных запретов.

## KNOWN ISSUES

- Буквальный тест 3881 дефектен для fixed-кода: старый абсолютный worktree path и
  assertion, требующий старый bug-pattern. Его результат не подменялся; вместо этого
  сохранены receipts старого запуска и исправленного независимого retest.
- Проверки binding для четырёх каналов без живой БД — кодовые/структурные guards плюс
  чистый scope resolver, не живой inbound call.
- Миграция и реальная запись/чтение `UserNotebookFocus` не проверялись.
- Живые SIP/Signal/Telegram/WhatsApp звонки и поведение модели при `null` не проверялись.
- Полный `tsc`, `next build`, release build, deployment, production и платные провайдеры
  не использовались.
- Для тестов использован существующий локальный `node_modules` через symlink в новом
  worktree; зависимости не устанавливались.

## Артефакты

- acceptance branch commit: `5e4f372618904fcedf1d4015bf160f41f7908217`;
- bundle создан от линии `refs/waves/l115n` через `--not refs/waves/l115n`;
- SHA-256 bundle: `7fc3bd35cc7b1ddbbf805359fb2bd54fc1b5f0043472402c3740296c1eb066f6`;
- evidence directory содержит receipts, исходники моих тестов и `SHA256SUMS`.

## KNOWN ISSUES

- `timeout` и сборки не используются; разрешены только запуски `node --test`.
- В новом worktree подключён существующий локальный `node_modules`; ничего не ставлю.
2026-09-15T15:47:20.190Z · coordinator
[15.09 15:47Z координатор] M1/DeepSeek 4064 read-only reconciliation: это landing gap, не post-QA gap; SHA evidence 5/5. В l115o нет `telephony/runtime/notebookFocus.ts`, миграции `20260914170000_web580_notebook_focus`, модели `UserNotebookFocus` и profile location в telephony path; предыдущие wave-объекты отсутствуют. Клиентский `activeNotebookId` недоступен SIP. Нужна новая source-починка server-side TTL focus store + migration + profile location on telephony path, затем независимая приёмка, посадка и live voice receipt. Карточка возвращается в in_progress.
2026-09-15T16:41:37.220Z · coordinator
[15.09 16:41Z координатор] [15.09 16:40Z координатор] Neo/Terra 4086 дала INCOMPLETE, а не ложный GO. Candidate `63ae94fb2ac2d6132c06fbdf94e12b357a263f7f`: integrity/ancestry/migration/Prisma/lint зелёные, focused/cross-channel behavior 40/40, independent ACL-deny control PASS. Единственный разрыв — scoped TypeScript: supplied log пуст; со stale Prisma client были ожидаемые missing-model ошибки, после candidate generate TypeScript 5.9.3 упал `RangeError: Maximum call stack size exceeded`. Это пока не доказанный source-дефект и не зелёный typecheck. M4/Terra 4090 запущена только для причинной и воспроизводимой TypeScript-расписки; статус остаётся in_progress.
2026-09-15T16:52:39.921Z · coordinator
[15.09 16:52Z координатор] [15.09 16:47Z coordinator] M4/Terra 4090 closes the sole TypeScript limitation from independent acceptance 4086. Immutable candidate 63ae94fb2ac2d6132c06fbdf94e12b357a263f7f has BASE as direct parent; all author and acceptance checksums verify. After isolated Prisma 5.22 generation, original scoped tsc and an independent harness covering all eight changed production TypeScript files both exit 0 under 60 seconds on the ordinary stack. The prior stale-client errors and compiler stack overflow do not reproduce; no candidate type defect remains. Combined with 4086 behavior 40/40 and security/schema review, candidate is accepted as source input, not yet production. Ticket remains in_progress until integration, build, landing and live post-QA.
2026-09-22T18:22:00.529Z · triage-m1
РЕШЕНИЕ=in_progress
ОСНОВАНИЕ=Комментарий 5414 от 2026-09-15T16:52:39Z: source candidate принят, но integration/build/landing/live post-QA ещё не выполнены.
ЧТО НУЖНО=Интегрировать candidate, собрать и посадить его, затем получить live post-QA receipt по focus-поведению.
triage-m1 4612
2026-09-23T12:11:33.395Z · triage-neo
РЕШЕНИЕ=parked
ОСНОВАНИЕ=2026-09-15, волна 4090 INCOMPLETE; candidate 63ae94fb принят как source, посадки нет; открыты integration/build/landing и live voice receipt.
ЧТО НУЖНО=первый шаг: интегрировать candidate, собрать и посадить, затем получить live post-QA по focus-поведению; ПРЕДЛОЖЕНИЕ=split
triage-neo 4704
2026-09-27T12:09:15.346Z · coordinator
[27.09 12:09Z координатор] [27.09 12:09Z координатор] Взят в работу по слову владельца 11:52. Волна 5106 (Neo, Luna high): перенос принятого кандидата 63ae94fb на текущую линию r14 (1bf2f869), тесты на свой/чужой фокус и адрес профиля, scoped tsc. Без стенда и сети. Дальше: вливание в следующую сборку и живая пост-QA голосом на стенде.
2026-09-27T12:56:34.426Z · coordinator
[27.09 12:56Z координатор] Волна 5106 (Neo, Luna high) GO. Кандидат 63ae94fb уже входит в r14 1bf2f869 (предок), повторный cherry-pick не нужен. Добавлено недостающее: фокус на чужую тетрадь отклоняется даже при разрешающем ACL-колбэке, адрес из профиля (город/координаты) доходит до location_search, неадресные поля — нет. Тесты WEB-580 24/24, adversarial 5 guard-ов ловятся, tsc новых ошибок 0. Патч сохранён. Ждёт включения в следующую сборку (r16) и живой проверки голосом на стенде.
2026-09-27T14:33:42.717Z · coordinator
[27.09 14:33Z координатор] 5110 (27.09 14:09Z): интеграция r16 = r15 + WEB-580 + WEB-057 без конфликтов, тесты GREEN (580 15/15, 057 7/7, guard миграции, 651 30/30, новых диагностик 0). Сборка NO-GO: миграция WEB-057 не зарегистрирована в compatibility manifest, а бриф координатора ошибочно запретил временную БД для fresh-db. Исправлено в 5111 (регистрация миграции + цепочка на временной изолированной БД).
2026-09-27T16:11:41.738Z · coordinator
[27.09 16:11Z координатор] [27.09 16:15Z координатор] Волна 5115: r16 79bbf331 выкачен на стенд 4400-r2, VERDICT=GO (evidence SHA256SUMS OK; ready 5/5, ready/paid 5/5, реальный логин, realtime 401 без сессии, воркер стартует, миграция WEB-057 применена к стендовой БД после проверенного pg_dump, nginx-патч proxy_max_temp_file_size 0). Откат: sudo timeout 480s python3 /home/ubuntu/waves/5115-work/rollback-release.py. Пост-QA:
WEB-580 PASS по серверному пути: своя тетрадь PUT/GET 200, чужая PUT 404, focus resolver PASS, регрессия 15/15. Живой голосовой звонок не делался (реальных AI-вызовов нет) — проверить после посадки. evidence postqa-web580.json.
Воркер
не проверен wq-acc-maps-language M4 движение в панели: неизвестно
Подключиться и смотреть/перехватить руками. Колесо мыши листает; клавишами — Ctrl-b затем [, выход из прокрутки q. Отсоединиться — Ctrl-b затем d:
Прочитать историю панели без подключения — листается и ищется (/ поиск, q выход), воркеру не помешает:
Обновлён
2026-09-27T12:56:35.634Z