- пришедшего называет заголовок Remote-User от прокси, и верят ему только с адреса из перечня trusted_proxies; своего входа у сервиса не осталось — ни корня /auth, ни кук, ни срока сессии, ни секрета клиента в конфиге и в базе - учётная запись заводится первым обращением: EnsureUser в пакете хранилища, шаг схемы 202608220001 с колонкой provider_login и снятыми правилами users - cmd/oidcstub заменён на cmd/devtools с подкомандой proxy; заодно закрыт унаследованный DL3066 — пользователь образа назван числом
46 KiB
Ревью изменения trusted-header-login
Сводка
- Режим прогона: по графу. Метка:
large. Размер и сложность в задании триажу не переданы — обоснование разметки воспроизвести нечем, метка взята как объявленная. - База диффа:
origin/master. Изменение читалось вместе с непрослеженными файлами (cmd/devtools/,internal/adapter/repo/pocketbase/identity.go,migrations/202608220001_trusted_header_login.go,internal/controller/http/identity.go, дельта-спеки change). - Состояние гейта: зелёный целиком, кроме шага
dockerfile—hadolintдаётDL3066наDockerfile:94. Отказ унаследован (воспроизведён на чистомorigin/master), долг объявлен строкой вCLAUDE.md, раздел «Гейт». Новым красным шагом это изменение гейт не красит. - На входе: 31 пронумерованная находка проходов + 4 пункта «дешевле
переделать до мерджа» от
architecture+ 1 замер без дефекта отops. На выходе: 3 + 4 = 7 в основных секциях, остальное — ниже, ничего не выброшено молча.
План с исходом по каждой теме
| тема | дом | глубина | кто закрывает | исход |
|---|---|---|---|---|
| requirements | дельта-спеки change + openspec/specs/ |
разбор | specs |
закрыта, 8 находок (5–12); в отчёт ушли 2, ещё 4 — ниже |
| autotests | CLAUDE.md, «Гейт» |
— | autotests |
закрыта, 4 находки (1–4); в отчёт не ушла ни одна самостоятельно, одна закрывается правкой №3 |
| conventions | docs/conventions/ |
разбор | code |
закрыта, 8 находок (15–22); в отчёт ушли 2 |
| architecture | docs/architecture.md + docs/passport.md |
доказательство | architecture |
закрыта, 2 находки + 4 пункта «до мерджа»; в отчёт ушла 1 |
| security | docs/security.md |
доказательство | adversary |
закрыта, 6 находок (23–28); в отчёт ушли 3 |
| operations | docs/architecture.md «Эксплуатация» + docs/database.md |
доказательство | ops |
закрыта, 3 находки + 1 замер без дефекта; в отчёт не ушла ни одна, старшая — в гипотезах |
- Тем без отчёта нет. Тем без дома в плане нет.
basicsне запускался — своих тем проекта нет, все шесть разобраны именными проходами.- Сигнал о заниженной метке:
review-codeотработал и возражений по метке не подал;review-basicsне запускался, поднять метку было некому. Молчание здесь — молчание одного корректора из двух, а не двух из двух.
Блокирует мердж
Запрос, отвергнутый ограничителем частоты, всё равно заводит учётную запись
- Файл:
internal/controller/http/identity.go:91,internal/controller/http/errors.go:219 - Severity: major (
criticalне ставлю: инвариантCLAUDE.mdэтим не нарушен, а путь ведёт к мусору в коллекции, а не к порче чужих данных) - Confidence: high
- Оракул: свой падающий тест, прогнан на этом прогоне. Слой узнавания стоит
на
DefaultLoadAuthTokenMiddlewarePriority + 1= −1019, ограничитель библиотеки — на −1000 (apis/middlewares_rate_limit.go:16), то есть узнавание отрабатывает до ограничителя. Прогон: 120 запросов выбирают бюджет, следующие 50 запросов новыми логинами получают429— все 50, — а учётных записей в коллекции становится2 → 52. - Последствие: ограничитель частоты не защищает ничего из того, что делает
слой узнавания. Каждый отвергнутый запрос — это как минимум чтение из базы
(поиск по логину идёт на каждом запросе по норме
access), а с новым именем — ещё и запись. Учётная запись, заведённая мусором, из сервиса не убирается: спекаaccessназывает цену прямо — «слить их или убрать нечем». Барьер «заголовок ставит прокси» этот путь сужает, но не закрывает:docs/review.md, «Недоступно проверке», записывает поведение прокси как непроверяемое отсюда, и строить на нём защиту базы значит держать один барьер вместо двух. - Предложение: перенести слой узнавания на
apis.DefaultRateLimitMiddlewarePriority + 1(−999), аRequireUser— на+ 2(−998). Порядок «токен (−1020) → ограничитель (−1000) → узнавание (−999) → требование записи (−998) → предел тела (−990)» сохраняется целиком. - Найдено проходом:
adversary(25); подтверждено собственным прогоном триажа. - Действие: инлайн
Ответ на первый вопрос задания — перестановка проверена запуском. С приоритетами −999/−998 прогнан весь
go test ./...: единственным красным остался мой временный тест на находку про общий бюджет (ниже, №4), все существующие проверки пакетаinternal/controller/http— включая оба критерия приёмки, победу предъявленного токена над заголовком и «проба здоровья учётной записи не заводит» — прошли. Заведение записи после429при этом пропало:2 → 2. Предел тела на −990 действительно остаётся после требования учётной записи, «отказ неузнанному до чтения тела» сохраняется.Одно последствие перестановки задание не называет, и оно настоящее. Сейчас
RequireUser(−1018) отказывает неузнанному до ограничителя, и отказы бюджета не тратят. После переноса — тратят: замер на этом прогоне, 125 запросов без заголовка, и следующий запрос узнанного человека получает429(до перестановки —200). Само по себе это правильнее (счёт отказов и есть работа ограничителя), но вместе с общим бюджетом (№4) оно даёт анониму, дотянувшемуся до контейнера, выключение сервиса для всех. Поэтому №1 и №4 едут одной правкой, а не порознь.
Ключ учётной записи правится рукой в панели, и архив уезжает следующему с этим именем
-
Файл:
internal/adapter/repo/pocketbase/migrations/202608220001_trusted_header_login.go:77-82,internal/adapter/repo/pocketbase/panel.go:33-34 -
Severity: major
-
Confidence: high
-
Оракул: дословное требование дельта-спеки
openspec/changes/trusted-header-login/specs/access/spec.md: «Ключ учётной записи MUST не правиться ничем, кроме заведения самим сервисом. Ни запросом снаружи, ни рукой в панели». Плюсgrep -rn "OnRecordUpdateRequest" --include=*.go internal/ cmd/→ единственная привязка стоит наmigrations.RecordsCollection, наusersхука нет. Шаг схемы закрывает правила API (UpdateRule = nil), а панель работает суперпользователем — это записано комментарием самого шага, строки 48–50. -
Последствие: переписанный в панели
provider_loginотдаёт весь архив прежнего владельца тому, кто придёт с этим именем следующим. Владелец записи назначается один раз и не меняется — вернуть архив нечем. Правка своя, сознательная, но необратимая и молчаливая: журнала событий у коллекции пользователей нет. -
Предложение: развилка ниже.
-
Найдено проходом:
specs(6) -
Действие: развилка
Вопрос владельцу. Требование спеки «ключ не правится рукой в панели» сегодня не держит ничто: правила API закрыты, но панель ходит суперпользователем. Три варианта: (А) завести хук
OnRecordUpdateRequest(users), отвергающий смену непустогоprovider_login— в проекте уже есть тот же приём для записей (pbrepo.BindPanelRules), цена — десяток строк и проверка; (Б) переписать требование, назвав панель доверенной и записав цену («ключ правится владельцем сервиса, откатить правку нечем»); (В) оставить как есть — тогда спека и код расходятся молча, и это расхождение переживёт мердж.
Заведение учётной записи не оставляет в журнале ни строки, а поломка контура пишется на уровне, которого в бою нет
- Файл:
internal/controller/http/identity.go:101-108,128-151,internal/adapter/repo/pocketbase/identity.go:52-87 - Severity: major
- Confidence: high
- Оракул: свой прогон. Запрос новым логином с доверенного адреса →
ответ
200, учётная запись заведена, перехваченный журнал — пустая строка (""). Дословное требование дельта-спекиaccess: «Исход узнавания MUST оставлять строку журнала — и когда заголовок пришёл с недоверенного адреса, и когда учётная запись заведена. […] Строка несёт адрес пира и идентификатор учётной записи». Второй симптом: ветвь «более одного значенияRemote-User» (строки 103–106) пишетDebug, боевой уровень —Info(docs/conventions/logging.md, «Расхождение: уровень зашит константой»). - Последствие: сервис заводит учётные записи молча — владелец не отличит «никто
не заходил» от «завелось двадцать записей», а по спорной учётной записи не
скажет, когда и с какого адреса она появилась. Хуже второе:
docs/security.mdназывает прокси, добавляющий заголовок вместо замены, главным барьером контура, и половину этой беды сервис закрывает сам — но закрывает невидимо: строка о двух значениях в боевом журнале не появляется вовсе. Поломка контура, которую сервис поймал, владельцу неотличима от тишины. - Предложение:
logger.Infoна исходе «учётная запись заведена» — с адресом пира и идентификатором записи, без значения заголовка (инвариант «Содержимое записи остаётся приватным» и требование спеки «MUST не нести значения заголовка»); уровень ветви «два значения» поднять доWarn— ровно как у ветви недоверенного пира, по той же причине и с той же ценой. - Найдено проходами:
specs(5) иadversary(26) — две находки об одной причине, слиты; оракула у согласия проходов нет, оракул свой. - Действие: инлайн
Стоит исправить сейчас
Бюджет ограничителя частоты общий на весь сервис: один человек выключает сервис остальным
-
Файл:
internal/controller/http/rate_limit.go:20-23,internal/controller/http/app.go:43-63,internal/config/config.go:91-96,cmd/transcriber/main.go:238-246 -
Severity: major
-
Confidence: high
-
Оракул: свой падающий тест. 125 запросов первого человека выбирают бюджет, первый запрос второго человека получает
429. Причина — в исходниках библиотеки:checkRateLimitберёт ключомe.RealIP()(apis/middlewares_rate_limit.go), аRealIP()при пустомSettings.TrustedProxy.Headersоткатывается к адресу пира (core/event_request.go:40-75). Пир теперь всегда обратный прокси. -
Последствие: объявленная сервисом частота опроса выводится из доли бюджета (
pollBudgetShare = 8,PollIntervalMs = 4000) в расчёте на то, что бюджет делят немногие. Делят его все: восемь одновременных опросов выбирают его целиком, и девятый человек получает429на пустом месте. Отказ шумный, но причина невидима — по журналу он неотличим от собственной активности. -
Дефект старше этой задачи (правило заведено
spa-skeleton2026-08-15, прокси перед сервисом стоял и тогда). Уточнение к формулировке прохода:docs/database.md:344иapp.go:43-49не утверждают обратного — там прямо написано «бюджет считается по адресу спрашивающего, а не по учётной записи», и даже назван случай «двое за одним домашним адресом делят его пополам». Сломалось не правило, а основание правила: «адрес спрашивающего» выродился в один адрес на всех, и число 120/60 выбиралось не под это. -
Найдено проходами:
architecture(13) иadversary(23); подтверждено собственным прогоном. -
Действие: развилка
Ответ на второй вопрос задания: чинить здесь. Не потому, что дефект этой задачи — он не её, — а потому, что правка №1 делает его достижимым для анонима: после переноса
RequireUserза ограничитель отказы неузнанному начинают тратить общий бюджет (замерено на этом прогоне: 125 анонимных запросов → узнанный человек получает429). Мерджить №1 без ответа на №4 значит завести новый путь к отказу сервиса.Вопрос владельцу. Чем считать бюджет ограничителя, когда весь трафик приходит с одного адреса: (А) заполнять
Settings.TrustedProxy.Headersпри подъёме (там же, гдеApplyAppRateLimit) — тогда ключом станет адрес человека изX-Forwarded-For. Доверие к этому заголовку той же природы, что доверие кRemote-User, и опирается на тот же перечень адресов; цена — ещё одно требование к контуру, которое отсюда не проверить; (Б) признать бюджет общим на сервис: поднять числа под ожидаемое число людей, переписатьdocs/database.mdи обоснованиеpollBudgetShare; (В) отложить в урожай и мерджить №1 с известным ухудшением — тогда анонимный поток через прокси выключает приложение всем.
Негодное значение Remote-Name запирает человека в сервисе навсегда пятисоткой
- Файл:
internal/adapter/repo/pocketbase/identity.go:71-87,147-159,internal/controller/http/identity.go:133-147 - Severity: major
- Confidence: high
- Оракул: свой прогон. Запрос с доверенного адреса,
Remote-User: namebearer,Remote-Nameиз 5000 знаков → ответ500{"error_code":"internal","message":"Внутренняя ошибка сервиса"}, в журналеlevel=ERROR msg="Failed to resolve account by login header" error="failed to create user account: name: Must be no more than 255 character(s)..". Значение заголовка в ответ и в журнал при этом не уехало — инвариант приватности цел. - Последствие: приёму подвергается только логин (
AcceptProviderLogin), а имя и почта уезжают в колонку как есть. Человек, чьё имя у провайдера длиннее 255 знаков, получает500на каждом запросе и в сервис не попадёт никогда; владелец получаетERRORна каждый такой запрос. Смежный симптом той же причины: разбор отказа судит по наличию колонки вvalidation.Errors, а не по причине отказа — негодный адрес почты неотличим от занятого, и запись молча заводится без почты вместо честного разбора. - Предложение: привести имя и почту к годным значениям в одном месте с логином —
имя усечь до предела колонки, негодную почту не ставить вовсе. Этой же правкой
закрывается непокрытая ветвь «отказ хранилища → отказ сервиса»
(
identity.go:144-146), которуюautotestsназывает единственным намеренным исключением из «слой отказа не выдаёт»: у неё появляется достижимый и проверяемый вход. - Найдено проходами:
specs(7, 10),code(15, 16),adversary(24) — пять формулировок, две причины, одно место правки; слиты. Согласие пяти проходовConfidenceне повышает — оракул свой. - Действие: инлайн
Образец конфига велит верить всей подсети docker: любой контейнер на хосте входит под любым именем
-
Файл:
config.example.toml:69-77 -
Severity: major
-
Confidence: medium (путь не построен: второго контейнера в прогоне нет)
-
Оракул: положение
docs/security.md:109дословно — источник заголовков есть «Обратный прокси — и всякий, кто дотянулся до сервиса с доверенного адреса». Образец задаёт этим адресом172.16.0.0/12, то есть весь умолчательный диапазон docker: под него попадает любой контейнер на хосте, а не только Caddy. -
Последствие: барьер узнавания держится только на адресе пира (кук и токенов сервис не выдаёт). Любой сосед по хосту — а хост держит пет-проекты, а не один сервис — ставит
Remote-User: <чужой логин>и получает чужой архив целиком, молча. Класс риска вsecurity.mdназван, ширина перечня — нет. -
Предложение: развилка ниже.
-
Найдено проходом:
adversary(27) -
Действие: развилка
Вопрос владельцу. Чем сузить перечень доверенных адресов до самого прокси: (А) выделенная сеть compose с фиксированной подсетью под пару «прокси ↔ сервис» — правка живёт в
pet-project-server, здесь остаётся значение образца и строка требования к контуру; (Б) статический адрес прокси в сети docker — то же, но проще и хрупче; (В) принять/12как цену и записать её строкой вdocs/security.mdрядом с уже названным классом — тогда это осознанная цена, а не умолчание образца.
Три конвенции проекта описывают снятый механизм, и одна противоречит себе внутри файла
- Файл:
docs/conventions/web-ui.md:107-109,docs/conventions/config.md:60-64против:99,docs/conventions/logging.md:34-37 - Severity: minor
- Confidence: high
- Оракул: чтение файлов на этом прогоне.
web-ui.md: «Сессия живёт кукойtranscriber_session» — кук сервис больше не ставит вовсе.config.mdстрока 60–64: «Секретов в этой секции больше нет», строка 99 в том же файле по-прежнему перечисляетauth.client_secretсреди секретов.logging.mdстроки 34–37 описывают расхождение у пакетаcmd/oidcstub, который этим же изменением удалён (git status→D cmd/oidcstub/main.go). - Последствие: конвенции — канон проекта, по ним работает следующая задача.
Запись про куку заставит следующего агента искать куку и объяснять её
отсутствие; запись про
client_secretзаставит охранять секрет, которого нет. Машина этого не ловит: согласованность документов между собой и с кодом судят агенты, и звать их надо руками (CLAUDE.md, «Гейт»). - Предложение: снять три записи, поправить перечень секретов в
config.md. - Найдено проходом:
code(20) - Действие: инлайн
Гипотезы без доказательства
Понижено и не влезло в потолок; каждая с причиной понижения.
- Откат бинаря поверх шага схемы
202608220001обрывает вход (ops, 29; былоmajor). Оракул у прохода настоящий — падающий тест на восстановленном старом коде во временном worktree,401вместо302. Понижаю доminorпо стадии и по перекрытию: (1) стадия — стройка, сервис на сервере остановлен, данных нет, откатываться не с чего; (2)docs/architecture.md:142уже объявляет откат неработающим начиная с шага202608140002— то есть любой бинарь старше 2026-08-14 и так не поднимается; новое окно отката — только версии между 14 и 22 августа, ни одна из которых не выкладывалась; (3) старый бинарь на новом конфиге и вовсе не стартует, если владелец убрал ключи OIDC по процедуре из «Эксплуатации» — тогда отказ громкий, а не молчаливый. Ответ на третий вопрос задания: да, стадия меняет серьёзность. Правка тем не менее дешёвая и уместная — строка вdocs/architecture.md, «Эксплуатация», рядом с уже стоящей про202608140002: не потому что откат сегодня возможен, а потому что порог перехода там принято называть прямо. - Уникальный индекс по
provider_loginне частичный (code, 17). Оракул прохода — исходники библиотеки (core/collection_model.go:648-671, индекс собирается безWHERE, сосед по коллекции —WHERE email != ''). Понижаю: сервис пустого логина в колонку не кладёт никогда (AcceptProviderLoginотвергает), коллекция пользователей на выкладке пуста, и последствие сводится к «в панели рукой не завести вторую запись без логина». Промах будущего, не дефект сегодня. - Предел логина считается в байтах, колонка — в знаках (
specs, 8;code, 18). Проверено чтением:len(login) > 255вidentity.go:174противMax: 255у текстовой колонки. Понижаю: перекос только в сторону строгости, кириллический логин длиннее 127 знаков у Authelia не встречается, следаDebugхватает. Пути нет — гипотеза. - Склейка значений заголовка запятой обходит защиту «два значения»
(
adversary, 28). Пути нет и построить его отсюда нечем:net/httpзаголовки запятой не сливает, а прокси, который сливает, — ровно тот класс, которыйdocs/review.mdотносит к «не проверит ни один проход». Последствие при этом было бы неприятным и тихим (человек попадает в новую пустую учётную запись вместо своей), поэтому строка оставлена, а не выброшена. - Откат шага схемы не восстанавливает
OAuth2.Enabledи минирует записи без почты (specs, 9;ops, 30). Оракул уopsесть — прогон на временной базе,email: cannot be blank. Понижаю по тому же основанию, что и №29:downна сервере не зовётся, а применённый шаг не переписывается по инварианту. - Отказ хранилища при узнавании не покрыт ничем (
autotests, 2). Не выведена отдельной строкой: правка про негодное имя даёт этой ветви достижимый вход, и тест на неё пишется той же правкой. Если владелец выберет не чинить негодное имя — эта строка возвращается самостоятельной находкой. - Непокрытые ветви
retryAfterConflictиweb/src/api.ts(autotests, 1, 3, 12). Понижаю по записанному решению проекта:CLAUDE.md, «Гейт» — «покрытие изменённых строк не считается ничем». Своегоapi.test.tsу новогоweb/src/api.tsдействительно нет (провереноls web/src/), и это стоит завести — но это задача, а не блокирующая находка.
Promote candidates
Претензии на правило, а не на этот код.
- Поле
peerзаведено мимо словаря имён журнала (code, 21).docs/conventions/logging.md:99требует точечной иерархии для системных доменов (http.*,ext.*,webapp.*), а в файле уже стоит Расхождение с перечнем самозаведённых имён. Кандидат в конвенцию: внестиhttp.peer_addrв таблицу полей — тогда следующий агент возьмёт имя из словаря, а не сочинит третье. - Третий способ вывода в репозитории (
code, 22).cmd/devtoolsпечатает через stdliblog, тогда какlogging.mdзнает два. Записанной конвенции об оснастке у проекта нет — значит это кандидат в правило, а не нарушение. - Единственный дом имён заголовков
Remote-*(architecture, 14).cmd/devtools/main.go:92-97пишет"Remote-User","Remote-Name","Remote-Email"своими литералами, тогда как нормативные константы лежат вinternal/controller/http/identity.go:23-27. Правка на две строки, но правило общее — «нормативное имя объявляется один раз» — и его стоит записать, а не чинить точечно. Смежный вопросdocs/review.md, «Вопросы по темам»: не завёл ли инструмент разработчика второй дом тому, что уже есть в проверках. - Второй словарь текстов на клиенте (
code, 19). Половина находки — ложноположительная:docs/conventions/web-ui.md:118сам разрешает клиентский текст «связи нет» состоянием, а не ошибкой. Настоящий остаток — расхождение текстов: сервер на401отвечает «Требуется вход» про вход, которого у сервиса больше нет, а экран показывает свой текст поверх. Кандидат в правило: конвенция сегодня не различает «текст под код ответа» и «текст под состояние клиента», и на этом различии находка и разошлась.
Урожай
Настоящее, но не для этого мерджа.
- Бюджет ограничителя частоты по человеку, а не по адресу — если владелец выберет вариант (В) на развилке №4.
- Секция конфига называется
[auth]при отсутствии всякого входа (architecture). Имя ключа конфига — из перечня необратимого вCLAUDE.md(«спрашивается у человека всегда»), но сегодня стадия стройки: на сервере ничего нет, и переименование стоит ноль. После первой выкладки — уже решение человека. Если переименовывать, то сейчас. - Колонка почты без единого читателя (
architecture). Почта пишется при заведении и не читается ничем. Не дефект: спека объявляет её необязательной и берёт «при заведении». Но колонка без читателя — долг, который стоит либо оправдать спекой, либо снять. - Корень
/authперестал быть зарезервированным (architecture). Проверено: упоминаний/authвapp.goбольше нет. Освободившееся адресное пространство стоит либо занять, либо назвать свободным в спекеwebapp. - Свой тест у
web/src/api.ts— файл новый, разбор отказов в нём есть, проверки нет. - Строка про порог отката в
docs/architecture.md, «Эксплуатация» — см. гипотезу №29.
Границы покрытия
План: темы, дома, глубины
Воспроизведён таблицей в сводке выше целиком. Тем без дома в плане нет, тем без отчёта нет.
Что запускалось
- Режим: по графу, метка
large. Запущеныspecs,autotests,code,architecture,adversary,ops. basicsне запускался — своих тем проекта у него на этом прогоне нет, все шесть тем разобраны именными проходами. Следствие: сигнал о заниженной метке мог подать толькоreview-code, и он его не подал.
Чего не хватило триажу, и это находки о прогоне
- Блоков «Coverage of this pass» в сырых выводах нет ни у одного прохода. Контракт находок требует их у каждого, и сводить мне было нечего: строку «что этот проход не мог проверить в принципе» я по каждому проходу воспроизвести не могу. Ниже — только то, что выводится из плана и из документов проекта.
- Сработавшие потолки ни один проход не назвал. Сколько находок проход показал, каков был его потолок и что осталось за срезом — не сообщено никем. Значит утверждать «проход показал всё, что нашёл» нельзя ни про один из шести.
- Размер и сложность изменения триажу не переданы — только метка. Обоснование разметки в отчёте воспроизвести нечем.
Что осталось целиком на человеке
Из docs/review.md, «Недоступно проверке». Два списка, и они не сливаются.
Не проверит ни один проход:
operations: поведение внешних сервисов под нагрузкой и на границах — SpeechKit и Object Storage поднять в тесте нечем;operations: реальный профиль нагрузки; утверждения о росте остаются условиями, а не замерами;security: стойкостьffmpegк вредоносному входу;security: поведение настоящей Authelia и правило обратного прокси на домен сервиса. С 2026-08-22 от прокси зависит весь барьер: он обязан заголовкиRemote-*перезаписывать, а не пропускать пришедшие. Правило живёт вpet-project-server, и отсюда его не проверить. Прямо на этом висят находки №1 (насколько узок путь к заведению мусорных записей), №6 (ширина перечня доверенных адресов) и гипотеза про склейку запятой;security: поведение браузера с куками — своих кук сервис больше не ставит, класс сузился до кук панели хранилища.
Перестали проверять сознательно:
autotests: разбор вывода настоящегоffprobe— решение и цена вADR-2026-08-11-stub-adapters-in-tests.md;- работа сервиса с настоящими внешними собеседниками в остатке: за настоящие
SpeechKit и Object Storage живой прогон не отвечает — ключи выдуманные,
распознавание подменяется в коде. Вход при этом с 2026-08-22 живой прогон
проверяет целиком (
cmd/devtools proxy), и это сдвиг в другую сторону.
Плюс общее, что не проверяет ни один прогон: история инцидентов, поведение под реальным потоком, поведение внешних систем в их версиях, завязка потребителей на текущее поведение и вопрос «а нужна ли эта функциональность вообще».
Каких документов проекта не хватило
docs/review.md, «Типовые ложноположительные» — есть и не пуст, отсев по нему выполнен. Одна находка (code, 19) отсеяна наполовину по общим критериям, а не по нему: разрешение клиентского текста для состояния «связи нет» стоит вdocs/conventions/web-ui.md, а не в разделе ложноположительных.CLAUDE.md, «Инварианты» — есть; ни одна находка этого прогона доcriticalпо этому основанию не поднята, потому что ни одна инвариант не нарушает: проверено поимённо для приватности содержимого (значение заголовка в журнал и в ответ не уходит — замерено), для секрета в конфиге (секретов в секции[auth]больше нет) и для запрета переписывать применённый шаг схемы.- Прочих пробелов в документах проходы не назвали — но назвать их было некому: блоков границ у проходов нет (см. выше). Отсутствие строки здесь не означает, что документов хватило.
Четыре строки, которых не принёс ни один проход
- Решения проекта не сверялись.
docs/adr.*— процессный документ, прогон его не открывает. Изменение заводитADR-2026-08-22-login-by-trusted-header.md, и сверку его с остальными записанными решениями — включая те, что это изменение помечает (ADR-2026-08-12-oidc-exchange-via-own-route,ADR-2026-08-12-session-without-refresh), — конвейер ревью не делал. Расхождение изменения с записанным решением ловит скиллav-dev:doc-healthcheck, и звать его надо руками. - Записанные наблюдения проекта не использовались.
docs/research.*— тоже процессный. Всякое число в этом отчёте снято на этом прогоне: приоритеты слоёв взяты из исходников библиотеки вGOMODCACHE, счёт учётных записей и коды ответов — из прогонов, приложенных к находкам. - Поимённая сверка с руководствами по стилю Go и Vue не задавалась ни одним проходом. Различение «идиоматично против распространено» на этом прогоне не спрашивал никто.
- Альтернативной реализации, с которой можно сдиффить решения, у конвейера нет. Проход независимой реализации снят по стоимости, а не по замеру. «Не знаю, чего не знаю» про схему узнавания по доверенному заголовку никто не доставал.
Пятой строки — про метку small — на этом прогоне нет: метка large, дома тем
security, operations и architecture открывались.
Что делал сам триаж
- Прочитаны: дифф изменения,
CLAUDE.md,docs/review.mdцеликом,docs/security.md,docs/architecture.md«Эксплуатация»,docs/database.md«Настройки с числовым значением», три конвенции, дельта-спекаaccess, исходникиpocketbase@v0.39.10(apis/middlewares*.go,core/event_request.go). - Запущено во временном файле проверок пакета
internal/controller/http(удалён после прогона, дерево восстановлено,go build ./...иgo test ./internal/controller/http ./internal/adapter/repo/pocketbaseзелёные):- заведение учётных записей запросами, ответившими
429(2 → 52); - общий бюджет ограничителя между двумя людьми;
500иERRORна длинномRemote-Name;- пустой журнал на заведении учётной записи;
- расход бюджета анонимными отказами до и после перестановки приоритетов
(
200→429).
- заведение учётных записей запросами, ответившими
- Перестановка приоритетов на −999/−998 применена временно и откачена;
на ней прогнан весь
go test ./.... Код изменения не правился.