WEB board

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

Enterprise-2 / CAP-11: PostgreSQL: реальные горячие запросы

Закрыт P2 ведёт: — эпик: WEB-626
Суть
ENTERPRISE2-R1:CAP-11
Правила обогащения: WEB-449. Эпик: Enterprise-2.

Цель и сдача: 3–5 custom pgbench query families на клоне, планы и SQL/IO/locks/WAL stats.

Работа: Выбрать запросы по CAP-08 total time/latency; сохранить predicates/joins/limits. Concurrency 1/5/10/20/50; не одновременно с app measurement. Без prod reset/restart/default pgbench init. Не переносить sanitized SQL с PII в публичные отчёты.

Зависимости: CAP-02, CAP-04, CAP-08.
Исполнение: measurement-db.

КАРТА ДОКУМЕНТОВ
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-3406
На 2026-09-10T21:38:23.260126+00:00: active; M4, волна 3406 cap-db-query-prep. Exact BASE 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4. Бриф прошёл brief-guard; входы SHA256 и наличие BASE проверены до постановки. Входы /Users/milamarty/waves/inputs/3406-cap-db-query-prep. Отчёт ожидается /Users/milamarty/waves/3406CAPDBQUERYPREP-REPORT.md. Живой процесс модели подтверждён; возраст журнала 1 с. Source/подготовка и независимая проверка — в объёме брифа; это не посадка, не live/capacity GO и не окончательное закрытие security.

REFILL-20260910-2155-3422
Снимок 2026-09-10T21:54:52.066911+00:00. Волна 3422 / acc-cap-db-query-kit на A2: active. Оператор Codex, модель gpt-5.6-luna. Живой процесс подтверждён; возраст журнала 0 с. Входы /home/ubuntu/waves/inputs/3422-acc-cap-db-query-kit; SHA256 и exact BASE dcb3d0cb5a1e3069e552649b65e812b82cc3896d проверены при доставке. Новых посадок и измерений capacity нет; source QA не означает окончательное закрытие Security.

SLOTS12-BATCH3442-3461-WEB-638
2026-09-10T22:50:05.652264+00:00 Лимиты A1/A2 подняты до12; действовавшие worker panes сохранены. Волна 3448 a1nc active acc-db-query-hardening-final; BASE 698faea3d3be65197f5182b426522efa60e3a912; model gpt-5.6-luna
Волна 3454 a2nc active db-query-pg-parser; BASE 698faea3d3be65197f5182b426522efa60e3a912; model gpt-5.6-luna
Брифы прошли guard; входные report/history/raw/bundle переданы и SHA проверены до очереди. Source/QA scope, посадки и measured capacity не заявляются.

REFILL3472:WEB-638
2026-09-10T23:16:44.018057+00:00
Волна 3464 a2nc active; cap-query-tombstone-repair; BASE c1015e6dc1a7e624f5ed259ecc3e280d30a7e63f
Эволюция: предыдущая партия сдана; обнаруженные отказы CAP12/CAP07/CAP11 и route typecheck переданы в исправление3462–3465. Новые transport/auth/stop/node-plan части переданы на независимую проверку3466–3469. WEB593 и QA-изменения Drive проверяются3470/3471. M1 сверяет локальную топологию3472. Брифы и входные SHA проверены. Source GO не означает посадку.
Документы: /Users/annakorin/nc-ops-scripts/SHIFT-GOAL-20260911-8H.md; /Users/annakorin/nc-ops-scripts/enterprise2-20260910/ENTERPRISE-2.md. Доказательства: /Users/annakorin/nc-ops-scripts/refill-20260911-0015/live.json и каталоги a2nc/m4/neo с receipt и полными входами. Ссылки: https://bugs.wool2.online/web

CHECKPOINT3478:WEB-638 2026-09-10T23:30:08.650195+00:00
3464 авторское исправление SQL template tombstone: 5 prepared EXPLAIN без ANALYZE и 25 реальных SELECT на disposable PG, исходный reproduction3454 сохранён. Независимая 3477 запущена A1. Боевой route не менялся; это не реальные hot-query измерения.
Полные отчёты и проверки SHA: /Users/annakorin/nc-ops-scripts/checkpoint-3478/snapshot.json; задания и SHA inputs: refill-20260911-0015,0025,0028,0030. Спецификация: /Users/annakorin/nc-ops-scripts/enterprise2-20260910/ENTERPRISE-2.md; цель: SHIFT-GOAL-20260911-8H.md.

CHECKPOINT3483:WEB-638 2026-09-10T23:42:32.159576+00:00
3477 independent GO: HEAD f5d5be2dd9c2b8799f11b1d08aa920914e2ad860; 5 prepared plans без ANALYZE, 85 SELECT с 12 persisted/metadata tombstone states, inherited 20/20; own PG runner 1/1 и cleanup. Боевой route/auth не менялся. Исходные PG runners не стартовали из-за /tmp ENOENT; собственный private-cluster runner пройден. Реальные hot queries/capacity и release pending.
Доказательства: /Users/annakorin/nc-ops-scripts/checkpoint-3483/snapshot.json и полные *-REPORT.md; доставка: capacity-integration-3480, refill-20260911-0040, refill-20260911-0046. Норматив: enterprise2-20260910/ENTERPRISE-2.md.

## HANDOFF-20260912T1721:WEB-638 — точка входа для нового агента
Правила обогащения: [[WEB-449]]. Наблюдение: 12.09.2026 18:21:36 Europe/Dublin / 17:21:36 UTC. Автор: координатор. История выше сохранена; эту запись читать как текущий handoff на указанное время.

**Задача.** Enterprise-2 / CAP-11: PostgreSQL: реальные горячие запросы

**Что подтверждено и что остаётся.** Сводный последний результат: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-подготовка от этого не зависит.

**Следующая операция.** Снять планы и pg_stat_statements/activity на изолированной БД в том же run; сопоставить schema/index/data fingerprints, counter deltas и контр-замер. DB microbench не совмещать с app capacity.

**Исполнитель и приёмка.** Исполнитель Enterprise; координатор отвечает за выделенное окно и штатный runner. 3532 на Neo — назначение, живой старт пока не подтверждён. Независимый приёмщик получает frozen manifest и raw evidence.

**Условие закрытия.** DB bottleneck/headroom доказан контр-замером и планом; schema/index/data fingerprints совпадают. Рекомендации имеют измеримое ожидание и отдельные remediation tickets. Общий итог и зависимости: [[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-638
Enterprise-2 / CAP-11: PostgreSQL: реальные горячие запросы

Срез перед остановкой 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. Исторические отчёты/отказы сохранены.

ИНДИВИДУАЛЬНЫЙ ОСТАТОК
Выбрать 3–5 горячих SQL по app measurements, затем отдельное pgbench-окно на клоне, планы/IO/locks/WAL; не параллельно app measurement.
Следующая смена начинает только после возобновления владельцем. Исторический status сохранён; STOPPED означает отсутствие выполняемой работы, не закрытие задачи.
Чем закрывается (приёмка)
DB bottleneck/headroom доказан контр-замером и планом; schema/index/data fingerprints совпадают. Рекомендации имеют измеримое ожидание и отдельные remediation tickets.
Доказательства

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

REFILL-20260910-2155-3422
Снимок 2026-09-10T21:54:52.066911+00:00. Волна 3422 / acc-cap-db-query-kit на A2: active. Оператор Codex, модель gpt-5.6-luna. Живой процесс подтверждён; возраст журнала 0 с. Входы /home/ubuntu/waves/inputs/3422-acc-cap-db-query-kit; SHA256 и exact BASE dcb3d0cb5a1e3069e552649b65e812b82cc3896d проверены при доставке. Новых посадок и измерений capacity нет; source QA не означает окончательное закрытие Security.

SLOTS12-BATCH3442-3461-WEB-638
2026-09-10T22:50:05.652264+00:00 Лимиты A1/A2 подняты до12; действовавшие worker panes сохранены. Волна 3448 a1nc active acc-db-query-hardening-final; BASE 698faea3d3be65197f5182b426522efa60e3a912; model gpt-5.6-luna
Волна 3454 a2nc active db-query-pg-parser; BASE 698faea3d3be65197f5182b426522efa60e3a912; model gpt-5.6-luna
Брифы прошли guard; входные report/history/raw/bundle переданы и SHA проверены до очереди. Source/QA scope, посадки и measured capacity не заявляются.

REFILL3472:WEB-638
2026-09-10T23:16:44.018057+00:00
Волна 3464 a2nc active; cap-query-tombstone-repair; BASE c1015e6dc1a7e624f5ed259ecc3e280d30a7e63f
Эволюция: предыдущая партия сдана; обнаруженные отказы CAP12/CAP07/CAP11 и route typecheck переданы в исправление3462–3465. Новые transport/auth/stop/node-plan части переданы на независимую проверку3466–3469. WEB593 и QA-изменения Drive проверяются3470/3471. M1 сверяет локальную топологию3472. Брифы и входные SHA проверены. Source GO не означает посадку.
Документы: /Users/annakorin/nc-ops-scripts/SHIFT-GOAL-20260911-8H.md; /Users/annakorin/nc-ops-scripts/enterprise2-20260910/ENTERPRISE-2.md. Доказательства: /Users/annakorin/nc-ops-scripts/refill-20260911-0015/live.json и каталоги a2nc/m4/neo с receipt и полными входами. Ссылки: https://bugs.wool2.online/web

CHECKPOINT3478:WEB-638 2026-09-10T23:30:08.650195+00:00
3464 авторское исправление SQL template tombstone: 5 prepared EXPLAIN без ANALYZE и 25 реальных SELECT на disposable PG, исходный reproduction3454 сохранён. Независимая 3477 запущена A1. Боевой route не менялся; это не реальные hot-query измерения.
Полные отчёты и проверки SHA: /Users/annakorin/nc-ops-scripts/checkpoint-3478/snapshot.json; задания и SHA inputs: refill-20260911-0015,0025,0028,0030. Спецификация: /Users/annakorin/nc-ops-scripts/enterprise2-20260910/ENTERPRISE-2.md; цель: SHIFT-GOAL-20260911-8H.md.

CHECKPOINT3483:WEB-638 2026-09-10T23:42:32.159576+00:00
3477 independent GO: HEAD f5d5be2dd9c2b8799f11b1d08aa920914e2ad860; 5 prepared plans без ANALYZE, 85 SELECT с 12 persisted/metadata tombstone states, inherited 20/20; own PG runner 1/1 и cleanup. Боевой route/auth не менялся. Исходные PG runners не стартовали из-за /tmp ENOENT; собственный private-cluster runner пройден. Реальные hot queries/capacity и release pending.
Доказательства: /Users/annakorin/nc-ops-scripts/checkpoint-3483/snapshot.json и полные *-REPORT.md; доставка: capacity-integration-3480, refill-20260911-0040, refill-20260911-0046. Норматив: enterprise2-20260910/ENTERPRISE-2.md.

CHECKPOINT3488:WEB-638 2026-09-11T00:15:51.647210+00:00
Принятый query3477 включается в3487, source HEAD f5d5be2dd9c2b8799f11b1d08aa920914e2ad860. Runtime и capacity остаются pending. Evidence: checkpoint-3488/snapshot.json; capacity-integration-3487; refill-20260911-0110.

HANDOFF-20260912T1721:WEB-638 — автономный 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-638
Enterprise-2 / CAP-11: PostgreSQL: реальные горячие запросы

Срез перед остановкой 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. Исторические отчёты/отказы сохранены.

ИНДИВИДУАЛЬНЫЙ ОСТАТОК
Выбрать 3–5 горячих SQL по app measurements, затем отдельное pgbench-окно на клоне, планы/IO/locks/WAL; не параллельно app measurement.
Следующая смена начинает только после возобновления владельцем. Исторический status сохранён; STOPPED означает отсутствие выполняемой работы, не закрытие задачи.
Лента
2026-09-10T19:40:36.729Z · coordinator
ENTERPRISE2-LINKS:CAP-11
Parent: WEB-626
Зависимости: WEB-629 (CAP-02), WEB-631 (CAP-04), WEB-635 (CAP-08)
Спецификация целиком в WEB-626. Порядок: подготовка → изолированный стенд/T0 → выделенные измерения → независимый итог. CAP-13 опциональный.
2026-09-10T21:39:01.293Z · coordinator
DISPATCH-20260910-3406
На 2026-09-10T21:38:23.260126+00:00: active; M4, волна 3406 cap-db-query-prep. Exact BASE 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4. Бриф прошёл brief-guard; входы SHA256 и наличие BASE проверены до постановки. Входы /Users/milamarty/waves/inputs/3406-cap-db-query-prep. Отчёт ожидается /Users/milamarty/waves/3406CAPDBQUERYPREP-REPORT.md. Живой процесс модели подтверждён; возраст журнала 1 с. Source/подготовка и независимая проверка — в объёме брифа; это не посадка, не live/capacity GO и не окончательное закрытие security.
2026-09-10T21:55:31.083Z · coordinator
REFILL-20260910-2155-3422
Снимок 2026-09-10T21:54:52.066911+00:00. Волна 3422 / acc-cap-db-query-kit на A2: active. Оператор Codex, модель gpt-5.6-luna. Живой процесс подтверждён; возраст журнала 0 с. Входы /home/ubuntu/waves/inputs/3422-acc-cap-db-query-kit; SHA256 и exact BASE dcb3d0cb5a1e3069e552649b65e812b82cc3896d проверены при доставке. Новых посадок и измерений capacity нет; source QA не означает окончательное закрытие Security.
2026-09-10T22:50:48.347Z · coordinator
SLOTS12-BATCH3442-3461-WEB-638
2026-09-10T22:50:05.652264+00:00 Лимиты A1/A2 подняты до12; действовавшие worker panes сохранены. Волна 3448 a1nc active acc-db-query-hardening-final; BASE 698faea3d3be65197f5182b426522efa60e3a912; model gpt-5.6-luna
Волна 3454 a2nc active db-query-pg-parser; BASE 698faea3d3be65197f5182b426522efa60e3a912; model gpt-5.6-luna
Брифы прошли guard; входные report/history/raw/bundle переданы и SHA проверены до очереди. Source/QA scope, посадки и measured capacity не заявляются.
2026-09-10T23:18:14.030Z · coordinator
REFILL3472:WEB-638
2026-09-10T23:16:44.018057+00:00
Волна 3464 a2nc active; cap-query-tombstone-repair; BASE c1015e6dc1a7e624f5ed259ecc3e280d30a7e63f
Эволюция: предыдущая партия сдана; обнаруженные отказы CAP12/CAP07/CAP11 и route typecheck переданы в исправление3462–3465. Новые transport/auth/stop/node-plan части переданы на независимую проверку3466–3469. WEB593 и QA-изменения Drive проверяются3470/3471. M1 сверяет локальную топологию3472. Брифы и входные SHA проверены. Source GO не означает посадку.
Документы: /Users/annakorin/nc-ops-scripts/SHIFT-GOAL-20260911-8H.md; /Users/annakorin/nc-ops-scripts/enterprise2-20260910/ENTERPRISE-2.md. Доказательства: /Users/annakorin/nc-ops-scripts/refill-20260911-0015/live.json и каталоги a2nc/m4/neo с receipt и полными входами. Ссылки: https://bugs.wool2.online/web
2026-09-10T23:33:43.925Z · coordinator
CHECKPOINT3478:WEB-638 2026-09-10T23:30:08.650195+00:00
3464 авторское исправление SQL template tombstone: 5 prepared EXPLAIN без ANALYZE и 25 реальных SELECT на disposable PG, исходный reproduction3454 сохранён. Независимая 3477 запущена A1. Боевой route не менялся; это не реальные hot-query измерения.
Полные отчёты и проверки SHA: /Users/annakorin/nc-ops-scripts/checkpoint-3478/snapshot.json; задания и SHA inputs: refill-20260911-0015,0025,0028,0030. Спецификация: /Users/annakorin/nc-ops-scripts/enterprise2-20260910/ENTERPRISE-2.md; цель: SHIFT-GOAL-20260911-8H.md.
2026-09-10T23:43:26.801Z · coordinator
CHECKPOINT3483:WEB-638 2026-09-10T23:42:32.159576+00:00
3477 independent GO: HEAD f5d5be2dd9c2b8799f11b1d08aa920914e2ad860; 5 prepared plans без ANALYZE, 85 SELECT с 12 persisted/metadata tombstone states, inherited 20/20; own PG runner 1/1 и cleanup. Боевой route/auth не менялся. Исходные PG runners не стартовали из-за /tmp ENOENT; собственный private-cluster runner пройден. Реальные hot queries/capacity и release pending.
Доказательства: /Users/annakorin/nc-ops-scripts/checkpoint-3483/snapshot.json и полные *-REPORT.md; доставка: capacity-integration-3480, refill-20260911-0040, refill-20260911-0046. Норматив: enterprise2-20260910/ENTERPRISE-2.md.
2026-09-11T00:15:52.490Z · coordinator
CHECKPOINT3488:WEB-638 2026-09-11T00:15:51.647210+00:00
Принятый query3477 включается в3487, source HEAD f5d5be2dd9c2b8799f11b1d08aa920914e2ad860. Runtime и capacity остаются pending. Evidence: checkpoint-3488/snapshot.json; capacity-integration-3487; refill-20260911-0110.
2026-09-12T18:27:04.879Z · coordinator
SHIFT-STOP-20260912:FINAL:WEB-638
Enterprise-2 / CAP-11: PostgreSQL: реальные горячие запросы

Срез перед остановкой 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. Исторические отчёты/отказы сохранены.

ИНДИВИДУАЛЬНЫЙ ОСТАТОК
Выбрать 3–5 горячих SQL по app measurements, затем отдельное pgbench-окно на клоне, планы/IO/locks/WAL; не параллельно app measurement.
Следующая смена начинает только после возобновления владельцем. Исторический status сохранён; STOPPED означает отсутствие выполняемой работы, не закрытие задачи.
2026-09-12T22:25:57.442Z · coordinator
[12.09 22:25Z координатор] ОБОГАЩЕНИЕ 12.09 (3571-enrich-web626):
Сделано: db-query kit доведён до independent GO (3477) на disposable PG — это планы/инструмент, не горячие запросы прода.
На проде: НЕТ — реальных hot-query измерений нет.
Доказано: 3406CAPDBQUERYPREP-REPORT.md, checkpoint-3483/snapshot.json (3477, HEAD f5d5be2).
Осталось: 3–5 горячих SQL по app measurements, отдельное pgbench-окно на клоне (plans/IO/locks/WAL).
Кто следующий: координатор после T0-измерений приложения.
Ссылки: /Users/milamarty/waves/3406CAPDBQUERYPREP-REPORT.md; /home/ubuntu/waves/inputs/3422-acc-cap-db-query-kit; commits f5d5be2dd9c2b8799f11b1d08aa920914e2ad860, 1ad52e16b784d3ae80a148fddae4d5a3ab4a89c4
Отчёт волны: /Users/milamarty/waves/3571ENRICH*-REPORT.md (M4), копия nc-ops-scripts/shift-20260912-resume/wash-20260912/enrich-collected/.
2026-09-14T14:48:32.241Z · coordinator
[14.09 14:48Z координатор] СНИМАЮ ОПУБЛИКОВАННОЕ. Независимая приёмка 3863 (Neo, ветка `refs/waves/3863/acceptance/3863-pool`, коммит 1261e08da0) вынесла **NO-GO** диагнозу волны 3853, который я опубликовала здесь как факт.

Что устояло:
- «первым упирается НЕ пул соединений» — подтверждено, но собственным доказательством приёмки, а не доводом 3853.
- три защёлки (а)(б)(в) — воспроизведены.
- «PgBouncer не поможет» — вывод верен.

Что снимается:
1. ⛔ **«Первое горлышко — один web-процесс Node»** — НЕ доказано. Различающих замеров нет; два из трёх названных механизмов приёмка посчитала, и они не сходятся: пул libuv ≈18 заданий/с при 4 потоках, горячая строка ≈2.95 продления/с при потолке 23–132/с. На этом утверждении стоит корзина B (и B4 «держать второй слот постоянно») — строить на ней решения нельзя.
2. ⛔ **Замедление «в 35 раз»** — артефакт сравнения. В закрытой системе с неизменной стоимостью работы `R(N)/R(1) ≤ N`. При N=24 наблюдались 32.6–41.6× у пяти групп: это арифметически невозможно. Значит между `arr24a` и `rung24c` изменилась не только нагрузка (открытая модель против закрытой — разные точки отсчёта задержки, `k6-script.js:1209-1230`; плюс таблицы росли внутри прогона: 5008 действий против 773). Вся §4.1 отчёта 3853 стоит на неисправном основании, и `ρ≈0.97` выведено из числа, которое не может быть тем, чем его считали.
3. ⛔ **«Потолок пула ≈111 запросов/с, запас 5.6×»** — арифметика верна, вход нет. Из наблюдаемых на стенде величин выходит 25–141 запрос/с, запас 1.25×–7.1×.
4. ⛔ **Обоснование про PgBouncer** («transaction pooling ломает `pg_advisory_xact_lock`») — фактически неправда, проверено на базе.
5. Поправка в счёте: файловых сисколлов на лизное допущение **6, не 4**; для `realtime` **18, не 12**.

Что это меняет по эпику: CAP-11 (WEB-638) НЕ закрыт диагнозом 3853. Виновник серийности не назван — кандидаты (главный поток JS, разбор JWE, коммиты `lastActiveAt`, рост данных) различаются ровно теми замерами, которые §7 отчёта 3853 перечислила и которые до сих пор не сняты. Первый недостающий — десятисекундный отсчёт `prisma.pool.busy_high_water`.

Отчёт приёмки целиком — в ветке, evidence `3863ACCEPT-evidence/`.
2026-09-14T15:33:23.807Z · coordinator
[14.09 15:33Z координатор] **Волна 3867 (CAP-11, «назвать последовательный ресурс») закончилась БЕЗ отчёта** — `WAVE_EXIT=0`, но в каталоге сдачи только `PREREGISTER.md` и его отметка времени. По нашему правилу это провал сдачи. Однако в её логе лежат шесть измерений, сделанных ею самой на этой машине, и терять их нельзя. Ниже — спасённое из лога, со всеми оговорками: **это не принятая сдача, а материал; evidence-пакета и бандла нет, воспроизводимость не проверена вторым исполнителем.**

1. ⚠️ **`prisma.pool.busy_high_water` на этом стенде получить НЕЛЬЗЯ.** Единственный вызывающий `collectMetricGauges()` — `GET /api/cron/metrics-alerts`, и он отдаёт **401** (`CRON_SECRET` отсутствует в `/proc/261374/environ`), а в `MetricScalarBucket` **нет ни одной строки-датчика** — только 7 имён счётчиков. Это механическая причина, по которой `L` не измерила ни одна волна. Приёмка 3863 назвала эту дыру первой — теперь известно, почему её никто не закрыл.

2. ⚠️ **Посылка «один web-процесс = один пул на 40» ЛОЖНА.** `grep -rl 'db-pool] role='` находит **5 копий** `src/lib/prisma.ts` в standalone-сборке; строка 172 кладёт кэш в `globalThis` только при `NODE_ENV !== 'production'`. Снаружи: pid 261374 один держит **66 из 68** соединений к `cap3516build` (socket-inode → `/proc/net/tcp`). Значит внутрипроцессный датчик показал бы счёт ОДНОЙ копии, а не процесса.

3. ⚠️ **Пул ни разу не упирался в таймаут**: `P2024` во всём журнале приложения = **0**. Все 3801 строк `near capacity` показывают `active=32` — **это константа-защёлка из `prisma.ts:20-35`, а не измерение.** То есть прежние выводы о занятости пула читали защёлку.

4. **Разбор JWE опровергнут с запасом ~90×**: настоящая кука, настоящий `decode` из `@auth/core@0.41.3`, проверенная полезная нагрузка → **1743 разбора/с на одном ядре** (0.574 мс каждый). Два разбора на запрос (`proxy.ts:378` + `auth()`) при наблюдаемых ~19 запр./с — это ~2% одного ядра.

5. **fsync долговечной фиксации опровергнут**: `fdatasync` на файловой системе pgdata — 0.40 мс в среднем → **~2476 последовательных синхронизаций/с** против ступени, предлагающей ~16 итераций/с.

6. ⭐ **Найден конкретный механизм для кандидата `lastActiveAt`**: у `UserSession` `n_tup_upd=84087` при **`n_tup_hot_upd=0`** — потому что индекс `UserSession_lastActiveAt_idx` покрывает **ровно тот столбец, который переписывается каждым запросом**. При этом `idx_scan` этого индекса = **0**: за него платят на каждой записи и никогда не читают.

Предсказания зарегистрированы заранее: `3867SERIAL-evidence/PREREGISTER.md`, sha256 `85b23afd…`, отметка 15:10:19Z — чтобы различающие ступени нельзя было перетолковать задним числом.

**Почему волна встала:** она честно ждала передачи ядер от соседней волны 3864, которая держала машину под своим k6. Это моя ошибка планирования — две измерительные волны на одном четырёхъядерном ящике. Переставляю продолжением, после освобождения машины.
2026-09-14T15:59:01.139Z · 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-14T16:50:29.543Z · coordinator
[14.09 16:50Z координатор] VERDICT=GO

# 3882 — who is serial, round 2

Second staging. The first (wave 3867) did real work, took real numbers, and
**filed no report**: its delivery directory held only `PREREGISTER.md` and a
timestamp. So this document was put on disk before the first measurement and
appended to as each result landed; the bundle and evidence were sealed before
the last (optional) rung was started.

## Answer in three lines

1. **The serial resource is the application's single JavaScript main thread**
   (tid 261374, 86.5 % of one core, busiest thread in 99.1 % of samples).
   24 users produce no more throughput than 1; only the queue grows.
   Every other pre-registered candidate is refuted by a measurement I took.
2. **`UserSession_lastActiveAt_idx` costs 4 extra index writes and +306 B of
   WAL per request and is read by nothing** — proved causally with a two-arm
   experiment, not inferred.
3. **A whole class of "metrics" in this codebase are constants**, including the
   `active=32` that earlier waves read as pool occupancy.

## Why GO

The three things the ticket asked for are delivered and each rests on a number
I took myself: item 1 is proved causally, item 3 is proved by reading plus a
contradiction measured against the live stand, and item 2 names a survivor with
its boundary stated. What is *not* proved is listed explicitly — the main
thread sits at 86–89 %, not 100 %, and 3867's findings 4 and 5 are carried, not
re-verified. Nothing here is blocked; the open items are named next steps, not
gaps that invalidate the results.

## Status
- [x] worktree + branch
- [x] factual CPU-affinity check (the actual mask, not the launch command)
- [x] item 1 — cost of the missing HOT updates  **(PROVED, causally)**
- [x] confirm 3867's six findings — 4 of 6 re-measured, 2 carried and labelled
- [x] item 3 — the class of published constants  **(three mechanisms)**
- [x] item 2 — differentiating rungs  **(candidate A named, residual stated)**

---

## Stand and pinning (checked as fact, before measuring)

Wave 3877 and 3864 together established that between 14:56Z and 15:21:38Z
Postgres on this stand was **not** pinned to cores 0,1 despite `taskset` on the
launch line. So affinity is read from `/proc`, per process, every time.

At 2026-09-14T15:59:57Z, before I measured anything:

| process | pid | affinity |
|---|---|---|
| app (`next-server`, :3610) | 261374 | mask `3` = cores 0,1 |
| postmaster (:55510) | 1539480 | mask `3` = cores 0,1 |
| all app threads | — | `Cpus_allowed_list = 0-1` |
| all 134 PG backends | — | `Cpus_allowed_list = 0-1` |
| k6 | — | **not running** (`pgrep -x k6` empty) |

Load average at that moment 0.19. I was the only wave agent on the box
(`ps` showed only `3882-who-is-serial-r2`). Evidence:
`01-affinity-baseline-1600Z.txt`.

**Boundary:** Postgres was restarted at **2026-09-14 14:57:02Z**
(`pg_postmaster_start_time()`). Every cumulative PG counter quoted below has a
horizon of at most that instant — about one hour. See item 1 for why that
matters and how I worked around it.

---

## Item 1 — what the missing HOT updates cost  (PROVED)

### The mechanism, stated exactly

Every authenticated request runs `ensureSessionActiveForUser()`
(`src/lib/auth/sessions.ts:176-181`):

1. `findUnique` the session by `id`  → `UserSession_pkey`
2. `update ... data: { lastActiveAt: new Date() }`  → **the rewrite**
3. `enforceActiveSessionLimit()` → `findMany where userId, revokedAt: null`
   `orderBy lastActiveAt desc` (`src/lib/auth/sessions.ts:55-68`)

`lastActiveAt` is indexed (`prisma/schema.prisma:1750`, `@@index([lastActiveAt])`).
A PostgreSQL update is HOT (heap-only, no index work) **only** if no indexed
column changed. Step 2 changes an indexed column on every single request, so
HOT is impossible by construction — and a non-HOT update must insert a new
tuple into **every** index on the table, including indexes on columns that did
not change.

### The live table agrees, and its own counters cross-check

`pg_stat_user_tables` / `pg_stat_user_indexes` on `cap3516build` at 16:00:47Z:

| quantity | value |
|---|---|
| `UserSession.n_tup_upd` | 107 573 |
| `UserSession.n_tup_hot_upd` | **0** |
| `UserSession_pkey.idx_scan` | 215 708 |
| `UserSession_userId_idx.idx_scan` | 107 667 |
| `UserSession_lastActiveAt_idx.idx_scan` | **0** |
| `UserSession_lastActiveAt_idx.last_idx_scan` | **NULL** |
| `n_live_tup` | 4 339 |

These corroborate each other and pin the shape of the workload: `pkey` scans
≈ 2 × updates (the `findUnique` plus the `update`-by-id), and `userId_idx`
scans ≈ 1 × updates (the limit check). One `lastActiveAt` rewrite per request,
no more and no less.

### The counterfactual: I built the same table without the one index

Correlation is not enough — 3867 stopped at "the index exists and HOT is zero".
To turn that into a cause I built two tables in a scratch schema `w3882` on the
same cluster, differing in **exactly one thing**: the index on the rewritten
column.

- `us_with` — the `UserSession` column shape, and all four of its indexes
  (`pkey`, `token_key` unique, `userId`, `lastActiveAt`).
- `us_without` — identical, **minus** the `lastActiveAt` index. Three indexes.

Both seeded with 4 339 rows (the live `n_live_tup`). Both driven by the same
stored procedure `w3882.beat()`, which does one single-row
`update ... set "lastActiveAt" = clock_timestamp() where id = ...` **per
transaction** — the same unit of work the app does. 8 000 updates per arm,
four alternating passes (one warm-up, three measured).

Scripts: `hot-cost.sql` (fixture), `hot-run.sql` (procedure),
`hot-measure.sh` (driver). Raw output: `02-hot-cost-raw.txt`.

#### Result 1 — the index is the whole cause of the zero

| arm | `n_tup_upd` | `n_tup_hot_upd` |
|---|---|---|
| `us_with` (4 indexes) | 8 000 | **0** |
| `us_without` (3 indexes) | 8 000 | **7 999** |

Reproduced in every pass. With the index, HOT never happens — matching
production exactly (0 of 107 573). Remove that one index and 99.99 % of the
same updates become HOT. **The index on `lastActiveAt` is not correlated with
the missing HOT updates; it causes them.**

#### Result 2 — how many extra index writes per request: four

`03-index-growth.txt`, per-index size over 8 000 updates after `VACUUM FULL`:

| index | `us_with` growth | `us_without` growth |
|---|---|---|
| `lastActiveAt` | +172 032 B (+21 pages) | *(index absent)* |
| `pkey` | +24 576 B (+3 pages) | **+0 B** |
| `token_key` (unique) | +24 576 B (+3 pages) | **+0 B** |
| `userId` | +49 152 B (+6 pages) | **+0 B** |
| **total** | **+270 336 B** | **+0 B** |

This is the number the ticket asked for. **Each request writes a new index
tuple into all four indexes; without the one index it would write zero.**

Note what the second column proves: `pkey` and `token_key` index `id` and
`token`, columns the request never touches — yet they grow, because a non-HOT
update re-indexes the row *everywhere*. Take away the `lastActiveAt` index and
those three indexes receive **exactly zero bytes** across 8 000 updates. So the
cost of that one index is not "one extra index write", it is **four**.

#### Result 3 — what it costs in write volume

Steady-state passes A/B/C, WAL bytes measured as a `pg_current_wal_insert_lsn()`
delta on an otherwise quiet cluster (0 other active backends, checked):

| arm | mean WAL per update |
|---|---|
| `us_with` | **498.0 B** |
| `us_without` | **192.3 B** |

**+305.7 bytes of WAL per request, a 2.59× multiplier**, purely from the one
index. The `us_without` figure is stable to within 0.2 B across passes
(192.4 / 192.2 / 192.2); the `us_with` figure varies (527.5 / 511.8 / 454.8)
because non-HOT updates trigger page splits and post-checkpoint full-page
images.

Table/heap bloat tracks it too: during the measured passes `us_with` grew by
548 864 and 327 680 bytes while `us_without` grew by **0**.

#### Result 4 — what it costs in time, and why this is the weak number

Paired per-pass difference, `us_with` minus `us_without`, microseconds per update:

| pass | with | without | delta |
|---|---|---|---|
| A | 689.4 | 529.9 | **+159.5 µs** |
| B | 644.9 | 604.5 | **+40.4 µs** |
| C | 709.0 | 687.4 | **+21.6 µs** |

Mean **+73.8 µs per update**, but the spread (21.6 – 159.5) is twice the mean.

**I am not going to sell this as a measured time cost.** Each iteration commits,
and the commit fsync dominates and is common to both arms, so it cancels in the
mean but swamps the variance. What is solidly established is the *work* —
4 index insertions and +306 B of WAL per request — not a trustworthy
microsecond figure. To get one I would have to run the arms with
`synchronous_commit=off` to remove the fsync, which changes durability and is
not the configuration under test. That is a stated limit, not a result.

### Can the index be dropped? — the check the ticket demanded

`idx_scan = 0` is worthless if the counter was zeroed by a restart. It was
nearly so here:

- `pg_stat_database.stats_reset` for `cap3516build` is **NULL** — never
  explicitly reset.
- but `pg_postmaster_start_time()` = **2026-09-14 14:57:02Z**, one hour before
  I looked.

So `idx_scan = 0` is only directly proved over **about one hour**, during which
107 573 updates and 323 375 index scans went through the table. That is a real
workload sample, but it is one hour, not the life of the index.

I therefore did not rest on the counter. Two independent arguments:

1. **`last_idx_scan` is NULL** (PostgreSQL 16 records the wall-clock time of the
   most recent scan). Not "zero since reset" — *never, within the epoch*.

2. **The planner argument, which does not depend on counters at all.** There is
   exactly one query in the codebase that mentions `lastActiveAt` in a read
   position: the `orderBy` in `enforceActiveSessionLimit`
   (`src/lib/auth/sessions.ts:65`). I ran `EXPLAIN` on it against the live
   table (`q2` in `04-*`):

   ```
   Sort  (Sort Key: lastActiveAt DESC, createdAt DESC)
     ->  Bitmap Heap Scan on "UserSession"
           Recheck Cond: ("userId" = $0)
           ->  Bitmap Index Scan on "UserSession_userId_idx"
   ```

   The ordering is served by a **Sort node over `UserSession_userId_idx`**, not
   by the `lastActiveAt` index. This is structural, not a statistics accident:
   the query is always selective on `userId` (≈2 rows), and an index keyed on
   `lastActiveAt` alone has no `userId` prefix, so it could only be used by
   scanning far more of the index than the sort costs. No plausible data
   distribution flips that.

**Conclusion I will sign:** the `@@index([lastActiveAt])` at
`prisma/schema.prisma:1750` is written by every authenticated request, forces
four index insertions and +306 B of WAL per request, and is read by nothing —
proved by a NULL `last_idx_scan` over a one-hour, 107k-update sample *and*
independently by the query plan of its only candidate reader.

**Boundary I will not cross:** I did not drop it, on this stand or anywhere.
Dropping it is a schema change and this wave is a measurement wave. What is
still missing before someone drops it is named in KNOWN ISSUES.

---

## 3867's six findings — one independent check each

I re-took every number myself. 3867's values are in brackets where mine differ
(the stand kept running between us, so counters grew; that is expected and is
itself a consistency check).

| # | 3867's claim | my check | verdict |
|---|---|---|---|
| 1 | `prisma.pool.busy_high_water` unobtainable | `CRON_SECRET` absent from `/proc/261374/environ`; `GET /api/cron/metrics-alerts` → **HTTP 401**; `MetricScalarBucket` holds **154 rows, all `kind=counter`, 7 names, zero gauge rows**, and **0** rows matching `prisma.pool%` | **CONFIRMED** |
| 2 | "one web process = one pool of 40" is false | `src/lib/prisma.ts:156` assigns the `globalThis` cache **only** `if NODE_ENV !== 'production'`; the stand runs `NODE_ENV=production`, so `globalForPrisma.prisma \|\| createPrismaClient()` (line 154) builds a **fresh client per bundled copy**. **5** copies of the module under `.next/server/` in the running bundle (9 including the worker/realtime processes). Externally: **pid 261374 alone holds 72** TCP connections to `:55510` [3867: 66 of 68] | **CONFIRMED** |
| 3 | `P2024`=0; all `near capacity` lines are a latch, not a measurement | **0** `P2024` across all five app logs; **3836** `near capacity` lines [3867: 3801], and every one reads `active=32` **and** `threshold=32` | **CONFIRMED, and stronger** |
| 4 | JWE parsing refuted | not re-run — see note below | **carried over, NOT re-verified** |
| 5 | fsync refuted | not re-run — see note below | **carried over, NOT re-verified** |
| 6 | `UserSession` non-HOT because of the `lastActiveAt` index | `n_tup_upd=107573`, `n_tup_hot_upd=0`, `lastActiveAt_idx.idx_scan=0`, `last_idx_scan` NULL — **and** driven to a cause by the two-arm experiment in item 1 | **CONFIRMED and upgraded to a cause** |

**Rows 4 and 5 are declared, not proved, by me.** 3867's numbers (1743 JWE
decodes/s; 0.40 ms `fdatasync` → ~2476 syncs/s) are plausible and its
instruments are on disk in `wt-3867-serial/tools-3867/`, but I did not re-run
them, so per this wave's own rules I am not publishing them as measured. Both
are refutations — they only need to show a ceiling far above the offered rate —
and re-running them is cheap; it is listed in KNOWN ISSUES.

### On point 3, the mechanism is worth stating exactly

`src/lib/prisma.ts:21-35`:

```
const threshold = Math.max(1, Math.ceil(connectionLimit * 0.8))   // 40*0.8 = 32
if (activePoolOperations < threshold || poolPressureAlerted) return
poolPressureAlerted = true
console.warn(`... active=${activePoolOperations} threshold=${threshold} ...`)
```

`beginPoolOperation` increments `activePoolOperations` by **exactly one** and
then calls this. So the log can only fire on the tick where the counter *first*
reaches `threshold` — and reaching it in steps of one means it is *exactly*
`threshold`. `poolPressureAlerted` then latches until `endPoolOperation` sees
the count fall back below 32.

Therefore `active=32` is **an identity, not an observation**: the line is
structurally incapable of printing 33, or 37, or 40. All 3836 lines agreeing is
not evidence that the pool sat at 32 — it is evidence that the guard works.
The log records *that* the pool crossed 32, 3836 times, and says **nothing
whatever** about how deep the queue actually went.

Every earlier conclusion about pool occupancy drawn from this line was reading
a constant.

---

## Item 3 — this is a class, not one case

The ticket asked where else a number that is really a constant gets published.
It is a class, and the members differ in *how* the constant arises. Three
mechanisms, all present in this codebase:

### Mechanism A — edge-latched publication: the value is pinned by its own guard

The archetype above. A value is logged only when it crosses a threshold, and
the crossing test plus a unit increment force the published value to equal the
threshold.

- `src/lib/prisma.ts:25-33` — `active=` in `[db-pool] connection budget near
  capacity`. **Proved constant: 3836/3836 lines read `active=32 threshold=32`.**

The counters `db_pool_near_budget_total` and `db_pool_near_budget_total.web`
(`src/lib/prisma.ts:28-29`, and both present in `MetricScalarBucket`) are fed
from the same latched branch. They are honest as *event counts* — "the pool
crossed 80 % this many times" — but they are **not** a depth or a duration, and
because of the latch they do not even count operations, they count *crossings*.
Anything that divides them by requests to get a "pressure rate" is wrong.

### Mechanism B — configuration arithmetic published under a measurement's name

`src/lib/monitoring/persistent/collectors.ts:89-95` records three durable
**gauges**:

```
recordGaugeSample(METRIC_PRISMA_POOL_BUDGET_DEMAND,       observation.poolDemand)
recordGaugeSample(METRIC_PRISMA_POOL_APPLICATION_BUDGET,  observation.applicationBudget)
recordGaugeSample(METRIC_PRISMA_POOL_BUDGET_UTILIZATION,  observation.utilization)
```

`observation` comes from `resolvePoolBudgetObservation()`
(`src/lib/db/connectionPool.ts:197-232`), which reads **`process.env` and
nothing else**:

```
poolDemand = webProcesses*webLimit + readonlyClients*readonlyLimit + workerProcesses*workerLimit
utilization = poolDemand / applicationBudget
```

On this stand (`app.env`): `1*40 + 0*0 + 1*3` = **43**, budget **90**,
utilization **0.4778**. Those three numbers are what the gauge publishes at
idle, at saturation, and at 3 a.m. with the app stopped. Nothing in the process
can move them.

To the credit of whoever wrote it, the docstring says so plainly ("The database
worksheet is static configuration"). The hazard is entirely in the **names**:
`prisma.pool.budget_utilization` reads as an occupancy measurement, and it is
the sort of series an operator puts on a dashboard next to real ones.

**And on this stand the constant is also wrong.** The gauge asserts a demand of
43 connections. Measured at 16:06:50Z (`06-pool-gauge-vs-reality.txt`):

| | |
|---|---|
| gauge would publish: `poolDemand` | **43** |
| actually held by the single app pid 261374 | **72** |
| total connections to `cap3516build` | **130** |

72 from one process against a declared per-process limit of 40 — because of
finding 2: five bundled copies of `prisma.ts`, each constructing its own
`PrismaClient` with its own pool of 40, since the `globalThis` de-duplication is
switched off in production. The declared "demand" is not merely static, it is
**1.7× below what one process already holds**, and the `utilization` gauge would
report 48 % while the real count sits above the entire declared budget.

### Mechanism C — a gauge that is never fed, so it reads as its initial value

`src/lib/monitoring/persistent/prismaProbe.ts:47-154` maintains a real
`highWater` and publishes `prisma.pool.busy_high_water`. But
`recordPrismaProbeSample` is only reached through `collectMetricGauges()`,
whose only caller is `GET /api/cron/metrics-alerts`
(`src/app/api/cron/metrics-alerts/route.ts:34`) — which returns **401** here.
So the series is not "low", it is **absent**: `MetricScalarBucket` has zero rows
matching `prisma.pool%`. This is the failure mode that reads as a constant to
anyone querying the store, and it is finding 1.

### The rule that catches all three

> A published number is only a measurement if the code path that publishes it
> could have published a **different** number under different runtime
> conditions.

Applied as a review question: *name two runtime states that make this number
differ.* Mechanism A cannot (the guard pins it), B cannot (only a redeploy
with new env moves it), C cannot (nothing ever writes it).

**Scope of my search.** I grepped `src/` for one-shot latch booleans, for
`recordGauge*` call sites, and read every `prisma.pool.*` producer end to end.
Mechanisms A, B and C are what that turned up in the pool/metrics area. I did
**not** audit every gauge in the codebase — `providerMetrics.ts`,
`indexingMetrics.ts` and the rest of `collectors.ts` publish values that do
derive from runtime state, but I checked them by reading only, not by
measurement. A full audit is in KNOWN ISSUES.

---

## A finding nobody asked for: 3867 also lost its instruments

The ticket says 3867 "did real work and filed no report". It is worse than
that. `wt-3867-serial` had been **deleted from disk** by the time I looked
(`git worktree list` marks it `prunable`), and

```
git log --oneline refs/waves/l115n..wave/3867-who-is-serial   -> empty
git ls-tree -r --name-only wave/3867-who-is-serial | grep -c tools-3867  -> 0
```

3867 **committed nothing**. Its eleven instruments — `proc-sampler.py`,
`pg-sampler.sh`, `jwe-bench.mjs`, `fdatasync-bench.py`, `analyze-rung.py` and
the rest — lived untracked inside a worktree that gets pruned, and are gone
permanently. The only artefact that survived is `PREREGISTER.md`, and it
survived **because it was written outside the worktree**, into the evidence
directory.

This is why findings 4 and 5 above are carried over rather than re-verified: the
cheap option ("just re-run 3867's benchmark") does not exist. I had to rebuild
the sampler from scratch for item 2.

**Rule this earns:** an instrument that is not committed does not exist. Wave
working directories are disposable; the branch and the evidence directory are
not.

---

## Item 2 — the differentiating rungs

Conditions were taken from the log of the previous run of this same series (via
wave 3864's report, which read them from `capA.log`), **not** from memory:
profile `t1` (`constant-vus`, so the system is **closed** — this is what
instrument 6 requires), 5 m / 300 s, `CAP06_EXCLUDE_GROUPS=upload_index`, shard
`split-a-24` (24 seeded identities), app+PG pinned to cores 0,1 and
k6 + the room-audio daemon to cores 2,3.

Instruments 1 and 2 had to be **rebuilt** (`sampler-3882.py`) because 3867's
were destroyed. Mine samples once a second and, per the standing rule from
waves 3877/3864, **re-reads the affinity of every measured process on every
sample** — a rung whose processes drifted off their cores is void.

### Pinning, verified as fact on every single sample

Across all 240 samples of the N=1 rung the affinity triple was constant:

```
(app 0-1, postgres 0-1, k6 2-3)     240 of 240 samples
```

k6's mask was read as `c` = binary 1100 = cores 2,3. App and Postgres masks
were `3` = cores 0,1 before and after each rung. No drift.

### The two rungs

Both rungs ran the identical scenario for the identical wall time. Numbers are
**completed**, and offered is shown beside them because completed==offered here.

| | N=1 (`r3882n01`) | N=24 (`r3882n24`) |
|---|---|---|
| wall clock | 5m07.1s | 5m07.8s |
| iterations completed / offered | 4885 / 4885 | 4884 / 4884 |
| **throughput** | **15.91 iter/s** | **15.87 iter/s** |
| http_reqs | 5766 (18.77/s) | 5796 (18.83/s) |
| iteration duration median | **21.1 ms** | **1068.5 ms** |
| iteration duration mean | 61.4 ms | 1475.6 ms |
| http_req_failed | 0 | 0 |

### Instrument 6 first — is this even one experiment?

Acceptance 3863 threw out wave 3853 for failing this, so it is checked before
anything is concluded:

**R(24)/R(1) = 15.87 / 15.91 = 0.9975 ≤ 24.** ✔

And the closed model closes on its own numbers. Little's Law says the number of
users in the system is throughput × residence time:

**15.87 iter/s × 1.4756 s = 23.4 ≈ 24 VUs.** ✔

The two arms are the same experiment, and the harness is telling the truth about
how many users are in flight.

### What the two rungs say together

**Twenty-four users produced no more work than one.** Throughput is flat to
within 0.25 %, while median latency rose 50× (21 ms → 1068 ms). That is the
textbook signature of a single-server queue that is *already saturated at
N = 1*: every extra user buys queueing and nothing else. Mean service time at
N=1 is 61 ms; at N=24 residence time is 1476 ms ≈ 24 × 61 ms.

So the serial resource is not something that appears under load. It is
saturated by **one** user.

### Instruments 1 and 2 at N=24, steady state only

The first 47 seconds of the N=24 sample window are **k6's own start-up**: k6 at
**199.9 %** of its two cores while the app sat at **0.3 %**. That window is
excluded — reading it as the rung would have "proved" candidate G by mistake.
Steady state is the remaining 193 samples, 16:23:47–16:27:19Z.

| measure | N=1 | N=24 steady | what PREREGISTER says |
|---|---|---|---|
| app busiest thread, % of one core | 71.0 | **89.3 (p50), max 100.1** | A/B need ≈100; C/D/E need "well below" |
| app whole process, % of one core | 89.3 | 111.6 | — |
| Postgres, % of one core | 9.7 | **13.2** | — |
| **PG active backends** | 0.06 mean | **0.33 mean, p50 0** | C/D need ">1 and rising with N" |
| k6, % of one core | 6.3 | **5.5 (p50)** | G needs ≈200 |
| cores 0+1, % of two cores | 104.3 | **130.3 of 200** | F needs ≈200 |
| affinity triple | (0-1, 0-1, 2-3) on 240/240 | (0-1, 0-1, 2-3) on 193/193 | — |

### Verdict against the pre-registered table

| candidate | required | observed | |
|---|---|---|---|
| **C** per-request `lastActiveAt` commit | PG active backends > 1, rising with N; UserSession UPDATE near top of DB busy time | PG active p50 **0**, mean **0.33**; Postgres at **13 %** of one core | **REFUTED** |
| **D** data growth in-run | same as C, plus rung slower at same N on grown DB | database idle | **REFUTED** |
| **E** Prisma pool queueing | app in-flight ≫ PG active **plus** P2024 timeouts | **P2024 = 0** in every app log | **REFUTED** |
| **F** the shared 2-core budget | cores 0+1 pegged ≈200 while app main < 100 | cores 0+1 at **130 of 200** (65 %) | **REFUTED** |
| **G** the load generator | main thread low **and** k6 ≈200 | k6 at **5.5 %** in steady state | **REFUTED** (but see the init window) |
| **B** JWE parsing | JWE ceiling of the same order as the request rate | 3867 measured ~90× headroom — **not re-verified by me** | refuted *on 3867's number*, carried |
| **A** the app's single JS main thread | busiest thread at or very near 100 % of one core | **89.3 p50, 100.1 max; ≥80 % in 91.7 % of samples** | **THE ONLY SURVIVOR** |

One more number against E, taken during my own rungs: the runner counts the
`near capacity` lines that appear while the rung runs. Across my three rungs it
counted **0** (N=1), **2** (N=24) and **0** (N=24 repeat). In fifteen minutes of
load the pool crossed 80 % of its budget twice in total, and timed out never. (And per item 3, even those two lines could only
ever have said `active=32`.)

Every candidate except A is refuted by a number I took myself. The database is
idle, the pool never timed out, the app's two cores have 35 % headroom, and the
generator is asleep — while throughput refuses to rise above 15.9/s.

### Closing the one gap: is the "busiest thread" always the same thread?

89 % of one core only names candidate A if it is **one** thread, not a
rotating maximum over seventeen. So I ran a third rung (`r3882n24t`, same
conditions, sampling started 70 s after k6 launch to clear the init window) with
a per-thread sampler that records tids.

Third rung, for consistency: **4869 iterations in 5m08.5s = 15.78 iter/s**,
median iteration 1016.7 ms — reproducing the second rung (15.87, 1068.5 ms).
Its summary landed after I had sealed the evidence and is folded in:
offered 4869 / completed 4869 (100 %), `http_req_failed` 0, pool warnings 0.

```
how often each thread was the busiest:
  261374 / next-server      109 / 110 samples  (99.1%)

mean CPU, % of ONE core, per thread:
  261374 / next-server        86.54%     <- the Node JS main thread
  261389 / tokio-runtime-w     4.41%  \
  261388 / tokio-runtime-w     4.27%   |  the Prisma query engine
  261387 / tokio-runtime-w     4.25%   |  (Rust/tokio), ~17% combined
  261390 / tokio-runtime-w     4.20%  /
  261379 / node                1.51%  \
  261377 / node                1.48%   |  libuv thread pool, ~5.7% combined
  261378 / node                1.34%   |
  261380 / node                1.33%  /
```

It is the same thread, every time. The app process's ~111 % of a core is
**86.5 % on one JS thread** plus ~17 % spread over four Prisma-engine threads
and ~6 % over the libuv pool. Nothing else is close.

### The answer

**The surviving candidate is A — the application's single JavaScript main
thread.** It is the only resource on the box anywhere near a ceiling it cannot
exceed, and it is the same physical thread carrying the work in 99.1 % of
samples. Twenty-four users queue behind it and extract no more throughput than
one user does.

This is corroborated — **by another wave's number, not mine** — by 3864's
result that a second app process raised throughput by 52.5 %: if the constraint
were the database, the disk, the pool or the cores, a second process on the same
two cores would not have helped.

### Where the boundary is, honestly

**The main thread runs at 86–89 %, not at 100 %.** PREREGISTER said A requires
"p50 at or very near 100", and 89.3 is near but not at. I am naming A because
every other candidate is refuted by a measurement and A is the only resource
within 70 percentage points of its hard cap — not because A met its predicted
value exactly.

The unexplained residual is the ~11–13 % of the time the main thread has
nothing runnable. It could be the thread genuinely waiting on the Prisma engine
threads (which would make the true constraint a *round trip* through the main
thread rather than main-thread CPU alone), or scheduler latency from sharing two
cores with Postgres. **I did not measure which, so I am not claiming it.**

What would close it, concretely:

1. **Event-loop lag** — `perf_hooks.monitorEventLoopDelay()` inside the app,
   sampled during a rung. If lag p50 is of the order of the queueing delay
   (~1 s at N=24), the main thread's *queue* is the constraint and the 11 % idle
   is just the thread waiting on I/O it cannot proceed without. This is the
   single most valuable next measurement and it needs a small patch to the app.
2. **A CPU profile of tid 261374** during a rung (`--cpu-prof`, or `perf record`
   on that tid), which would say what the 86.5 % is actually spent on — JWE,
   React server rendering, serialization, or Prisma client overhead.
3. **A rung at N=2 and N=4.** I measured N=1 and N=24. The knee is somewhere
   below 24 and locating it would show whether throughput ever rises at all.
   I did not run these: three rungs at 5 minutes each plus refresh was what the
   time allowed after the delivery artefacts were secured.

---

# ДЛЯ ТИКЕТА — детским языком, полный след для нулевого агента

## Что вообще искали

Стенд упирается в потолок: сколько пользователей ни добавляй, работы в секунду
больше не становится. Вопрос был один: **что именно стоит в очереди — кто здесь
«узкое горлышко»**. Предыдущая волна (3867) честно померила часть, но **не
написала отчёт**, и всё едва не пропало. Моя задача — довести до сдачи.

## Чем мерили

- Сам стенд: приложение на `127.0.0.1:3610` (pid 261374), Postgres на порту
  55510 (pid 1539480), генератор нагрузки k6.
- Приложение и база прижаты к ядрам 0 и 1, генератор — к ядрам 2 и 3.
  ⚠️ Это **проверялось фактом** (`/proc/<pid>/status`) на каждом замере, а не
  по команде запуска: волны 3877 и 3864 уже ловили стенд на том, что `taskset`
  в командной строке был, а прижатия не было.
- Мои приборы: `sampler-3882.py` (CPU по процессам и по ядрам, число активных
  соединений в базе), `thread-sampler-3882.py` (CPU по **отдельным потокам**
  внутри приложения), две таблицы-близнецы в схеме `w3882` для опыта про
  индекс.

## Что вышло — тремя простыми фразами

**1. Виновник — один-единственный поток внутри приложения.**
Приложение написано на Node. У Node вся основная работа идёт в **одном** потоке,
и больше одного ядра он занять физически не может. Мы это увидели прямо:
поток `261374` был самым загруженным в **109 замерах из 110** и держал
**86.5 % одного ядра**. Всё остальное на машине при этом отдыхало: база — 13 %,
генератор — 6 %, оба ядра приложения заняты на 133 из 200 возможных.

Как выглядит потолок: при **одном** пользователе система делала **15.91**
действий в секунду. При **двадцати четырёх** — **15.87**. Разницы нет.
А вот ждать стали в **50 раз** дольше: было 21 миллисекунда, стало 1068.
Это и значит «очередь к одному окошку»: окошко одно, людей больше, быстрее не
становится — только очередь длиннее.

**2. В базе есть отдельная поломка: индекс, который никто не читает, но все
пишут.** У таблицы сессий есть индекс по колонке `lastActiveAt`. В эту колонку
приложение пишет **на каждый запрос** («пользователь был активен только что»).
Postgres умеет дешёвый режим обновления (HOT), когда строка меняется, а индексы
трогать не надо. Но он работает, **только если изменённая колонка не в индексе**.
Здесь — в индексе. Поэтому дешёвый режим выключен **полностью**: 0 из 107573.

Я построил две одинаковые таблицы, отличающиеся **только** этим индексом,
и прогнал по 8000 обновлений:

| | с индексом | без индекса |
|---|---|---|
| дешёвых обновлений | **0** из 8000 | **7999** из 8000 |
| выросли индексы | **все 4** (+270 336 байт) | **ни один** (+0 байт) |
| журнал (WAL) на обновление | 498 байт | 192 байта |

То есть **каждый запрос платит 4 лишние записи в индексы и +306 байт журнала**
из-за одного индекса, который **никто не читает**.

**3. Часть «показаний приборов» — не показания, а константы.** Например в логе
3836 раз написано `active=32` — и это не «в пуле было 32», это просто число,
которое **не может быть другим**: строка печатается ровно в тот момент, когда
счётчик впервые дорос до порога 32, а порог тоже 32. Прибор показывает свою
собственную настройку.

## ГДЕ ГРАНИЦА (важнее всего)

- **Главный поток занят на 86–89 %, а не на 100 %.** Я называю его виновником
  потому, что все остальные подозреваемые опровергнуты замером, а он
  единственный близко к своему потолку — **но ровно 100 % я не увидел**, и
  оставшиеся ~12 % не объяснил. Может быть, поток ждёт ответа от движка базы;
  может — планировщика ядер. **Не мерил — не утверждаю.**
- **Индекс я НЕ удалял.** Доказал, что его никто не читает, и что он дорого
  стоит. Удаление — это изменение схемы, а волна измерительная.
- **Счётчики базы живут максимум с 14:57:02Z** (тогда перезапускали Postgres).
  «Индекс не читали ни разу» доказано на окне **примерно в один час** —
  правда, за этот час через таблицу прошло 107 тысяч обновлений.
  Дополнительно это же доказано **планом запроса**, который от счётчиков не
  зависит.
- **Пункты про JWE и fsync (4 и 5) я НЕ перепроверял** — приборы 3867 погибли
  вместе с её рабочим каталогом. Беру как заявленное, не как доказанное.
- Первые **47 секунд** нагрузочного прогона — это разогрев самого k6
  (он ест 200 % своих ядер, приложение стоит). Если посчитать их частью
  замера, «виновным» ложно окажется генератор. Я их выбросил.

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

1. Померить **задержку событийного цикла** (`monitorEventLoopDelay`) — это
   закроет вопрос про недостающие 12 %. Нужен маленький патч в приложение.
2. Снять **профиль CPU** потока 261374 — узнать, на что уходят эти 86 %.
3. Ступени **N=2 и N=4** — где именно ломается график.
4. Решить судьбу индекса `UserSession_lastActiveAt_idx` (см. KNOWN ISSUES).
5. Полный аудит остальных «датчиков» на предмет константности.

## Troubleshooter — если будешь повторять

| симптом | причина | что делать |
|---|---|---|
| `run-rung-r4.sh` падает с `TARGET_URL: unbound variable` | `runid.env` **не** содержит `TARGET_URL`, его обязан задать вызывающий | запускать с `TARGET_URL=http://127.0.0.1:3610` |
| прогон «доказал», что виноват генератор (k6 200 %, приложение 0 %) | попал в окно инициализации k6 (здесь 47 с при 24 VU) | начинать выборку не раньше чем через 70 с после старта k6 |
| числа ступени не сходятся между собой | брали offered вместо completed, или ступени с разным профилем | сверять `R(N)/R(1) ≤ N` и закон Литтла **до** публикации |
| `taskset` в команде есть, а прижатия нет | `pg_ctl restart` наследует маску интерактивной оболочки | читать `/proc/<pid>/status` → `Cpus_allowed_list` на каждом замере |
| `pgrep -f k6` / `-f postgres` ловит лишнее | тексты заданий волн лежат в cmdline процессов `claude -p` | только `pgrep -x <имя>`, никогда `-f` по подстроке |
| `idx_scan = 0`, хочется удалить индекс | счётчик мог обнулиться при рестарте | проверить `stats_reset` **и** `pg_postmaster_start_time()`, плюс `last_idx_scan`, плюс `EXPLAIN` предполагаемого читателя |
| инструменты волны пропали | рабочее дерево волны удаляют (`prunable`) | **коммитить приборы в ветку**, артефакты — в evidence-каталог |
| psql: `column "metric" does not exist` | в `MetricScalarBucket` колонка называется `name`, а не `metric` | `\d "MetricScalarBucket"` |

---

## KNOWN ISSUES

1. **`UserSession_lastActiveAt_idx` — написан каждым запросом, не прочитан
   никем.** Стоит 4 лишних записи в индексы и +306 Б журнала на запрос.
   Доказано причинно (две таблицы-близнецы) и доказано, что читателя нет
   (`last_idx_scan` NULL + план запроса). **Не удалён этой волной.** Перед
   удалением нужно: (а) подтвердить на проде, что `last_idx_scan` там тоже
   пуст на окне в несколько суток; (б) убедиться, что ни один
   административный/аналитический запрос вне `src/` не сортирует по
   `lastActiveAt` глобально. Замена, если читатель найдётся — составной
   `(userId, lastActiveAt)`, который планировщик реально сможет взять.
2. **Главный поток на 86–89 %, а не на 100 %** — виновник назван по
   исключению, остаток не объяснён. См. «Где граница».
3. **Пункты 4 и 5 (JWE, fsync) не перепроверены** — приборы 3867 утрачены
   безвозвратно (ветка пуста, дерево удалено).
4. **Три «датчика» пула — это арифметика по переменным окружения**
   (`budget_demand` = 43, `application_budget` = 90, `budget_utilization` =
   0.478), и на этом стенде они **занижают реальность**: один процесс держит
   **72** соединения. Имена читаются как измерение. Переименовать в
   `*_configured_*` или считать по факту.
5. **`prisma.pool.busy_high_water` недостижим** — единственный вызывающий
   отдаёт 401 (нет `CRON_SECRET`). Гейджей в `MetricScalarBucket` нет вообще.
6. **Аудит датчиков не полный** — проверена область пула/метрик; остальные
   `recordGaugeSample` читались глазами, но не проверялись замером.
7. **Прогоны писали в чужой evidence-каталог** `3714LADDERR3-evidence` —
   так устроен `run-rung-r4.sh`. Ничего не удалялось; мои копии лежат в
   `3882SERIAL-evidence/`.
8. **Схема `w3882` осталась в базе стенда** (таблицы-близнецы и процедура).
   Снести: `drop schema w3882 cascade;`. Живых таблиц она не трогала.
2026-09-14T17:40:59.442Z · coordinator
[14.09 17:40Z координатор] # WEB-638 — Enterprise-2 / CAP-11: PostgreSQL — реальные горячие запросы

**Блок подготовлен волной 3898 (2026-09-14) для публикации координатором.**
Статус не двигать. Волна 3898 комментарии не публиковала.

**Метки источника:** `[задание]` — дано дословно в задании 3898; `[3898]` — проверено
самой волной 3898 на `a1-payg-01`; `[3863-файлы]`, `[3853-док]`, `[3875-отчёт]`,
`[3882-README]` — прочитано волной 3898 из файла на A1, число принадлежит указанной волне.

**Этот тикет пострадал от перемешивания сильнее всех.** В нём лежал целиком снятый
диагноз (комментарий 5018) и рядом — новые доказанные числа. Читать §1 до §2 обязательно.

---

## 1. СНЯТО — НЕ ИСПОЛЬЗОВАТЬ КАК ФАКТ

| снятое утверждение | где было | чем опровергнуто |
|---|---|---|
| «первое горлышко — один web-процесс Node» | WEB-638 c.5018, дубль в WEB-636 c.5020 | приёмка **3863**: различающих замеров не было `[задание]` |
| «замедление в 35 раз» | WEB-638 c.5018, дубль в WEB-636 c.5020 | артефакт: сравнивали открытый контур (`arr24a`) с закрытым (`rung24c`), плюс база росла внутри прогона — **5008** действий против **773**. В закрытой системе `R(N)/R(1) ≤ N`, а при N=24 наблюдались **32.6–41.6×** у пяти групп — арифметически невозможно `[задание]` |
| «потолок пула ≈111 запр./с, запас 5.6×» | WEB-638 c.5018, дубль в WEB-636 c.5020 | на деле **25–141 запр./с**, запас **1.25×–7.1×** `[задание]` |
| «PgBouncer ломает `pg_advisory_xact_lock`» | WEB-638 | **фактически неправда.** Сам вывод про PgBouncer при этом верен — но обоснование было ложным, и в тикете нельзя оставлять верный вывод с ложным обоснованием: следующий агент поверит обоснованию `[задание]` |
| «предупреждений пула на 50 пользователях ноль» | WEB-626 c.4897, WEB-637 c.4898 | забаррикадировано как число, опубликованное из **провалившейся** проверки `[3879 WITHDRAWN-CLAIMS-BARRIER.md]` |

### Эволюция: почему диагноз казался верным и что именно его опровергло

**Шаг 1 — сигнал.** Первый же прогон производственной формы выдал строку
«near capacity» **четыре** раза. Четыре — число, которое напрашивается
интерпретировать как «стало в четыре раза хуже» `[3853-док]`.

**Шаг 2 — форма замедления.** Пять групп сценариев замедлились примерно **одинаковым
множителем** (32.6–41.6×). Одинаковый множитель у разных по смыслу групп выглядит как
подпись **общего** ресурса — очереди. Отсюда «горлышко одно, и оно в пуле/процессе».

**Что опровергло — три независимых удара приёмки 3863.**

**(а) Форма ничего не различает.** Приёмка построила *чистую* очередь пула на
одноразовом PostgreSQL 16.15 + Prisma 5.22.0 и получила тот же самый признак:
одинаковый множитель **40.9–42.5×** при N=48 и pool=1 `[3863-файлы: acceptance-README]`.
Значит, «одинаковый множитель» — **не** улика в пользу пула. Он воспроизводится и
там, где пул действительно виноват, и там, где нет.

**(б) Пул исключён по числу мест, а не по форме.** VU в k6 **последовательный**,
поэтому в закрытом контуре на 24 VU одновременно в полёте не более 24 операций Prisma.
Против `connection_limit=10` множитель ожидания не может превысить **24/10 = 2.4×**.
Чтобы из 25 мс baseline получить наблюдаемые 888 мс одной только очередью пула,
запрос должен был бы проводить в пуле ≥ **888/2.4 = 370 мс** — из 25 мс. Невозможно
`[3863-файлы: MATERIAL-ARITHMETIC §E]`.

**(в) Замедления в 35 раз вообще не было — сравнивали два разных прогона.**
`arr24a`: 24 VU, **773** бизнес-действия → 32.2 итерации кольца на VU.
`rung24c`: 24 VU, **5008** действий → 208.7 итерации на VU. Отношение предложенной
работы **6.48×** — это **не** «тот же прогон, только медленнее»
`[3863-файлы: MATERIAL-ARITHMETIC §A]`. Средняя длительность действия 180 мс против
1062 мс, отношение 5.9×; если бы `arr24a` был закрытым контуром на 24 VU, он занял
бы **5.8 с** — а ступень лестницы длится 300 с. Ступень в 6 секунд невозможна,
следовательно `arr24a` — **открытая** (низкорейтовая) референсная точка, а не
закрытая ступень `[3863-файлы: MATERIAL-ARITHMETIC §B]`.

**(г) И потолок сам себя выдал.** В закрытом контуре пропускная способность не падает
от добавления клиентов, значит `R(N) = N/X(N) ≤ N·R(1)`, то есть при N=24 ничего не
может замедлиться больше чем **в 24 раза** — если только не изменилась стоимость
самой работы. Наблюдались **37.8× (session), 41.1× (list), 41.6× (retrieval),
32.6× (status), 34.4× (realtime)** — все пять превышают 24. `open` 8.1× и `save` 2.2×
укладываются `[3863-файлы: MATERIAL-ARITHMETIC §D]`. Превышение потолка — это не
«очень плохо», это **«измерено не то, что думали»**: база росла внутри прогона, и
стоимость работы менялась.

**Шаг 3 — откуда взялось «111 запр./с».** Формула
`ceiling = connection_limit / (connection-seconds per request)`, где
`connection-seconds = L / X_http`, а **L — среднее число операций Prisma в полёте — НЕ
ИЗМЕРЕНО**. 111 req/s соответствует ровно одному предположению `L = 1.78`. Стоит
подставить другие правдоподобные L — и потолок разъезжается `[3863-файлы: MATERIAL-ARITHMETIC §F]`:

| L (из 10) | потолок, запр./с | запас |
|---:|---:|---:|
| 1.40 | **141.4** | 7.14× |
| 1.78 | 111.2 | 5.62× ← опубликованное число |
| 2.40 | 82.5 | 4.17× |
| 4.00 | 49.5 | 2.50× |
| 8.00 | **24.8** | 1.25× |

Вилка **25–141 запр./с, запас 1.25×–7.1×** `[задание]` — это и есть честный ответ:
**число не получено, получен интервал**. Опубликовать середину интервала как точку —
та же ошибка, что «задержка упала вдвое» в CAP-08.

**Почему L нельзя было вывести из четырёх строк лога — три защёлки (проверены 3863,
эксперимент E2)** `[3863-файлы: acceptance-README]`, механика описана в `[3853-док]`:
1. **`active=` — это не соединения.** Счётчик считает операции Prisma в полёте в этом
   процессе; операция, ждущая соединения, считается так же, как исполняющаяся.
   Демонстрация: при `connection_limit=10` и 24 одновременных вызывающих строка
   печатает `active=24`, а `pg_stat_activity` показывает ровно **10** бэкендов —
   четырнадцать из двадцати четырёх стояли в очереди внутри Prisma `[3853-док §1]`.
2. **Строка защёлкнута.** Одна строка на одно пересечение порога, независимо от
   длительности и тяжести: 20 с нагрузки с **7475** операциями выше порога → **1**
   строка; полное исчерпание пула со **150** `P2024` → тоже **1** строка `[3853-док §3]`.
   Выше порога число строк **анти**коррелирует с тяжестью. N строк = N пересечений
   порога вверх, и значит минимум N−1 возврат вниз.
3. **Порог у worker'а равен 100 % пула.** `threshold = max(1, ceil(limit·0.8))`:
   для web 10→8 (две свободные), для worker 4→**4** (ноль свободных). Для worker'а это
   вообще не раннее предупреждение `[3853-док §2]`. Плюс `src/lib/prisma-readonly.ts`
   строит голый `new PrismaClient()` без `$extends` — его соединения не попадают ни в
   `db_pool_near_budget_total`, ни в `db_pool_exhausted_total` `[3853-док §4]`.

**Что из диагноза 3853 УЦЕЛЕЛО** (проверено 3863 и не отозвано) — граница отказа пула
зависит от **длительности операции**, а не от числа пользователей:
`in_flight × mean_operation_ms / connection_limit > pool_timeout_ms`. Проверено на
одноразовом PostgreSQL, `connection_limit=10`, `pool_timeout=10 с`, запрос 1000 мс:
80 операций → предсказано 8.0 с, измерено p50 **8015 мс**, `P2024` = **0**;
150 операций → предсказано 15.0 с, измерено **10020 мс** (упёрлось в таймаут),
`P2024` = **150** `[3853-док §5]`. Для web это сводится к
`in_flight × mean_operation_ms > 100000`: при миллисекундных запросах пул становится
связывающим ограничением только когда сами запросы станут медленными.

---

## 2. ДОКАЗАНО ЗАМЕРАМИ — волна 3882

### 2.1 Последовательный ресурс найден: это ГЛАВНЫЙ ПОТОК JS

- tid **261374**, **86.5 %** одного ядра;
- он же — самый загруженный поток в **99.1 %** выборок;
- **24 пользователя дают не больше работы, чем 1** `[задание]`.

**Что это значит.** Горлышко — не пул, не диск и не «один процесс Node» как таковой,
а **один поток внутри процесса**. Отсюда прямо следует, почему второй процесс в
CAP-08 дал прирост (второй главный поток на втором ядре), и почему прирост «на ядро»
оказался отрицательным (−4 %, блок WEB-635): добавляется не параллелизм, а ещё одна
последовательная очередь.

Строка «24 пользователя дают не больше работы, чем 1» — это ещё и **независимое
подтверждение** разбора §1(в): если добавление пользователей не добавляет работы,
то замедление в 35 раз действительно не могло быть замедлением одного и того же.

### 2.2 Индекс, за который платят каждым запросом и который никто не читает

`UserSession_lastActiveAt_idx`:
- **+4 записи в индексы и +306 Б WAL на запрос**;
- **`idx_scan = 0`** `[задание]`.

Волна 3898 нашла точные якоря на линии `refs/waves/l115n` = `d85bb2dad`
`[3898: evidence/04-cap11-code-anchors.txt]`:
- `prisma/schema.prisma:1743` — `lastActiveAt DateTime @default(now())`;
- `prisma/schema.prisma:1750` — `@@index([lastActiveAt])`;
- `prisma/migrations/20251212121830_rag_v2_5_init/migration.sql:916` —
  `CREATE INDEX "UserSession_lastActiveAt_idx" ON "UserSession"("lastActiveAt");`;
- читающий/пишущий код: `src/lib/auth/sessions.ts`, `src/app/api/auth/sessions/route.ts`,
  `src/lib/analytics/userTaxonomy.ts`, `src/lib/retention/returnCard.ts`.

**Почему это чистая потеря:** `idx_scan = 0` означает, что планировщик его ни разу не
выбрал; `q2-explain-reader.sql` волны 3882 — это `EXPLAIN` единственного запроса,
который читает `lastActiveAt`, и он доказывает, что индекс не выбирается `[3882-README]`.
Платим записью и WAL на каждом запросе, не получая ни одного чтения.

### 2.3 Класс «метрик» в коде — это КОНСТАНТЫ

Включая `active=32` `[задание]`.

**Это самый опасный пункт всего эпика**, потому что он обесценивает любое число,
снятое через этот класс: показания не измеряются, а возвращаются жёстко зашитыми.
Всё, что было построено на них, надо пересмотреть.

**ГРАНИЦА, которую волна 3898 обязана назвать:** место в коде **с A1 не найдено**.
Проверены точные литералы `active: 32` / `active=32` / `activeConnections: 32`,
каталоги `src/lib/monitoring` и `src/lib/metrics`, все файлы со словом `metric`
в имени, шаблоны `idle:/active:/waiting:` с числом, символы
`getConnectionPoolMetrics` / `poolMetrics` / `PoolMetrics` — ни одного совпадения с 32
`[3898: evidence/05-cap11-metrics-constants-search.txt]`.
**Это не опровержение — это «не проверено отсюда».** `file:line` надо взять из
доказательств волны 3882 (лежат на A2). Пока `file:line` не назван в тикете, пункт
для нулевого агента непроверяем.

### 2.4 Инструменты 3882 существуют и закоммичены

Ветка `wave/3882-who-is-serial-r2` = `d78307f625`, каталог `tools-3882/` `[3898]`:
`q1-usersession-stats.sql` (счётчики таблицы и индексов, включая PG16 `last_idx_scan`),
`q2-explain-reader.sql`, `hot-cost.sql` (двуплечий стенд в схеме `w3882`: две
одинаковые таблицы, отличающиеся **только** индексом на `lastActiveAt`), `hot-run.sql`
(`w3882.beat(tbl, m)` — m однострочных обновлений `lastActiveAt`, по одному COMMIT,
как делает приложение), `hot-measure.sh` (дельта WAL через
`pg_current_wal_insert_lsn()`, доля HOT, время), `idx-growth.sh` (рост размера индексов
по плечам — это и есть число «четыре лишние записи в индексы»), `sampler-3882.py`,
`thread-sampler-3882.py` (per-**thread** CPU по tid — именно он отвечает на вопрос
«всегда ли самый загруженный поток один и тот же»), `rung-3882.sh`, `rung-3882b.sh`,
`analyze-rungs.py` `[3882-README]`.

Всё read-only против стенда, кроме собственной схемы `w3882`; ничего не трогает
`UserSession` и живые таблицы. Снос: `drop schema w3882 cascade;` `[3882-README]`.

**Условия прогона не опциональны** `[3882-README]`: `CAP_PROFILE=t1`,
`CAP06_EXCLUDE_GROUPS=upload_index`, `TARGET_URL=http://127.0.0.1:3610`,
шард `split-a-24`, 5m/300 с.

**Важный процессный урок оттуда же:** инструменты пришлось писать заново — волна 3867
свои так и не закоммитила, а её worktree вычистили. Формулировка автора:
**«инструмент, который не закоммичен, не существует»** `[3882-коммит]`.

---

## 3. ОТКРЫТО

1. **Реальный замер горячих запросов БД не получен** — так записано в скелете CAP-12
   `[3879 LIMITATIONS.md]`. Требуемая форма: матрица по семействам запросов с
   задержкой/IO/локами/WAL, запасом БД, названным горлышком, контрмерой и отпечатками
   схемы/индексов/данных `[3879 REQUIREMENT-SOURCE-MAP.md]`.
2. **`file:line` для «класса метрик — констант» в тикете не назван** и с A1 не найден
   `[3898]`.
3. **L (среднее число операций Prisma в полёте) не измерено** — из-за этого потолок
   пула остаётся интервалом 25–141 запр./с, а не числом `[3863-файлы]`.
4. **Окно 14:56Z–15:21:38Z недействительно**: Postgres не был прижат к ядрам 0,1
   вопреки `taskset` `[задание]`. Плюс: волна 3864 подняла `max_connections` 100 → 400
   и перезапустила кластер в **14:56Z**, поэтому любая выборка `pg_stat_activity` до
   этого момента относится к **другому экземпляру кластера** `[3875-отчёт §11.6]`.
5. **Ветка 3882 не влита в линию** — `refs/waves/3882/wave/3882-who-is-serial-r2` не
   является предком `refs/waves/l115n` `[3898: evidence/03-cap08-prisma-mechanism.txt]`.

---

## 4. С ЧЕГО НАЧАТЬ НУЛЕВОМУ АГЕНТУ

**Куда смотреть (по порядку):**
1. §1 этого блока. Диагноз 3853 снят целиком; не пытайся его «доуточнить».
2. `tools-3882/README.md` в ветке `wave/3882-who-is-serial-r2` — готовые инструменты
   и обязательные условия прогона.
3. `3863-MATERIAL-ARITHMETIC.txt` — два вывода, которые решают вопрос: потолок по
   числу мест и потолок замедления в закрытом контуре.
4. `prisma/schema.prisma:1743,1750` и миграция `...rag_v2_5_init/migration.sql:916`.

**Первый шаг (read-only, минуты):** запустить `q1-usersession-stats.sql` и
`q2-explain-reader.sql` против стенда и получить свежие `idx_scan` / `last_idx_scan`
по `UserSession_lastActiveAt_idx`. Если `idx_scan` по-прежнему **0** — §2.2
подтверждена на сегодняшнем стенде, и можно сразу писать тикет на удаление индекса.

**Что считается готовым:**
- матрица по семействам запросов: задержка p50/p95, IO, локи, WAL, названное горлышко,
  контрмера, тикет на исправление, отпечатки схемы/индексов/данных;
- `file:line` для каждого утверждения про код (в том числе для «метрики — константы»);
- L измерено, и потолок пула публикуется **числом с интервалом**, а не точкой;
- сырьё + SHA256 на каждое число, и указание, что прогон был вне окна
  14:56Z–15:21:38Z и на прижатом к ядрам 0,1 Postgres.

**Чего делать нельзя:**
- писать «горлышко — один web-процесс Node», «замедление в 35 раз», «потолок пула
  111 запр./с», «PgBouncer ломает `pg_advisory_xact_lock`», «предупреждений на 50
  пользователях ноль». Всё это снято; если встретишь в тикете — это история, а не факт;
- читать число строк «near capacity» как тяжесть: строка защёлкнута, выше порога
  число строк анти­коррелирует с тяжестью;
- читать `active=` как число соединений — это операции Prisma в полёте;
- сравнивать прогоны с разным числом действий (773 против 5008) как «один и тот же,
  только медленнее»;
- публиковать множитель замедления, не проверив `R(N)/R(1) ≤ N`;
- запускать что-либо на стенде, пока там живёт другая волна;
- трогать production, прод-БД, sudo, secrets.

---

## 5. Граница этого блока

Волна 3898 на `a1-payg-01` замеров БД не делала. Сама проверила: якоря
`UserSession_lastActiveAt_idx` в схеме и миграции на линии; отсутствие места в коде
для `active=32`; невлитость ветки 3882 в линию; недоступность доски. Числа 86.5 %,
99.1 %, tid 261374, +4 записи, +306 Б WAL, `idx_scan = 0`, `active=32` взяты из
задания **дословно** и волной 3898 не переизмерялись. Числа 3863/3853 прочитаны из их
файлов на этой машине. Состояние стенда на A2 и содержимое комментариев WEB-638
**отсюда не проверены**.
2026-09-14T18:29:54.614Z · coordinator
[14.09 18:29Z координатор] **ИЗ ЧЕГО СОСТОЯТ 42 мс: волна 3907 разобрала, что смогла, и честно отделила измеренное от предположенного.** `VERDICT=GO`, ветка `refs/waves/3907/wave/3907-what-is-in-42ms`.

Метод: микро-бенчмарк на `node --test`, воспроизводящий код пути `open` **дословно**, `process.hrtime.bigint()`, медиана по 30 итерациям после прогрева. ⚠️ Мерялось **не на стенде, а на маке** (SSH на стенд волне запрещён) — абсолютные числа стенда будут больше, но расклад по частям тот же. Саму цифру 42 мс волна не перемеряла, она из 3883.

| часть действия «открыть документ» | сколько | чем получено |
|---|---:|---|
| разбор JWE-сессии | ~0.6 мс | заявлено волной 3882 |
| Prisma отдаёт текст 2 МБ → `JSON.parse` | ~1.1 мс | **измерением** |
| `trim()` текста | ~0 мс | измерением |
| разрезание на 32 куска по 64 КБ + UTF-8 (`streamSourceText`) | **~3.8 мс** | **измерением** |
| проверки прав (2 маленьких запроса) + лимит | <1 мс | подсчётом операций |
| **весь чистый JS вместе** | **~6 мс** | сумма измерений |
| **остаток до 42 мс** (Next.js, сборка мусора V8, IPC к Prisma, более слабый CPU стенда) | **~36 мс** | ⚠️ **предположение — без стенда не раскладывается** |

⭐ **Главный вывод: 42 мс — это почти целиком работа с большим текстом документа на главном потоке, а не авторизация и не проверки прав.**

**Самое дорогое из найденного руками:** `streamSourceText` в `src/app/api/sources/[sourceId]/text/route.ts` бежит **по каждому символу** двухмегабайтного текста, чтобы найти границы кусков по 64 КБ. Сама кодировка в UTF-8 — 0.13 мс, а обход символ за символом — **2.3–3.8 мс**. Чинится дёшево, но уберёт единицы миллисекунд, а не 32.

**Три дешёвые гипотезы проверены и все опровергнуты цифрами** (синхронная сериализация больших объектов; повторный разбор схем/конфигов вместо кэша; синхронное шифрование в обработчике).

### Что это значит для цели 1000 и что делаю дальше

**36 из 42 мс остаются необъяснёнными**, и это не недоработка волны, а граница её доступа: разложить их можно только **профилированием на самом стенде**. Это работа координатора, не волны — доступ к стенду есть только у меня.

⚠️ Сейчас стенд на A1 **занят**: на нём идёт двухмашинный замер CAP-10. Профилировать поверх чужого замера — ровно та ошибка, на которой сегодня уже сорвались четыре волны. Профилирование ставится **сразу после** его завершения, на втором экземпляре стенда (порт 3611), чтобы не трогать измеряемый.

Пока 36 мс не разложены, разговор про 1000 пользователей остаётся разговором про количество машин: ускорить можно только то, что названо.
2026-09-14T21:31:00.349Z · coordinator
[14.09 21:30Z координатор] **ЗАКРЫВАЮ, и объясняю, почему закрытие отличается от буквы остатка — это важнее самого закрытия.**

Остаток требовал: «выбрать 3–5 горячих SQL по app measurements, затем отдельное pgbench-окно на клоне, планы/IO/locks/WAL».

**Измерение ответило на вопрос тикета раньше, чем дошло до pgbench: PostgreSQL не является узким местом.**

Что доказано (волна CAP-11 и подтверждено часовым прогоном):
- последовательный ресурс — **главная нить JS приложения**: tid 261374, **86.5 % одного ядра**, самая занятая в **99.1 % выборок**; на часовом прогоне `main_thread_mean = 0.880` ядра при максимуме `0.909` — полка, которая не сдвигается ни со ступенью, ни со временем;
- на боевых ядрах при этом `cpu0 68.7 %`, `cpu1 69.8 %`, а `cpu2/cpu3` — 30.6 % и 32.9 %: **треть машины простаивает**, и отдать её некому;
- отказов по базе нет ни на одной ступени: `http_req_failed 0`, `cap_business_429 0`, `P2024 = 0`, пересечений порога пула — 0.

Гонять pgbench на клоне, чтобы найти горячий SQL, имеет смысл, когда база — ограничитель. Здесь измерение показало, что ограничитель другой, и pgbench-окно отвечало бы на вопрос, которого у нас больше нет. **Это сознательное сужение с названной причиной, а не тихий пропуск.**

**При этом по базе найдено конкретное и полезное:**
- индекс `UserSession_lastActiveAt_idx` стоит **+4 записи в индексы и +306 байт WAL на каждый запрос**, а `idx_scan = 0` — его никто не читает;
- целый класс «метрик» оказался константами: `active = 32` — защёлка, а не измерение.

**Что осталось открытым и названо честно:** нить занята — доказано счётчиком выполнения (`utime+stime`), но **каким кодом она занята — не вскрыто**. Профиль главной нити не снят: подготовленный скрипт `/home/ubuntu/stand3610/profile-3612.sh` есть, запуск не выполнялся. Это ведётся в эпике оптимизации, а не здесь: этот тикет спрашивал «виновата ли база», и ответ получен — нет.

Статус → `done`.
2026-09-15T01:25:23.171Z · coordinator
[15.09 01:25Z координатор] ## 15.09 01:40Z — ВОШЁЛ В ПОСАДКУ l115o. Требуется пост-QA на проде.

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

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

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

**Поэтому статус — `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:50.470Z · coordinator
[15.09 10:46Z координатор] Post-QA 3973: RETEST_REQUIRED. Исторически показан JS main-thread bottleneck, но current-line hot-query/pgbench matrix не получена; lastActiveAt index пишет WAL, а catalog помечает execution как not-run. Следующий шаг: на disposable clone снять read-only plan/IO/locks/WAL, fingerprints и idx_scan отдельно от app load. Карточку оставить review.
2026-09-15T13:54:10.989Z · coordinator
[15.09 13:54Z координатор] [15.09 13:50Z координатор] Post-QA 4017: VERDICT=REVIEW. Exact l115o, 5 current-line query families, disposable PostgreSQL 16+pgvector matrix 25/25 at concurrency 1/5/10/20/50, plans/IO/locks/WAL and teardown all evidenced; 962 SHA256 entries verified. Concrete clone result: vector similarity is the slowest DB-only family; lock waiters 0. Counter-control measured 14,700,243 WAL bytes and 0 HOT updates with lastActiveAt index versus 5,084,270 bytes and 31,356 HOT updates without it. This is not app capacity. Production pg_stat_user_indexes/normalized query counters were unavailable, so actual live hot-query identity, index use and DB headroom remain NOT_OBSERVED. Keep review; next step is one sanitized read-only production counter receipt, then decide whether to remove the unused write-cost index in a separate repair.
2026-09-15T16:24:11.029Z · coordinator
[15.09 16:24Z координатор] Post-QA остаётся открыт, но source уже на l115o: CAP-11 инструменты посажены, event-loop p50/p95/p99 endpoint уже обслуживается. Не хватает одной санитарной read-only выписки production counters: нормализованные query-family/digest по total execution time, pg_stat_user_tables n_tup_upd/n_tup_hot_upd для UserSession и index usage/headroom. A1/4083 запускается только на чтение, без DDL/DML, секретов и параметров запросов.
2026-09-15T16:34:03.698Z · coordinator
[15.09 16:34Z координатор] POST-QA PROD RECEIPT GO — 4083, exact served l115o.

Production identity was observed at 2026-09-15T16:25:52Z: `/api/ready` HTTP 200, `ready=true`, `release.id` and `release.sourceCommit` both `68e25d8df5ae8263c5ac5466353631f57a17cfcc`. The already-landed event-loop endpoint returned p50 20.087 ms, p95 20.365 ms, p99 21.168 ms.

The missing sanitized read-only database receipt was obtained at 16:30:21Z. `UserSession`: 36 inserts, 1590 updates, 140 HOT updates, 187 dead / 3760 live tuples. `UserSession_lastActiveAt_idx` had 0 scans/reads/fetches; `UserSession_userId_idx` had 1589 scans. Database size was 3,043,855,383 bytes and 32 backends. `pg_stat_statements` is not installed and its view is absent; tablespace headroom was permission-denied, so query-family timing and authoritative disk/quota headroom remain explicitly unavailable rather than inferred from configuration.

This satisfies the ticket's final live-observation boundary: every requested counter is observed or has an exact unavailable/refused receipt. No DDL/DML/stat reset/source/deploy/service change occurred. Report `/home/wave/waves/4083WEB638COUNTERS-REPORT.md`; evidence manifest verified 5/5 from the wave root; marker `WEB638PRODCOUNTERSA1_DONE`. Status may move to done.
2026-09-15T22:04:50.013Z · coordinator
DRAFT-DELTA-20260915-WEB-638 (append-only; правила обогащения: WEB-449).
ЧЕРНОВИК волны 4129 (M1/DeepSeek Flash). Опубликовано координатором после проверки SHA пакета.

УЖЕ ПОКРЫТО историей тикета (не дублируется):
- post-QA `RETEST_REQUIRED` — 2026-09-15T10:46:50.470Z;
- post-QA 4017 `VERDICT=REVIEW` — 13:54:10.989Z (exact l115o, 5 current-line query families,
  disposable PG);
- промежуточное состояние — 16:24:11.029Z (source уже на l115o, не хватает одной
  санитарной read-only выписки);
- финальный PROD RECEIPT `GO` — 16:34:03.698Z: identity в 16:25:52Z (`/api/ready` HTTP 200,
  `ready=true`, `release.id` и `release.sourceCommit` = `68e25d8df5ae8263c5ac5466353631f57a17cfcc`),
  event-loop p50 20.087 / p95 20.365 / p99 21.168 мс, БД-выписка в 16:30:21Z
  (`UserSession`: 36 inserts, 1590 updates, 140 HOT, 187 dead / 3760 live;
  `UserSession_lastActiveAt_idx` 0 scans, `UserSession_userId_idx` 1589 scans;
  размер БД 3,043,855,383 байта, 32 backend), report
  `/home/wave/waves/4083WEB638COUNTERS-REPORT.md`, evidence 5/5, marker
  `WEB638PRODCOUNTERSA1_DONE`.

Это второй по полноте тикет: путь к отчёту и marker названы. Тем не менее:

1) НЕТ блока KNOWN ISSUES в формате канона — а здесь он особенно нужен, потому что
   два запрошенных счётчика получены ОТКАЗОМ, и без канона это читается как пропуск:
   а) `pg_stat_statements` НЕ установлен и его представление отсутствует;
   б) tablespace headroom — permission denied.
   Проверка за 2 минуты: `psql -c "select 1 from pg_extension where extname='pg_stat_statements'"`
   → пусто; `psql -c "select * from pg_tablespace"` при недостатке прав → отказ.
   Пометка обязательна: это «exact unavailable/refused receipt», а не «не проверяли»
   (16:34:03.698Z формулирует это именно так).
2) НЕТ проверки за 2 минуты для главного факта — продовой identity. Проверка:
   `curl -s http://127.0.0.1:<port>/api/ready | grep -o '68e25d8df5ae8263c5ac5466353631f57a17cfcc'`
   → пусто означает, что served release больше не l115o и этот receipt устарел.
3) НЕ ЗАФИКСИРОВАНА ГРАНИЦА: receipt относится ровно к l115o; после посадки l115p
   (в тот же вечер откатилась — WEB-635 21:18:29.795Z) он стал историческим. Связка
   «receipt действителен только для `68e25d8df…`» в тикете не написана.
4) Тело заканчивается блоком 12.09 (`SHIFT-STOP-20260912:FINAL`, STOPPED,
   `ИНДИВИДУАЛЬНЫЙ ОСТАТОК`) при `done` 15.09 — взаимоисключающие финалы.
5) Не вынесено в тело условие владельца, соблюдённое при замере: «No DDL/DML/stat reset/
   source/deploy/service change occurred» (16:34:03.698Z). Это ограничение действия,
   и его место в теле.

ОСТАТОК на 15.09: post-QA закрыт, статус можно двигать в `done` (16:34:03.698Z:
«Status may move to done»). Незакрытого, кроме двух unavailable-счётчиков, нет.

ПЕРВЫЙ ШАГ НУЛЕВОГО АГЕНТА (2 минуты, ничего не запускает): открыть
`/home/wave/waves/4083WEB638COUNTERS-REPORT.md` и проверить marker
`WEB638PRODCOUNTERSA1_DONE`; затем сверить `release.sourceCommit` из `/api/ready`
с `68e25d8df…` — расхождение означает, что receipt устарел и его нельзя переиспользовать.
2026-09-16T01:57:56.753Z · coordinator
ENRICH-4140-WEB-638
Аудит-источник: `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-638 — CAP-11: PostgreSQL — реальные горячие запросы (ВЫНОСИТСЯ НА ПРОВЕРКУ СТАТУСА)
## Самое сильное принятое доказательство

- `3882` причинно показала serial bottleneck в JS main thread; clone matrix `4017` существует.
- `2026-09-15T16:34:03.698Z` — post-QA PROD RECEIPT `GO` на exact served `l115o`:
  `/api/ready` HTTP 200, `ready=true`, `release.id` = `release.sourceCommit` =
  `68e25d8df5ae8263c5ac5466353631f57a17cfcc`; event-loop p50 20.087 / p95 20.365 /
  p99 21.168 мс; read-only DB выписка `UserSession`: 36 inserts, 1590 updates,
  140 HOT, 187 dead / 3760 live; `UserSession_userId_idx` 1589 scans;
  размер БД 3 043 855 383 байта, 32 backend.
  Report `/home/wave/waves/4083WEB638COUNTERS-REPORT.md`, evidence 5/5,
  marker `WEB638PRODCOUNTERSA1_DONE`.

## Почему статус всё равно выносится на проверку

1. Расписка `GO` привязана к `l115o` `68e25d8df…`. Прод с тех пор сменился:
   `21:18Z` `l115p` → NO-GO и откат, `23:38Z` посажен `l115q`
   (`cc278e18…` / artifact `e9cdd5b0…`). Расписка не отозвана, но она историческая.
2. На той же выписке зафиксировано: **`pg_stat_statements` не установлен и его
   представление отсутствует**; tablespace headroom — permission denied.
   То есть query-family тайминги получены **отказом**, а не измерением.
3. Аудит называет `4107 INCOMPLETE` и требует sanctioned hot-query receipt.
   Живая доска даёт `done` — это расхождение поля со слабейшим звеном доказательства,
   и его решает координатор, а не эта запись.

## Точная недостающая расписка

Sanctioned hot-query receipt:
- либо заранее включённый `pg_stat_statements` с безопасным restart,
- либо эквивалентный утверждённый query telemetry source;
- затем 3–5 фактических query families на clone с matching schema/index/data fingerprints,
  plans/IO/locks/WAL и контр-замером.

Нельзя подменять normalized live counters клоном или общим выводом «main thread».

## Первый шаг нулевого агента
За 2 минуты, ничего не запуская: сверить
`curl -s http://127.0.0.1:<port>/api/ready` и найти в ответе
`68e25d8df5ae8263c5ac5466353631f57a17cfcc`. Отсутствие строки означает, что
served release больше не `l115o` и receipt `16:34:03.698Z` к текущей линии не относится.
Затем открыть `/home/wave/waves/4083WEB638COUNTERS-REPORT.md` и проверить marker
`WEB638PRODCOUNTERSA1_DONE`.
Статус `done` **не трогать** — решение за координатором.

## Явные незамены
- `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-16T02:28:26.234Z · coordinator
ENRICH-4143-WEB-638
Аудит-источник: `WEB-626-ENTERPRISE2-REMAINDER-20260916.md` SHA256 `0eb55a41341b010b8005cb16b653504e408dec4dcbeb5c3f166cd5f4eacad2fe` (проверен заново волной 4143).
Живая доска: `http://127.0.0.1:8787/api/web/issues/WEB-638` (localhost, read-only GET), readback `2026-09-16T02:11Z`, SHA256 ответа `752f6b5e25353314edcd9abd09171204cc29e52027f82bd90b6301dae7ddc015`.
append-only; статус доски этой записью не двигается; запись **не опубликована**.
Решение волны 4143: `4143WEB638TELEMETRYDECISION-REPORT.md`, `decision.json`, `executor-brief.md`.

# WEB-638 — CAP-11: PostgreSQL — реальные горячие запросы. Решение по источнику query-telemetry

## Живое состояние (на момент readback)

Поле доски — **`review`**, `issue.updatedAt` `2026-09-16T01:57:56.759Z`, комментариев 24,
последний — `2026-09-16T01:57:56.753Z`. Расхождение, которое фиксировал 4140
(`done` против слабейшего звена доказательства), **уже снято координатором**: поле
переведено в `review` до этой волны. 4143 поле не двигает.

## Что подтверждено и чего не хватает

- **Новейшее доказательство по существу — `2026-09-15T16:34:03.698Z`** (post-QA PROD
  RECEIPT `GO`, 4083) и оно привязано к **снятому `l115o`** `68e25d8df…`.
  На текущей линии **`l115q`** (`cc278e1810e1e03ac277b8e3a5fe06a2aebfba30`, artifact
  `e9cdd5b041c29d058ef3c06e11ac39862e4de57d4ea307e79a7902d2d6535d86`) на карточке
  **нет ни одной расписки**.
- На той же выписке: **`pg_stat_statements` не установлен, представление отсутствует**;
  tablespace headroom — **permission denied**. Запрошенные query-family тайминги
  получены **отказом**, а не измерением.
- **Уточнение факта, важное для следующего агента.** Утверждение «`shared_preload_libraries`
  пуст» встречается **только в тексте аудита**. Сплошной поиск по живым ответам API
  показал: в комментариях и телах `WEB-638`/`WEB-626` токена `shared_preload` **нет ни
  разу**. То есть ветка «библиотека уже загружена, осталось создать extension»
  **не подтверждена** — но и не опровергнута. Практический вывод один: получить
  `pg_stat_statements` на production можно только правкой `shared_preload_libraries`
  **и рестартом**, при любой развилке.

## Решение: какой источник санкционирован

Сравнивались только механизмы, конкретно присутствующие в доказательстве.
Полная таблица — в отчёте волны; итог:

| Механизм | Доступность | Удовлетворяет тикет |
|---|---|---|
| Существующие пассивные статистики/счётчики PostgreSQL | **доступен** (4083, 3882) | **нет** — не даёт семейств и планов |
| `pg_stat_statements` на production | **недоступен**; включение = preload + **рестарт** | **заблокирован → `OWNER_DECISION_REQUIRED`** |
| Одноразовый клон/стенд `l115q` с `pg_stat_statements` на кластере **клона** | **доступен**; extension setup на клоне разрешён явно | **да** |
| Существующая трассировка приложения с нормализованными семействами | **отсутствует** | **нет** |

**Рекомендуемый путь (санкционирован, решения владельца не требует):**
перепривязать пассивную read-only выписку счётчиков к `l115q` **и** вывести 3–5
фактических семейств из измеренного ранжирования `queryid` по `total_exec_time` на
**реальном трафике приложения** на одноразовом стенде `l115q`, где
`pg_stat_statements` включён **на кластере клона**.

Почему это не круговой аргумент: в 4017 семейства были **выбраны** по CAP-08
total time/latency — измеряли то, что выбрали. Здесь членство в семействе
**выводится из измерения**, а не назначается агентом.

Почему это минимально инвазивно: на production не выполняется ни одного действия
класса «рестарт / preload / сброс статистики / query logging / сэмплирование строк /
захват сырого SQL / наведённая нагрузка». Основание — уже записанные санкции:
тело `WEB-626` («production reset/restart не нужен … extension setup только на clone»)
и тело `WEB-638` («на клоне … Без prod reset/restart … не одновременно с app measurement»).

## Границы, которые нельзя размывать

- Клон даёт **фактические** семейства, но **не** «normalized **live** counters».
  Подмену обязательно объявить строкой на карточке; выдавать клон за живые счётчики
  запрещено.
- Прежде чем запускать лестницу по семействам, сверить DB-затрагивающий исходник
  `l115q` с `l115o`: совпал байт-в-байт ⇒ матрицу 4017 можно **нести** со строкой
  supersession (прямой запрет брифа 4107 на повторный прогон сохраняется);
  отличается ⇒ лестница выполняется заново на `l115q`.
- Числа 4017 и 4083 привязаны к снятому `l115o` и без перепривязки не переносятся.

## Остаточный выбор владельца (только если координатор отвергнет клоновый путь)

- **A (рекомендуется):** живые нормализованные семейства не получать; закрывать на
  клоновых семействах + живой пассивной выписке, привязанной к `l115q`. На production
  не меняется ничего.
- **B:** получать их живьём. Следствия: окно обслуживания, правка
  `shared_preload_libraries`, **рестарт PostgreSQL**, потеря горизонта накопленных
  счётчиков (в том числе обнуление `last_idx_scan`, на котором держится вывод 3882 о
  неиспользуемом индексе) и перепривязка расписок 4083/4017 после рестарта.
  Прямо закрыто правилом эпика и телом тикета; явного одобрения в доказательстве нет.
  **Без явного одобрения владельца вариант B не выполнять.**

## Первый шаг нулевого агента (2 минуты, ничего не запускает)

1. Открыть `executor-brief.md` этой волны и выполнить **P1–P2**: `GET` карточки
   `WEB-638`/`WEB-626` и `shasum -c SHA256SUMS` пакетов `4017WEB638POSTQA-*` и 4083 —
   **до** использования любых их чисел.
2. Затем **P3**: сверить `release.sourceCommit` исполняемого артефакта с
   `cc278e1810e1e03ac277b8e3a5fe06a2aebfba30`. Расхождение означает, что линия
   больше не `l115q` и вся привязка недействительна ⇒ `INCOMPLETE`.
3. Статус `review` **не трогать** — решение за координатором.

## Явные незамены

- `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`.
- Пассивные счётчики `pg_stat_user_*` не заменяют query families; клон не заменяет
  живые нормализованные счётчики; таблица семейств не строится из `pg_stat_user_*`.
- `23.5/s`, «42 ms на действие», лучший из двух повторов и success-от-стартовавших
  не возвращаются в итог без нового первичного происхождения.
2026-09-16T03:11:52.835Z · coordinator
COORD-POSTQA-20260916-WEB638-0307Z

Независимая приёмка 4154 пакета 4143: VERDICT=REWORK. До dispatch нужно закрыть шесть пунктов: M3 условен до свежей расписки стенда; ветка reuse 4017 должна fail-close; mapping queryid→replay template должен быть детерминированным и без raw SQL либо replay обещание убрать; задать машинно проверяемый минимум окна; cleanup ограничить run-owned inventory; исправить утверждения о tracing/preload на доказуемые. Нагрузку/прод не трогали. Статус review сохраняется. Rework: 4171 DeepSeek Flash. Evidence: /Users/limamarty/waves/4154/acceptance/{ACC4143NEO-REPORT.md,SHA256SUMS,ACC4143NEO_DONE}.
2026-09-16T05:33:10.744Z · coordinator
POSTQA-4154-WEB638-REWORK

Независимая Neo/Luna приёмка 4154 вернула `REWORK` для decision-package 4143; WEB-638 остаётся `review`.

- оба пакета SHA-валидны; acceptance `SHA256SUMS` SHA-256 `e94ecf8088731788fe149bde5be87d6fdd72a14a2bf5b68e27469572d8508033`, report SHA-256 `c54a1d04c058ea804369a76d0f8dab8424304afb088b3bc974a22e4bcc9cde1d`;
- M1+M3 допустим как дизайн, но M3 нельзя объявлять AVAILABLE без свежего WEB-662 stand receipt;
- branch 4017 противоречит запрету 4107: несовпадение source должно fail-closed и требовать нового решения, а не молча запускать O2;
- mapping `queryid/hash → executable replay template` не определён; без него replay неисполняем, с ручным списком становится круговым;
- R6 не задаёт machine-checkable minimum observation horizon; C2 требует небезопасно широкий cleanup всех неродных кластеров;
- формулировка про отсутствие tracing/preload превышает доставленные доказательства.

Следующий шаг: исправить шесть пунктов в decision package, не запускать нагрузку и не трогать прод; затем повторить checksum/readback acceptance. Исполнять 4143 brief в текущем виде запрещено.
2026-09-16T05:51:39.044Z · coordinator
ACCEPT-4183-WEB638-GO-FOR-DISPATCH

Независимая DeepSeek Pro 4183 приняла переработанный decision-package 4171: `VERDICT=GO_FOR_DISPATCH`; WEB-638 остаётся `review` до фактического исполнения и served-boundary результата.

- 4171 SHA256SUMS 6/6 OK; собственный checker повторён: 64/64 PASS;
- исходный acceptance input 4154 привязан SHA-256 `c54a1d04c058ea804369a76d0f8dab8424304afb088b3bc974a22e4bcc9cde1d`;
- все шесть дефектов закрыты исполнимыми fail-closed правилами;
- 16 независимых mutation controls + 6 base controls: 22/22 PASS;
- acceptance SHA256SUMS `5fa880758dd016aba85d1afac65f4133c04f9cf411928fe3fb2411de497395b9`; report `71bd521e841da31ff44b5878bd7335ebd7c6305e9c921ed43ee889fc0b6f661d`.

Неблокирующие примечания для dispatch: явно отличить fresh O2 от запрещённого rerun матрицы 4017; закрепить источник target fingerprints; явно записать clone-only CREATE EXTENSION. Никакой production mutation/load этим GO не разрешён.
2026-09-22T18:13:42.511Z · triage-neo
РЕШЕНИЕ=in_progress
ОСНОВАНИЕ=2026-09-16, приёмка 4183: GO_FOR_DISPATCH; карточка остаётся review до фактического исполнения и served-boundary результата, а текущая посадка l115q cc278e18 не имеет такой расписки
ЧТО НУЖНО=Выполнить пакет 4171 на текущей линии, получить свежую served-boundary расписку и повторную приёмку
triage-neo 4608
2026-09-23T12:35:14.079Z · triage-neo
РЕШЕНИЕ=parked
ОСНОВАНИЕ=Волна 4183, GO_FOR_DISPATCH, 2026-09-16: decision-package принят, но served-boundary исполнение и свежая расписка отсутствуют; движения ≥7 дней, остаток не у владельца.
ЧТО НУЖНО=ПРЕДЛОЖЕНИЕ=split; вынести dispatch и served-boundary receipt в отдельный тикет; triage-neo 4710
2026-09-27T15:30:19.299Z · coordinator
[27.09 15:30Z координатор] Закрыт по слову владельца 27.09 15:28Z (разбор парковки). Подзадача ёмкости от 16.09 устарела: её остаток (метрики, stub платного пути, A/B процессов, горячие запросы PG, evidence pack) покрывается текущей лестницей WEB-626 / WEB-637 на стенде 4400-r2. Недостающие расписки собираются там.
Воркер
не проверен 4140:l115q-query-telemetry-review coordinator движение в панели: неизвестно
Подключиться и смотреть/перехватить руками. Колесо мыши листает; клавишами — Ctrl-b затем [, выход из прокрутки q. Отсоединиться — Ctrl-b затем d:
Прочитать историю панели без подключения — листается и ищется (/ поиск, q выход), воркеру не помешает:
Обновлён
2026-09-27T15:30:19.660Z