- в конфиг добавлены секция [auth.test_headers] и предохранитель [server] debug: заголовки входа подставляет слой транспорта, второго процесса локальный запуск больше не требует - подкоманда devtools proxy удалена целиком: всё, ради чего её поднимали, делает сам сервис - адресного предохранителя нет по решению владельца — цена названа в ADR и в модели угроз
87 lines
7.4 KiB
Markdown
87 lines
7.4 KiB
Markdown
# Заголовки входа отладочного запуска подставляет сам сервис, а не второй процесс
|
||
|
||
- **Дата:** 2026-08-23
|
||
- **Источник:** [../../openspec/changes/archive/2026-08-23-config-test-headers-login/design.md](../../openspec/changes/archive/2026-08-23-config-test-headers-login/design.md),
|
||
решения 1, 6, 7 и 10
|
||
|
||
## Решение
|
||
|
||
Заголовки входа на машине разработчика ставит сам сервис — отдельным слоем
|
||
цепочки корня приложения, а не вспомогательным процессом рядом:
|
||
|
||
> отдельный слой цепочки корня приложения, стоящий **перед**
|
||
> `TrustedHeaderIdentity` и **после** ограничителя частоты. Он правит заголовки
|
||
> запроса и ничего больше не делает: учётной записи не заводит, отказов не
|
||
> выдаёт, в контекст не пишет.
|
||
|
||
Включают слой два новых ключа настроек — предохранитель `[server] debug` и
|
||
секция значений `[auth.test_headers]`. Прежний вспомогательный процесс уходит:
|
||
|
||
> **Решено** владельцем на чекпоинте: подкоманда удаляется. Назначения у неё не
|
||
> остаётся — всё, ради чего её поднимали, делает сам сервис, — и второго способа
|
||
> входить локально не остаётся тоже.
|
||
|
||
Имена заголовков служат именами ключей секции, но набор принимаемых имён
|
||
порождают константы транспорта: дом у имён остаётся один, а ключ, не совпавший
|
||
ни с одним из них, роняет старт.
|
||
|
||
## Почему
|
||
|
||
Владелец назвал желаемое: один бинарник, различия между запусками — в конфиге.
|
||
Дизайн записал это целью:
|
||
|
||
> Локальный запуск идёт одним процессом и одной командой; **вход** — то, кем
|
||
> назвался пришедший, — отличает тестовый прогон от боевого содержимым файла
|
||
> настроек, и ничем больше.
|
||
|
||
Довод в пользу слоя перед узнаванием, а не внутри него:
|
||
|
||
> Отлаживается **та же** ветка кода, что работает в бою: подставленный заголовок
|
||
> неотличим от пришедшего от Caddy к моменту, когда его читает узнавание.
|
||
|
||
Отвергнуты три очевидных подхода, и у каждого названа цена. **Подстановка внутри
|
||
`TrustedHeaderIdentity`** — «узнавание получило бы второй источник значений и
|
||
ветку, которой в бою нет. Отлаживалась бы не боевая ветка, а её отладочный
|
||
двойник». **Произвольная карта имён заголовков в конфиге** — «опечатка
|
||
`Remote-Usr` даёт „сервис меня не узнаёт“ без единого следа». **Вырезать
|
||
подстановку из боевой сборки тегом сборки:**
|
||
|
||
> сборка образа в гейте не проверяется вовсе (`CLAUDE.md`, «Гейт»), и тег,
|
||
> забытый в одной ступени, дал бы ровно ту тишину, которой избегает пункт 3.
|
||
|
||
## Почему это ADR
|
||
|
||
Триггер сработал дважды. **Дорогой откат:** решение заводит два имени ключа
|
||
настроек, а имя ключа конфига `CLAUDE.md` называет необратимым; вернуться к
|
||
вспомогательному процессу значит поднять удалённую подкоманду, убрать оба ключа
|
||
из настроек и переписать рецепт локального запуска, разошедшийся по образцу
|
||
конфига, `README.md`, `CLAUDE.md` и конвенции настроек. **Намеренный отказ:**
|
||
вырезать отладочный код из боевой сборки тегом сборки — то, что делают по
|
||
умолчанию, и отказ от этого объясняется один раз здесь, а не на каждом вопросе
|
||
«почему подстановка вообще есть в боевом бинарнике».
|
||
|
||
## Последствия
|
||
|
||
- `+` Локальный запуск идёт одним процессом и одной командой; приложение
|
||
открывают по адресу сервиса, второго порта нет.
|
||
- `+` Отлаживается боевая ветка узнавания: подставленный заголовок неотличим от
|
||
пришедшего от прокси к моменту, когда его читают.
|
||
- `+` Второго способа входить локально не остаётся, и документация перестаёт
|
||
каждый раз говорить, какой из способов чей.
|
||
- `+` Имена заголовков остаются с одним домом — константами транспорта; ключ, не
|
||
совпавший ни с одним из них, роняет старт и называет принимаемые имена.
|
||
- `−` **Местный инструмент больше не воспроизводит поломки контура.** Цена
|
||
названа в источнике прямо: два значения `Remote-User`, заголовок с
|
||
недоверенного адреса, цепочка `X-Forwarded-For` — всё это теперь
|
||
воспроизводит только автотест, ставящий заголовок сам.
|
||
- `−` В боевом бинарнике появляется код, называющий пришедшего без провайдера.
|
||
Что его держит и чего у него нет — [ADR-2026-08-23-no-address-guard-for-debug-login](ADR-2026-08-23-no-address-guard-for-debug-login.md).
|
||
- `−` У ключа `[server] debug` закрытый перечень следствий, и держать его
|
||
придётся руками: новое поведение привязывается к ключу только отдельным
|
||
решением владельца и получает своё требование спеки
|
||
[access](../../openspec/specs/access/spec.md). Ключ с открытым перечнем
|
||
следствий обрастает ими молча.
|
||
- `−` Каждый новый логин имитации заводит учётную запись, а удалять их сервис не
|
||
умеет. Локальная база ронится и пересоздаётся свободно, в бою подстановка
|
||
выключена — но лишние записи копятся.
|