WEB board

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

P1 [голос/ядро]: под нагрузкой POST /api/realtime/session-end отдаёт 503 из-за самостолкновения аренды active-passive (100% отказов группы realtime на 10 VU)

Закрыт P1 · важно ведёт: —
Суть
## 1. Суть
При одновременной работе нескольких пользователей запросы группы realtime падают с 503 не из-за нехватки ресурсов, а из-за нашей собственной аренды active-passive: процесс сталкивается сам с собой. `handleBrowserSessionEndCore` обёрнут в `withActivePassiveLease`, при отказе бросает, и `activePassiveLeaseErrorResponse` превращает это в 503.

## 2. ГДЕ МЫ СЕЙЧАС (13.09)
Найдено нагрузочной линией Enterprise-2 на ступени 10 одновременных пользователей: группа realtime даёт 100% отказов при `cap_business_non_http_failures{group:realtime}=0%` — то есть это ответы сервера, а не обрывы. Повторилось на двух независимых прогонах (rung10h: 10 из 10; rung10i: 40 из 40). Корневая причина установлена волной 3730, фикс НЕ делался — он в общем ядре, а не в стенде.

## 3. МЕХАНИКА / УСТРОЙСТВО
Аренда active-passive задумана как «одна боевая копия рантайма на область» (`ACTIVE_PASSIVE_LEASE_SCOPE = 'web370-runtime'`). Но она общая на весь процесс: когда несколько запросов одного и того же владельца приходят одновременно, гонка того же владельца трактуется как отказ чужаку, и пользователь получает 503. Это ограничение ёмкости, встроенное в нашу защиту: чем больше одновременных пользователей, тем чаще столкновение.

## 4. ХРОНОЛОГИЯ
- 13.09 rung10h — realtime 10/10 ошибок, найдено координатором.
- 13.09 волна 3730 — корень назван цитатами из кода и логов; фикс не делался по условиям волны.
- 13.09 rung10i — повторилось в большем масштабе: 40 из 40.

## 5. КАРТА ДОКУМЕНТОВ
- `src/app/api/realtime/session-end/route.ts:284-309` — обёртка `withActivePassiveLease`.
- `src/lib/telephony/runtime/request.ts` — `activePassiveLeaseErrorResponse` → 503.
- `src/lib/activePassiveLease.ts` — сама аренда.
- Доказательства: `3730STANDINDEXER-evidence/04-realtime-root-cause.txt`, `3714LADDERR3-evidence/k6-rung10h-stdout.log:976-1037` (A2).

## 6. ОСТАТОК
1. Решить, каким должен быть правильный ответ: гонка ТОГО ЖЕ владельца — это ожидание и повтор, а не отказ; либо realtime-маршрутам нужна своя область аренды, как её уже получили индексация и извлечение.
2. Тест, воспроизводящий столкновение двух одновременных session-end одного владельца.
3. Негатив: настоящий чужой держатель аренды по-прежнему должен получать отказ — защита обязана остаться.

## 7. КРИТЕРИЙ ЗАКРЫТИЯ
На ступени 10 VU группа realtime даёт 0 ошибок, при этом тест доказывает, что чужая копия рантайма всё ещё отбивается.

## Связь
Enterprise-2 WEB-626 / CAP-06 WEB-633. Смежное: WEB-370 (аренда как часть переезда звонкового стека).
Лента
2026-09-13T18:48:13.057Z · coordinator
[13.09 18:48Z координатор] ПОПРАВКА К ТЕЛУ ТИКЕТА по итогам волны 3735 — механизм я описал неточно, исправляю честно.

**Чего НЕ подтвердилось.** Формулировка «~192 последовательных вызова эмбеддингов внутри ОДНОЙ интерактивной транзакции Prisma с потолком 5000 мс» **не воспроизвелась**. Волна прошла по всему дереву (`grep '$transaction'`, 81 файл) и по полному пути пайплайна: `process-source-indexing-queue.ts` → `processSourceIndexingQueue()`/`indexSourceNow()` → `processDocument()`. Запись чанков (`vectorStore.addDocuments` → `writeSourceBoundChunks`, `src/lib/rag/sourceChunkWriter.ts`) действительно идёт через `$transaction`, но КОРОТКИМИ транзакциями, а не одной длинной с сетевыми вызовами внутри. Я взял эту формулировку из наблюдения волны 3730 и не проверил её сам — это моя ошибка, а не её.

**Что подтвердилось как настоящий, независимый дефект.** Потеря аренды на ОДНОМ источнике убивает ВЕСЬ процесс воркера. Место: `src/lib/ingest/sourceIndexingQueue.ts:1279` — единый `catch` в `runSourceIndexing()`; и в `scripts/process-source-indexing-queue.ts` единственный `withActivePassiveLease()` оборачивал ВЕСЬ цикл `do/while`, поэтому любая икота аренды валила цикл целиком, а не одну итерацию.

**Исправлено двумя уровнями** (оба нужны, проверено эмпирически): `processCandidateSurvivingLeaseHiccups()` — одна попытка обработки источника переживает `ActivePassiveLeaseError`; и снятие общей обёртки аренды со всего цикла воркера. Негативный тест `web658WorkerSurvivesLeaseHiccup.test.ts` доказывает, что НАСТОЯЩИЙ чужой держатель аренды по-прежнему останавливает воркер — защита «одна боевая копия рантайма» жива.

**Сопутствующий факт, который объясняет частоту отказов** (находка волны 3736): TTL аренды по умолчанию — **15 секунд** (`ACTIVE_PASSIVE_LEASE_DEFAULT_TTL_MS`, `src/lib/activePassiveLease.ts:11`) при продлении раз в 5 секунд. Это запас в три такта; на загруженном боксе таймер продления не успевает, и воркер честно кончает себя по собственной защите. Оба воркера стенда — и извлечения, и индексации — страдали этим сегодня.

**Практическое следствие, которое стоило нам двух ступеней лестницы:** супервизор есть только у индексирующего воркера. Экстрактор умер в 16:44Z, никто его не поднял, `ExtractionJob` накопил 26 задач в очереди, `/api/ingest/status` никогда не доходил до `done`, и засев новых пользователей падал по таймауту 60 секунд — из-за этого не стартовали ступени 25 и 50. Координатор поднял экстрактор руками в 19:0xZ: очередь ушла с 26 до 7 за 25 секунд, засев пошёл за 11 секунд на пользователя. Присмотр за экстрактором и перенос живучести 3735 в дерево стенда отданы волне 3740.

Зеркало: `refs/waves/3735` = `812654e10`. Фикс WEB-659 (самостолкновение аренды на session-end) в том же коммите, помечен FIXED.
2026-09-13T23:52:07.445Z · coordinator
[13.09 23:52Z координатор] ПОСТ-QA ЛИНИИ l115l (волна 3755, VERDICT=GO). Пять тикетов последней посадки, все пять — `PROVEN-OFFLINE`, с прогонами на ДВУХ коммитах: до фикса и на коммите, который сейчас в проде (`1628b697d`).

| Тикет | Тест | Коммит | Результат |
|---|---|---|---|
| WEB-188 | `videoRunPolling.test.ts` | `4a6e99ed4` (до фикса) | **16/17, одно НАСТОЯЩЕЕ падение проверки** |
| WEB-188 | тот же | `1628b697d` (прод) | 17/17 |
| WEB-195 | `web195TranscriptOnlyPdfQuality.test.ts` | `4a6e99ed4` (до фикса) | **4/6, два НАСТОЯЩИХ падения** |
| WEB-195 | тот же | `1628b697d` (прод) | 6/6 |
| WEB-658 | `source-indexing-worker-lease-hiccup.test.ts` | `1628b697d` | 5/5 |
| WEB-658 | `web658WorkerSurvivesLeaseHiccup.test.ts` | `1628b697d` | 6/6, включая красную форму и 3 негатива |
| WEB-659 | `activePassiveLease.web659.real-db.test.ts` (настоящая Postgres) | `1628b697d` | 3/3 — красный, зелёный, негатив |
| WEB-659 | `web659SharedOwnerAdmission.test.ts` | `1628b697d` | 1/1 |

Ключевое отличие от прошлого раза: у WEB-188 и WEB-195 красное до фикса — это **настоящие падения проверок**, а не ошибка сборки и не отсутствие модуля. Именно на этом отличии независимая приёмка 3743 в прошлый раз вернула мне три закрытия, и теперь оно соблюдено.

**Закрываю: WEB-188 и WEB-195** — доказательство полное, пары настоящие, оба дефекта клиентские/отчётные и живой пробы сверх этого не требуют.

**НЕ закрываю пока: WEB-658 и WEB-659.** Их офлайн-доказательства сильные (красная форма плюс негативы, для WEB-659 — на настоящей Postgres), но у обоих есть живая проверка, которая вот-вот будет: стенд ёмкости сейчас пересобирается под коммит прода, и группа `realtime` там обязана позеленеть. Если позеленеет — закрою WEB-659 живым наблюдением, а не только тестами. Если нет — значит причина не та, что мы думаем, и это важнее любого зелёного теста.

**WEB-593 остаётся открытым по существу:** доказан ИНСТРУМЕНТ повторной загрузки, а не восстановление документов. Тикет закроется, когда 185 застрявших документов реально вернутся в индекс.
2026-09-14T00:15:57.192Z · coordinator
[14.09 00:15Z координатор] ПЕРЕЛОМ. Стенд пересобран из коммита прода (`1628b697d`) — и картина изменилась полностью.

ПРОГОН 25 VU, 3 минуты, `upload_index` исключён явно (`rung25g`):
**1534 действия предложено, 1509 выполнено = 98.37%.** Общая p50 523 мс, p95 3089 мс. Пул БД 0 предупреждений, блокировок входа 0, память 2 ГБ из 23.

| группа | offered | completed | errors | p50 | p95 |
|---|---|---|---|---|---|
| session | 150 | 150 | **0** | 199 | 461 |
| list | 425 | 425 | **0** | 518 | 1346 |
| open | 402 | 402 | **0** | 2275 | 3935 |
| retrieval | 32 | 32 | **0** | 364 | 2531 |
| status | 350 | 350 | **0** | 295 | 552 |
| **realtime** | **150** | **150** | **0** | 1266 | 2664 |
| save_readback | 25 | 0 | **25** | 229 | 525 |

**ГОЛОСОВАЯ КОМНАТА ПОЗЕЛЕНЕЛА: 150 из 150, ноль ошибок.** До пересборки она была 100% красной на каждой ступени — 10, 25, 50, 75. Это живое подтверждение фикса **WEB-659** (`sharedOwner: true` на `session-end`), а не только зелёный тест.

И это доказывает главное обвинение, которое я выдвинул стенду: **мы сутки мерили код, которого в проде нет.** Разница видна в масштабе — 1534 предложенных действия против 116 на той же ступени 25 до пересборки. Прежние числа были испорчены не нагрузкой, а устаревшей сборкой и застревающей группой.

НОВАЯ КРАСНАЯ ГРУППА, и я не выдаю её за старую: **save_readback 25/0/25**. До пересборки она была зелёной на всех ступенях. То есть пересборка на код прода сломала сохранение с обратным чтением — либо в коде линии l115l есть дефект, который стенд раньше не видел, либо изменился контракт, которого ждёт харнесс (например из-за трёх новых миграций или чанкинга WEB-651). Это следующая задача, и она важнее дальнейших ступеней: если сохранение документа на проде действительно сломано, это дороже любого числа ёмкости.

Что делаю: разбираю причину отказов `save_readback` прежде, чем продолжать лестницу.
2026-09-14T04:14:26.721Z · coordinator
[14.09 04:14Z координатор] **WEB-658 и WEB-659 ПОДТВЕРЖДЕНЫ ЖИВЬЁМ НА СТЕНДЕ. Проба 4/4, включая негативный контроль.**

Пост-QA линии l115l (волна 3755) честно оставил эти два тикета открытыми: «не закрываю до живого подтверждения на стенде». Механику активной-пассивной аренды нельзя доказать офлайн — нужны две копии, реально спорящие за одну аренду.

Волна **3789** написала пробу (живые прогоны делает координатор — правило после девяти волн, умерших на «запустил в фоне»), я её прогнал против стенда 3610 (`1628b697`, тот же коммит, что на проде).

```
PROBE RESULT: 4/4 scenarios OK
  OK: a-two-copies-simultaneous
  OK: b-passive-takeover-on-expiry
  OK: c-sharedowner-admits-same-owner-workers
  OK: d-negative-control-must-go-red
```

**Что именно доказано, по маякам `ACTIVE_PASSIVE_LEASE`:**

- **(a) две копии стартуют одновременно — активной становится ровно одна.** Вторая получает `type=denied reason=active_holder` с именем держателя.
- **(b) перехват при истечении срока — в границах.** Пассивная копия забрала аренду через **147 мс после `expiresAt`**, при объявленных границах «не раньше `expiresAt−20 мс` и не позже `expiresAt + опрос(150) + запас(500) = 650 мс`». Переход виден как `type=owner_changed reason=slot_handoff`. То есть **не перехватила раньше срока** (что означало бы двух активных сразу) и **не опоздала**.
- **(c) это и есть фикс WEB-659.** При `sharedOwner` все три воркера одного владельца допущены — `admitted=["workerA","workerB","workerC"]`, — а посторонний владелец в тот же момент отвергнут как `active_holder`. Ровно то поведение, ради которого фикс и делался: завершение сессии не должно упираться в собственную аренду, но и не должно открывать дверь чужому.
- **(d) негативный контроль краснеет как надо.** При `ACTIVE_PASSIVE_LEASE_REQUIRED=0` и при подменённом хранилище, которое всегда допускает, проверка исключительности **корректно провалилась** (`fulfilled=2`). Без этого пункта проба ничего бы не доказывала: зелёное на сломанной аренде означало бы, что она не смотрит туда, куда обещает.

Проба идемпотентна, убирает за собой, использует собственный префикс области (`PROBE_SCOPE_PREFIX`), **порт 3610 не занимает** — ей нужен только `DATABASE_URL` стенда. Прогон занял секунды.

**Команда для повтора** (из готового дерева волны, пересобирать не нужно):
```
cd /home/ubuntu/waves/wt-3789-lease && \
DATABASE_URL="$(grep -m1 '^DATABASE_URL=' /home/ubuntu/waves/.3610-stand/app.env | cut -d= -f2-)" \
PROBE_SCOPE_PREFIX="lease-probe-3610-$(date +%s)" \
timeout 60 node node_modules/tsx/dist/cli.mjs scripts/lease-live-probe.ts
```

**Чего проба не доказывает** (названо волной честно): это две копии **одной машины**, а не настоящая пара узлов. Свойства, зависящие от сетевого разделения между узлами, остаются непроверенными — для них нужен настоящий второй узел.

**Статусы WEB-658 и WEB-659 двигаю сейчас, в момент вердикта.** Сдача: `refs/waves/3789/wave/3789-lease-live-probe`.
2026-09-14T12:07:48.336Z · coordinator
[14.09 12:07Z координатор] WEB-659 — завершение голосового разговора отдаёт ошибку под нагрузкой
Обновление от 2026-09-14. Затронутая посадка: l115l (1628b697d9b20a4446b9bdba20ada0a6c2820d11, 13.09 20:38:15Z).
Карточка написана для человека, который открывает её впервые и не имеет ни журнала смены, ни переписки.

1. ЧТО БЫЛО СЛОМАНО

Пользователь заканчивает голосовой разговор — и получает ошибку. Сессия не закрывается. Когда пользователей мало, это почти не встречается; при одновременной работе нескольких — воспроизводится постоянно.

2. КАК НАШЛИ И ПОЧЕМУ НЕ ПОЙМАЛИ РАНЬШЕ

Нашли на нагрузочном стенде: на прогоне ступени 10 пользователей группа голосового режима давала 10 ошибок из 10 попыток.
Важно, КАК было доказано, что это настоящие ответы сервера, а не обрывы соединения: счётчик небизнесовых, то есть транспортных, отказов по этой группе был равен нулю. Значит сервер ОТВЕЧАЛ, и отвечал отказом.
Почему не поймали раньше: дефект — самостолкновение при ОДНОВРЕМЕННЫХ запросах ОДНОГО владельца. В обычной работе один человек редко завершает две сессии одновременно; нагрузка сделала это нормой.

3. ЭВОЛЮЦИЯ, ВКЛЮЧАЯ ТУПИКИ

3.1. ПРИЧИНА. Аренда «активный-пассивный» — механизм, гарантирующий, что боевую работу делает одна копия рантайма, — была общей на процесс. Одновременные запросы ОДНОГО И ТОГО ЖЕ владельца трактовались как ЧУЖАК, и запрос завершения сессии упирался в собственную же аренду.

3.2. ТУПИК, СТОИВШИЙ СУТОК — группа оставалась красной ПОСЛЕ починки, и это списывалось на продукт.
После посадки фикса в l115l группа голосового режима на стенде продолжала быть красной НА КАЖДОЙ ступени — 10, 25, 50, 75. Это выглядело как «фикс не помог» или «есть второй дефект».
Настоящая причина: ПРИЛОЖЕНИЕ СТЕНДА РАБОТАЛО ИЗ СБОРКИ ДРУГОГО КОММИТА. Стенд был собран из коммита `e1f6cb526`, а прод за сутки ушёл на две линии вперёд, до `1628b697`. Фикс уже был в проде и до сборки стенда просто не доехал.
После пересборки стенда на коммит прода группа стала 150/150 без единой ошибки. Формулировка из журнала: «сутки мы мерили код, которого в проде нет».
СЛЕДУЮЩЕМУ АГЕНТУ: оба утверждения — «группа красная, это дефект» и «та же группа красная, это артефакт» — истинны, но для разных сборок стенда. Прежде чем верить красному, проверить, ИЗ КАКОГО КОММИТА собрано приложение стенда.

3.3. ОСТАТОК, КОТОРЫЙ НЕ УДАЛОСЬ ОБЪЯСНИТЬ. На ступени 75 пользователей в этой группе осталась ОДНА ошибка, и причину установить НЕ УДАЛОСЬ: харнесс не сохранял ни код состояния, ни сообщение. Это дефект прибора, а не продукта; сохранение причины отказа было добавлено позже отдельной правкой (там же нашлось, что сломанный старт сессии рапортовал успешный код состояния).

4. ЧТО СДЕЛАЛИ В ИТОГЕ

Волна 3735, сдача `812654e10`, посажена в линии l115l (`1628b697d9b20a4446b9bdba20ada0a6c2820d11`) 13.09 20:38:15Z.
Изменение: маршрут завершения сессии помечен признаком общего владельца (`sharedOwner: true`), то есть несколько рабочих ОДНОГО владельца допускаются одновременно, а посторонний владелец — нет.

5. ЧЕМ ДОКАЗАНО И ГРАНИЦЫ ЗАЯВЛЕНИЯ

ДОКАЗАНО ОФЛАЙН (независимая приёмка 3741): признак общего владельца НЕ является обходом для чужого владельца — это доказано ОТДЕЛЬНЫМ route-тестом приёмщика, а не тестом автора. Опасного окна «две копии взяли один источник» не найдено.

ДОКАЗАНО ЖИВЬЁМ (волна 3789 написала пробу, координатор прогнал её против стенда на коммите `1628b697`, том же, что на проде): 4 сценария из 4 OK. Сценарий (в) — это и есть фикс WEB-659: при признаке общего владельца допущены ВСЕ ТРИ воркера одного владельца, а посторонний владелец В ТОТ ЖЕ МОМЕНТ отвергнут. Завершение сессии больше не упирается в собственную аренду и при этом не открывает дверь чужому.
Отрицательный контроль (сценарий г) краснеет как надо: при выключенном требовании аренды и подменённом всегда-допускающем хранилище проверка исключительности корректно провалилась.

ДОКАЗАНО НАГРУЗКОЙ: после пересборки стенда на коммит прода — 150/150 без ошибок в этой группе; прогон 25 пользователей с исключением группы загрузки документов дал 1534 предложенных действия и 1509 выполненных = 98.37%.
Статус переведён в `done` в момент вердикта.

ГРАНИЦЫ — читать обязательно:
  Проба проверяет ДВЕ КОПИИ ОДНОЙ МАШИНЫ. Поведение при СЕТЕВОМ РАЗДЕЛЕНИИ УЗЛОВ не проверено. Ограничение назвала сама волна.
  «Живьём» здесь означает «на стенде, собранном из коммита прода», а не «на проде». Голосовой разговор реального человека на боевом сервере под нагрузкой не наблюдался.
  Одна необъяснённая ошибка на верхней ступени осталась без разбора — прибор не сохранил причину.

6. ЧТО ОСТАЛОСЬ ОТКРЫТЫМ

  Поведение при сетевом разделении узлов.
  Единственная неразобранная ошибка на верхней ступени нагрузки (нужен повтор уже с сохранением причины отказа).

7. TROUBLESHOOTER — ЕСЛИ ДЕФЕКТ ПОВТОРИТСЯ

Шаг 1. Отличить отказ сервера от обрыва связи: смотреть счётчик небизнесовых (транспортных) отказов по группе голосового режима. Ноль означает, что сервер ОТВЕЧАЛ — это его отказ, а не сеть.

Шаг 2. ЕСЛИ КРАСНОЕ ПОЛУЧЕНО НА СТЕНДЕ — сначала проверить, из какого коммита собрано приложение стенда, и сверить с коммитом прода. Это уже один раз стоило суток: красное описывало код, которого в проде нет.

Шаг 3. Прогнать живую пробу аренды: `refs/waves/3789/wave/3789-lease-live-probe` = `28a8fa116c`. Нужен только `DATABASE_URL` стенда, порт 3610 не занимается, прогон занимает секунды. Смотреть сценарий (в): три воркера одного владельца допущены, посторонний отвергнут.

Шаг 4. Если проба 4/4 и стенд на коммите прода, а ошибка остаётся — искать не в аренде. В журнале искать сохранённые код состояния и сообщение по группе голосового режима; если их нет, обновить харнесс, иначе причину назвать будет нечем (так уже было).
2026-09-15T01:25:28.993Z · coordinator
[15.09 01:25Z координатор] ## 15.09 01:40Z — ВОШЁЛ В ПОСАДКУ l115o. Требуется пост-QA на проде.

Прод работает на `l115o-68e25d8d` (коммит `68e25d8df5ae8263c5ac5466353631f57a17cfcc`, артефакт `cd2773f65d66b09ec03d39b553041d2814ce09413e72d72b07bc9de89df536c8`) с 00:56Z 15.09. Посадка проверена: `ready=true`, коммит совпал, браузерная проба открыла документ, 0 новых ошибок.

**Работу по этому тикету принесли волны:** 3741.

**Что это значит для этого тикета.** Работа по нему пролежала принятой, но НЕ посаженной — в некоторых случаях неделями. Теперь она на проде. Приёмка волной доказывала, что код правильный в дереве волны; она НЕ доказывала, что фича работает на живом проде. Это разные вещи, и мы на этом уже обжигались.

**Поэтому статус — `review`, а не `done`.** Закрыть тикет имеет право только пост-QA, который проверит поведение на работающем проде и приложит доказательство. До тех пор «сделано» — это заявка.

### Для нулевого агента (тот, кто будет делать пост-QA)

Ты приходишь на этот тикет без нашей истории. Что надо знать:

1. **Проверяй на проде, не на стенде.** Стенд сейчас вообще не поднят — у него не было своего артефакта, он запускался из каталога боевого релиза и заблокировал проверку посадки; пересобирается волной 3948 (WEB-662).
2. **`/api/health` ничего не доказывает** — он проходит и на пустом приложении. Нужно деловое действие: аутентифицированная сессия делает то, про что этот тикет.
3. **Отсутствие ошибок в логе — не доказательство жизни.** Нужен положительный результат, а не тишина.
4. **Числа в обосновании закрытия получай командой в момент закрытия.** Я однажды закрыла тикет числом `4255`, взятым из `pg_stat_user_tables.n_live_tup` — это ОЦЕНКА. Настоящее `count(*)` дало `107 817`. Разница в 25 раз.
5. **Не верь имени волны в теме коммита** — проверяй наличие содержимого (`git cherry`), а не упоминание номера.
6. Запускатель тестов в проекте — `node --test`. Vitest нет.

2026-09-15T12:26:12.491Z · coordinator
[15.09 12:26Z координатор] Post-QA 4001: REVIEW, не DONE. На exact l115o текущая цепочка `session-end → withActivePassiveLease(sharedOwner=true)` на месте; focused route 1/1, lease suite 14/14, независимый synthetic same-owner 3/3 и foreign-owner deny 1/1 прошли. Evidence 24/24 SHA256 проверены координатором.

Живого подтверждения текущей линии нет: процесс на стенде 3610 запущен до l115o из уже удалённого каталога, в его журнале нет session-end/lease событий, а raw «150/150» в доступных материалах не найден. Нули трактуются как NOT_OBSERVED, не PASS. Для закрытия нужен новый проверяемый live receipt именно executable l115o: same-owner success и foreign-owner denial. До него карточка остаётся review.
2026-09-15T14:10:46.306Z · coordinator
[15.09 14:10Z координатор] [15.09 14:09Z координатор] Post-QA 4032 завершён: VERDICT=DONE, evidence 7/7 SHA256. Текущий POST /api/realtime/session-end на exact l115o вызван с изолированными synthetic identities: same-owner получил closed=true и ровно одну terminal close mutation; foreign-owner получил closed=false ownership_mismatch и ноль дополнительных terminal effects. Lease sharedOwner сохраняет same-owner sharing и отказывает другой copy. Живые данные/DB/provider/deploy не затронуты. WEB-659 закрыт.
Воркер
не привязан — привязать: curl -X POST https://bugs.wool2.online/api/web/assign -H 'content-type: application/json' \ -d '{"issueId":"WEB-659","session":"<имя tmux-сессии>","host":"m4"}'
Обновлён
2026-09-15T14:10:46.783Z