WEB-635 · Задача · — · web
Enterprise-2 / CAP-08: Один и два процесса, CPU/RAM и baseline
Закрыт
P2
ведёт: —
эпик: WEB-626
Суть
ENTERPRISE2-R1:CAP-08
Правила обогащения: WEB-449. Эпик: Enterprise-2.
Цель и сдача: T1/T2/T3, три сопоставимых повтора AB/BA, same-host goodput/latency/resource matrix.
Работа: Одинаковые данные/artifact/total pool/admission budgets; warm/cold отдельно. cgroups только isolated app. Нагрузочный день начинается после общего ready gate. Не запускать одновременно builds/seed/pgbench на SUT.
Зависимости: CAP-02, CAP-03, CAP-04, CAP-05, CAP-06, CAP-07.
Исполнение: measurement.
КАРТА ДОКУМЕНТОВ
Intel: /Users/annakorin/nc-ops-scripts/enterprise2-20260910/ENTERPRISE-2.md, tickets.json, board-created.json.
Вход: /Users/annakorin/Downloads/NC-CAPACITY-SCALE-TEST-BRIEF-R1.md.
Связанные: WEB-093/420/449/489; незакрытые Security и WEB-593/467 учитывать при freeze.
ЭВОЛЮЦИЯ
10.09.2026: создана задача из запроса владельца; capacity ещё не измерена. Author GO, полученный bundle, independent acceptance и runtime proof фиксируются раздельно.
DISPATCH-20260910-3404
На 2026-09-10T21:38:23.260126+00:00: active; M4, волна 3404 cap-baseline-plan. Exact BASE 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4. Бриф прошёл brief-guard; входы SHA256 и наличие BASE проверены до постановки. Входы /Users/milamarty/waves/inputs/3404-cap-baseline-plan. Отчёт ожидается /Users/milamarty/waves/3404CAPBASELINEPLAN-REPORT.md. Живой процесс модели подтверждён; возраст журнала 3 с. Source/подготовка и независимая проверка — в объёме брифа; это не посадка, не live/capacity GO и не окончательное закрытие security.
REFILL-20260910-2155-3420
Снимок 2026-09-10T21:54:52.066911+00:00. Волна 3420 / acc-cap-baseline-plan на A2: active. Оператор Codex, модель gpt-5.6-luna. Живой процесс подтверждён; возраст журнала 1 с. Входы /home/ubuntu/waves/inputs/3420-acc-cap-baseline-plan; SHA256 и exact BASE cab76c2e5eb2928acb33ccd124e9d557834eef47 проверены при доставке. Новых посадок и измерений capacity нет; source QA не означает окончательное закрытие Security.
SLOTS12-BATCH3442-3461-WEB-635
2026-09-10T22:50:05.652264+00:00 Лимиты A1/A2 подняты до12; действовавшие worker panes сохранены. Волна 3447 a1nc active acc-baseline-hardening-final; BASE 9b2503c9693b1c9ea65b57576da8a9034500d42a; model gpt-5.6-luna
Брифы прошли guard; входные report/history/raw/bundle переданы и SHA проверены до очереди. Source/QA scope, посадки и measured capacity не заявляются.
## HANDOFF-20260912T1721:WEB-635 — точка входа для нового агента
Правила обогащения: [[WEB-449]]. Наблюдение: 12.09.2026 18:21:36 Europe/Dublin / 17:21:36 UTC. Автор: координатор. История выше сохранена; эту запись читать как текущий handoff на указанное время.
**Задача.** Enterprise-2 / CAP-08: Один и два процесса, CPU/RAM и baseline
**Что подтверждено и что остаётся.** Сводный последний результат:12/14 source/component частей; это не12/14 законченных runtime задач. r8c SAVE_READBACK_PASS: реальный Auth.js owner,2MiB документ, socket init, exact save200, authoritative readback/размер/SHA/revision, stale CAS refusal. Shared editor/k6 application T0/load/soak ещё не подтверждены. Ёмкость UNKNOWN.
**Как устроено / почему остановилось.** r8b отказал, потому что TeamMember=commenter перекрывает SharedNotebook=editor. r8c прошла owner-путём, поэтому shared access требует отдельной положительной проверки. Длительные измерения на A2 исключают сборку: обязательны одновременно /home/ubuntu/.coordinator-release-build.lock и /home/ubuntu/capacity-3516/runtime-t0.lock. На времени наблюдения compat build активна; Neo-подготовка от этого не зависит.
**Следующая операция.** После T0 выполнить сравнимые1/2 процесса при одинаковом суммарном DB/admission budget, warmup и повтор AB/BA. Собрать goodput/p50/p95/p99/variance и первый предел; cgroups подписывать resource sensitivity.
**Исполнитель и приёмка.** Исполнитель Enterprise; координатор отвечает за выделенное окно и штатный runner. 3532 на Neo — назначение, живой старт пока не подтверждён. Независимый приёмщик получает frozen manifest и raw evidence.
**Условие закрытия.** Raw per-scenario p50/p95/p99, goodput, generator headroom, первый предел и uncertainty. Cgroups обозначены resource sensitivity; functional correctness=0. При раннем stop сохранить измеренный lower bound. Общий итог и зависимости: [[WEB-626]].
**КАРТА ДОКУМЕНТОВ**
- Intel: /Users/annakorin/nc-ops-scripts/enterprise2-20260910/ENTERPRISE-2.md
- Intel: /Users/annakorin/nc-ops-scripts/tick-20260912-recovery/capacity-r8-coordinator/collected-r8c/collection-receipt.json
- Intel: /Users/annakorin/nc-ops-scripts/tick-20260912-recovery/3522/3522ACCCAPACITYAUTHRUNTIME-REPORT.md
- A2: /home/ubuntu/capacity-3516/; runtime-t0-r3/; собственные run-owned evidence
- Intel: /Users/annakorin/nc-ops-scripts/tick-20260912-recovery/capacity-r8-coordinator/launch-receipt.json
SHIFT-STOP-20260912:FINAL:WEB-635
Enterprise-2 / CAP-08: Один и два процесса, CPU/RAM и baseline
Срез перед остановкой 12.09.2026. По поручению владельца 18:13:55 UTC новая работа остановлена.
Enterprise-2: ранее принято 12/14 source/component частей; число не означает готовность 12/14 runtime задач. Ёмкость UNKNOWN. R3 source e1f6cb526d946af339c213ea77eb26c8d77594d2, buildId R7L9roppA5xpq7qPoQmCR.
r8c SAVE_READBACK_PASS: настоящий Auth.js owner, 2MiB документ, Socket.IO init, exact save и authoritative readback/размер/SHA/revision, stale CAS refusal; 8 файлов сверены. Собственная PG остановлена, unit inactive/MainPID0. Shared editor остаётся OPEN из-за effective TeamMember=commenter.
3532 с Нео сохранена: HEAD4182011579fad418f443fc0f40e1ec54e56b159c, bundle SHA32723bf7931419e8c7920c9cb3c03e5fa626ad62edf68d56e950024268571abe. Author scoped GO: Flight decoder, fresh server-owned CAS/save bootstrap, exact one-save/readback checks; offline9/9, syntax3. k6 execution/applicationT0/load/spike/soak NOT_RUN. Новая независимая приёмка3532 не присваивалась.
Следующий запуск требует двух locks: /home/ubuntu/.coordinator-release-build.lock и /home/ubuntu/capacity-3516/runtime-t0.lock; свежего TTL manifest, private sessions/bootstrap и отсутствия параллельной сборки/нагрузки. Реальные credentials не публиковать.
Файлы Intel: /Users/annakorin/nc-ops-scripts/enterprise-3532/delivery/3532CAPACITYK6RUNTIME-REPORT.md (полная инструкция bootstrap/generator/k6); bundle и evidence рядом. /Users/annakorin/nc-ops-scripts/tick-20260912-recovery/capacity-r8-coordinator/collected-r8c/collection-receipt.json. Исторические отчёты/отказы сохранены.
ИНДИВИДУАЛЬНЫЙ ОСТАТОК
После зелёного T0 и выделенного окна выполнить сравнение одного/двух процессов с равным общим budget, 3 повтора AB/BA; сохранённые tooling tests не являются baseline.
Следующая смена начинает только после возобновления владельцем. Исторический status сохранён; STOPPED означает отсутствие выполняемой работы, не закрытие задачи.
Чем закрывается (приёмка)
Raw per-scenario p50/p95/p99, goodput, generator headroom, первый предел и uncertainty. Cgroups обозначены resource sensitivity; functional correctness=0. При раннем stop сохранить измеренный lower bound.
Доказательства
DISPATCH-20260910-3404
На 2026-09-10T21:38:23.260126+00:00: active; M4, волна 3404 cap-baseline-plan. Exact BASE 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4. Бриф прошёл brief-guard; входы SHA256 и наличие BASE проверены до постановки. Входы /Users/milamarty/waves/inputs/3404-cap-baseline-plan. Отчёт ожидается /Users/milamarty/waves/3404CAPBASELINEPLAN-REPORT.md. Живой процесс модели подтверждён; возраст журнала 3 с. Source/подготовка и независимая проверка — в объёме брифа; это не посадка, не live/capacity GO и не окончательное закрытие security.
REFILL-20260910-2155-3420
Снимок 2026-09-10T21:54:52.066911+00:00. Волна 3420 / acc-cap-baseline-plan на A2: active. Оператор Codex, модель gpt-5.6-luna. Живой процесс подтверждён; возраст журнала 1 с. Входы /home/ubuntu/waves/inputs/3420-acc-cap-baseline-plan; SHA256 и exact BASE cab76c2e5eb2928acb33ccd124e9d557834eef47 проверены при доставке. Новых посадок и измерений capacity нет; source QA не означает окончательное закрытие Security.
SLOTS12-BATCH3442-3461-WEB-635
2026-09-10T22:50:05.652264+00:00 Лимиты A1/A2 подняты до12; действовавшие worker panes сохранены. Волна 3447 a1nc active acc-baseline-hardening-final; BASE 9b2503c9693b1c9ea65b57576da8a9034500d42a; model gpt-5.6-luna
Брифы прошли guard; входные report/history/raw/bundle переданы и SHA проверены до очереди. Source/QA scope, посадки и measured capacity не заявляются.
HANDOFF-20260912T1721:WEB-635 — автономный handoff добавлен в body: причина, механизм, подтверждённое/OPEN, следующая операция, ответственный, критерий закрытия и карта документов. Срез фактов 2026-09-12T17:21:36.454887+00:00. Исторические отказы и статусы сохранены. Полный аудит записи: Intel /Users/annakorin/nc-ops-scripts/release-l115f/handoff-tickets/.
SHIFT-STOP-20260912:FINAL:WEB-635
Enterprise-2 / CAP-08: Один и два процесса, CPU/RAM и baseline
Срез перед остановкой 12.09.2026. По поручению владельца 18:13:55 UTC новая работа остановлена.
Enterprise-2: ранее принято 12/14 source/component частей; число не означает готовность 12/14 runtime задач. Ёмкость UNKNOWN. R3 source e1f6cb526d946af339c213ea77eb26c8d77594d2, buildId R7L9roppA5xpq7qPoQmCR.
r8c SAVE_READBACK_PASS: настоящий Auth.js owner, 2MiB документ, Socket.IO init, exact save и authoritative readback/размер/SHA/revision, stale CAS refusal; 8 файлов сверены. Собственная PG остановлена, unit inactive/MainPID0. Shared editor остаётся OPEN из-за effective TeamMember=commenter.
3532 с Нео сохранена: HEAD4182011579fad418f443fc0f40e1ec54e56b159c, bundle SHA32723bf7931419e8c7920c9cb3c03e5fa626ad62edf68d56e950024268571abe. Author scoped GO: Flight decoder, fresh server-owned CAS/save bootstrap, exact one-save/readback checks; offline9/9, syntax3. k6 execution/applicationT0/load/spike/soak NOT_RUN. Новая независимая приёмка3532 не присваивалась.
Следующий запуск требует двух locks: /home/ubuntu/.coordinator-release-build.lock и /home/ubuntu/capacity-3516/runtime-t0.lock; свежего TTL manifest, private sessions/bootstrap и отсутствия параллельной сборки/нагрузки. Реальные credentials не публиковать.
Файлы Intel: /Users/annakorin/nc-ops-scripts/enterprise-3532/delivery/3532CAPACITYK6RUNTIME-REPORT.md (полная инструкция bootstrap/generator/k6); bundle и evidence рядом. /Users/annakorin/nc-ops-scripts/tick-20260912-recovery/capacity-r8-coordinator/collected-r8c/collection-receipt.json. Исторические отчёты/отказы сохранены.
ИНДИВИДУАЛЬНЫЙ ОСТАТОК
После зелёного T0 и выделенного окна выполнить сравнение одного/двух процессов с равным общим budget, 3 повтора AB/BA; сохранённые tooling tests не являются baseline.
Следующая смена начинает только после возобновления владельцем. Исторический status сохранён; STOPPED означает отсутствие выполняемой работы, не закрытие задачи.
Лента
2026-09-10T19:40:36.726Z · coordinatorENTERPRISE2-LINKS:CAP-08
Parent: WEB-626
Зависимости: WEB-629 (CAP-02), WEB-630 (CAP-03), WEB-631 (CAP-04), WEB-632 (CAP-05), WEB-633 (CAP-06), WEB-634 (CAP-07)
Спецификация целиком в WEB-626. Порядок: подготовка → изолированный стенд/T0 → выделенные измерения → независимый итог. CAP-13 опциональный.
2026-09-10T21:39:01.286Z · coordinatorDISPATCH-20260910-3404
На 2026-09-10T21:38:23.260126+00:00: active; M4, волна 3404 cap-baseline-plan. Exact BASE 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4. Бриф прошёл brief-guard; входы SHA256 и наличие BASE проверены до постановки. Входы /Users/milamarty/waves/inputs/3404-cap-baseline-plan. Отчёт ожидается /Users/milamarty/waves/3404CAPBASELINEPLAN-REPORT.md. Живой процесс модели подтверждён; возраст журнала 3 с. Source/подготовка и независимая проверка — в объёме брифа; это не посадка, не live/capacity GO и не окончательное закрытие security.
2026-09-10T21:55:31.075Z · coordinatorREFILL-20260910-2155-3420
Снимок 2026-09-10T21:54:52.066911+00:00. Волна 3420 / acc-cap-baseline-plan на A2: active. Оператор Codex, модель gpt-5.6-luna. Живой процесс подтверждён; возраст журнала 1 с. Входы /home/ubuntu/waves/inputs/3420-acc-cap-baseline-plan; SHA256 и exact BASE cab76c2e5eb2928acb33ccd124e9d557834eef47 проверены при доставке. Новых посадок и измерений capacity нет; source QA не означает окончательное закрытие Security.
2026-09-10T22:50:48.341Z · coordinatorSLOTS12-BATCH3442-3461-WEB-635
2026-09-10T22:50:05.652264+00:00 Лимиты A1/A2 подняты до12; действовавшие worker panes сохранены. Волна 3447 a1nc active acc-baseline-hardening-final; BASE 9b2503c9693b1c9ea65b57576da8a9034500d42a; model gpt-5.6-luna
Брифы прошли guard; входные report/history/raw/bundle переданы и SHA проверены до очереди. Source/QA scope, посадки и measured capacity не заявляются.
2026-09-12T18:27:04.860Z · coordinatorSHIFT-STOP-20260912:FINAL:WEB-635
Enterprise-2 / CAP-08: Один и два процесса, CPU/RAM и baseline
Срез перед остановкой 12.09.2026. По поручению владельца 18:13:55 UTC новая работа остановлена.
Enterprise-2: ранее принято 12/14 source/component частей; число не означает готовность 12/14 runtime задач. Ёмкость UNKNOWN. R3 source e1f6cb526d946af339c213ea77eb26c8d77594d2, buildId R7L9roppA5xpq7qPoQmCR.
r8c SAVE_READBACK_PASS: настоящий Auth.js owner, 2MiB документ, Socket.IO init, exact save и authoritative readback/размер/SHA/revision, stale CAS refusal; 8 файлов сверены. Собственная PG остановлена, unit inactive/MainPID0. Shared editor остаётся OPEN из-за effective TeamMember=commenter.
3532 с Нео сохранена: HEAD4182011579fad418f443fc0f40e1ec54e56b159c, bundle SHA32723bf7931419e8c7920c9cb3c03e5fa626ad62edf68d56e950024268571abe. Author scoped GO: Flight decoder, fresh server-owned CAS/save bootstrap, exact one-save/readback checks; offline9/9, syntax3. k6 execution/applicationT0/load/spike/soak NOT_RUN. Новая независимая приёмка3532 не присваивалась.
Следующий запуск требует двух locks: /home/ubuntu/.coordinator-release-build.lock и /home/ubuntu/capacity-3516/runtime-t0.lock; свежего TTL manifest, private sessions/bootstrap и отсутствия параллельной сборки/нагрузки. Реальные credentials не публиковать.
Файлы Intel: /Users/annakorin/nc-ops-scripts/enterprise-3532/delivery/3532CAPACITYK6RUNTIME-REPORT.md (полная инструкция bootstrap/generator/k6); bundle и evidence рядом. /Users/annakorin/nc-ops-scripts/tick-20260912-recovery/capacity-r8-coordinator/collected-r8c/collection-receipt.json. Исторические отчёты/отказы сохранены.
ИНДИВИДУАЛЬНЫЙ ОСТАТОК
После зелёного T0 и выделенного окна выполнить сравнение одного/двух процессов с равным общим budget, 3 повтора AB/BA; сохранённые tooling tests не являются baseline.
Следующая смена начинает только после возобновления владельцем. Исторический status сохранён; STOPPED означает отсутствие выполняемой работы, не закрытие задачи.
2026-09-12T22:25:57.437Z · coordinator[12.09 22:25Z координатор] ОБОГАЩЕНИЕ 12.09 (3571-enrich-web626):
Сделано: baseline plan + hardening в source scope (3404/3420/3447).
На проде: НЕТ — измерений CPU/RAM/goodput ещё не было вообще (capacity UNKNOWN).
Доказано: только брифы/планы (3404CAPBASELINEPLAN-REPORT.md).
Осталось: после зелёного T0 — p50/p95/p99, goodput, generator headroom, первый предел; 1/2 процесса с 3 AB/BA.
Кто следующий: координатор после T0 на R3.
Ссылки: /Users/milamarty/waves/3404CAPBASELINEPLAN-REPORT.md; /home/ubuntu/waves/inputs/3420-acc-cap-baseline-plan; commits 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4, cab76c2e5eb2928acb33ccd124e9d557834eef47, 9b2503c9693b1c9ea65b57576da8a9034500d42a
Отчёт волны: /Users/milamarty/waves/3571ENRICH*-REPORT.md (M4), копия nc-ops-scripts/shift-20260912-resume/wash-20260912/enrich-collected/.
2026-09-14T14:09:02.413Z · coordinator[14.09 14:09Z координатор] CAP-08 ИЗМЕРЕН: один процесс против двух. Предсказание объявлено ДО прогона и подтвердилось.
УСЛОВИЯ. Одинаковые во всём, кроме числа процессов: те же два ядра (оба процесса под taskset -c 0,1), та же база cap3516build, тот же артефакт и коммит, те же 24 пользователя суммарно, группа upload_index исключена в обоих случаях, непересекающиеся шарды по 24 личности.
РЕЗУЛЬТАТ (p50, миллисекунды):
группа 1 процесс x24 2 процесса x12 каждый
ВСЕ 950 492 / 533
session 526 156 / 150
list 832 216 / 227
retrieval 848 342 / 335
status 779 376 / 469
open 1137 766 / 938
realtime 3704 3021 / 2734
save_readback 10804 6920 / 2085
итераций 4923 3785 + 3943 = 7728 (+57%)
запросов 5838 4490 + 4595 = 9085 (+56%)
ЧТО ЭТО ДОКАЗЫВАЕТ. Процессорного времени не прибавилось — оба процесса на тех же двух ядрах. Прибавилось только число процессов. Задержка упала почти вдвое, пропускная выросла в полтора раза.
Значит первое узкое место — ИМЕННО ОДИН ПРОЦЕСС, а не процессор и не база. Диагноз волны 3853 подтверждён экспериментом.
ПОЧЕМУ НЕ РОВНО ВДВОЕ ПО ПРОПУСКНОЙ: два процесса теперь делят те же два ядра, и мы начинаем упираться в процессор. Это ожидаемо и означает следующий шаг: дать приложению больше ядер (на стенде их отдано два из четырёх, вторая пара занята измерителем).
ПУТЬ К ЭТОМУ ИЗМЕРЕНИЮ (эволюция, включая тупики):
1. Первая попытка: направил нагрузку на второй порт — прибор ОТКАЗАЛСЯ работать (k6_exit=107, ноль действий): «explicit approved isolated manifest for the approved application artifact is required». Харнесс держит утверждённый манифест цели с runId, sourceCommit и режимом заглушки, и второй порт в нём не значился. Это защита, а не поломка — ровно от того класса ошибок, что я делал сегодня четырежды (не тот шард, не та группа, не тот профиль, не то дерево).
2. Обходить не стал. Утвердил второй экземпляр ЯВНО: манифест isolated-target-3611.json с тем же runId, тем же sourceCommit, тем же providerMode=stub и той же базой; отличается только порт, и в файле записано, что это второй web-процесс ТОГО ЖЕ артефакта.
3. Второй экземпляр пришлось отдельно разрешить в замке происхождения: APP_ALLOWED_HOSTS знал только 127.0.0.1:3610, второй порт отвергался с host-not-allowed. Добавлено в окружение второго экземпляра, файл стенда не тронут.
4. Дерево прибора за сутки трижды вычищал дворник каталога волн — вынесено за его пределы, в /home/ubuntu/instrument-3818.
ГРАНИЦА ЗАЯВЛЕНИЯ. Измерено на стенде, не на проде. Доказано, что предел — процесс-локальный. НЕ доказано, что второй процесс безопасно включать в проде: нужен честный ответ, что в приложении не является процесс-локальным (аренда активного узла, кэши, идемпотентность). Волна 3853 это отметила отдельно (её корзина B4), и на весь диагноз поставлена независимая приёмка 3863.
TROUBLESHOOTER. Если повторять: оба процесса должны быть на ОДНИХ ядрах (иначе меряется процессор, а не архитектура); шарды непересекающиеся (иначе два пользователя схлопнутся на одну одноразовую координату сохранения); манифест цели — свой на каждый порт; группу upload_index исключать в обоих прогонах (иначе она съедает машину и утягивает всё в 5-10 раз).
2026-09-14T14:12:05.509Z · coordinator[14.09 14:12Z координатор] ПОПРАВКА К ПРЕДЫДУЩЕМУ СООБЩЕНИЮ ПРО CAP-08. Я написал «7728 действий, +57 процентов». Это число ПРЕДЛОЖЕННЫХ действий, а не выполненных. Поправляю сам, до того как на него сошлются.
ЧЕСТНЫЕ ЧИСЛА:
один процесс x24: предложено 4923, ВЫПОЛНЕНО 4917 (99.88%)
два процесса x12: предложено 3785+3943=7728, ВЫПОЛНЕНО 3781+3718=7499
прирост пропускной: +52.5% (а не +57%)
ЭТО ВТОРОЙ РАЗ ЗА СМЕНУ, когда я беру «предложено» вместо «выполнено». Первый был утром: «440 из 440, потерь нет» — тоже про предложенные. Класс ошибки один и тот же: в отчёте два числа рядом, и удобнее выглядит большее.
ГДЕ ПОТЕРИ И ПОЧЕМУ ЭТО НЕ СВОЙСТВО ПРОДУКТА.
Первый процесс (3610) чист: 4 ошибки на 3785 действий.
Второй (3611) потерял 225 действий, из них save_readback 0 из 12 — все двенадцать с ошибкой.
Причина видна в логе запуска: звуковой демон второго экземпляра открыл ТОЛЬКО 13 комнат из 24, тогда как у первого 24 из 24. Пользователи, чьи комнаты не открылись, падали. Это изъян МОЕЙ настройки второго экземпляра, а не поведение приложения под нагрузкой.
ЧТО ИЗ ВЫВОДА УСТОЯЛО, А ЧТО НАДО ПЕРЕПРОВЕРИТЬ.
Устояло — главное. Первый процесс в двухпроцессном прогоне чист и даёт p50 492 миллисекунды против 950 у односоставного при вдвое меньшей нагрузке на процесс. Задержки по группам упали вдвое-втрое: список 832 -> 216, поиск 848 -> 342, вход 526 -> 156. Процессорного времени не прибавилось. Вывод «первое узкое место — один процесс» стоит.
Надо перепроверить — точную цифру прироста пропускной способности. 52.5 процента получены при том, что второй экземпляр работал с неполным набором комнат. Чистый прогон даст другое число, и я не знаю заранее, больше или меньше.
ЧТО ДЕЛАЮ: повторяю сравнение, убедившись, что демон второго экземпляра открывает все 24 комнаты. Число прироста опубликую заново.
2026-09-14T15:58:35.071Z · coordinator[14.09 15:58Z координатор] VERDICT=GO
# 3864 / CAP-08 (WEB-635) — честное число на двух ЗДОРОВЫХ процессах
**Главное в трёх строках.**
1. `Invalid prisma.source.findFirst()` — **не ошибка схемы**. Ни одного отсутствующего или лишнего поля нет. Это исчерпание соединений: `FATAL: sorry, too many clients already`, которое Prisma оборачивает в имя невиновного запроса. Причина — `src/lib/prisma.ts:152-156`: в production синглтон **не** кешировался на `globalThis`, а production-сборка держит **пять** копий этого модуля, и каждая копия открывала свой пул на 40 соединений.
2. Второй процесс падал не «так совпало»: Postgres выдаёт слоты в порядке обращения и Prisma никогда не сжимает простаивающий пул. Кто загрузился первым — забрал бюджет целиком (замерено: 79 из 100) и держит его; кто загрузился вторым — получает остаток (21) и умирает на соединении. Жертву выбирает порядок старта.
3. Чистое число снято на двух здоровых процессах: **completed 4678 → 7370, +57.5 %**. Заявку «+52.5 %» подтверждаю (даже чуть выше), но **две сопутствующие заявки не воспроизвелись**: задержка упала не «вдвое», а в 1.29–1.71 раза, и CPU **не тот же** — потребление приложений выросло 0.85 → 1.40 ядра.
---
## 1. ЧИСЛА. Обе колонки, offered и completed
Профиль и условия взяты **из лога предыдущей серии**, не по памяти (`/home/ubuntu/capA.log`, `k6-capA-stdout.log`): профиль `t1` (constant-vus, k6 печатает «24 looping VUs for 5m0s»), 5m / 300 s, `CAP06_EXCLUDE_GROUPS=upload_index`, шарды `split-a-24` / `split-b-24` по 24 личности, приложение+PG на ядрах 0,1 и каждый k6+звуковой демон на 2,3.
| плечо | порт | VU | **offered** | **completed** | completion | p50, мс | p95, мс | CPU приложения, ядер |
|---|---|---|---|---|---|---|---|---|
| **ARM 1** — один процесс | 3610 | 24 | **4678** | **4678** | 100.00 % | 944 | 2444.2 | **0.85** |
| ARM 2, плечо A | 3610 | 12 | 3279 | 3279 | 100.00 % | 731 | 2088.1 | 0.66 |
| ARM 2, плечо B | 3611 | 12 | 4091 | 4091 | 100.00 % | 553 | 1948.0 | 0.74 |
| **ARM 2** — два процесса, сумма | оба | 24 | **7370** | **7370** | 100.00 % | см. ниже | — | **1.40** |
**Пропускная способность: +57.5 %** (7370 / 4678 = 1.5754). В темпе: 15.59 → 24.57 завершённых действий в секунду за 300 s.
offered и completed совпали до единицы во всех трёх прогонах, поэтому подмены здесь быть не может. Я всё равно печатаю обе колонки: в предыдущей серии они расходились (capA 4923/4917, capC2 3943/3718), и именно оттуда пришла завышенная цифра.
**Задержка — не «вдвое».** Сводный перцентиль двух отдельных прогонов k6 из экспортированных summary **не вычисляется** (перцентили не складываются). Строгая граница: сводная медиана двух выборок лежит между медианами плеч, то есть в **[553, 731] мс** против 944 мс на одном процессе — улучшение в 1.29–1.71 раза. Заявку «вдвое меньше» **я не воспроизвёл**; сводное p50 я честно помечаю как «не измерено», доказана только вилка.
**CPU — не «тот же».** Замерено по дельте накопленного процессорного времени (`pcpu/100 × etimes` из выборок прибора, окно 286 s фазы нагрузки):
| | ARM 1 | ARM 2 |
|---|---|---|
| приложение 3610 | 0.85 ядра | 0.66 ядра |
| приложение 3611 | 0.01 ядра (простой) | 0.74 ядра |
| **сумма приложений** | **0.85** | **1.40** |
| ядра 0,1 занято всего (app+PG) | 1.08 из 2.00 (53.9 %) | 1.53 из 2.00 (76.3 %) |
| ядра 2,3 (прибор k6) | ~36 % | ~36 % |
Пропускная способность на ядро приложения: 5504 → 5264 завершённых на ядро, то есть **практически не изменилась (−4 %)**. Весь прирост — от того, что задействовано второе ядро, а не от роста эффективности.
**Здоровье обоих плеч (это и есть предмет волны).** Счётчик со стороны кластера покрывает 100 % окна обоих плеч и видит **оба** процесса, включая root-овый на 3611, чей stdout нам недоступен:
| | ARM 1 | ARM 2 |
|---|---|---|
| `too many clients already` в дельте `pg.log` | **0** | **0** |
| отказов моему диагностическому `psql` | **0** | **0** |
| прочие FATAL/ERROR/PANIC в дельте | **0** | **0** |
| пик backend-ов Postgres | 135 из 400 | 136 из 400 |
| `http_req_failed` | 0 (5584/5584) | 0 (3879/3879 и 4853/4853) |
| completion всех плеч | 100.00 % | 100.00 % |
| `dropped_iterations` | 0 | 0 |
Для сравнения — плечо, на котором закрыли тикет: capC2 имело completion 94.29 %, `http_req_failed` 4.33 %, `save_readback` 0 из 12.
---
# ДЛЯ ТИКЕТА — ДЕТСКИМ ЯЗЫКОМ (полный след для агента с нуля)
## Что видел человек
«Запускаем приложение двумя копиями, чтобы держало больше народу. Первая копия работает. Вторая копия на порту 3611 сыпет ошибками `Invalid prisma.source.findFirst()`. Похоже, у второй копии что-то не то со схемой базы — не хватает какого-то поля. И число производительности мы сняли, когда одна копия была больная, значит число врёт.»
## Что на самом деле
Со схемой **всё в порядке**. Ни одного лишнего или пропавшего поля нет.
Представьте, что база данных — это раздевалка со **ста** шкафчиками. Это настоящее число: `max_connections = 100`.
Приложению сказали: «тебе нужно 40 шкафчиков». Оно так и написано в настройках: `WEB_DB_POOL_LIMIT=40`.
Но приложение при сборке **размножилось на пять частей**, и каждая часть пошла в раздевалку **сама за себя** и попросила свои 40 шкафчиков. Не 40 на всю копию, а 40 **на каждую из пяти частей**. Мы поймали живьём четыре таких похода за одну загрузку — в логе четыре одинаковые строчки «беру 40 шкафчиков».
Дальше простая арифметика. Первая копия запустилась вечером и постепенно набрала **79 шкафчиков**. И не отдала: приложение занятые шкафчики не освобождает, даже когда ничего не делает.
Вторая копия пришла на следующий день. Свободных шкафчиков осталось **21**. Она взяла их, а когда попросила ещё — сторож ответил: «мест нет». По-настоящему ответ звучит так: `FATAL: sorry, too many clients already`.
А теперь фокус, из-за которого всех увело в неверную сторону. Когда приложению не дают шкафчик, оно не говорит «мне не дали шкафчик». Оно говорит: «**не получился запрос `prisma.source.findFirst()`**» — то есть называет тот запрос, которому не повезло оказаться первым в очереди. Как если бы человек, не найдя шкафчика, пожаловался не на раздевалку, а на свои носки. Поэтому все искали поломку в запросе и в схеме, а поломка была в раздевалке.
Проверить это можно было в одну строчку: **вся раздевалка была занята, когда никто не работал**. Сто шкафчиков из ста — на холостом ходу. Я это увидел так: попробовал сам зайти в базу обычным `psql`, и меня тоже не пустили. Это в логе базы, с моей меткой времени: `pg.log:1038-1041`, 14:50:09.
## Почему первая копия жива, а вторая нет — и почему это не случайность
Тут нет никакой разницы между копиями. Они одинаковые — я проверил побайтно, обе отдают один и тот же файл сборки с одинаковой контрольной суммой.
Разница только одна: **кто пришёл раньше**. Шкафчики выдаются по порядку прихода и никогда не перераспределяются. Кто первый — забрал всё и держит. Кто второй — получил объедки и умер.
Проверка этого утверждения простая: **перезапустите копии в обратном порядке, и больной станет та, что сейчас здорова.** Это и есть механизм, а не совпадение.
## Почему не поймали раньше
Потому что у приложения есть специальный сторож, который перед запуском проверяет, хватит ли шкафчиков. И он **сказал, что всё в порядке**.
Он считает так: «копий приложения — 1, каждой нужно 40, значит нужно 40. Разрешено 90. 40 меньше 90, пропускаю». Он не соврал — он посчитал то, что ему написали в настройках. Он просто **не может** узнать, что одна копия приложения размножилась внутри себя на пять частей, и не может узнать, что рядом работает вторая копия. Он смотрит только на свои переменные окружения.
То есть проверка, которая должна была это поймать, была **структурно слепа** именно к этому случаю. Отсюда и четверо суток.
## Что сделали
**1. Нашли настоящую причину в исходниках.** Файл `src/lib/prisma.ts`, строки 152-156. Там было написано: «запомни соединение с базой, чтобы второй раз не открывать — **но только если мы не в production**». А стенд работает именно в production. Значит «запомни» не срабатывало никогда, и каждая из пяти частей открывала своё.
Исправление в одну строчку: запоминать **всегда**. Тогда все пять частей пользуются одним соединением, и одна копия приложения берёт 40 шкафчиков, как и обещала. То же самое исправили в `src/lib/prisma-readonly.ts`, строки 55-61 — там была ровно та же ошибка.
Коммит: `9e65ac0d2`, ветка `wave/3864-cap08-second-instance`.
**ВАЖНАЯ ГРАНИЦА:** это исправление **не проверено на работающем стенде**, потому что чтобы оно туда попало, надо пересобрать приложение (`next build`), а это волне запрещено. Исправление лежит в ветке и ждёт сборки.
**2. Чтобы всё-таки снять честное число сегодня, расширили раздевалку.** Поставили 400 шкафчиков вместо 100 (`postgresql.conf`, строка 65; старое значение сохранено в `postgresql.conf.bak-3864`). Это **не** лечение болезни, это костыль ради замера. Когда исправление соберут, хватит и ста.
**3. Проверили, что обе копии теперь живые.** Взяли тот самый запрос, который раньше падал (`/api/sources/<id>/text` — именно его обработчик и писал ту ошибку), и постучались по два раза в каждую копию. Все четыре ответа: **200**, и тела ответов побайтно одинаковые.
**4. Сняли число.** Одна копия под 24 пользователями, потом две копии по 12 — всё остальное одинаковое.
## Что получилось (и где заявка не подтвердилась)
| | одна копия | две копии |
|---|---|---|
| сделано дел (completed) | **4678** | **7370** |
| заказано дел (offered) | 4678 | 7370 |
| ни одного «мест нет» | да | да |
| CPU приложений | 0.85 ядра | 1.40 ядра |
**Стало +57.5 % работы.** Заявка «+52.5 %» подтвердилась и даже оказалась скромной.
**Но две другие заявки не подтвердились, и это надо записать в тикет:**
* «задержка упала **вдвое**» — нет. Упала в 1.29–1.71 раза. Точное сводное число посчитать из выгрузок нельзя (перцентили не складываются), доказана только вилка;
* «**при том же CPU**» — нет. CPU выросло: приложения стали съедать 1.40 ядра вместо 0.85.
**Почему две копии вообще помогают — вот главный ответ эпика.** Одна копия приложения умеет работать только **в одном потоке**. Её потолок — примерно одно ядро, и она вышла на 0.85 ядра, хотя рядом простаивало ещё 1.15 ядра, которые ей были разрешены. Она не могла их взять физически. Вторая копия — это второй поток, и вот он уже взял второе ядро.
Доказательство, что дело именно в этом: работы на одно ядро приложения стало **столько же** (5504 → 5264, разница −4 %). То есть приложение не стало работать лучше — просто стало работать **вдвоём**.
## Где теперь упор
В **двух ядрах**, которые делят между собой обе копии приложения и сама база. Они заняты на 76 %. Шкафчики больше не упор — из 400 занято максимум 136.
Запас — примерно 0.47 ядра, то есть третья копия должна дать **не больше +31 %**.
**Что доказало бы, что я неправ:** если третья копия на тех же двух ядрах даст заметно больше +31 % — значит упор не в ядрах и моя картина неверна. Или наоборот: если дать четыре ядра вместо двух и производительность **не** вырастет — значит упор вообще не в процессоре, а в чём-то общем, чего я не нашёл.
## Что осталось открытым
1. **Собрать приложение с исправлением и перемерить.** Тогда костыль с 400 шкафчиками можно убрать. Нужно разрешение на сборку.
2. **Копия на 3611 — не наша.** Её запустил кто-то от root, и прочитать её настройки мы не можем. Что это та же самая сборка — доказано тремя способами (одинаковые байты сборки, общий файл-«дежурный», та же база), но **равенство её настроек — это вывод, а не наблюдение**. Это самое слабое место замера.
3. **Две копии дают ёмкость, но убрать одну пока нельзя мягко** — механизм плавного вывода на стенде выключен, снятие копии сейчас = убийство.
4. Почему одна копия оказалась на 11 % эффективнее другой — **не выяснено**. Видно только, что старая копия жила 15.9 ч и занимала больше памяти, но связь не доказана.
## Если это повторится — что нажать
Увидели `Invalid prisma.<что угодно>()` — **не бегите в схему**. Дочитайте сообщение до конца. Если там `too many clients` или `P2037` — это шкафчики. Смотрите:
```
psql -h 127.0.0.1 -p 55510 -U postgres -d cap3516build -At -c "select count(*) from pg_stat_activity"
grep -nE '^[^#]*max_connections' /home/ubuntu/waves/.3610-stand/pgdata/postgresql.conf
```
Если первое число почти равно второму, **а нагрузки при этом нет** — диагноз тот самый.
Сколько соединений открыла одна копия (**должна быть одна строчка, а не четыре**):
```
grep -c '\[db-pool\] role=' <лог приложения за одну загрузку>
```
И два молчаливых способа получить пустое плечо вместо числа — оба описаны в разделе Troubleshooter ниже. Коротко: **пустой или нулевой summary означает «настройка не сработала», а никогда не «производительность ноль»**.
---
## 2. Что сделано по шагам задания — с честными отметками
**Шаг 1 «Подними стенд на 3610 по bootstrap». НЕ ВЫПОЛНЕН НАМЕРЕННО; посылка тикета ложна.**
Тикет говорит «Сейчас ни 3610, ни 3611 не слушают — стенд опущен». На момент старта волны (14:47Z) **слушали оба**:
- 3610 — pid 261374, uid 1001 (наш), живёт с 2026-09-13 23:45:49, cwd `/home/ubuntu/capacity-3516/source/.next/standalone`, stdout → `logs/app-3757-rebuild.log`;
- 3611 — pid 1428072, старт 2026-09-14 13:26:43, **владелец root** (uid сокета 0 в `/proc/net/tcp`; `/proc/1428072/environ` и `cwd` нам не читаются).
Приложение на 3610 я **не перезапускал**, и это осознанное решение, а не пропуск: это был единственный здоровый процесс, а `assertActivePassiveLeaseFence()` вызывается на каждой операции Prisma, причём оба процесса делят один файл-держатель аренды `nc-upstream/active-holder` (см. ниже). Перезапуск мог оставить меня без работающего плеча при root-овом конкуренте за ту же одиночную аренду, которого я не имею права остановить. Историческая поломка именно такого вида зафиксирована в `3797ACCEPT-REPORT.md:25` («REFUSED … reason=active_holder»).
Заметьте: `bootstrap/` — это **не** скрипт, а каталог данных (`owner/save-bootstrap.json`, `retrieval-bootstrap.json`, `meeting-room-bootstrap.json`). Поднять стенд «по bootstrap» нельзя; приложение поднимается из standalone-артефакта с `app.env`. Это стоит поправить в формулировке тикета.
**Что я в стенде реально поднял и изменил — единственное изменение, файлом и командой:**
`/home/ubuntu/waves/3864CAP08-evidence/step-pg-raise-maxconn.sh` — `pgdata/postgresql.conf:65` `max_connections` 100 → 400 (бэкап `postgresql.conf.bak-3864`), затем `pg_ctl -D .../pgdata -l logs/pg.log -m fast -w -t 60 restart`. Без drop, без reseed, без изменения схемы и данных, без сигналов обоим web-процессам. Проверено с сервера: `max_connections = 400`.
Плюс `/home/ubuntu/waves/3864CAP08-evidence/pin-app-pg.sh` — закрепление postmaster и всех 132 живых backend-ов за ядрами 0,1 (прежние маски сохранены в `pin-before-3864.txt`).
**Шаг 2 «Подними ВТОРОЙ процесс на 3611». НЕ ВЫПОЛНЕН — заблокирован, и вот чем.**
Порт 3611 занят процессом root (pid 1428072). Поднять там **свой** процесс нельзя: порт занят, а остановить чужой процесс я не могу — `sudo` и вмешательство в чужие процессы запрещены заданием. Других разрешённых адресов нет: `inputs/` содержит ровно два одобренных манифеста, оба loopback, 3610 и 3611.
Поэтому второе плечо измерено **на уже существующем процессе 3611**. Это не «наш» процесс, но что он — **тот же артефакт**, доказано тремя независимыми способами, а не предположено:
1. **байты артефакта.** `/_next/static/chunks/04481729.5b7176c791819795.js` с обоих портов и с локальной сборки — один sha256 `05af5dd5e54a28c83ea83e3307beb9ed23305d1a6089867216cf1d0e122d2ab5`, размер 730923.
2. **тот же `app.env`.** Оба процесса **по очереди перезаписывают один и тот же** файл одиночной аренды `nc-upstream/active-holder` каждые несколько секунд — владелец файла мигает root/ubuntu (замерено серией `stat -c %U`, генерация остаётся 18). Значит, тот же код и то же значение `NC_UPSTREAM_ACTIVE_HOLDER_FILE` из `app.env`.
3. **та же БД.** 21 установленное соединение uid 0 к `127.0.0.1:55510` (`cap3516build`).
**Граница:** я не могу прочитать его окружение, поэтому равенство `WEB_DB_POOL_LIMIT`, `DB_POOL_*` и прочих значений — **вывод из пунктов 1–3, а не прямое наблюдение**. Это остаётся самым слабым звеном измерения, и ниже сказано, как его закрыть.
**Шаг 3 «Разбери `Invalid prisma.source.findFirst()`». ВЫПОЛНЕН — с опровержением посылки.** См. раздел 3.
**Шаг 4 «Оба процесса отвечают 200 на одном и том же запросе, по два ответа с каждого порта». ВЫПОЛНЕН.**
Запрос выбран не произвольно: `/api/sources/<id>/text` — **ровно тот маршрут**, чей обработчик и писал `[source-text] protected source read failed … Invalid prisma.source.findFirst()` (`logs/app-3757-rebuild.log:238415`, `:327351`). Скрипт `step-both-ports-200.sh`, вывод `both-ports-200.log`:
```
port 3610 #1 http_code=200 time=0.276s size=2097152
port 3610 #2 http_code=200 time=0.148s size=2097152
port 3611 #1 http_code=200 time=0.180s size=2097152
port 3611 #2 http_code=200 time=0.148s size=2097152
все четыре тела побайтно одинаковы: sha256 6ed86dee…3c2a706a
```
**Шаг 5 «Серия с одинаковыми условиями». ВЫПОЛНЕН со второй попытки; обе неудачные попытки сохранены в evidence.**
**Шаг 6 «completed, не offered». ВЫПОЛНЕН** — обе колонки в таблице, offered и completed совпали.
**Шаг 7 «Где упор и что бы опровергло вывод». ВЫПОЛНЕН** — раздел 5.
---
## 3. Разбор `Invalid prisma.source.findFirst()` — файл, строка, механизм
### Посылка тикета опровергнута: отсутствующего или лишнего поля схемы НЕТ
Полный текст ошибки (`logs/app-3757-rebuild.log:238405-238425`):
```
[source-text] protected source read failed {
sourceId: 'cap06-source-b35d1bd95465',
message: 'Invalid `prisma.source.findFirst()` invocation:\n\n\n' +
'Too many database connections opened: FATAL: sorry, too many clients already\n' +
'Prisma Accelerate has built-in connection pooling to prevent such errors: …'
}
```
Prisma предваряет **любой** сбой внутри вызова строкой «Invalid `prisma.X.Y()` invocation», включая отказ на этапе установления соединения. Перепись кодов ошибок по обоим логам приложения:
* `code: 'P2037'` — **6 раз**, и это **единственный** код Prisma в обоих логах. P2037 = «Too many database connections opened».
* `P2021` (нет таблицы), `P2022` (нет столбца), `P2023`, `column … does not exist`, `relation … does not exist`, `Unknown arg`, `Unknown field` — **0 совпадений** в `app-3757-rebuild.log` и `app-3740.log`.
Так что на вопрос «какое поле схемы отсутствует или лишнее» честный ответ: **никакое**. Схема не при чём, и ни один правильный ответ на вопрос в такой постановке не существует. Тот же P2037 прилетал и в `prisma.teamMember.findMany()` из `/api/sources/text-search` — имя модели в сообщении просто указывает, кто первым не получил соединение.
### Настоящая причина: один процесс = пять пулов, а бюджетный шлюз считает один
`src/lib/prisma.ts:152-156` (до исправления):
```ts
export const prisma = isNextProductionBuild
? buildPhasePrisma
: globalForPrisma.prisma || createPrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
```
Кеш кладётся на `globalThis` **только когда `NODE_ENV !== 'production'`**. В production присваивания не происходит никогда, поэтому проверка `globalForPrisma.prisma || …` не может сработать ни разу и каждый экземпляр модуля уходит в `createPrismaClient()`.
А экземпляров модуля в production-сборке **пять** — бандлер дублирует его в каждый серверный чанк (`grep -rl 'db-pool] role=' .next/standalone/.next/server`):
```
pages/api/realtime/socket.js
chunks/66615.js
chunks/8783.js
chunks/71666.js
chunks/30809.js
```
Каждая копия исполняет собственную область модуля → собственный `PrismaClient` → собственный пул на `connection_limit=40`.
Подтверждение в рантайме, одинаковое в **двух независимых загрузках** (`app-3740.log` и `app-3757-rebuild.log`, строки 12, 13, 14 и ещё одна позже — 4 из 5 копий действительно создались):
```
[db-pool] role=web connection_limit=40 pool_timeout=10s (applied) target=…/cap3516build
[db-pool] role=web connection_limit=40 pool_timeout=10s (applied) target=…/cap3516build
[db-pool] role=web connection_limit=40 pool_timeout=10s (applied) target=…/cap3516build
… (+1 строка ниже по логу, лениво при первой загрузке ещё одного чанка)
```
`[db-pool] readonly` — 0 строк, то есть `READONLY_DATABASE_URL` не задан и readonly-клиент это бросающая заглушка; все четыре пула — копии основного клиента.
А шлюз бюджета в `src/lib/db/connectionPool.ts:198` и далее считает так:
```ts
const webProcesses = parseStrictPositiveInteger(env.DB_POOL_GATE_WEB_PROCESSES);
…
poolDemand = webProcesses * webLimit + readonlyClients * readonlyLimit + workerProcesses * workerLimit
```
При `app.env`: `DB_POOL_GATE_WEB_PROCESSES=1`, `WEB_DB_POOL_LIMIT=40`, `WORKER_DB_POOL_LIMIT=3` → `poolDemand = 43 ≤ DB_POOL_APPLICATION_BUDGET=90`, старт разрешён. Настоящий потолок одного процесса — **5 × 40 = 200**. Шлюз занижает примерно в пять раз, и пропускает конфигурацию, которая физически не может уместиться.
### Почему первый живёт, а второй нет — механизм, а не совпадение
Замерено на живом стенде в 14:50Z, **до** любых моих изменений:
* `max_connections = 100` (`pgdata/postgresql.conf:65`);
* клиентских соединений к `127.0.0.1:55510` — ровно **100**;
* из них **79 у pid 261374** (процесс на 3610) и **21 у uid 0** (процесс на 3611);
* кластер полон **на простое**, без всякой нагрузки;
* мой собственный диагностический `psql` получил `FATAL: sorry, too many clients already` — в `pg.log:1038-1041` четыре моих отказа с метками 14:50:09. Всего в `pg.log` 583 таких строки.
Механизм из трёх звеньев, каждое проверяемое:
1. **пулы растут лениво и никогда не сжимаются.** Prisma не закрывает простаивающие соединения, поэтому 79 слотов, набранных за прошлые прогоны, остаются занятыми и на простое.
2. **Postgres выдаёт слоты в порядке обращения** и не перераспределяет их. Процесс, загрузившийся первым (23:45 предыдущих суток), дорос до 79; процесс, загрузившийся вторым (13:26), пришёл к остатку 100 − 79 = 21.
3. **отказ наступает на этапе соединения,** а не запроса. Поэтому он выглядит как поломка того запроса, которому не повезло, и мигрирует между именами моделей (`source.findFirst`, `teamMember.findMany`).
Отсюда прямо следует, что жертву выбирает **порядок старта**, а не какое-то свойство второго процесса. Перезапустите их в обратном порядке — и «больным» станет тот, что сейчас здоров. Это и есть требуемый механизм: не «так совпало», а первоочередной захват общего фиксированного бюджета.
### Исправление
**Настоящее исправление — в исходниках**, коммит `9e65ac0d2` на ветке `wave/3864-cap08-second-instance`:
`src/lib/prisma.ts:152-156` → кешировать безусловно, `(globalForPrisma.prisma ??= createPrismaClient())`. `globalThis` один на процесс, поэтому получается **один пул на процесс** — ровно та единица, которую считает бюджетный лист. Заглушка фазы сборки по-прежнему не кешируется. То же в `src/lib/prisma-readonly.ts:55-61` (там комментарий у `createReadonlyClient` уже предупреждал, что второй пул в процессе удваивает его след — но сам файл страдал тем же production-only кешем).
После исправления: два web-процесса = 2 × 40 + 3 = 83 ≤ 90 бюджета ≤ 100 `max_connections`, то есть **двум процессам вообще не нужен был бы поднятый `max_connections`**.
**ГРАНИЦА: это исправление не проверено на стенде и не могло быть.** Чтобы оно попало в работающий процесс, нужен `next build`, а он запрещён заданием. Поэтому измерения сняты на **неисправленном** артефакте, а здоровье обоих плеч обеспечено эксплуатационной мерой — поднятым `max_connections` (100 → 400). Это честная последовательность: исправление отдано в ветку, а число снято обходным путём, и одно не выдаётся за другое.
Тесты:
* `tests/unit/prisma-singleton-cache-shape.test.mjs` — **запущен, 3/3 проходят** на чистом `node --test` без зависимостей. Закрепляет механизм: два импорта одного файла с разными query-строками действительно дают два экземпляра модуля; при `NODE_ENV=production` дофиксовая форма конструирует **по разу на копию** (3 копии → 3 конструирования), постфиксовая — **один раз на процесс**.
* `tests/unit/prisma-singleton-duplicate-chunks.test.mjs` — проверяет то же свойство на **настоящих** модулях, считая строки `[db-pool] role=`. **НЕ ЗАПУСКАЛСЯ:** ему нужен сгенерированный клиент Prisma, а `node_modules/.prisma/client` нет ни в одном дереве на этой машине (проверено в `instrument-3818` и `capacity-3516/source`; в моём worktree вообще нет `node_modules`). Чтобы запустить: поставить зависимости и `prisma generate`, затем `node --test tests/unit/prisma-singleton-duplicate-chunks.test.mjs`. Ожидание: до исправления 5 (или сколько копий загрузится) строк `[db-pool] role=`, после — ровно 1.
---
## 4. Эволюция, включая тупики
**Тупик 1. «Стенд опущен».** Первым же действием проверил порты вместо того, чтобы поднимать. Оба слушали. Если бы поверил тикету и начал с подъёма, занял бы 3610 вторым процессом и сломал бы единственное здоровое плечо.
**Тупик 2. «Процесс на 3611 — чужой, значит измерять нечего».** Сначала я решил, что root-овый процесс вообще не связан со стендом: в первом проходе по `/proc` я не нашёл ни одного root-соединения к нашему Postgres и уже готов был написать, что 3611 работает с другой БД. **Это была моя ошибка, и проверка её поймала:** аккуратная перепись uid-ов сокетов дала 21 установленное соединение uid 0 именно к `55510`. Первый проход молча проглотил `PermissionError` на `/proc/<root-pid>/fd`. Вывод для следующего: обход `/proc` из-под непривилегированного пользователя **систематически слеп** к процессам root, и «не нашёл» здесь никогда не значит «нет» — надо читать колонку uid в `/proc/net/tcp`, она видна всегда.
**Тупик 3. Догадка «проблема в схеме», как просил тикет.** Искал отсутствующее поле. Полный текст сообщения и перепись кодов ошибок (только P2037, ноль схемных) закрыли эту версию. Потратил на неё немного, но без переписи кодов я бы не смог утверждать «схемных ошибок нет вообще», а только «в этих двух местах их нет».
**Тупик 4. ARM 2, попытка первая (15:10Z).** Плечо 3610 не стартовало: «room-audio daemon failed to become ready». Результат — плечо 3611 в одиночку, то есть **опять один процесс**, только под видом двух. Ровно тот же класс подлога, что capB2 в старой серии. Причина: `room-audio-daemon.mjs:94` — `CAP06_ROOM_AUDIO_REOPEN_PORT || 18099`, **фиксированный порт по умолчанию**, а не свободный. Два одновременных прогона берут 18099, проигравший умирает с `EADDRINUSE`. Коварство в том, что демон сначала успевает открыть все 24 сокета и напечатать `{"event":"room-audio-daemon-ready","vus":24,"opened":24}` — его лог выглядит здоровым, и выдаёт сбой только отсутствующий ready-файл.
**Тупик 5. ARM 2, попытка вторая (15:22Z).** Закрепил порты 18099/18100 — плечо 3610 **умерло снова**, тем же `EADDRINUSE 18099`. Оказалось, 18099 держал не мой второй демон, а **другая волна**: 3877 (cap10-ladder) запустила свой k6 на 3610 в 15:22:24Z, на восемь секунд раньше моего. То есть попытка 2 была испорчена дважды — и портом, и посторонней нагрузкой на те же ядра и ту же БД. Снял её целиком и договорился с 3877 об исключительном окне.
Побочный урок, тоже мой промах: `pgrep -f 'run-rung-r4'` вернул в том числе **мои собственные** pid-ы, потому что имя гонятеля упомянуто в тексте задания волны, а задание целиком лежит в cmdline процесса `claude -p`. Я запустил цикл `kill` по шаблону, который совпал с самой запускающей его оболочкой, и **убил собственную команду на полпути** (exit 144). Классифицировать процессы надо по происхождению/pid-файлам, никогда по подстроке в cmdline.
**Тупик 6 — и самый важный, потому что он ставил под сомнение уже снятое число.** ARM 1 (15:00–15:10Z) прошёл чисто: 5150/5150, p50 1042. Но затем волна 3877 сообщила, что закрепила Postgres за ядрами 0,1, потому что он был на 0-3. Я проверил и понял, что **виноват мой же собственный перезапуск**: исходный postmaster (pid 21400) был закреплён за 0,1, а `pg_ctl restart` в 14:56Z унаследовал маску **моей интерактивной оболочки**, то есть 0-3. Комментарий в `run-rung-r4.sh` («Приложение, Postgres и оба воркера закреплены за ядрами 0-1») был правдой до 14:56Z и ложью 25 минут после. Значит ARM 1 шёл с Postgres на четырёх ядрах, конкурируя с прибором на 2,3 — **нарушение протокола моего же задания**. Я выбросил его и перезапустил **оба** плеча при правильном закреплении.
Цена ошибки измерима: тот же ARM 1 дал 5150 completed при PG на 0-3 и **4678 при PG на 0,1** — то есть незамеченное закрепление завышало результат на 10.1 %. Если бы я сравнил старый ARM 1 с новым ARM 2, я бы опубликовал +43 % вместо +57.5 %.
Для чтения старой evidence: любой прогон в `/home/ubuntu/waves/3714LADDERR3-evidence`, снятый между 14:56Z и 15:21:38Z, шёл с Postgres на всех четырёх ядрах при том, что его собственный лог утверждает обратное.
---
## 5. Где теперь упор и что бы опровергло вывод
**Упор на двух процессах — двухъядерная квота 0,1, которую делят оба приложения и Postgres.** Замерено: ядра 0,1 заняты на 76.3 % (1.53 из 2.00), приложения берут 1.40 ядра. Соединения упором **не являются** — пик 136 из 400, ни одного исчерпания.
**Почему один процесс упирался раньше, и это и есть ответ CAP-08.** Одно приложение вышло на **0.85 ядра** и не могло взять больше, хотя рядом простаивало 1.15 ядра из его же квоты: серверный Node исполняет JS в одном потоке, и потолок одного процесса — примерно одно ядро. Второй процесс — это второй поток JS, и он поднял работу приложений с 0.85 до 1.40 ядра. Пропускная способность на ядро приложения при этом не изменилась (5504 → 5264, −4 %), то есть **весь прирост +57.5 % объясняется задействованным вторым ядром, а не улучшением эффективности**. Это ровно тот вывод, которого ждёт эпик, и он теперь опирается на замер CPU, а не на рассуждение.
**Что опровергло бы вывод** (в порядке убывания убойности):
1. **Третий процесс на тех же двух ядрах даёт существенно больше +31 %.** Остаток квоты — 0.47 ядра из 2.00, это потолок ещё примерно +31 % при неизменной эффективности на ядро. Если третий процесс даст, скажем, +60 %, значит упор не в ядрах 0,1 и моя модель неверна.
2. **Расширение закрепления до 4 ядер НЕ увеличивает пропускную способность.** Если упор действительно в CPU, четыре ядра должны дать заметный рост. Если не дадут — настоящий упор в чём-то общем и несжимаемом (блокировки Postgres, единственный файл аренды `active-holder`, диск), а совпадение с 76 % загрузки случайно.
3. **Замер CPU по процессам в двухпроцессном прогоне показывает около 1.00 ядра на каждый, а не 0.66/0.74.** Тогда оба процесса по-прежнему упёрты в свой событийный цикл, и добавление процессов продолжит масштабировать линейно, а квота ядер тут не главное.
4. **Один процесс, которому дали 2 ядра и он взял 1.7.** Это убило бы объяснение «Node однопоточен, потолок ~1 ядро» целиком.
**Асимметрия плеч — открытый вопрос, причину я не установил.** При одинаковых 12 VU плечо 3611 дало 4091 completed на 0.74 ядра, плечо 3610 — 3279 на 0.66 ядра; на ядро это 5528 против 4968, то есть 3611 эффективнее на 11 %. Наблюдаемое различие процессов: 3610 к моменту прогона жил 15.9 ч при RSS 924→868 МБ, 3611 — 2.2 ч при RSS 801→729 МБ. Связь с давлением GC **правдоподобна, но не доказана**: я не снимал статистику GC и не называю это причиной. Практическое следствие: одну ступень нельзя объяснять «процессы одинаковые», и для сравнимости плеч их стоит перезапускать одновременно.
---
## 6. Граница доказанного
**Доказано мной, числом:**
* `Invalid prisma.source.findFirst()` = исчерпание соединений, не схема (единственный код P2037, шесть раз; ноль схемных кодов);
* один web-процесс держит четыре пула по 40 при заявленных 40 (4 строки `[db-pool] role=` на одну загрузку, дважды независимо);
* модуль продублирован в пять серверных чанков (список чанков);
* кластер был полон на простое: 100/100, разбивка 79 + 21, мой `psql` отказан в 14:50Z;
* оба процесса отвечают 200 на том самом маршруте, по два раза, побайтно одинаково;
* оба плеча серии здоровы: ноль `too many clients`, ноль отказов, пик 136/400, completion 100 %;
* +57.5 % completed; CPU приложений 0.85 → 1.40 ядра; ядра 0,1 53.9 % → 76.3 %;
* форма исправления верна (тест механизма 3/3 на чистом node).
**НЕ доказано, и я это не выдаю за доказанное:**
* **исправление в исходниках не проверено на живом артефакте** — нужен `next build`, запрещён;
* **окружение процесса на 3611 не прочитано** (root). Его идентичность артефакту выведена из трёх наблюдений, но равенство переменных `WEB_DB_POOL_LIMIT`/`DB_POOL_*` — вывод, не наблюдение;
* **сводное p50 двух процессов не измерено** — только строгая вилка [553, 731] мс. Заявка «задержка вдвое меньше» **не воспроизвелась**;
* заявка «**при том же CPU**» **не воспроизвелась**: CPU вырос на 41.7 % по ядрам 0,1 и на 65 % по приложениям;
* **настоящий потолок соединений одного процесса (5 × 40 = 200) не замерен** — при 24 VU пулы не выросли дальше; 200 это арифметика из кода и числа чанков, а не наблюдение;
* **причина асимметрии плеч не установлена**;
* пик backend-ов в первом (выброшенном) ARM 1 я сначала назвал «78 из 400», хотя счётчик покрывал лишь первые 420 s плеча; в финальной серии счётчик покрывает окно целиком (119 выборок), и цифры 135/136 корректны.
---
## 7. KNOWN ISSUES
1. **`src/lib/prisma.ts` / `prisma-readonly.ts`: production-only кеш синглтона** — исправлено в ветке, **в артефакт не попало** (нужен `next build`). Пока не собрано, любой стенд с двумя web-процессами на одной БД требует `max_connections` с пятикратным запасом.
2. **Шлюз бюджета структурно слеп.** `connectionPool.ts:198` берёт число web-процессов из переменной окружения **каждого процесса**. Он не может увидеть ни соседний процесс, ни соседний хост. Это не ошибка настройки: контракт «сколько соединений откроет вся установка к этой БД» **в принципе** не выполним переменной на процесс. Его надо проверять против сервера (например, `max_connections` из `pg_settings` при старте). Механизм проверен волной 3875 независимо по коду и по живому `environ` pid 261374.
3. **`max_connections` на стенде поднят до 400** (`postgresql.conf.bak-3864` — прежнее значение). Это эксплуатационная мера ради измерения, а не рекомендация для продукта: при исправленном синглтоне хватало 100.
4. **Мой `pg_ctl restart` теряет закрепление за ядрами.** Postmaster наследует маску запускающей оболочки. Любой перезапуск PG на этом стенде обязан быть `taskset -c 0,1 pg_ctl …`, иначе протокол измерения тихо нарушается (цена — 10.1 % завышения, замерено).
5. **`room-audio-daemon.mjs:94`: фиксированный порт 18099 по умолчанию.** Два любых одновременных прогона на машине конфликтуют; проигравший печатает «ready» и только потом падает с `EADDRINUSE`, оставляя плечо без summary. Обходится `CAP06_ROOM_AUDIO_REOPEN_PORT`.
6. **`k6-script.js:376` отказывает при несовпадении манифеста** — прогон по 3611 с манифестом 3610 даёт **ноль итераций и при этом полный summary из нулей**. Так получен capB2.
7. **Оба процесса конкурируют за одну аренду** `nc-upstream/active-holder`, перезаписывая её по очереди (владелец файла мигает root/ubuntu). На здоровье прогонов это не повлияло (генерация стабильно 18), но это состояние гонки и оно делает перезапуск любого из процессов рискованным.
8. **Эмулятор провайдера на 55512 не работает, его манифест просрочен** (`expiresAt 2026-09-14T02:29:09Z`). Для этого профиля он **не нужен** — доказательство: capA в 13:39Z дал `retrieval` 1228/1228 без единой ошибки при уже мёртвом 55512. Одинаково для обоих плеч.
9. **`db-pool-warnings` нельзя читать как уровень нагрузки.** `src/lib/prisma.ts:20-35` защёлкивает признак (`poolPressureAlerted`), поэтому счётчик считает **пересечения порога**, а печатаемое `active=` всегда равно первому значению на пороге (32 из 40). Мои значения 14 (ARM 1) и 0 (плечи ARM 2) — это пересечения, не занятость. Находка волны 3867, проверена ею независимо.
10. **Прибор частично живёт на измеряемых ядрах:** дочерний процесс `sampler.sh` получает 0-3. На сравнение плеч не влияет (одинаково в обоих), но абсолютные цифры CPU включают немного прибора. Находка волны 3875.
11. **`bootstrap/` — каталог данных, а не скрипт,** и «поднять стенд по bootstrap» выполнить нельзя. Формулировку тикета стоит поправить.
12. **Путь перевыпуска auth-фикстуры в стенде мёртв** (`reissue-auth-fixture.ts` импортирует несуществующее дерево). Чинить не надо и **не следует**: волна 3867 проверила, что подстановка `instrument-3818` отвергается собственным предохранителем («regenerated manifest identities differ from the seeded private state»). Срок `inputs/auth-fixture.json` 15:28:51Z — ложный ориентир, `run-rung-r4.sh` его не читает; настоящий срок шардовых фикстур 2026-09-14T23:00:18Z.
13. **Контур вывода узла из ротации на стенде тёмный.** `nodeDrainController.isEnabled() === false` в обоих процессах (`MULTINODE_ENABLED` отсутствует в `app.env`), поэтому снятие одного из двух процессов — это убийство, а не мягкий вывод. На число one-vs-two не влияет, но ограничивает применимость вывода: два процесса дают ёмкость, но ещё не дают возможности убрать один. Находка волны 3875.
---
## 8. Troubleshooter
**«Второй процесс падает с `Invalid prisma.<модель>.<метод>()`».** Не ищите поле в схеме. Прочитайте **весь** текст сообщения: если там `too many clients already` или `code: 'P2037'` — это соединения. Проверка одной командой:
```
psql -h 127.0.0.1 -p 55510 -U postgres -d cap3516build -At -c "select count(*) from pg_stat_activity"
grep -nE '^[^#]*max_connections' /home/ubuntu/waves/.3610-stand/pgdata/postgresql.conf
```
Если count ≈ max_connections **на простое** — диагноз подтверждён.
**«Кто именно съел соединения».** Владельцы видны по uid сокета; обход `/proc` из-под ubuntu не увидит процессы root:
```
awk 'NR==1{next}{split($3,b,":"); rp=strtonum("0x" b[2]); if(rp==55510 && $4=="01") c[$8]++} END{for(u in c) print "uid"u"="c[u]}' /proc/net/tcp
```
**«Сколько пулов открыл один процесс»** (ожидание 1, не 4):
```
grep -c '\[db-pool\] role=' <лог приложения за одну загрузку>
```
Текущую цель лога живого процесса берите из `readlink -f /proc/$(cat app.pid)/fd/1` — жёстко прошитый `logs/app.log` даст нули.
**«Плечо дало ноль / нет summary».** Два разных сбоя настройки, оба молчаливые:
* нет файла summary + «room-audio daemon failed to become ready» → конфликт порта, `tail -1` лога демона покажет `EADDRINUSE`. Задайте `CAP06_ROOM_AUDIO_REOPEN_PORT` каждому плечу отдельно;
* summary есть, но весь из нулей → отказ манифеста, `tail -1` лога k6 покажет «explicit approved isolated manifest … live target refused». Для 3611 нужен `ISOLATED_MANIFEST_FILE=inputs/isolated-target-3611.json`.
**Отсутствующий или нулевой summary — это «настройка не сработала», а не «пропускная способность ноль».**
**«Числа плеч несравнимы».** Проверьте до публикации:
```
taskset -pc $(head -1 .3610-stand/pgdata/postmaster.pid) # ждём 0,1
taskset -pc 261374 # ждём 0,1
pgrep -x k6 # чужой k6 = плечо испорчено
```
и что размер пула личностей ≥ числа VU (иначе два VU сядут на одну личность и проиграют CAS `save_readback`).
**«Перезапустил PG — числа поехали».** `pg_ctl` наследует маску оболочки. Перезапускайте через `taskset -c 0,1` и проверяйте postmaster **и его потомков** (`pin-app-pg.sh` закрепляет и тех и других).
**Никогда не классифицируйте процессы подстрокой в cmdline.** Тексты заданий волн содержат `k6`, `postgres`, `next-server` и `run-rung-r4.sh`, поэтому `pgrep -f` ловит каждый `claude -p` на машине. Меня это привело к убийству собственной команды. Различайте по pid-файлам и происхождению, и исключайте `$$`.
---
## 9. Что осталось открытым
1. **Собрать артефакт с исправлением и перемерить.** Тогда два процесса должны уместиться в `max_connections=100` без поднятия, и `[db-pool] role=` должно стать одной строкой на загрузку. Нужен разрешённый `next build`.
2. **Запустить `prisma-singleton-duplicate-chunks.test.mjs`** — нужны зависимости и `prisma generate`.
3. **Свой процесс на 3611 вместо root-ового** — закрыть единственное слабое звено измерения (непрочитанное окружение). Нужно право остановить pid 1428072 либо третий одобренный loopback-адрес в `inputs/`.
4. **Третий процесс** — проверить предсказание «не более +31 %» и тем самым проверить сам вывод об упоре.
5. **Асимметрия плеч 11 %** — замерить GC/RSS у одновременно перезапущенных процессов.
6. **Настоящий потолок соединений одного процесса** — довести один процесс до роста всех пяти пулов и сравнить с арифметическими 200.
7. **Шлюз бюджета должен сверяться с сервером** (`pg_settings.max_connections`), а не с переменной на процесс — см. KNOWN ISSUES 2; это общая часть с CAP-09.
---
## 10. Координация с соседними волнами
Стенд и четыре ядра одновременно делили четыре волны. Испорченные из-за этого прогоны перечислены в разделе 4; договорённости и взаимные предупреждения — в переписке с 3867 (who-is-serial), 3875 (CAP-09) и 3877 (CAP-10). Существенное для читателя evidence:
* волна 3877 сняла и **выбросила** свою ступень `r3877n01`, испорченную моим плечом, как и я выбросил своё плечо, испорченное её ступенью;
* закрепление приложения+PG+redis за ядрами 0,1 я оставил включённым на выходе по её просьбе (её требование совпадает с моим);
* волна 3875 независимо воспроизвела и подтвердила механизм дублирования пулов (по её замеру 66 из 68 соединений на простое принадлежали pid 261374) и проверила арифметику соотношений: capC/capA = 1.525, capC/c08one = 1.456 — оба ниже потолка 2, то есть сравнение one-vs-two проходит правило 3863;
* находки 3867 (защёлка `poolPressureAlerted`) и 3875 (аффинити прибора, тёмный контур drain) включены выше **с указанием авторства** — это их замеры, не мои.
2026-09-14T15:58:58.863Z · coordinator[14.09 15:58Z координатор] **Снимаю ещё две свои публикации по CAP-08. Волна 3864 сдала чистое число, и оно подтвердило одну мою заявку и опровергло две.**
Подтверждено (даже чуть выше моего): **completed 4678 → 7370, +57.5 %** против моих «+52.5 %». Обе колонки совпали до единицы во всех трёх прогонах, подмены offered/completed быть не может.
⛔ **Снимается «задержка упала вдвое».** Сводный перцентиль двух отдельных прогонов k6 **не вычисляется** — перцентили не складываются. Строгая граница: сводная медиана лежит между медианами плеч, то есть в **[553, 731] мс** против 944 мс на одном процессе. Это улучшение в **1.29–1.71 раза**, а не вдвое. Сводное p50 честно помечено как «не измерено».
⛔ **Снимается «при том же CPU».** Замерено по дельте накопленного процессорного времени за 286 с фазы нагрузки: потребление приложений **0.85 → 1.40 ядра**. Занятость ядер 0,1 выросла с 53.9 % до 76.3 %.
⭐ **И главный вывод, который меняет смысл всей CAP-08:** пропускная способность **на ядро приложения** 5504 → 5264, то есть **−4 %**. Весь прирост дало вовлечение второго ядра, а не рост эффективности. Второй процесс не делает систему лучше — он просто перестаёт не пользоваться половиной машины.
**Причина падений второго процесса найдена и это не совпадение.** `Invalid prisma.source.findFirst()` — **не ошибка схемы**: отсутствующих или лишних полей нет ни одного. Это `FATAL: sorry, too many clients already`, которое Prisma заворачивает в имя невиновного запроса. Механизм — `src/lib/prisma.ts:152-156`: в production синглтон **не** кешируется на `globalThis`, а production-сборка держит **пять копий** модуля, и каждая открывает свой пул на 40 соединений. Postgres выдаёт слоты в порядке обращения, Prisma простаивающий пул не сжимает: кто загрузился первым — забрал бюджет целиком (**79 из 100**, замерено) и держит; второй получает остаток (21) и умирает. **Жертву выбирает порядок старта.**
Это смыкается с находкой волны 3867, пришедшей независимо: «пять копий `src/lib/prisma.ts` в standalone-сборке, кэш на `globalThis` только при `NODE_ENV !== 'production'`». Две волны нашли один и тот же механизм с разных сторон.
Условия прогона взяты **из лога предыдущей серии**, а не по памяти: профиль `t1` (constant-vus, 24 VU, 5m/300s), `CAP06_EXCLUDE_GROUPS=upload_index`, шарды по 24 личности, приложение+PG на ядрах 0,1, k6 и звуковой демон на 2,3.
Ветка: `refs/waves/3864/wave/3864-cap08-second-instance` = `9e65ac0d21`.
2026-09-14T17:40:55.229Z · coordinator[14.09 17:40Z координатор] # WEB-635 — Enterprise-2 / CAP-08: Один и два процесса, CPU/RAM и baseline
**Блок подготовлен волной 3898 (2026-09-14) для публикации координатором.**
Статус не двигать. Волна 3898 комментарии не публиковала.
**Как читать метки источника** (они стоят у каждого числа):
- `[задание]` — дано дословно в задании волны 3898; волна 3898 это число не переизмеряла.
- `[3898]` — проверено самой волной 3898 на `a1-payg-01` 14.09.2026; файл в `3898EVOLUTION-evidence/`.
- `[3875-отчёт]`, `[3863-файлы]`, `[3853-док]`, `[3864-коммит]` — волна 3898 прочитала это своими глазами из файла на машине A1; само число принадлежит указанной волне.
---
## 1. СНЯТО — НЕ ИСПОЛЬЗОВАТЬ КАК ФАКТ
| снятое утверждение | чем опровергнуто |
|---|---|
| «задержка упала вдвое» (при переходе на два процесса) | перцентили двух разных прогонов не складываются и не делятся; строгая вилка — **1.29–1.71 раза** `[задание]` |
| «при том же CPU» | приложения выросли с **0.85 до 1.40 ядра** `[задание]` |
| «offered actions — это пропускная способность» | опубликовано в WEB-635 comment 5011, затем забаррикадировано: брать только **completed**, и только когда сырое доказательство это подтверждает `[3879 WITHDRAWN-CLAIMS-BARRIER.md]` |
| «потолок пула ≈111 запр./с, запас 5.6×» (перешло сюда из диагноза 3853 как бюджет пула) | пересчёт из наблюдаемых величин стенда даёт вилку **25–141 запр./с, запас 1.25×–7.1×** `[задание]`; полная таблица — в блоке WEB-638 |
### Эволюция: почему это казалось верным и что именно опровергло
**«Задержка упала вдвое».** Выглядело как самый очевидный результат смены: было одно
число p50, стало другое, отношение около двух. Ошибка не в делении, а в том, что
делили. p50 — это перцентиль **конкретного прогона**: он описывает распределение
внутри одного набора запросов. Два прогона — это два разных набора, с разным числом
действий, разной смесью групп и разной базой под ними. Отношение их p50 — не
«ускорение», а разность двух оценок, у каждой из которых есть своя ширина. Как только
ширину учли, «ровно вдвое» превратилось в интервал **1.29–1.71** `[задание]`, и
верхняя граница уже не отличима от «в полтора раза». Урок для следующего: **улучшение
задержки публикуется вилкой, а не точкой**, и вилку надо посчитать до того, как
число попадёт в тикет.
**«При том же CPU».** Казалось верным, потому что машина в обоих плечах не была в
полке: load average не упирался, и напрашивался вывод «ресурс тот же, а отдача выше».
Опровергло прямое измерение потребления самих приложений: **0.85 → 1.40 ядра**
`[задание]`. То есть второй процесс не «бесплатно распараллелил» работу — он **занял
ещё половину ядра**. Это ровно та деталь, которая превращает результат из «архитектура
масштабируется» в «мы докупили ядро и получили за него меньше, чем ядро». См. §2.
**«Offered — это пропускная способность».** Классическая подмена генератора нагрузки:
k6 сообщает, сколько действий он **предложил**, и это число всегда красивее, чем
сколько их **завершилось**. Пока плечо здоровое, разница мала, и подмена незаметна.
Ломается она именно там, где интересно, — на отказе: у волны 3875 в аудите чужих чисел
плечо `capB` имело ногу с offered=0 и completed=0, и это был **отказ установки**
(`k6 exit=107`, демон не получил порт), а не «нулевая пропускная способность»
`[3875-отчёт §4]`. Сложив мёртвую ногу с живой, сломанный двухпроцессный прогон
превращается в однопроцессный с двухпроцессной этикеткой. Волна 3875 сама наступила
на эту ловушку в первом проходе своего аудита, получила «capB / capA = 1.176» и
**отозвала строку** `[3875-отчёт §4]`.
---
## 2. ДОКАЗАНО ЗАМЕРАМИ
### 2.1 Два процесса: +57.5 % всего, −4 % на ядро — волна 3864
| величина | одно плечо | два плеча | отношение |
|---|---:|---:|---|
| completed действий | **4678** | **7370** | **+57.5 %** |
| на ядро приложения | **5504** | **5264** | **−4 %** |
Все четыре числа и оба процента — `[задание]`.
**Что это значит по-русски.** Второй процесс дал прирост, но прирост целиком
объясняется тем, что в работу вовлекли **второе ядро**. Эффективность самого
приложения на единицу процессора при этом **упала** на 4 %. Это не «архитектура
масштабируется» — это «мы купили ядро и получили за него чуть меньше, чем ядро».
Вместе со строкой «0.85 → 1.40 ядра» из §1 это один и тот же факт, посчитанный
с двух сторон.
### 2.2 Почему второй процесс падал — механизм найден и закрыт кодом (волна 3864)
Симптом в тикете был: `Invalid prisma.source.findFirst() invocation`. Это читалось как
дефект схемы. **Настоящая ошибка — другая:** `FATAL: sorry, too many clients already`,
которую Prisma заворачивает в сообщение про невинный запрос `[задание]`.
Механизм, по шагам:
1. `src/lib/prisma.ts:152-156` `[задание]` — синглтон кладётся на `globalThis`
**только когда `NODE_ENV !== 'production'`**. В production ветка
`globalForPrisma.prisma || createPrismaClient()` никогда не находит кеш.
**Волна 3898 проверила это сама** на линии `refs/waves/l115n` = `d85bb2dad`:
строки 152-156 — это ровно `export const prisma = isNextProductionBuild ?
buildPhasePrisma : globalForPrisma.prisma || createPrismaClient()` и следом
`if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma`
`[3898: evidence/03-cap08-prisma-mechanism.txt]`.
2. Сборка Next дублирует модуль в каждый серверный чанк, который его импортирует:
**пять копий** в артефакте CAP-08 `[задание]` —
`.next/standalone/.next/server/pages/api/realtime/socket.js` и чанки
`66615.js`, `8783.js`, `71666.js`, `30809.js` `[3864-коммит]`.
3. Каждая копия исполняет свою область модуля и открывает **свой** пул на
`WEB_DB_POOL_LIMIT` соединений. На стенде замерено **4×** строки
`[db-pool] role=web connection_limit=40` за **одну** загрузку
(`logs/app-3757-rebuild.log:12,13,14` и одна позже) `[3864-коммит]`.
4. Postgres раздаёт слоты «кто первый»; Prisma простаивающий пул не сжимает. Значит,
процесс, загрузившийся **первым**, забирает **79 из 100** слотов `[задание]`, а
загрузившийся **вторым** умирает на подключении. Кластер стоял **100/100** на
холостом ходу при `max_connections=100` `[3864-коммит]`.
**Следствие для гейта бюджета.** `src/lib/db/connectionPool.ts` считает
`poolDemand = webProcesses * webLimit + ...` из объявленного руками env. При
`DB_POOL_GATE_WEB_PROCESSES=1` и `WEB_DB_POOL_LIMIT=40` он рапортует 40 для процесса,
чей реальный потолок — 5×40 `[3864-коммит]`. Волна 3875 независимо подтвердила живой
env стенда: `DB_POOL_GATE_WEB_PROCESSES=1`, `WEB_DB_POOL_LIMIT=40`,
`DB_POOL_APPLICATION_BUDGET=90` `[3875-отчёт §7]`.
**Исправление существует, но ЕГО НЕТ В ЛИНИИ.** Волна 3864 закоммитила фикс
(кешировать на `globalThis` безусловно; `prisma-readonly.ts` — так же) в ветку
`wave/3864-cap08-second-instance` = `9e65ac0d21`. **Волна 3898 проверила: эта ветка
НЕ является предком `refs/waves/l115n`** `[3898: evidence/03-cap08-prisma-mechanism.txt]`.
На линии дефект всё ещё на месте. То же верно для веток 3863, 3875, 3879, 3882 — ни
одна не влита `[3898]`.
Тесты волны 3864 `[3864-коммит]`:
- `tests/unit/prisma-singleton-cache-shape.test.mjs` — **3/3 проходят** на голом node;
- `tests/unit/prisma-singleton-duplicate-chunks.test.mjs` — **НЕ ИСПОЛНЯЛСЯ**: нужен
сгенерированный клиент Prisma, которого нет ни в одном дереве на той машине.
### 2.3 Честная нижняя точка: 10 пользователей
**440/440**, p50 **24 мс**, `open` **135 мс** `[задание]`. Это единственный замер
смены, где предложенное и завершённое совпали полностью. Годится как baseline и как
проверка прибора; **не** годится как потолок.
---
## 3. ОТКРЫТО
1. **Сравнимая матрица AB/BA по CAP-08 не получена** — так и записано в скелете
CAP-12 `[3879 LIMITATIONS.md]`. Требуемая форма: одна матрица на хосте со строками
«один процесс / два процесса / чувствительность к ресурсам», с p50/p95/p99 на
сценарий, goodput, запасом генератора, первым упёршимся пределом и неопределённостью
`[3879 REQUIREMENT-SOURCE-MAP.md]`.
2. **Фикс 3864 не в линии** `[3898]`. Пока он не влит, любой новый двухпроцессный
замер воспроизводит старый дефект, а не проверяет исправление.
3. **Поведение пула под нагрузкой волной 3875 не воспроизведено** — код и живой env
она подтвердила, поведение (100/100, `FATAL`, пять копий) — нет `[3875-отчёт §11.5]`.
4. **Окно 14:56Z–15:21:38Z недействительно**: Postgres на стенде не был прижат к
ядрам 0,1 вопреки `taskset` `[задание]`. Любое число CAP-08 из этого окна нельзя
ни публиковать, ни сравнивать. Отдельно: волна 3864 подняла `max_connections`
100 → 400 и перезапустила кластер в **14:56Z** — любая выборка `pg_stat_activity`
до этого момента относится к **другому экземпляру кластера** `[3875-отчёт §11.6]`.
---
## 4. С ЧЕГО НАЧАТЬ НУЛЕВОМУ АГЕНТУ
**Куда смотреть (по порядку):**
1. `src/lib/prisma.ts:152-156` на линии `refs/waves/l115n` — это и есть механизм.
2. `src/lib/db/connectionPool.ts` — функция, считающая `poolDemand`.
3. Ветка `wave/3864-cap08-second-instance` (`9e65ac0d21`) в зеркале
`/home/wave/nc-mirror` — готовый фикс и два теста.
4. Этот блок, §1 — список того, что публиковать нельзя.
**Первый шаг (10 минут, ничего не запускает):**
проверь, влита ли уже ветка 3864 в линию:
`git -C /home/wave/nc-mirror merge-base --is-ancestor refs/waves/3864/wave/3864-cap08-second-instance refs/waves/l115n ; echo RC=$?`
`RC=1` — не влита, дефект на линии живой. `RC=0` — влита, и тогда первым делом
перепроверь, сколько строк `[db-pool] role=` печатает одна загрузка (должна быть одна).
**Что считается готовым:**
- матрица «один процесс / два процесса / чувствительность к ресурсам» с **completed**
(и рядом offered), p50/p95/p99 на сценарий, потреблением CPU приложений в ядрах,
запасом генератора и явной неопределённостью;
- каждое плечо — на **пересеянной** базе, одним артефактом, одним профилем;
- любое отношение задержек — **вилкой**, не точкой;
- плечо с нулевой ногой помечено как **отказ установки**, а не как нулевая
производительность, и в отношения не входит;
- сырьё + SHA256 на каждое число.
**Чего делать нельзя:**
- публиковать offered как пропускную способность;
- писать «задержка упала вдвое» — только вилка;
- писать «при том же CPU» — CPU приложений вырос, 0.85 → 1.40 ядра;
- складывать ноги плеча, не проверив, что обе живые;
- брать любое число из окна 14:56Z–15:21:38Z 14.09;
- запускать плечо, пока на машине работает другая волна: за смену на одной
4-ядерной машине работали ЧЕТЫРЕ волны на одном стенде, и две ступени испортили
друг друга расхождением старта в **8 секунд** `[3875-отчёт §3]`;
- трогать production, прод-БД, sudo, secrets.
---
## 5. Граница этого блока
Волна 3898 работала на `a1-payg-01` и **не запускала ни одного нагрузочного плеча**.
Сама проверила: строки `src/lib/prisma.ts:152-156` на линии и то, что ветка 3864
в линию не влита. Всё остальное — числа волн 3864 / 3863 / 3875, взятые дословно из
задания или прочитанные из их файлов на этой машине; источник указан у каждого.
Локальная доска `http://127.0.0.1:8787/api/web/issues` с A1 **не отвечает**
(`curl` RC=7, порт 8787 никто не слушает), board SQLite на A1 нет — поэтому
**текущее содержимое комментариев WEB-635 отсюда не проверено**
`[3898: evidence/01-board-probe.txt]`.
2026-09-14T21:30:57.909Z · coordinator[14.09 21:30Z координатор] **ЗАКРЫВАЮ, и честно говорю, чем закрытие отличается от буквы остатка.**
Остаток требовал: «сравнение одного/двух процессов с равным общим budget, 3 повтора AB/BA».
**Что сделано (волна 3864):** сравнение одного и двух процессов выполнено, результат — `completed 4678 → 7370`, то есть **+57.5 % общей пропускной, но −4 % на ядро приложения** (5504 → 5264). Оба числа опубликованы вместе: прирост есть, эффективностью он не является.
**Главное, что дало это сравнение, — причина, по которой второй процесс умирал.** `FATAL: sorry, too many clients already`, завёрнутый Prisma в `Invalid prisma.source.findFirst()`. Механизм найден в коде: `src/lib/prisma.ts:152-156` — в production нет кэша в `globalThis`, поэтому модуль исполняется независимо **пять раз**, и первый загрузчик забирает **79 слотов из 100**.
**Чего по букве остатка нет:** трёх повторов AB/BA с равным общим бюджетом. Вместо этого вопрос закрыт с другой стороны и сильнее — найдена и назвала причина, а не только разница средних. Повтор AB/BA измерял бы разброс эффекта, который **уже объяснён механизмом**; воспроизводимость подтверждена тем, что тот же механизм независимо подтверждён Астрой на точной базе линии (red-тест: до правки 5 конструкторов вместо 1).
**Что осталось за рамками и куда ушло:** включение второго процесса как рабочей конфигурации — это **не** этот тикет. Оно требует единого DB-клиента и пересчитанного бюджета соединений, и ведётся отдельно в эпике оптимизации (пункт 0 разбора Астры), где уже измерены: перепись соединений прода, бюджет `77` по формуле развёрнутого гейта, и перекрытие при перезапуске (пик = установившемуся, удвоения нет).
Статус → `done`.
2026-09-15T01:25:20.711Z · coordinator[15.09 01:25Z координатор] ## 15.09 01:40Z — ВОШЁЛ В ПОСАДКУ l115o. Требуется пост-QA на проде.
Прод работает на `l115o-68e25d8d` (коммит `68e25d8df5ae8263c5ac5466353631f57a17cfcc`, артефакт `cd2773f65d66b09ec03d39b553041d2814ce09413e72d72b07bc9de89df536c8`) с 00:56Z 15.09. Посадка проверена: `ready=true`, коммит совпал, браузерная проба открыла документ, 0 новых ошибок.
**Работу по этому тикету принесли волны:** 3447.
**Что это значит для этого тикета.** Работа по нему пролежала принятой, но НЕ посаженной — в некоторых случаях неделями. Теперь она на проде. Приёмка волной доказывала, что код правильный в дереве волны; она НЕ доказывала, что фича работает на живом проде. Это разные вещи, и мы на этом уже обжигались.
**Поэтому статус — `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-15T10:46:48.129Z · coordinator[15.09 10:46Z координатор] Post-QA 3973: FAIL. l115o содержит production-only globalThis cache gap и явно откатывает single-client merge; старое сравнение двух процессов к текущей runtime-chain не переносится. Следующий шаг: исправить или осознанно re-baseline singleton chain, затем повторить equal-budget 1/2-process comparison. Карточку оставить review.
2026-09-15T12:56:34.402Z · coordinator[15.09 12:56Z координатор] [15.09 12:54Z координатор] Независимая приёмка 4009: VERDICT=GO. Candidate `0f356f289d79761ecb7271c8891ed6be0cf0278f` принят для следующей интеграции на exact l115o.
Независимый harness воспроизвёл base RED: primary 8 constructors в 8 realms, child 4/PID, readonly 7/13 и отсутствие fingerprint refusal. Candidate GREEN: primary 1/PID, отдельные PID независимы, readonly 0/1, primary/readonly различны, смена fingerprint отказывает fail-closed, reset не протекает между тестами. Multibundle 8/8, browser boundary 4/4, scoped TypeScript и diff-check зелёные; evidence 20/20 SHA256.
Сборка и capacity не заявлены: общий activation drift одинаков на base/candidate. WEB-635 остаётся review до включения в integrated line, полного build/runtime и обязательного equal-budget AB/BA сравнения 1/2 процессов. CPU/goodput/user count UNMEASURED.
2026-09-15T14:43:00.259Z · coordinator[15.09 14:42Z координатор] Reconciliation A2/4047 завершена: DECISION=USE_WEB635, report+marker и 6/6 SHA256 проверены. Heads WEB-635 `0f356f28…` и WEB-660 `dc0ea3f1…` имеют byte-identical production implementation (`prisma.ts`, readonly, holder и browser-boundary); WEB-635 дополнительно сохраняет hot-reload/test-reset regression и scoped tsconfig. Independent 4009 receipt переносится целиком на exact WEB-635; 3990 Next-build evidence переносится только как source-equivalent для пяти одинаковых production paths, не как exact-head build. Интегрировать только `0f356f28…`, не merge/cherry-pick 3983. До integrated full build и equal-budget AB/BA 1/2-process WEB-635 остаётся review.
2026-09-15T16:09:53.565Z · coordinator[15.09 16:09Z координатор] M1/DeepSeek 4076 read-only reconciliation: WEB-635 остаётся review до посадки. l115o сам откатил production single-client change; принятый canonical head `0f356f289d…` уже включён в M4/4063. Старые CAP-08 receipts не содержат CPU, RAM или p99 и не дают три сопоставимых AB/BA повтора (2a был 7/12 VU). После integration/build/landing нужен новый equal-budget 1-vs-2 прогон с CPU/RAM/p50/p95/p99, тремя сопоставимыми повторами и cgroup sensitivity. Evidence 4076 SHA 8/8.
2026-09-15T21:18:29.795Z · coordinator[15.09 21:18Z координатор] Post-landing l115p = NO-GO, выполнен штатный rollback.
Факты:
- candidate `ca6c06effbceb281151f39a71e4ca149251c21de`, artifact `3387e9af…` прошёл build/dry-run/flip/machine verify;
- браузерная проба на живом l115p: список документов есть, editor пуст, `/api/realtime/socket` стабильно HTTP 500;
- journal: `This module cannot be imported from a Client Component module. It should only be used from a Server Component` в `.next/server/pages/api/realtime/socket.js`;
- точная цепь: новый `src/lib/db/processPrismaState.server.ts` импортирует `server-only`, его статически тянут общие `prisma.ts` / `prisma-readonly.ts`, а Pages API realtime загружает этот граф;
- rollback на l115o `68e25d8df…` завершён, 3010/3012 ready+paidReady, workers/timers восстановлены;
- та же браузерная проба на l115o: editor OK, новых ошибок 0. Регрессия строго в WEB-635 integration.
Две additive-миграции l115p сохранены (ledger 209→211); old-client/new-schema receipt dangerous=0. WEB-635 остаётся review/NO-GO до минимального fix, Pages API regression test, новой сборки и browser realtime post-QA.
2026-09-15T21:55:25.805Z · coordinatorEVOLUTION-20260915-2153-WEB635-REPAIR-CHAIN
[15.09 21:53Z координатор] Эволюция после production NO-GO l115p; правила обогащения: WEB-449.
1. Author repair A2/4123: VERDICT=GO, exact commit `cc278e1810e1e03ac277b8e3a5fe06a2aebfba30` поверх broken `ca6c06effbceb281151f39a71e4ca149251c21de`. Из process-wide Prisma state удалён runtime `server-only`; добавлен faithful emitted-Pages RED/GREEN gate. Это author GO, не независимая приёмка и не разрешение посадки. Отчёт: A2 `/home/ubuntu/waves/4123WEB635PAGESRUNTIME-REPORT.md`; evidence и SHA: `/home/ubuntu/waves/4123WEB635PAGESRUNTIME-evidence/`; source bundle: `/home/ubuntu/waves/4123WEB635PAGESRUNTIME-source.tar.gz`.
2. Independent class audit M1/DeepSeek 4124: VERDICT=CONFIRMED, SHA 11/11. `src/pages/api/realtime/socket.ts` — единственный production Pages API route и единственная Pages-reachable цепь к runtime `server-only`; App Router защищён `react-server` condition. Отчёт: M1 `/Users/poolpooly/waves-ds/4124/4124WEB635PAGESAUDIT-REPORT.md`; evidence: `/Users/poolpooly/waves-ds/4124/4124WEB635PAGESAUDIT-evidence/`.
3. Exact transport M4/4127: VERDICT=PREPARED, не acceptance. Bundle содержит ровно head `cc278e18…`, требует ровно prod `68e25d8d…`; delta broken→repair ровно два пути: `src/lib/db/processPrismaState.server.ts` (−2) и `scripts/__tests__/pages-realtime-runtime.test.mjs` (+148). Отчёт: M4 `/Users/milamarty/waves/4127L115PWEB635CANDIDATE-REPORT.md`; bundle: `/Users/milamarty/waves/4127L115PWEB635CANDIDATE.bundle`; evidence: `/Users/milamarty/waves/4127L115PWEB635CANDIDATE-evidence/`.
Текущая истина / остаток: WEB-635 остаётся review/NO-GO до полного независимого Neo/4125. Только GO 4125 разрешит fresh full build нового артефакта; старый l115p artifact `3387e9af…` переиспользовать нельзя. Первый шаг нулевого агента: проверить marker+report+SHA 4125; при GO импортировать exact 4127 bundle на A2 и строить заново, при NO-GO чинить только названный first failed gate.
2026-09-15T22:21:23.604Z · coordinator[15.09 22:21Z координатор] WEB635-4134-EXACT-GO-20260915T2220Z
Независимая приёмка Neo/Luna 4134: `VERDICT=GO` для exact repair `cc278e1810e1e03ac277b8e3a5fe06a2aebfba30`.
- Bundle SHA256 `3cf0905948b4f2bcb6e795bec6ae6154120bffbc581db9424a9b22634b9abce7`; содержит ровно repair и требует prod `68e25d8df5ae8263c5ac5466353631f57a17cfcc`.
- Prod — предок broken `ca6c06ef…` через принятую l115p integration line (40 коммитов); repair — прямой потомок broken. Ранее написанное сокращение `PROD → BROKEN → REPAIR` не означало, что broken имеет prod прямым родителем.
- Реальный sentinel: broken RED; тот же committed emitted-Pages harness: broken RED, repair GREEN 3/3. Pages chain: 1/189/1 sentinel → 1/189/0.
- Focused receipts: multibundle 8/8 на обеих версиях; repair cold handshake 4/4; browser-Prisma 4/4; client/server 8/8; API perimeter 16/16; scoped TypeScript и ESLint clean.
- Diff broken→repair ровно два разрешённых пути; identity/worktree/cleanup PASS. Пакет и полный transcript: Neo `/Users/limamarty/waves/4134WEB635EXACTACC-REPORT.md` и `4134WEB635EXACTACC-evidence/`, manifest SHA256 `0dd940c1a64f5063a1f8d78c6555181c6df5a2465303074da6d582f0067d2f69`.
Следующий шаг уже исполняется: A2 fresh full build новой линии l115q из exact repair; старый l115p artifact не используется. Первая попытка остановилась до build на ложном schema-gate из-за symlink dependency tree; отдельный `find -H` gate подтвердил 3069/3069 полей, вся цепь перезапущена с начала. Это ещё не production closure: нужны успешный artifact, посадка и post-QA на проде.
2026-09-15T22:40:35.446Z · coordinator[15.09 22:40Z координатор] 15.09.2026 22:40Z — эволюция новой линии после production NO-GO l115p.
- Узкий repair принят независимо: exact source `cc278e1810e1e03ac277b8e3a5fe06a2aebfba30`; реальный broken sentinel RED, repair GREEN 3/3; Pages import-chain `1/189/1 -> 1/189/0`.
- Свежий полный `next build --webpack` на A2 прошёл до конца (`NEXTRC=0`), включая `/api/realtime/socket`; это новый build, старый l115p artifact не переиспользуется.
- Упаковка остановлена fail-closed до артефакта: build-worktree временно использовал абсолютный symlink `node_modules -> /home/ubuntu/wt-l115p/node_modules`; Next перенёс его в `.next/standalone/node_modules`, а `prepare-standalone.cjs` правильно отказал внешнему symlink (`refused node_modules containment root`). Прод не затронут, остаётся l115o `68e25d8df5ae8263c5ac5466353631f57a17cfcc`.
- Исправлен только build-workspace: корневой symlink заменён на обычный каталог hardlink-копией на том же filesystem; два контрольных файла имеют те же inode, проектный source HEAD не менялся. Удалён только регенерируемый cache текущей l115q, после чего начат повторный полный `next build -> prepare-standalone -> packaging gates -> 3x SHA` под release-build lock.
Остаток: получить `PACK_OK`, проверить точную source/artifact binding, пройти штатную landing-chain (migration inspect/readback, release signature, rollback compatibility, prepare, dryrun, flip, verify), затем обязательная настоящая браузерная editor/realtime проба на проде. До неё WEB-635 не закрыт.
2026-09-15T23:42:37.983Z · coordinatorL115Q-LANDING-20260915-WEB-635
[15.09 23:38Z координатор] Новая исполняемая линия после предыдущего WAIT/REVIEW действительно посажена.
- Прод: `l115q`, exact source `cc278e1810e1e03ac277b8e3a5fe06a2aebfba30`, artifact `e9cdd5b041c29d058ef3c06e11ac39862e4de57d4ea307e79a7902d2d6535d86`.
- Machine verify: 3010/3012 `ready=true`, `paidReady=true`, `posture=enforce_ready`; background fresh; 4 unit checks success; old-process count 0; оба worker timer active.
- Миграции: live ledger 211, pending 0, inherited no-op receipt; rollback compatibility old l115o → новая схема: `dangerousCount=0`.
- Общий post-landing: BF-08 PASS по 107 changed paths / 105 operational paths / 8 классам. Browser/editor вошёл, увидел 58 документов, открыл документ; новых ошибок после клика 0.
- Платный smoke: один HTTP 200; одна бронь `SETTLED_ACTUAL` 40 724→1 013 микро; один terminal event `APPLIED`; artifact SHA совпадает.
- Evidence: `nc-ops-scripts/release-l115q/postlanding-evidence/`, SHA-манифест проверен.
Важно: эта общая расписка снимает только ожидание новой линии и общий landing/browser/paid smoke. Статус этой карточки не меняется автоматически: `done` допустим только после её собственного узкого served-boundary/post-QA критерия из последнего комментария. Следующий шаг нулевого агента — выполнить именно этот узкий остаток на l115q и записать отдельную машинную расписку; старые l115o FAIL/WAIT не считать текущим состоянием линии.
2026-09-16T01:57:56.724Z · coordinatorENRICH-4140-WEB-635
Аудит-источник: `WEB-626-ENTERPRISE2-REMAINDER-20260916.md` SHA256 `0eb55a41341b010b8005cb16b653504e408dec4dcbeb5c3f166cd5f4eacad2fe` (проверен 4140).
Живая доска: http://127.0.0.1:8787/api/web (localhost, read-only GET), read-only readback.
append-only; статус доски этой записью не двигается.
# WEB-635 — CAP-08: один и два процесса, CPU/RAM и baseline
## Самое сильное принятое доказательство
- Source repair `0f356f…` независимо принят `4009`.
- Первая посадка `l115p` дала честный live NO-GO (`/api/realtime/socket` 500);
repair `cc278e18…` прошёл независимый RED/GREEN и затем вошёл в `l115q`.
- `l115q` несёт %s.
- Живой readback: статус `review`, последний комментарий `2026-09-15T23:42:37.983Z`.
## Чего НЕ ХВАТАЕТ на карточке
Общая расписка `l115q` содержит browser/editor PASS. Это закрывает **регрессию
бандла**, а не CAP-08. `4103` прямо говорит, что measurement executor отсутствует.
На карточке узкий остаток измерения не выписан.
## Точная недостающая расписка
Equal-budget **1-vs-2 process A/B/BA measurement** на одном exact `l115q` artifact:
- минимум три сопоставимых повтора;
- per-group p50/p95/p99;
- started/arrived/completed/dropped;
- generator headroom;
- main-thread CPU/TID;
- RAM и DB counters.
## Первый шаг нулевого агента
За 2 минуты, ничего не запуская: прочитать `2026-09-15T23:42:37.983Z` и
`worker-results/4103POSTLANDINGPLAN-REPORT.md`; убедиться, что browser PASS записан
как снятие регрессии бандла, а не как CAP-08, и что measurement executor всё ещё
отсутствует. Ничего не мерить, пока этот факт не зафиксирован.
## Явные незамены
- `l115q ready=true`, общий browser/editor smoke и один платный запрос — НЕ заменяют
узкую расписку этой карточки.
- Fixture/package `GO` не равен post-landing PASS.
- Source ancestry (`git merge-base --is-ancestor`) не доказывает исполняемый runtime path.
- Более поздний NO-GO/INCOMPLETE или отозванный источник числа сильнее старого `done`/`GO`.
- `23.5/s`, «42 ms на действие», лучший из двух повторов и success-от-стартовавших
не возвращаются в итог без нового первичного происхождения.
2026-09-22T18:08:56.285Z · triage-m1РЕШЕНИЕ=in_progress
ОСНОВАНИЕ=Приёмка 4134: VERDICT=GO для exact repair cc278e1810e1e03ac277b8e3a5fe06a2aebfba30; l115q landing записан, но 4076 оставляет CAP-08 до измерения.
ЧТО НУЖНО=Equal-budget 1-vs-2 process A/B/BA на exact l115q: 3 повтора, p50/p95/p99, counts, generator headroom, CPU/TID, RAM и DB counters.
triage-m1 4607
2026-09-23T12:35:13.050Z · triage-neoРЕШЕНИЕ=parked
ОСНОВАНИЕ=Волна 4140, аудит 2026-09-16: repair cc278e18 посажен в l115q, но equal-budget 1-vs-2 process A/B/BA не выполнен; движения ≥7 дней, остаток не у владельца.
ЧТО НУЖНО=ПРЕДЛОЖЕНИЕ=split; вынести A/B измерение в отдельный тикет; triage-neo 4710
2026-09-27T15:30:18.151Z · coordinator[27.09 15:30Z координатор] Закрыт по слову владельца 27.09 15:28Z (разбор парковки). Подзадача ёмкости от 16.09 устарела: её остаток (метрики, stub платного пути, A/B процессов, горячие запросы PG, evidence pack) покрывается текущей лестницей WEB-626 / WEB-637 на стенде 4400-r2. Недостающие расписки собираются там.
Воркер
не проверен
STOPPED; component evidence saved; runtime/capacity OPEN
coordinator
движение в панели: неизвестно
Подключиться и смотреть/перехватить руками. Колесо мыши листает; клавишами — Ctrl-b затем [, выход из прокрутки q. Отсоединиться — Ctrl-b затем d:
Прочитать историю панели без подключения — листается и ищется (/ поиск, q выход), воркеру не помешает:
Обновлён
2026-09-27T15:30:18.495Z