- адреса приложения переехали в своё пространство `/app/`, опрос готовности убран целиком: рубеж и причину остановки владелец узнаёт карточкой записи, текст — отдельным адресом названного вида - заведена единая точка отображения доменной ошибки и слой, приводящий к той же форме отказы библиотеки: тело несёт машиночитаемый код рядом с сообщением - у записи появились имя файла отправителя, длительность и размер своими колонками, а у ленты владельца — свой индекс: без него страница сканировала весь архив сервиса
102 lines
12 KiB
Markdown
102 lines
12 KiB
Markdown
# Ревью кода — app-json-contract
|
|
|
|
Метка `large`, режим «по графу». Состав: `autotests`, `specs`, `code`,
|
|
`architecture`, `adversary`, `ops`, `triage`. `basics` не запускался — своих тем
|
|
проекта нет, все темы ядра разобраны именными проходами.
|
|
|
|
## План против исхода
|
|
|
|
| Тема | Дом | Глубина | Кто закрыл | Исход |
|
|
| --- | --- | --- | --- | --- |
|
|
| `requirements` | `openspec/specs/` + дельты | разбор | `specs` | 7 находок |
|
|
| `autotests` | `CLAUDE.md`, «Гейт» | доказательство | `autotests` | гейт зелёный, 4 находки об отсутствующей верификации |
|
|
| `conventions` | `docs/conventions/` | разбор | `code` | 11 находок, потолок конвенционной половины сработал (4 из 4, за срезом двое) |
|
|
| `architecture` | `docs/architecture.md` + `passport.md` | доказательство | `architecture` | 3 находки, потолок сработал |
|
|
| `security` | `docs/security.md` | доказательство | `adversary` | 3 построенных пути, 3 свойства |
|
|
| `operations` | `docs/architecture.md` «Эксплуатация» + `database.md` | доказательство | `ops` | 2 постмортема с замерами, 1 закрытая гипотеза |
|
|
|
|
**Проход `ops` едва не остался незапущенным** — он ждал освобождения машины
|
|
после враждебного прохода, и оркестратор его не позвал. Поймал это триаж
|
|
сверкой плана с исходом; проход запущен и вернул две находки уровня `major`,
|
|
обе с замерами. Это дефект прогона, а не темы, и он записан здесь.
|
|
|
|
## Что найдено и починено
|
|
|
|
Каждая правка закрыта оракулом; тесты названы поимённо.
|
|
|
|
| Находка | Проходы | Правка и оракул |
|
|
| --- | --- | --- |
|
|
| `413`, `429` и неизвестный путь под `/app` уходили телом библиотеки — мимо единой формы, ради которой заведена задача | architecture, code, specs, adversary | слой `OneErrorForm` плюс свой перехват неизвестного пути; `TestTooLargeOnTheRealPath`, `TestRateLimitRefusalGoesThroughOneErrorForm`, `TestUnknownAddressUnderAppRootUsesOneErrorForm` |
|
|
| объявленная частота опроса равнялась **всему** бюджету ограничителя | architecture, code, specs, adversary | частота выведена из доли бюджета; `TestPollIntervalLeavesBudgetHeadroom` |
|
|
| перечень доступных видов строился по ссылке, а не по содержимому: карточка обещала текст, которого адрес не отдал бы никогда | specs | перечень строится по содержимому; `TestEmptyTextIsNotAnAvailableView` |
|
|
| поле перечня пропадало из тела вместо пустого перечня | specs, code | поле стало указателем на срез и присутствует всегда; `TestRecordCard_NoTextYetGivesEmptyViews`, `TestCardAndPageItemShareOneShape` |
|
|
| ключ страницы с негодным временем принимался молча и отдавал пустой архив при непустом счётчике | specs, code | время разбирается и приводится к виду хранилища; `TestList_MalformedCursorIsRejected` (пять случаев) |
|
|
| выборка с непозитивным пределом роняла процесс обращением по индексу −1 | code | предел приводится к умолчанию в самом адаптере |
|
|
| неизвестное состояние отбора отдавало весь архив вместо отказа | code | ветвь отказа вместо молчаливого «без сужения» |
|
|
| разрешение тем шло без сужения владельцем | adversary | сужение добавлено; `TestForeignTopicDoesNotResolve` |
|
|
| длинное расширение из имени отправителя давало `500` и строку `ERROR` в журнале | adversary | потолок длины расширения; `TestIntake_AbsurdExtensionDoesNotBecomeInternalError` |
|
|
| `401` собирался руками мимо единой точки — на первой строке её собственной таблицы | code | заведён свой признак, ответ идёт через отображение |
|
|
| разрешение тем и перечень видов не исполнялись под тестом ни разу | autotests, triage | `TestTopicsResolveToNames`, `TestAvailableViewsCoverEveryKind` |
|
|
| ветвь замены правила ограничителя не исполнялась: правила копились бы с каждой выкладкой | autotests | повторный вызов в `TestRateLimitRuleCoversAppRoot` |
|
|
| **страница владельца сканировала весь архив сервиса** — замер: рост архива в 40 раз растил время страницы в 20–30 раз при неизменных сорока его записях | ops | два индекса в том же шаге схемы; `EXPLAIN QUERY PLAN` показывает покрывающий поиск вместо полного сканирования |
|
|
| **включение ограничителя схлопнуло все внутренние обмены OIDC-кода в один счётчик** — третий вход в пределах трёх секунд отвергался с текстом «Войти не удалось» | ops | внутреннему запросу задан адрес; регрессия внесена самим изменением и им же закрыта |
|
|
|
|
## Решение человека по ходу ревью
|
|
|
|
**Норма «непрочитанная длительность отличима от нулевой» снята.** Проверено
|
|
прогоном: числовая колонка хранилища пустого значения не держит, пустое кладётся
|
|
нулём, и ветвь кода была недостижима. Отличимость стоила бы четвёртой колонки
|
|
либо текстового типа у чисел; платить не за что — обе величины ставит приём и
|
|
ставит всегда, а запись с непрочитанными метаданными отвергается отказом и не
|
|
заводится вовсе. Правлены спека `storage`, `docs/database.md`, комментарии и
|
|
критерий приёмки; мёртвая ветвь убрана.
|
|
|
|
## Урожай — реальное, но не для этого мерджа
|
|
|
|
- **Ключ ограничителя частоты — адрес, а не учётная запись.** Свойство
|
|
библиотеки: двое за одним адресом делят бюджет. Арифметика против высокой
|
|
цены — объявленная частота даёт восьмерых одновременно опрашивающих на адрес.
|
|
Оракула на ущерб нет и быть не может: реального профиля нагрузки проект не
|
|
знает.
|
|
- **`TrustedProxy.Headers` не настроен нигде**, а сервис публикуется через
|
|
обратный прокси. Значит бюджет ограничителя считается по адресу прокси, то
|
|
есть общий на всех посетителей. Проверено чтением; на живом прокси не
|
|
воспроизводилось — его конфигурация вне репозитория.
|
|
- **Отказ ограничителя не виден в журнале контейнера** — канале, который
|
|
архитектура называет основным: встроенный слой стоит раньше нашего журнала
|
|
запросов. Виден только во внутренней таблице хранилища.
|
|
- **Уборка имени файла снимает только категорию Cc.** Разворот направления и
|
|
невидимые пробелы доезжают до колонки, до ответа и до имени файла на диске.
|
|
Видит это владелец записи и владелец сервиса.
|
|
- **Точность длительности.** Колонка названа в миллисекундах, а источник даёт
|
|
целые секунды: значение всегда кратно тысяче.
|
|
- **Род узла «читающий обработчик и список» в `docs/review.md` не заведён** —
|
|
свойства вроде устойчивости окна и потолка страницы там не спрашивает никто.
|
|
|
|
## Границы покрытия
|
|
|
|
- Метка `large`, режим «по графу», проходов семь. Потолки сработали у `code`
|
|
(конвенционная половина, 4 из 4) и у `architecture` (3 из 3) — оба объявили.
|
|
- **`ops` едва не остался незапущенным**, и поймал это только триаж. Строка
|
|
оставлена намеренно: пропуск был неотличим от прохода без находок.
|
|
- **Триаж получил дайджест оркестратора, а не сырые выводы проходов** — часть
|
|
их находок дошла до него уже починенной. Дедупликация выполнена над
|
|
дайджестом; находка, которую проход показал, а оркестратор не перечислил, для
|
|
триажа была невидима.
|
|
- Решения проекта (`docs/adr/`) и записанные наблюдения (`docs/research/`)
|
|
прогоном не открывались: процессные документы. Расхождение изменения с
|
|
записанным решением ловится сверкой документации, а не ревью.
|
|
- Все числа отчёта сняты на этом прогоне: время страницы на 5k/50k/200k строк,
|
|
бюджет ограничителя, коды и тела ответов.
|
|
- Живой прогон сценария вошедшего локально невозможен по устройству проекта:
|
|
сессию выдаёт только провайдер OIDC, а локальный запуск наружу не ходит.
|
|
Этот путь закрыт машиной в тестах, через настоящий роутер и настоящее
|
|
хранилище.
|
|
- Не проверено ничем: поведение настоящих SpeechKit, Object Storage, Authelia и
|
|
обратного прокси; реальный профиль нагрузки и реальный размер архива;
|
|
стойкость `ffmpeg` к вредоносному входу; поведение браузера с куками.
|
|
- Альтернативной реализации, с которой можно сдиффить решения, у конвейера нет —
|
|
проход независимой реализации упразднён решением о стоимости. «Не знаю, чего
|
|
не знаю» здесь не достаёт никто, и на изменении, замораживающем публичную
|
|
форму ответов, это дорого.
|