fix(metrics): статика в bounded «static»-лейбл — кардинальность route (#362) #366
Open
agent_coder
wants to merge 2 commits from
fix/362-metrics-route-cardinality into develop
pull from: fix/362-metrics-route-cardinality
merge into: vvzvlad:develop
vvzvlad:main
vvzvlad:test/351-generative-converter
vvzvlad:feat/371-roles-catalog
vvzvlad:feat/370-page-versioning
vvzvlad:refactor/345-server-converter
vvzvlad:feat/196-multi-cursor
vvzvlad:refactor/294-spec-registry-cont
vvzvlad:fix/363-migration-order
vvzvlad:perf/348-backend-lowhanging
vvzvlad:fix/ai-sdk-partial-output-oom
vvzvlad:perf/344-background-rerenders
vvzvlad:develop
vvzvlad:perf/342-code-splitting
vvzvlad:feat/355-perf-metrics
vvzvlad:perf/346-compression-cache
vvzvlad:feat/git-sync-2
vvzvlad:perf/343-typing-latency
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:feature/offline-sync
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/approved
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#366
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/362-metrics-route-cardinality"
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
Схлопнуть route-лейбл статики в один
static— фикс кардинальности (follow-up #355). closes #362.http_request_duration_seconds.routeловил сырые хэш-имена ассетов (route="/assets/index-CAbxDtto.js",/assets/chunk-*.js).@fastify/staticотдаёт каждый файл через роут, чейrouteOptions.url= сырой хэш-путь, поэтому лейбл был неограничен — новый набор имён на каждый деплой, серия растёт вечно (ровно та утечка кардинальности, от которой защищены API-роуты).resolveRouteLabelтеперь СНАЧАЛА детектит статику по префиксу пути (/assets/,/vad/,/brand/,/locales/) и схлопывает в один лейблstatic(query-строка срезается до проверки); API-роуты по-прежнему используют темплейт, 404 →unknown. Edge-латентность статики и так меряет Traefik (traefik_router_request_duration_*).How verified
tsc— EXIT 0 (изолированный frozen install);metrics.spec— pass (добавил кейсы: схлопывание хэш-ассетов, срез query, и «реальный API-роут со словом assets НЕ схлопывается»).Checklist
static; кардинальность лейбла ограниченаРевью — #366 (fix(metrics): статика в bounded «static»-лейбл — кардинальность route, #362), round 1. Вердикт: CHANGES
Ядро фикса КОРРЕКТНО и полностью закрывает безграничную утечку кардинальности. Сверено (мной + coherence по исходнику
@fastify/static9.1.3):static.module.tsрегистрит статик сwildcard:false→ плагин глобитclient/distи вешает по маршруту НА КАЖДЫЙ файл, поэтомуrouteOptions.urlхэшированного ассета — это буквально/assets/index-CAbxDtto.js(ровно причина #362). Единственный per-deploy-меняющийся (хэшированный) корень —/assets/, и он покрыт. Pre-check по префиксу — верное решение; трейлинг-слэш на префиксах не даёт задеть реальный/api/...-роут (API подsetGlobalPrefix('api'), exclude-лист сверен — ничего не совпадает); non-static ветка байт-в-байт прежняя. Security/Conventions/Regressions LGTM. Критичного/эскалации нет. Открыто 3 (все bounded, low).Открыто: F1 (
/icons/пропущен в списке — сиблинг включённых/brand,/vad,/locales, дёргается на каждой загрузке); F2 (коммент над списком врёт про «content-hashed» для 3 из 4 префиксов); F3 (негативный тест проходит вхолостую — не проверяет границу префикса).Объективка зелёная (мой прогон, голова
f759084f, CI-условия): frozen install 0; ee build 0; server tsc 0;metrics.spec— 16 passed.📋 Do (F1–F3) + DROP + что сверено
Do — почини, потом ставь
review/needsF1 [stability/coherence · low]
/icons/пропущен вSTATIC_PATH_PREFIXES—apps/server/src/integrations/metrics/http-metrics.hook.ts:11.apps/client/public/(копируется вclient/distкак есть, БЕЗ хэша) содержитicons/— прямой сиблинг уже включённыхbrand/,vad/,locales/.index.htmlссылается на/icons/favicon-16x16.png,/icons/favicon-32x32.png(+/manifest.json), т.е. эти пути летят на КАЖДОЙ загрузке страницы и сейчас дают собственныеroute-лейблы вместоstatic. Не безграничная утечка (имена стабильны → bounded), НО это неполное исполнение цели самого PR («схлопнуть статику в один лейбл») на горячем пути, и явный недосмотр рядом стоящих сиблингов. Fix: добавь'/icons/'в массив; добавь/icons/app-icon-192x192.pngвit.eachвmetrics.spec.ts, чтобы новый префикс был покрыт.F2 [documentation · low] Коммент над списком over-generalize'ит «content-hashed» —
http-metrics.hook.ts:9-14.Коммент: «filenames are content-hashed (index-.js, chunk-.js), so a NEW set of names is minted on every deploy». Это верно ТОЛЬКО для
/assets/./vad/,/brand/,/locales/(и добавляемый/icons/) копируются изpublic/со СТАБИЛЬНЫМИ именами — их схлопывают не из-за хэш-черна, а потому что приwildcard:false@fastify/staticвешает по роуту на файл → высокая-но-ОГРАНИЧЕННАЯ per-file кардинальность (весьlocales/-tree = отдельные роуты). Тест-фикстуры (/vad/silero_vad_v5.onnx,/brand/logo.svg,/locales/en.json— не хэшированы) прямо противоречат формулировке. Fix (в той же правке, что F1): разведи в комменте две причины —/assets/= хэш-черн (безграничная), остальные = bounded-но-высокая per-file кардинальность подwildcard:false.F3 [test-coverage · low] Негативный тест проходит вхолостую — граница префикса не проверяется —
metrics.spec.ts:75-82(does NOT collapse a real API route)./api/pages/assets-guideначинается с/api/, до префикс-ветки НЕ доходит, и подстроки/assets/в нём нет — тест прошёл бы даже против баг-реализацииincludes('/assets/')и НЕ проверяет трейлинг-слэш-границу (единственную защиту от ложного схлопывания). Fix: добавь реальный boundary-кейс —/assets(без завершающего слэша) и/или/assetsx/fooсrouteOptions.urlреального роута → ожидать НЕ'static'. Тогда анти-false-positive гуард действительно исполняется.⛔ DROP — кодеру НЕ делать · калибровочный лог (оператору)
[below-threshold]low[simplification]const path = (req.url??'').split('?',1)[0]— мёртвая логика при ЧИСТО-префиксном матче (query — суффикс, наstartsWithне влияет:'/assets/x.js?v=2'.startsWith('/assets/')уже true). Безвредна (одна аллокация split/запрос), документирует намерение, покрыта отдельным тестом, снятие требует правки теста — автор вправе оставить. СТАНОВИТСЯ load-bearing, только если матч однажды уедет на суффикс/расширение. DROP.[below-threshold]low[stability/coherence] Корневые одиночные файлы/manifest.json,/vite.svg,/index.html,/index-template.htmlдают собственные лейблы — каждый ровно ОДНА стабильная серия (не директория), пренебрежимая bounded-кардинальность, и не выражается чистым dir-префиксом. Усложнять префикс-модель ради 1-серии-на-файл не стоит. DROP (захочет паритет — добавит точечным match'ем).[below-threshold]low[test-coverage] Веткаurl: undefined(?? ''-гуард) без теста — тривиальный гуард, низкая ценность. DROP.[speculative]low[coherence] 404 на несуществующий/assets/xxxтеперь лейблstaticвместоunknown(раньшеunknownчерез отсутствующийrouteOptions.url) — bounded, спорно-но-разумно (это И ЕСТЬ запрос статик-пути), чуть мутит 404-учёт. DROP.Сверено (9 аспектов + мои проверки, голова
f759084f):wildcard:false→ route-per-file (исходник@fastify/static9.1.3,index.js:141-197),routeOptions.urlстатика = сырой путь → #362 реальна и/assets/(единственный хэшированный корень) покрыт → безграничная утечка закрыта; нет PWA/service-worker/sw.js/workbox/хэш-manifest-*(гипотетические корни не существуют); API подsetGlobalPrefix('api'), exclude-лист[robots.txt, share/:shareId/p/:pageSlug, l/:alias, mcp]— ни один не начинается с 4 префиксов → реальный роут не схлопывается; query-strip корректен для всех краёв (/a?b→/a,''→'');isStreamingResponseне задет (static≠ SSE); non-static путь неизменён;it.eachпокрывает все 4 префикса и НЕ вакуумен (без фикса вернул бы сырой путь → тест упал бы). Остаток (/icons/+ корневые файлы) — bounded, не #362-регресс: F1 — единственный из них, что тянет на DO (сиблинг-недосмотр + горячий путь); F2/F3 едут в той же правке.Починил все 3,
review/needs(коммит43b11d92).F1: fixed — добавил
/icons/вSTATIC_PATH_PREFIXES.public/icons/копируется вclient/distкак есть (сиблинг уже включённых brand/vad/locales), аindex.htmlссылается на/icons/favicon-*.pngна каждой загрузке → эти запросы получали свои route-лейблы вместоstatic. Добавил/icons/*в позитивныйit.each.F2: fixed — поправил коммент: content-hashed только
/assets/(безграничная per-deploy утечка);/vad//brand//locales//icons/— стабильные имена (повторяющиеся, не безграничные). Ни те ни другие не место в API-route гистограмме.F3: fixed — негативный тест теперь реально проверяет границу трейлинг-слэша (единственный анти-false-collapse гуард):
/assets(без слэша),/assetsx/foo.js,/iconset/x.png→ НЕstatic. Ровно кейсы, которые баг-реализацияincludes()/без-слэша ложно схлопнула бы.Объективка: server
tsc0;metrics.specpass.Ревью — #366 (fix(metrics): статика в bounded «static»-лейбл — кардинальность route, #362), round 2. Вердикт: PASS ✅
Все 3 round-1 DO закрыты и сверены по коду: F1
/icons/добавлен вSTATIC_PATH_PREFIXES(+/icons/app-icon-192x192.pngв позитивный it.each) — прямой сиблинг/brand,/vad,/locales, дёргался favicon'ом на каждой загрузке; F2 коммент переписан — теперь корректно разводит/assets/(content-hashed → безграничный, #362) и/vad,/brand,/locales,/icons(копируются изpublic/verbatim, стабильные имена); F3 добавлен реальный boundary-негатив-тест (/assetsбез слэша,/assetsx/foo.js,/iconset/x.png→ НЕstaticИ равны route-шаблону) — падает против баг-реализации slashless-префикса, гуард трейлинг-слэша реально исполняется.Веер 9 аспектов — всё LGTM. Coherence подтвердил: безграничная утечка #362 закрыта полностью (единственный per-deploy-меняющийся корень
/assets/схлопнут; остаточные/manifest.json,/vite.svg,/index.html— bounded, стабильные имена, не растут — ниже порога, как и отмечалось в round-1 DROP).Объективка зелёная (мой прогон, голова
43b11d92): frozen install 0; ee build 0; server tsc 0;metrics.spec— 20 passed (было 16; +1 позитив/icons/, +3 boundary-негатива).Замечаний нет.
review/approved.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.