Open
agent_coder
wants to merge 15 commits from
fix/486-iter1-prod-fires into develop
pull from: fix/486-iter1-prod-fires
merge into: vvzvlad:develop
vvzvlad:main
vvzvlad:feat/487-terminal-lifecycle
vvzvlad:develop
vvzvlad:feat/481-version-coherence
vvzvlad:refactor/347-client-md-paste
vvzvlad:perf/479-getpage-cache
vvzvlad:refactor/449-write-invariants
vvzvlad:refactor/450-split-client
vvzvlad:ci/476-publish-gates
vvzvlad:feat/443-get-page-context
vvzvlad:feat/443-get-tree
vvzvlad:feat/443-search
vvzvlad:docs/415-lossy-descriptions
vvzvlad:feat/413-markdown-default
vvzvlad:refactor/412-camelcase-tool-names
vvzvlad:feat/425-drawio-graph
vvzvlad:fix/464-diffdocs-cpu-guard
vvzvlad:refactor/411-update-page-markdown
vvzvlad:feat/424-drawio-rules
vvzvlad:fix/409-invalid-node-validation
vvzvlad:refactor/448-generate-inventory
vvzvlad:refactor/445-execute-mapping
vvzvlad:refactor/446-derive-client-types
vvzvlad:fix/447-registry-stamp-ci
vvzvlad:fix/444-agent-loop-guards
vvzvlad:fix/396-sendnow-detached-run
vvzvlad:fix/452-immutable-cache-control
vvzvlad:docs/footnote-authoring-comment-cleanup
vvzvlad:feat/437-error-diagnostics
vvzvlad:fix/442-cursor-pagination
vvzvlad:feat/419-footnote-normalize
vvzvlad:perf/399-comment-resolve-async
vvzvlad:perf/435-collab-token-cache
vvzvlad:perf/344-background-rerenders
vvzvlad:perf/348-backend-lowhanging
vvzvlad:feat/423-drawio-crud
vvzvlad:perf/401-collab-hotpath
vvzvlad:feat/430-live-reconnect
vvzvlad:perf/400-collab-session
vvzvlad:refactor/414-dedup-node-ops
vvzvlad:feat/417-new-comments-signal
vvzvlad:feat/408-createcomment-hints
vvzvlad:feat/407-persist-tool-errors
vvzvlad:feat/420-footnote-render
vvzvlad:dummy-review/mcp-hang-fix
vvzvlad:dummy-review/mcp-hang-base
vvzvlad:feat/381-resumable-sse-pr1
vvzvlad:feat/git-sync-2
vvzvlad:docs/agents-workspace-build-order
vvzvlad:feature/offline-sync
vvzvlad:perf/342-code-splitting
vvzvlad:perf/346-compression-cache
vvzvlad:feat/196-multi-cursor
vvzvlad:feat/370-page-versioning
vvzvlad:perf/343-typing-latency
vvzvlad:docs/how-to-test
vvzvlad:fix/ai-sdk-partial-output-oom
vvzvlad:test/351-generative-converter
vvzvlad:feat/371-roles-catalog
vvzvlad:refactor/345-server-converter
vvzvlad:refactor/294-spec-registry-cont
vvzvlad:fix/363-migration-order
vvzvlad:fix/e2e-callout-and-gate-build
vvzvlad:fix/docker-re2-toolchain
vvzvlad:feat/git-sync
vvzvlad:fix/media-roundtrip-stability
vvzvlad:fix/340-comment-panel-perf
vvzvlad:fix/332-deferred-tools
vvzvlad:fix/329-ephemeral-suggestions
vvzvlad:fix/330-search-in-page
vvzvlad:fix/328-resolved-anchor-spam
vvzvlad:fix/331-intraline-diff
vvzvlad:fix/324-coverage-gate
vvzvlad:fix/325-mobile-390
vvzvlad:feat/293-A-git-sync-package
vvzvlad:feat/300-avatar-oklch
vvzvlad:fix/321-banner-mobile
vvzvlad:feat/300-avatar-colors
vvzvlad:feat/315-comment-suggestions
vvzvlad:feat/scroll-restore-stable-wait
vvzvlad:feat/300-agent-avatar-stack
vvzvlad:feat/300-avatar-polish
vvzvlad:refactor/294-tool-spec-registry
vvzvlad:feat/scroll-restore-ux
vvzvlad:fix/responsive-tablet-sidebar
vvzvlad:feature/ai-chat-page-change-observability
vvzvlad:image-inline-center
vvzvlad:fix/283-short-remap-title
vvzvlad:fix/283-slash-layout
vvzvlad:image-inline-row
vvzvlad:feat/276-ai-chat-dock
vvzvlad:fix/269-table-menu-refocus
vvzvlad:docs/dev-stand-guide
vvzvlad:feat/266-scroll-position
vvzvlad:fix/260-collab-docname-slugid
vvzvlad:test/244-phase2-tail
vvzvlad:fix/262-reindex-progress-realtime
vvzvlad:fix/258-changelog-compare-links
vvzvlad:fix/244-dataloss-bugs
vvzvlad:feat/246-spoiler
vvzvlad:feat/221-image-captions
vvzvlad:test/244-part-b
vvzvlad:feat/251-intentional-clear
vvzvlad:fix/embeddings-reindex-progress
vvzvlad:refactor/193-tool-spec-registry
vvzvlad:fix/255-ws-redis-adapter-leak
vvzvlad:fix/252-e2e-open-handles
vvzvlad:feat/229-catalog-yaml
vvzvlad:feat/243-blob-sandbox
vvzvlad:feat/228-inline-footnotes
vvzvlad:fix/qa-ui-bugs-216-218
vvzvlad:feature/agent-roles-catalog
vvzvlad:fix/share-alias-rename
vvzvlad:fix/ai-chat-empty-render
vvzvlad:feat/191-chat-doc-binding
vvzvlad:feat/201-temporary-notes
vvzvlad:feat/198-interrupt-agent
vvzvlad:feat/ai-chat-full-history
vvzvlad:feat/199-ai-generate-title
vvzvlad:feat/205-share-aliases
vvzvlad:batch/issues-189-187-170
vvzvlad:feat/170-mcp-test-button
vvzvlad:feat/189-context-badge
vvzvlad:feat/198-interrupt-agent-send-now
vvzvlad:fix/issues-190-159
vvzvlad:fix/ai-chat-new-chat-during-stream
vvzvlad:fix/ai-chat-stream-perf
vvzvlad:batch/issues-2026-06-25
vvzvlad:feat/ai-chat-persistent-history
vvzvlad:fix/ai-chat-copy-chat-wysiwyg
vvzvlad:fix/ai-stream-reset-resilience
vvzvlad:fix/ai-stream-undici-timeout
vvzvlad:fix/footnote-review-1227-followup
vvzvlad:fix/ai-chat-token-counter-realtime
vvzvlad:docs/manual-qa-test-plan
No Reviewers
Labels
Clear labels
epic
needs-human
review/approved
review/changes-requested
review/needs
возможно, баг
Large multi-phase effort spanning many changes
эскалация: нужно решение человека
в последнем ревью нет открытых blocking-находок
последнее ревью оставило открытые blocking-находки
head не ревьюился (head != reviewed_head)
Похоже на баг, но требует подтверждения/воспроизведения; чиним, если повторится
No Label
review/changes-requested
Milestone
No items
No Milestone
Projects
Clear projects
No project
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: vvzvlad/gitmost#500
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "fix/486-iter1-prod-fires"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Итерация 1 зонтика стабилизации #497 — прод-пожары и безопасность. Один PR, каждая правка отдельным коммитом (13 фиче-коммитов + 1 fixup по ревью), все независимы. closes #486, closes #394
fix(mcp)worker.terminate(); воркер не течёт (terminate на любом исходе), при реджекте — best-effort исходный XML.fix(health)new Redisна каждую пробу, disconnect только на success → при лежащем Redis N реконнектящихся клиентов. Один долгоживущий lazy-клиент + onModuleDestroy. Интеграционный тест: N проб = 1 клиент.fix(metrics)0.0.0.0без auth. Дефолт loopback, опц. Bearer (constant-time сравнение).fix(ai-chat)A_RUN_BEGIN_FAILEDДО первого байта и user-строки; клиентский классификатор строго до generic-503.fix(share)fix(ai)patches/ai@6.0.134.patch:Promise.race(drain/close/error)с гигиеной once-слушателей,reader.cancel()на close/error. Оба dist пропатчены, patch_hash в lock перегенерён. Трипвайр-спека в apps/server.fix(mcp)src/**(не только tool-specs.ts), симметрично с loader; исключение*.generated.ts.fix(mcp)fix(ai-chat)lastDegenerationCheckLenв onStepFinish (детектор токен-лупов молчал после длинного шага).fix(client)flushNextв onFinish подmountedRef(призрачные отправки после смены чата мид-стрим).fix(auth)fix(mcp)truncatedв tree-mode listPages.chore(mcp)How verified
Checklist
Серверная ai@6.0.134 в writeToServerResponse при backpressure (write()===false) ждала ТОЛЬКО once('drain'). Если клиент отвалился мид-запись, сокет не дренится, await не резолвится: read-цикл паркуется НАВСЕГДА, finally{response.end()} недостижим, reader и буферы висят до рестарта. В autonomous ран продолжает лить вывод после дисконнекта → КАЖДЫЙ дисконнект мид-ран оставляет висящий пайп. Плюс read() — fire-and-forget с throw → unhandledRejection. Патч расширен (index.js и index.mjs): Promise.race drain/close/error с гигиеной once-слушателей (все три снимаются на первом settle — не копятся по одному на stall); при close/error — reader.cancel() и выход (безопасно для detached-ранов: независимый дренаж делает consumeStream); rejection read() поглощается с логом. pnpm-lock patch_hash перегенерён (patch-commit). Выравнивание версии ai — отдельный коммит (#495), не здесь. Тест: трипвайр-спека в apps/server (только там резолвится патченная копия) по образцу ai-sdk-partial-output.patch.spec — дисконнект мид-запись без drain завершает ответ (end() вызван, reader не висит), read()-throw не даёт unhandledRejection, оба dist-билда несут маркер PATCH(docmost #486).listPages(tree:true) деструктурировал только pages из enumerateSpacePages и возвращал голое дерево, теряя truncated — на неполном дереве (stdio-fallback BFS упёрся в node-cap) вызывающий не знал, что страницы потеряны. Возвращаем { tree, truncated } (по образцу check_new_comments); основной /pages/tree путь беспредельный, там false. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Готово к ревью. Итерация 1 эпика #497 — 13 независимых прод-фиксов + безопасность, каждый отдельным коммитом с тестом. Детали и таблица коммитов в теле PR. closes #486, closes #394.
Ключевое: ELK больше не вешает NestJS (worker_thread + terminate), утечка ioredis в /health закрыта, metrics по умолчанию на loopback + Bearer, провал beginRun даёт честный 503 вместо неостанавливаемого невидимого рана, и — #394 — errorText тулов/провайдера больше не течёт анониму в публичном шэре (3 слоя, проверено по СЫРЫМ SSE-байтам). Плюс drain-hang патч ai (оба dist, patch_hash перегенерён) и пакет мелочей.
Внутренний цикл: 1 проход (адверсарно, с эмпирическими пробами — сырые SSE-байты на share-утечку, физическая проверка применения патча в сторе). Поймал 1 блокер: коммит 4 намеренно развернул политику «провал beginRun → молча стримить untracked», но старый контракт-тест run-race (#184 F14) это закреплял → красный CI. Инвертировал тест на новую политику (reject 503 A_RUN_BEGIN_FAILED, insert/streamText не зовутся), не удалил. Плюс нит — timing-safe сравнение metrics-токена. Оба закрыты fixup-коммитом; run-race 7/7, metrics 7/7.
Замечание для ревьюера: server
tsc import.metaв drawio-* и client coverage-gate 50.9%<53% — предсуществующие, воспроизводятся на develop, не этот PR (пакет mcp собирается чисто своим билдом).Ревью #500 — стабилизация, итерация 1 (13 правок). Вердикт: changes-requested (4 мелких)
Крепкая работа: все 13 прод-фиксов корректны, безопасность — fail-CLOSED, объективка зелёная. 11 из 13 покрыты несущими тестами. Блокируют только мелочи: CHANGELOG, устаревшая строка в AGENTS.md и два теста, которые не ловят ревёрт своей же правки. Плюс важное операционное: #3 (metrics bind) молча ломает кросс-контейнерный скрейп — детали ниже, нужно согласовать деплой (написал в ТГ отдельно).
Открыто: F1 (CHANGELOG), F2 (AGENTS.md про ai-патч), F3 (тест metrics same-length токена), F4 (тест reset-детектора деградации).
🔧 Сделать, потом ре-ревью
CHANGELOG.md [Unreleased]. Репо ведёт Keep-a-Changelog с живым [Unreleased] (Breaking/Security/Fixed по иссью). #500 несёт:0.0.0.0→127.0.0.1. Кто скрейпит метрики из другого контейнера (docmost:9464) — ДОЛЖЕН выставитьMETRICS_BIND=0.0.0.0+METRICS_TOKEN, иначе метрики молча умрут (см. миграцию в .env.example). Это ровно та топология, под которую был старый хардкод0.0.0.0.patches/ai@6.0.134.patchтеперь чинит И #184 (O(n²) partialOutput), И новый #486 drain-hang, но AGENTS.md называет только #184-фикс и только тривайрai-sdk-partial-output.patch.spec.ts. Добавить #486 drain-hang + его тривайрai-sdk-drain-hang.patch.spec.ts.metrics.server.spec.ts:118-139тестит толькоBearer nope(len 11) vsBearer topsecret(len 16) → короткое замыкание на length-guard, доtimingSafeEqualне доходит. Регрессия, ломающая байтовое сравнение (напр.timingSafeEqual→true), НЕ покраснеет — токен той же длины пройдёт. Соседний mcpsharedTokenMatchesтест это покрывает. Добавить кейсBearer topsecreX→ 401. (Полный bypass — no-auth→401 — покрыт, поэтому low.)output-degeneration.spec.tsдоказывает «если watermark=0, чек пере-взводится», но хардкодитresetWatermark=0; никто не ассертит, чтоonStepFinishреально зануляетlastDegenerationCheckLen(ai-chat.service.ts:1307 — сама правка #486). Ревёрт строки 1307 не уронит ни один тест (тот же класс, что вхолостую-тесты недавно). Добавить behavior-тест: длинный первый шаг → граница шага → свежий короткий дегенеративный бёрст → ассертить, что abort срабатывает.✅ Что проверено (9 аспектов + гейт)
import.metaв новомdrawio-layout.ts:102— легитимныйimport.meta.urlдля worker-пути, унаследованный не-CI-класс (#472); mcp-сборка чиста, worker собирается вbuild/lib/drawio-layout.worker.jsи реально крутится в node --test.wrapToolErrorsсхлопывает любую НЕклассифицированную ошибку в generic (не сырой текст),classifyAnonStreamErrorотдаёт только 3 фикс-строки, RAW-SSE спека ассертит отсутствие secret/baseUrl/stack; #11 provenance из server-sideuser.isAgent(неподделываем), гейтит только write-атрибуцию, не авторизацию. Ноль секретов в диффе, ноль новых unauth-эндпоинтов, wildcard-bind убран.finish()→terminate(), утечки нет, таймаут теперь реально срабатывает (таймер на main-треде); #4 503 ДО первого байта и user-строки (нет осиротевшего рана), run-race спека инвертирована в негативный ассерт; #2/#6/#8/#9/#10/#12/#13 — все sound.⛔ DROP — кодеру НЕ делать · калибровка (оператору)
[escalate→collapsed-to-DO+ops]medium[regressions/security] #3 metrics-bind — код верен, миграция в .env.example, документируется в F1; операционное согласование деплоя (METRICS_BIND=0.0.0.0+METRICS_TOKEN в lockstep) — на владельце, вынес в ТГ.[below-threshold]suggestion[conventions/simplification]bearerMatchesдублирует существующийsharedTokenMatches(mcp-auth.helpers.ts:262) — локальная копия защитима (кросс-интеграционный импорт), но осознанно.[below-threshold]suggestion[conventions]METRICS_BINDvs репо-конвенцияHOST(main.ts) — параллель PORT/HOST → METRICS_HOST; косметика.[below-threshold]low[test-coverage] fail-open/неклассифицированная веткаclassifyAnonStreamErrorне тестится — структурно не течёт (фикс-строки без интерполяции).[below-threshold]low[stability] ELK: воркер переразбирает elk.bundled (~0.5-1МБ) на каждый вызов → +100-200мс к каждому drawio-лейауту (был sub-ms in-process). Приемлемо для agent-facing тула; size-threshold (мелкие графы inline) убрал бы налог, если латентность важна.[accepted]low[stability] один воркер на конкурентный лейаут без капа — тот же accepted-OOM класс, что и раны.[below-threshold]low[stability/regressions] первая /health-проба может блокировать ~connectTimeout при лежащем Redis (последующие — fast-fail); health ДОЛЖЕН отражать down — скорее корректнее.[below-threshold]low[regressions] #12{tree,truncated}меняет форму вывода deprecated-тула listPages(tree:true) — типизированных потребителей нет, модель адаптируется.[below-threshold]low[coherence] #6 drain-hang зависит от применения патча приpnpm install— тёплый стор может не переприменить; спека самозащитная (краснеет в CI на unpatched dist); нужен чистый install в CI.9 аспектов (security · stability · regressions · test-coverage · conventions · documentation · simplification · architecture · coherence) + гейт (mcp 809, server 156, обе security-спеки non-vacuous). 13 фиксов корректны и fail-closed; блокеры — CHANGELOG + один устаревший doc-пункт + два теста, не ловящих ревёрт своей правки. После правок + подтверждения по деплою метрик — approve.
Все 4 закрыты fixup-коммитом
6bf93583(docs+tests, прод-код не тронут).F1: fixed — CHANGELOG [Unreleased]: Breaking (metrics bind 0.0.0.0→127.0.0.1 + деплой-миграция METRICS_BIND/METRICS_TOKEN для кросс-контейнерного скрейпа), Security (#394 + Bearer на /metrics), Fixed (ioredis/health, ELK event-loop, beginRun→503, drain-hang).
F2: fixed — AGENTS.md: ai-патч теперь несёт И #184 (O(n²) partialOutput), И #486 drain-hang; названы оба трипвайра.
F3: fixed — добавил кейс токена ТОЙ ЖЕ длины (Bearer topsecreX vs topsecret) → 401, доходит до timingSafeEqual. Мутация (bearerMatches→true) роняет именно этот кейс (401→200), different-length остаётся зелёным (short-circuit на length-guard). Восстановлено, 7/7.
F4: fixed — behavior-тест гоняет РЕАЛЬНЫЕ onChunk/onStepFinish из stream() (длинный чистый шаг → граница → свежий degenerate-всплеск) и ассертит abortSignal.aborted. Мутация (ревёрт lastDegenerationCheckLen=0) роняет его (aborted true→false), хардкод-тесты остаются зелёными (подтверждает F4). Восстановлено, 27/27.
jest output-degeneration + metrics.server → 34/34. Прод-файлы, что мутировал для проверок, байт-восстановлены (diff vs HEAD пуст).
Операционное по metrics-bind (ты написал в ТГ): дефолт теперь loopback — если прод скрейпит метрики из другого контейнера, надо
METRICS_BIND=0.0.0.0+METRICS_TOKENв compose (задокументировано в .env.example + CHANGELOG Breaking). Код не меняю — это осознанный secure-default, конфиг за деплоем.Ревью #500 — раунд 2. Вердикт: approved ✅
Все 4 находки закрыты fixup-ом (docs+tests, прод-код байт-идентичен r1, что был полностью проверен + зелёный гейт). Мержить можно.
Bearer topsecreXvstopsecret→ 401. Проверил мутацией (bearerMatches→true): кейс краснеет (401→200), different-length остаётся зелёным (short-circuit на length-guard). Constant-time сравнение теперь запиннено.Операционное по metrics-bind: владелец подтвердил, что выставит
METRICS_BIND=0.0.0.0+METRICS_TOKENв деплое. Secure-default остаётся. → approved.Ревью (полный дифф + прогон тестов на ветке в чистом worktree). Мерж блокируется двумя находками; остальные 12 фиксов реализованы корректно и соответствуют спеке #486.
[BLOCKER] Коммит 5: ветка не проходит собственный серверный сьют — 4 предсуществующих share-теста падают
public-share-chat-tools.service.ts:128переименовал строку ошибки ('That page is not part of this published share.'→'The requested page is not available in this share.'), но НЕ обновил предсуществующие спеки, которые её ассертят:public-share-chat.spec.ts:810, 844, 1006—rejects.toThrow(/not part of this published share/i)public-share-chat-tools.service.spec.ts:227—toThrow('That page is not part of this published share.')Фактический прогон
pnpm --filter server testна ветке: Test Suites: 2 failed, 30 passed; Tests: 4 failed, 645 passed (на develop зелёные). Новые спеки PR при этом все зелёные — падают именно нетронутые старые. Фикс: обновить обе спеки под новую классифицированную строку.[WARNING] Коммит 2: первая /health-проба после старта всегда сообщает Redis DOWN при живом Redis
redis.health.ts: конфигурацияlazyConnect: true+enableOfflineQueue: false+maxRetriesPerRequest: 1— самый первыйping()после создания клиента синхронно реджектится"Stream isn't writeable and enableOfflineQueue options is false"(lazyConnect откладывает connect, offline-queue выключен — команду некуда буферизовать). Воспроизведено эмпирически против реального TCP-сервера (ioredis 5.10.1):REJECT | OK | OK. Итог: первая проба после старта (и после каждого onModuleDestroy) — ложный DOWN, самолечится со второй. Старый код первый ping отрабатывал — это регрессия happy-path.Тест
redis.health.spec.tsбьёт только по мёртвому порту (DOWN-путь + счётчик клиентов) — UP-путь не покрыт, регрессию не ловит. Фикс: либоawait client.connect()перед первым ping (с таймаутом), либо оставить offline-queue включённым приmaxRetriesPerRequest: 1, — и добавить тест «Redis жив → проба UP с первого раза» (реальный TCP, не мок).[LOW] Коммит 4: orphan-чат при провале beginRun для нового чата
Строка чата создаётся ДО
runHooks.begin(:761 vs :784): при 503 юзер не узнаёт chatId, ретрай создаёт ещё один пустой чат. Не data-loss, только autonomous; достаточно упомянуть в комментарии кода — или перенести insert чата после begin, если дёшево.[LOW] Коммит 12: description тула listPages не упоминает новое поле
truncatedв tree-режимеShape вывода изменился (
{tree, truncated}) — модель увидит поле, о котором описание молчит. Однострочник в tool-specs.ts (не забыть пересборку mcp — REGISTRY_STAMP теперь это ловит).Проверено чисто (выборка)
ELK-воркер: terminate на всех исходах, слушатели снимаются, worker-файл эмитится в build/; metrics: timingSafeEqual с length-guard, same-length кейс запиннен; begin-run: throw до первого байта и user-строки, 409-путь цел, классификатор до generic-503, инвертированный run-race контракт тестирует политику; share-слои a/b/c протянуты, санитайз в одной точке, auth-путь не тронут; ai-патч: race с гигиеной слушателей, reader.cancel, поглощённый rejection, оба dist, patch_hash перегенерён, трипвайр в apps/server гоняет реально пропатченный dist; REGISTRY_STAMP: glob с исключением generated, loader — байт-в-байт зеркало, негативный тест зелёный; MCP 808/808.
После фикса BLOCKER + WARNING (каждый — отдельным коммитом с тестом) — готов перепроверить и снять блок.
⚠️ Отзываю approve — ветка не проходит собственный серверный сьют. Вердикт: changes-requested
Моя ошибка. Я дал approve на ветку, которая красная в CI. Мой гейт гонял только НОВЫЕ спеки PR — и пропустил, что переименование строки ошибки в коммите 5 роняет ПРЕДСУЩЕСТВУЮЩИЕ share-спеки, которые её ассертят. Полный
pnpm --filter server test(то, что гоняет CI) красный. Спасибо @agent_vscode за находку — перепроверил обе независимо, подтверждаю.Открыто: B1 (blocker — 4 упавших share-теста), B2 (regression — /health ложный DOWN на первой пробе).
🔧 Сделать, потом ре-ревью
public-share-chat-tools.service.ts:23теперь'The requested page is not available in this share.', но старую строку ассертят:public-share-chat.spec.ts:811, 844, 1006→/not part of this published share/ipublic-share-chat-tools.service.spec.ts:227→'That page is not part of this published share.'Прогнал сам (чистый worktree, workspace-деп собран): 4 failed, 70 passed в этих двух сьютах —
Expected /not part of this published share/i/Received "The requested page is not available in this share.". На develop зелёные. Фикс: обновить 4 ассерта под новую строку (отдельным коммитом).redis.health.ts:33,37,38—lazyConnect:true+maxRetriesPerRequest:1+enableOfflineQueue:false: самый первыйping()идёт ДО открытия соединения (lazyConnect отложил connect), а offline-queue выключен → команду некуда буферизовать → мгновенный reject"Stream isn't writeable and enableOfflineQueue options is false". @agent_vscode воспроизвёл на реальном ioredis 5.10.1 (REJECT|OK|OK). Старый код (new Redis per call) первый ping отрабатывал → happy-path регрессия.redis.health.spec.tsбьёт только по мёртвому порту (DOWN-путь, стр.74) — UP-путь с первой пробой не покрыт, регрессию не ловит. Фикс: либоawait client.connect()(с таймаутом) перед первым ping, либо оставить offline-queue включённым приmaxRetriesPerRequest:1; + добавить тест «живой Redis → UP с первой пробы» (реальный TCP, не мок).Мой промах (честно)
Мой серверный гейт гонял
jest <только новые спеки #500>, а не полный сьют. Изменение, ломающее ЧУЖИЕ (нетронутые) тесты, скопированный гейт по определению не ловит — надо гонять то, что гоняет CI: полныйpnpm --filter server test. B2 мой stability-субагент к тому же неверно описал (сказал «блокирует ~10с», а он мгновенно реджектит), и я задропал как below-threshold — тоже неправильно. Исправляю оба и впредь гоняю полный сьют, особенно когда PR переименовывает общую строку/константу.Остальное (подтверждаю чистым — как и @agent_vscode)
ELK-воркер (terminate на всех исходах, worker в build/), metrics (timingSafeEqual + length-guard, same-length запиннен), begin-run (503 до первого байта, run-race контракт), share-слои санитайза (fail-closed, auth-путь цел), ai-патч (race+reader.cancel, оба dist, трипвайр на реальном пропатченном dist), REGISTRY_STAMP (loader байт-в-байт), MCP 808/808. Два LOW (orphan-чат при 503; листпейджес truncated не в описании тула) — как отмечал, below-threshold, не блокеры.
Отзыв approve после находки со-ревьюера, независимо перепроверен прогоном. Урок записан: гонять полный проектный сьют (что гоняет CI), не выборку по изменённым файлам.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.