tasks: закрыта задача oidc-login, заведён урожай ревью
This commit is contained in:
+8
-1
@@ -22,7 +22,6 @@
|
||||
|
||||
- [🐞 Убирать записанный файл, когда приём отказал на середине](items/orphan-file-on-failed-intake.md) — Отказ чтения метаданных и отказ записи на диск оставляют файл в каталоге хранения без задачи и без учёта: сопоставить его не с чем, удалять приходится руками.
|
||||
- [🧹 Задать таймауты обращениям к внешним сервисам](items/external-call-timeouts.md) — Ни у Telegram, ни у Object Storage, ни у SpeechKit нет таймаута: молчащий собеседник держит шаг конвейера до истечения часового захвата.
|
||||
- [✨ Пускать в приложение только после входа через OIDC](items/oidc-login.md) — HTTP API открыт наружу без аутентификации: любой из интернета заводит задачи за наши деньги и читает чужие расшифровки по идентификатору.
|
||||
- [✨ Привязать запись к владельцу и отдавать только свои](items/record-ownership.md) — У задачи и файла нет владельца, поэтому знание UUID задачи и есть право её читать.
|
||||
- [✨ Сопоставить пользователя Telegram с учётной записью](items/telegram-account-link.md) — Белый список сверяется с именем пользователя Telegram, которое владелец меняет в любой момент, а записи из бота ни с кем не связаны.
|
||||
- [✨ Свести приём и чтение записей к одному контракту для приложения](items/json-api-for-spa.md) — Сегодняшний API отвечает 404 на любую ошибку чтения и 500 на любую ошибку приёма: строить на нём экраны нечем.
|
||||
@@ -64,4 +63,12 @@
|
||||
- [🧹 Закрепить версию рантайм-базы образа](items/pin-runtime-image-base.md) — Финальный слой Dockerfile собирается на alpine:latest, а task image идёт с --pull, поэтому два образа из одного коммита с разницей в неделю несут разный ffmpeg — регрессия конвертации после такой пересборки выглядит как задачи в failed при пустом диффе репозитория, и откат на прежний коммит её не чинит.
|
||||
- [🧹 Привести коды выхода шагов гейта к общему словарю](items/gate-step-exit-codes.md) — CLAUDE.md объявляет словарь кодов общим для четырёх проверочных шагов и описывает код 3 как «каталог или файл не найден», но все четыре обёртки в Taskfile.yml при отсутствующем скрипте отдают 1 («дрейф»), из-за чего читатель кода идёт искать разъехавшееся там, где просто неполно дерево.
|
||||
- [🧹 Настроить конвейер ревью по итогам прогона go-1-26-upgrade](items/review-config-from-go-upgrade.md) — Прогон вскрыл две прорехи настройки: «Типовые узлы» знают только рантайм и не знают рода «проверочный шаг набора проверок», а «Триггеры метки» не видят оси «изменение трогает канон» — и именно она дала обе блокирующие находки.
|
||||
- [🐞 Починить путь миграций в настройке сверки документов](items/fix-migrations-path-in-docs-config.md) — Ключ migrations указывает на каталог migrations/, которого в репозитории нет: шаг гейта зелен при изменённой миграции и нетронутом database.md, а конвенции числят этот род механизированным.
|
||||
- [🐞 Убрать код провайдера из журнала запросов хранилища](items/provider-code-out-of-storage-log.md) — Строка запроса с кодом входа целиком уезжает в таблицу _logs и лежит там пять суток, хотя спека access требует, чтобы код в журнал не попадал.
|
||||
- [🧹 Поднимать сервис локально без действующего токена бота](items/local-run-without-telegram-token.md) — Адаптер Telegram проверяет токен обращением к Telegram и роняет старт, а боевым токеном запускаться запрещено: проверить поведение живым прогоном не может ни одна задача.
|
||||
- [🧹 Строить адрес входа из настроек коллекции, а не из конфига](items/login-url-from-collection-settings.md) — Первая половина входа собрана руками из конфига и на настройки провайдера не смотрит, вторая берётся из коллекции: обновление библиотеки изменит только вторую половину.
|
||||
- [🧹 Судить ответ в тестах по готовому ответу](items/response-assertions-judge-result.md) — Проверка, читающая изменяемую карту заголовков обработчика, зелена при неработающем коде: класс всплыл трижды, последний раз на уборке куки входа.
|
||||
- [🧹 Назвать в необратимом, что откат кода не откатывает шаг схемы](items/rollback-does-not-undo-schema-step.md) — Откат бинаря оставляет применённый шаг схемы в силе, и на этом строятся решения о выкладке: сегодня об этом не сказано нигде.
|
||||
- [🐞 Вести учёт употреблённых состояний входа на сервере](items/server-side-login-state.md) — Одноразовость возврата держится на уборке куки, то есть на браузере: сервер не помнит, какие состояния уже потрачены.
|
||||
- [🔬 Четыре недоказанные гипотезы о поверхности входа](items/login-surface-hypotheses.md) — Ревью назвало четыре пути, которых не смогло ни подтвердить, ни опровергнуть: браузера и живого провайдера в прогоне не было.
|
||||
- [🔬 Квота по общему размеру загруженного на пользователя](items/per-user-size-quota.md) — Паспорт и security.md запрещают отказы по квоте пользователю, а заметка владельца просит квоту по умолчанию 5 ГБ — открытое противоречие с границей домена, которое владелец решил не разбирать сейчас.
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# 🐞 Починить путь миграций в настройке сверки документов
|
||||
|
||||
- **Тип:** fix
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Ключ migrations указывает на каталог migrations/, которого в репозитории нет: шаг гейта зелен при изменённой миграции и нетронутом database.md, а конвенции числят этот род механизированным.
|
||||
|
||||
Найдено ревью задачи `oidc-login` 2026-08-12, отчёт триажа —
|
||||
[review/report.md](../../openspec/changes/archive/2026-08-12-oidc-login/review/report.md),
|
||||
раздел «Promote candidates».
|
||||
|
||||
Проверка сверяет изменённые файлы с префиксом `migrations/`, а шаги схемы лежат
|
||||
в `internal/adapter/repo/pocketbase/`. Совпадений не бывает никогда, значит шаг
|
||||
проходит зелёным всегда. Оракул сегодняшнего состояния:
|
||||
`git ls-files | grep -c "^migrations/"` отдаёт `0`.
|
||||
|
||||
Цена уже заплачена дважды: задача `oidc-login` изменила шаг схемы и не тронула
|
||||
`docs/database.md`, и гейт этого не заметил — расхождение нашёл человек на
|
||||
ревью. Так же провалится всякая следующая миграция.
|
||||
|
||||
Развилка внутри задачи: либо поправить путь, либо снять пометку
|
||||
«механизировано» в `docs/conventions/README.md` и отдать род человеку. Второе
|
||||
дешевле, но тогда проверять его будет некому.
|
||||
|
||||
## Воспроизведение
|
||||
|
||||
1. Изменить любой файл шагов схемы в `internal/adapter/repo/pocketbase/`.
|
||||
2. `docs/database.md` не трогать.
|
||||
3. Прогнать `task docs BASE=origin/master`.
|
||||
4. Шаг проходит зелёным, хотя должен назвать расхождение. Сегодняшнее состояние
|
||||
настройки видно командой `git ls-files | grep -c "^migrations/"` — она отдаёт
|
||||
`0`, то есть каталога с таким именем в репозитории нет.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `docs/.docs.json`, ключ `migrations`;
|
||||
- `docs/conventions/README.md`, таблица «Механизировано», строка про миграцию;
|
||||
- шаг `docs` в `Taskfile.yml` — его исход меняется.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Изменённый шаг схемы при нетронутом `docs/database.md` роняет шаг гейта.
|
||||
Оракул — правка любого файла шагов схемы без правки схемы в документах, затем
|
||||
`task docs BASE=origin/master`: ненулевой код возврата.
|
||||
- Изменённый шаг схемы вместе с правкой `docs/database.md` шаг гейта проходит.
|
||||
Оракул — то же с обеими правками: код возврата 0.
|
||||
- Строка «Механизировано» в `docs/conventions/README.md` соответствует тому, что
|
||||
проверка делает на самом деле. Оракул — чтение таблицы против исхода первых
|
||||
двух проверок.
|
||||
@@ -0,0 +1,35 @@
|
||||
# 🧹 Поднимать сервис локально без действующего токена бота
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Адаптер Telegram проверяет токен обращением к Telegram и роняет старт, а боевым токеном запускаться запрещено: проверить поведение живым прогоном не может ни одна задача.
|
||||
|
||||
Замечено при попытке проверить вход вживую в задаче `oidc-login` 2026-08-12;
|
||||
подтверждено прогоном: с выдуманным токеном старт кончается отказом создания
|
||||
отправителя раньше, чем поднимается HTTP-сервер.
|
||||
|
||||
Отсюда следствие, которое стоит дороже самого неудобства: **поведенческая
|
||||
верификация живым запуском недоступна проекту вовсе**. Всякая задача, меняющая
|
||||
наблюдаемое поведение, проверяется только тестами, а «поднять и посмотреть»
|
||||
остаётся человеку с боевым конфигом.
|
||||
|
||||
Запрет запускаться боевым токеном снимать не надо: второй процесс с тем же
|
||||
токеном перехватывает обновления у работающего.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- создание отправителя Telegram при старте в `main.go`;
|
||||
- секция `[telegram]` конфига и её образец;
|
||||
- раздел «Запреты» в `CLAUDE.md` — строка про боевой токен остаётся, но рядом
|
||||
появляется способ поднять сервис без него;
|
||||
- `docs/review.md`, подраздел «Недоступно проверке»: строка про недоступность
|
||||
живого прогона снимается или сужается.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Сервис поднимается с пустым токеном бота: HTTP отвечает, воркеры идут, бот не
|
||||
создан. Оракул — запуск с конфигом без токена и запрос `GET /health`: код 200.
|
||||
- Отсутствие бота названо в журнале один раз при старте, а не молчанием. Оракул —
|
||||
тот же запуск: в выводе есть строка о том, что бот не поднят и почему.
|
||||
- Поведение с настоящим токеном не изменилось. Оракул — тест на создание
|
||||
отправителя с непустым токеном: прежний путь сохранён.
|
||||
@@ -0,0 +1,44 @@
|
||||
# 🔬 Четыре недоказанные гипотезы о поверхности входа
|
||||
|
||||
- **Тип:** research
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Ревью назвало четыре пути, которых не смогло ни подтвердить, ни опровергнуть: браузера и живого провайдера в прогоне не было.
|
||||
|
||||
Провенанс — отчёт триажа ревью задачи `oidc-login` 2026-08-12,
|
||||
[review/report.md](../../openspec/changes/archive/2026-08-12-oidc-login/review/report.md),
|
||||
раздел «Гипотезы без доказательства». Каждая либо становится задачей, либо
|
||||
закрывается с причиной; сегодня они не то и не другое.
|
||||
|
||||
## Вопрос
|
||||
|
||||
Работает ли хоть один из четырёх путей на самом деле, и если да — чего стоит
|
||||
каждый?
|
||||
|
||||
1. **Выход по чужой ссылке.** Адрес выхода сессии не требует и на запросе с
|
||||
чужого сайта отвечает успехом, убирая куку. Применит ли браузер эту куку при
|
||||
ограничении `SameSite=Lax` — по коду не выяснить. Если применит, человека
|
||||
выкидывает молча, а унесённое значение остаётся годным.
|
||||
2. **Слияние двух учётных записей провайдера с одной почтой.** Обмен ищет запись
|
||||
по неизменяемому признаку провайдера, а не найдя — по адресу почты. Выдаст ли
|
||||
Authelia двум разным субъектам один адрес, зависит от её настройки.
|
||||
3. **Поле снимка в ответе провайдера тянет данные наружу.** Оно сопоставлено
|
||||
файловому полю учётной записи, и библиотека скачивает названный там адрес —
|
||||
до потолка размера записи. Шлёт ли Authelia это поле и кто им управляет,
|
||||
неизвестно.
|
||||
4. **Анонимный запрос подтверждения почты.** Адрес отвечает успехом и заставляет
|
||||
сервис слать почту. Почта не настроена, и потолка числа запросов нет.
|
||||
|
||||
## Куда ляжет ответ
|
||||
|
||||
- подтверждённый путь — задачей в беклоге, с провенансом этой разведки;
|
||||
- опровергнутый — строкой в `docs/security.md`, раздел «Что вне модели» либо
|
||||
«Что разграничивает доступ», чтобы следующее ревью не открывало его заново;
|
||||
- то, что зависит от настройки Authelia, — строкой там же, с указанием, какая
|
||||
именно настройка это решает.
|
||||
|
||||
## Рамки
|
||||
|
||||
Смотрим только четыре названных пути. Первый требует настоящего браузера, второй
|
||||
и третий — настоящей Authelia либо её настройки из `pet-project-server`;
|
||||
четвёртый воспроизводится своим прогоном без внешних систем и потому берётся
|
||||
первым. Прогонов на боевом контуре не делаем.
|
||||
@@ -0,0 +1,38 @@
|
||||
# 🧹 Строить адрес входа из настроек коллекции, а не из конфига
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Первая половина входа собрана руками из конфига и на настройки провайдера не смотрит, вторая берётся из коллекции: обновление библиотеки изменит только вторую половину.
|
||||
|
||||
Найдено ревью задачи `oidc-login` 2026-08-12, отчёт триажа —
|
||||
[review/report.md](../../openspec/changes/archive/2026-08-12-oidc-login/review/report.md),
|
||||
пункт срезанного потолком под номером 7.
|
||||
|
||||
Сегодня адрес согласия собирается своим кодом: состав запрашиваемых сведений,
|
||||
способ проверочного кода и признак ответа записаны у нас, а обмен кода берёт
|
||||
настройки провайдера из коллекции хранилища. Библиотека умеет собирать этот
|
||||
адрес сама — она делает это своим обработчиком способов входа.
|
||||
|
||||
Цена расхождения отложенная: адреса провайдера и идентификатор клиента получают
|
||||
второго потребителя мимо единственного места, где настройки живут, а правка
|
||||
провайдера в панели на начало входа не влияет вовсе. Обновление библиотеки,
|
||||
тронувшее форму запроса согласия, доедет до половины протокола и разойдётся
|
||||
молча — отказом на живой выкладке, которого нечем воспроизвести.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- эндпоинт `GET /auth/login`, где сегодня адрес согласия собирается вручную;
|
||||
- поля `auth_url` и `client_id` в секции `[auth]` конфига и их проброс —
|
||||
часть из них перестаёт быть нужной приложению;
|
||||
- дельта-спека `access`, требование «Вход через внешнего провайдера» — состав
|
||||
запрашиваемых сведений и способ проверочного кода перестают быть нашими.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Адрес согласия строится из настроек коллекции: правка провайдера в панели
|
||||
меняет адрес, куда уводит вход. Оракул — тест: сменить настройки провайдера в
|
||||
хранилище и убедиться, что адрес перенаправления изменился.
|
||||
- Проверочный код и состав запрашиваемых сведений берутся у библиотеки, а не
|
||||
записаны у нас. Оракул — чтение кода: своих литералов состава больше нет.
|
||||
- Вход по-прежнему проходит целиком. Оракул — существующий тест входа через
|
||||
подставного провайдера остаётся зелёным.
|
||||
@@ -1,67 +0,0 @@
|
||||
# ✨ Пускать в приложение только после входа через OIDC
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** HTTP API открыт наружу без аутентификации: любой из интернета заводит задачи за наши деньги и читает чужие расшифровки по идентификатору.
|
||||
- **Теги:** goal:multi-user
|
||||
|
||||
Двигает пункты 1 и 3 «Завершения» цели: неаутентифицированный запрос к записям
|
||||
не проходит ни к странице, ни к API; вход идёт через OIDC у Authelia, а выход из
|
||||
сессии работает.
|
||||
|
||||
Провайдер — Authelia по OIDC. Своей регистрации и своих паролей не делаем, это
|
||||
граница из [паспорта](../../docs/passport.md). Разграничения записей по владельцу
|
||||
здесь ещё нет: после входа видно всё, что видно сейчас, — этим занимается
|
||||
`record-ownership`.
|
||||
|
||||
**Ответ провайдера разбирает PocketBase, а не наш код** — решено 2026-08-11
|
||||
([adr](../../docs/adr/ADR-2026-08-11-pocketbase-storage-with-admin-panel.md)).
|
||||
У её коллекции пользователей настраивается провайдер `oidc` с адресами Authelia,
|
||||
и учётные записи заводятся сами; проверено на версии 0.39.10,
|
||||
[docs/research/pocketbase.md](../../docs/research/pocketbase.md). Отсюда порядок:
|
||||
задача идёт после `pocketbase-storage`, до неё настраивать нечего.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- настройка провайдера `oidc` у коллекции пользователей PocketBase: адреса
|
||||
Authelia, идентификатор клиента, секрет, соответствие полей учётной записи;
|
||||
- эндпоинты входа и выхода, которые PocketBase приносит своими;
|
||||
- `POST /api/audio` и `GET /api/status/:id` — оба уходят за аутентификацию;
|
||||
- `GET /health` и `GET /metrics` — остаются открытыми и сессии не требуют;
|
||||
- хранение сессии: её ведёт PocketBase, и решить надо, чем она предъявляется
|
||||
приложению;
|
||||
- секция конфигурации под провайдера: адрес, идентификатор клиента, секрет;
|
||||
- `docs/security.md` — периметр меняется, и первая его строка перестаёт быть
|
||||
верной;
|
||||
- `config.dist.toml` и `internal/config`.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Запрос к `POST /api/audio` и `GET /api/status/:id` без сессии получает отказ, а
|
||||
не заводит задачу и не отдаёт текст. Оракул — тест на обоих эндпоинтах без
|
||||
куки: код ответа 401 либо 302 на вход, тело без данных задачи. Тот же тест
|
||||
проверяет вторую сторону границы: `GET /health` и `GET /metrics` без куки
|
||||
отвечают 200.
|
||||
- Сессия переживает перезапуск приложения. Оракул — тест: запрос с прежней кукой
|
||||
после пересоздания сервера проходит.
|
||||
- Выход из сессии закрывает доступ. Оракул — тест: после выхода тот же запрос
|
||||
получает отказ.
|
||||
- Секрет провайдера не попадает ни в лог, ни в ответ. Оракул — тест на отсутствие
|
||||
значения секрета в записанном выводе логгера.
|
||||
- Первая строка `docs/security.md` описывает новый периметр. Оракул — `task
|
||||
gate`, шаг `docs.py check`.
|
||||
|
||||
## Рамки
|
||||
|
||||
Владельца у записи здесь не заводим и выборку не сужаем: после входа видно
|
||||
столько же, сколько сейчас. Инвариант «бот отвечает только тем, кто в белом
|
||||
списке» не трогаем — он живёт до `telegram-account-link`. Панель администратора
|
||||
тоже не трогаем: наружу её закрывает Authelia на обратном прокси, а это работа
|
||||
выкладки.
|
||||
|
||||
`GET /health` и `GET /metrics` за аутентификацию не уходят — решено 2026-08-12.
|
||||
Сессии нет ни у пробы здоровья, ни у сборщика Prometheus, и вход по личным
|
||||
токенам эта задача не заводит. Наружу их закрывает то же правило обратного
|
||||
прокси, что и панель администратора, — работа выкладки. Пока правило не
|
||||
поставлено, `/metrics` отдаёт наружу объёмы работы сервиса: число задач, размеры
|
||||
и длительности записей; содержимого расшифровок в них нет.
|
||||
@@ -0,0 +1,44 @@
|
||||
# 🐞 Убрать код провайдера из журнала запросов хранилища
|
||||
|
||||
- **Тип:** fix
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Строка запроса с кодом входа целиком уезжает в таблицу _logs и лежит там пять суток, хотя спека access требует, чтобы код в журнал не попадал.
|
||||
|
||||
Найдено ревью задачи `oidc-login` 2026-08-12, отчёт триажа —
|
||||
[review/report.md](../../openspec/changes/archive/2026-08-12-oidc-login/review/report.md),
|
||||
пункт срезанного потолком под номером 2.
|
||||
|
||||
Наш собственный журнал чист — код туда не пишет ни одна строка приложения.
|
||||
Пишет его слой хранилища: он логирует всякий запрос вместе со строкой запроса,
|
||||
а адрес возврата несёт код параметром. Тест на отсутствие значений в журнале
|
||||
этого не видит, потому что смотрит только в наш логгер.
|
||||
|
||||
Код провайдера одноразовый и живёт минуты, поэтому это не захват сессии, а
|
||||
расхождение написанного со сделанным: комментарий в коде и норма спеки
|
||||
утверждают, что код в журнал не идёт.
|
||||
|
||||
## Воспроизведение
|
||||
|
||||
1. Поднять сервис с настроенным провайдером.
|
||||
2. Пройти вход и вернуться на адрес возврата.
|
||||
3. Открыть журнал запросов хранилища — панель, `GET /api/logs` либо файл базы.
|
||||
4. В строке запроса и в поле `url` виден код провайдера целиком.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- адрес возврата `GET /auth/callback` и слой журналирования запросов хранилища;
|
||||
- настройка срока хранения журнала запросов (сегодня умолчание, пять суток);
|
||||
- дельта-спека `access`, требование «Значение, дающее доступ, не печатается» —
|
||||
либо норма выполняется, либо изъятие называется поимённо;
|
||||
- `docs/security.md`, перечень мест, где оседает чувствительное.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- После входа код провайдера не встречается в журнале запросов хранилища.
|
||||
Оракул — тест: пройти вход подставным провайдером, затем отобрать записи
|
||||
журнала и убедиться, что значения кода в них нет.
|
||||
- Строка о запросе к адресу возврата в журнале остаётся: пропажа самого следа
|
||||
не годится, прослеживаемость входа нужна. Оракул — тот же тест: запись о
|
||||
запросе есть, кода в ней нет.
|
||||
- Норма и код сошлись: либо спека выполняется буквально, либо в ней названо
|
||||
изъятие с ценой. Оракул — чтение требования против исхода первого теста.
|
||||
@@ -0,0 +1,36 @@
|
||||
# 🧹 Судить ответ в тестах по готовому ответу
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Проверка, читающая изменяемую карту заголовков обработчика, зелена при неработающем коде: класс всплыл трижды, последний раз на уборке куки входа.
|
||||
|
||||
Найдено ревью задачи `oidc-login` 2026-08-12, отчёт триажа —
|
||||
[review/report.md](../../openspec/changes/archive/2026-08-12-oidc-login/review/report.md),
|
||||
раздел «Promote candidates», первый пункт.
|
||||
|
||||
Инструмент проверки устроен зеркально настоящему серверу: у сервера заголовки
|
||||
фиксируются в момент, когда ответ начинают писать, а у него живая карта остаётся
|
||||
доступной и после. Проверка, читающая живую карту, видит то, чего клиент не
|
||||
получит, и остаётся зелёной при любой регрессии в этом месте.
|
||||
|
||||
Класс повторяется третий раз — записи журнала дефектов от 2026-08-10, 2026-08-11
|
||||
и 2026-08-12, — и все три раза стоил зелёного гейта при неработающем поведении.
|
||||
Отсюда продвижение: правило в конвенции плюс механизация, а после механизации проза
|
||||
из конвенции убирается.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `docs/conventions/` — новая запись либо раздел существующей: чем судят ответ;
|
||||
- `docs/conventions/README.md`, перечень механизированного;
|
||||
- набор шагов `task gate` — место, где живёт проверка правила;
|
||||
- существующие тесты обработчиков: те, что читают живую карту заголовков.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Правило записано в конвенциях одной формулировкой, и названо место
|
||||
механизации. Оракул — чтение `docs/conventions/README.md`: строка есть, ссылка
|
||||
ведёт в существующее место.
|
||||
- Проверка, читающая живую карту заголовков, роняет гейт. Оракул — завести такую
|
||||
строку в любом тесте и прогнать гейт: ненулевой код возврата.
|
||||
- Проза из конвенции убрана после механизации, а не осталась дублем. Оракул —
|
||||
чтение записи: правило названо один раз, дальше ссылка на механизацию.
|
||||
@@ -0,0 +1,34 @@
|
||||
# 🧹 Назвать в необратимом, что откат кода не откатывает шаг схемы
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Откат бинаря оставляет применённый шаг схемы в силе, и на этом строятся решения о выкладке: сегодня об этом не сказано нигде.
|
||||
|
||||
Найдено ревью задачи `oidc-login` 2026-08-12, отчёт триажа —
|
||||
[review/report.md](../../openspec/changes/archive/2026-08-12-oidc-login/review/report.md),
|
||||
раздел «Promote candidates».
|
||||
|
||||
Проверено прогоном: шаг схемы применили новой ревизией, затем подняли хранилище
|
||||
кодом прежней — все правки шага остались на месте. Хранилище считает применённое
|
||||
по имени файла и о шагах, которых не знает, не догадывается. Обратный шаг
|
||||
запускается только руками отдельной командой.
|
||||
|
||||
Сегодня в разделе «Необратимое» сказано, что применённая миграция не
|
||||
переписывается. Не сказано главного для выкладки: **откат кода её тоже не
|
||||
отменяет**, и версия схемы после отката остаётся новее версии бинаря. Задача
|
||||
`oidc-login` оставила после себя ровно такой случай — защищённое поле файла
|
||||
переживает откат, а прежний код токена для него не запрашивает.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `CLAUDE.md`, раздел «Необратимое» — строка про применённую миграцию;
|
||||
- `docs/architecture.md`, раздел эксплуатации — что происходит при откате
|
||||
выкладки.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- В «Необратимом» сказано, что откат кода не отменяет применённый шаг схемы, и
|
||||
названо следствие: схема остаётся новее бинаря. Оракул — чтение раздела.
|
||||
- Названо, чем откат схемы делается на самом деле, если он всё же нужен. Оракул —
|
||||
чтение той же строки: команда или «руками, отдельным шагом» с адресом.
|
||||
- Гейт зелёный. Оракул — `task gate`.
|
||||
@@ -0,0 +1,54 @@
|
||||
# 🐞 Вести учёт употреблённых состояний входа на сервере
|
||||
|
||||
- **Тип:** fix
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Одноразовость возврата держится на уборке куки, то есть на браузере: сервер не помнит, какие состояния уже потрачены.
|
||||
|
||||
Найдено ревью задачи `oidc-login` 2026-08-12, отчёт триажа —
|
||||
[review/report.md](../../openspec/changes/archive/2026-08-12-oidc-login/review/report.md),
|
||||
остаток пункта 4.
|
||||
|
||||
Носитель состояния здесь — кука, которую сервис ставит на время входа: в ней
|
||||
лежат выданное состояние и проверочный код, и по ней сверяется возврат.
|
||||
|
||||
Спека требует, чтобы состояние было одноразовым: возврат с уже употреблённым
|
||||
отвергается наравне с невыданным. Сегодня это выполняется тем, что носитель
|
||||
состояния убирается у браузера на возврате — и для обычного человека этого
|
||||
достаточно: второй раз тот же адрес возврата сверку не пройдёт.
|
||||
|
||||
Чего это не закрывает: тот, кто носитель контролирует, поставит его себе заново
|
||||
и повторит возврат. Отказ тогда наступит только потому, что код у провайдера
|
||||
одноразовый, — то есть гарантия перенесена на внешнюю систему, чего норма не
|
||||
допускает.
|
||||
|
||||
Цена сегодняшнего состояния невелика, поэтому задача и отложена: код живёт
|
||||
минуты, а вход у провайдера всё равно нужен. Цена решения — своё хранение
|
||||
состояний со сроком жизни и его чистка.
|
||||
|
||||
## Воспроизведение
|
||||
|
||||
1. Пройти вход до конца: получить сессию по возврату от провайдера.
|
||||
2. Поставить носитель состояния заново — тем же значением, которое сервис выдавал
|
||||
на первом шаге.
|
||||
3. Повторить тот же запрос возврата.
|
||||
4. Сверка состояния проходит, и запрос уходит в обмен. Отказ наступает только
|
||||
потому, что код у провайдера одноразовый, — то есть одноразовость держит
|
||||
внешняя система, а не сервис.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- обработчики начала входа и возврата;
|
||||
- место хранения употреблённых состояний: своя коллекция хранилища либо память
|
||||
процесса — выбор входит в задачу;
|
||||
- дельта-спека `access`, требование «Вход через внешнего провайдера» — сценарий
|
||||
«Возврат нельзя переиграть» получает настоящий оракул.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Повторный возврат с тем же состоянием отвергается, даже если носитель
|
||||
восстановлен вручную. Оракул — тест: пройти вход, затем повторить тот же
|
||||
возврат с заново поставленным носителем; сессия не открывается.
|
||||
- Состояния не копятся без предела. Оракул — тест либо чтение кода: у записи
|
||||
состояния есть срок жизни, и просроченные убираются.
|
||||
- Вход по-прежнему проходит целиком. Оракул — существующий тест входа через
|
||||
подставного провайдера остаётся зелёным.
|
||||
Reference in New Issue
Block a user