diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml
index 3ea2a0f..d670f4c 100644
--- a/deploy/docker-compose.yml
+++ b/deploy/docker-compose.yml
@@ -39,6 +39,13 @@ services:
# e2e/smoke; в prod'е оставляем дефолты 3/час, 10/сутки.
RateLimiting__SignupPerIpPerHour: ${RATE_SIGNUP_HOUR:-3}
RateLimiting__SignupPerIpPerDay: ${RATE_SIGNUP_DAY:-10}
+ # Sprint 30: «быстрый вход» на тестовом стенде. По умолчанию выключено.
+ # На stage включается через QUICK_LOGIN_ENABLED=true в .env. Code
+ # имеет safety-override: даже с Enabled=true, если OpenIddict__Issuer
+ # смотрит на admin.food-market.kz — быстрый вход принудительно выключен
+ # (см. QuickLoginOptions.EffectivelyEnabled).
+ QuickLogin__Enabled: ${QUICK_LOGIN_ENABLED:-false}
+ QuickLogin__MasterPassword: ${QUICK_LOGIN_MASTER_PASSWORD:-123456}
# Host port mapping: pick free ports on existing stage server (80/443 taken by
# legacy nginx, 5000/5002/5005 taken by legacy .NET apps).
ports:
diff --git a/docs/quick-login.md b/docs/quick-login.md
new file mode 100644
index 0000000..0e48dd7
--- /dev/null
+++ b/docs/quick-login.md
@@ -0,0 +1,145 @@
+# Quick Login — быстрый вход на тестовом стенде
+
+**Статус:** включено на `https://test.admin.food-market.kz` (стенд).
+**Прод:** `https://admin.food-market.kz` — выключено, попытки активации
+принудительно блокируются safety-override'ом в коде.
+
+Это dev-удобство: на стенде не нужно каждый раз вводить логин+пароль.
+Выбираешь карточку аккаунта на `/login`, клик — ты внутри.
+
+## Как это работает
+
+### Server-side
+
+Опции в `appsettings.json` / env:
+
+```yaml
+QuickLogin:
+ Enabled: false # default — выключено
+ MasterPassword: "123456"
+```
+
+Включается через env в stage-compose:
+
+```
+QUICK_LOGIN_ENABLED=true
+QUICK_LOGIN_MASTER_PASSWORD=123456
+```
+
+Логика работает в двух местах:
+
+- **`POST /connect/token` (grant=password)** — при `EffectivelyEnabled=true`
+ пароль `MasterPassword` принимается у ЛЮБОГО активного пользователя наравне
+ с его собственным паролем. Константное сравнение (`CryptographicOperations.
+ FixedTimeEquals`), чтобы не светить длину. Деактивированные (`IsActive=
+ false`) не пускаются ни при каких условиях. `/connect/token` всё так же
+ гонит через rate-limit, 2FA и org-check (архивная org отшивает).
+
+- **`GET /api/dev/quick-login/accounts`** — анонимный эндпоинт, который
+ возвращает «человеческие» аккаунты для карточек: SuperAdmin → demo-trio →
+ остальные. Автотестовые логины отсеиваются по regex-патéрнам (всё с
+ суффиксом из 10+ цифр или prefix cat31-/visual-/factory-smoke-/mt6*/
+ quality-/...). Поиск `?q=` фильтрует по email/ФИО/роли/магазину.
+ На проде эндпоинт отдаёт 404 — наличие пути не палит окружение.
+
+### Client-side (SPA)
+
+- `` над формой логина сам опрашивает `/api/dev/quick-
+ login/accounts`. 200 → рендерит жёлтый блок с карточками + поиск. 404 →
+ возвращает `null`, никакого UI не рисует. Клик по карточке → login с
+ `MasterPassword`.
+- `` жёлтой полосой сверху всех страниц, когда эндпоинт
+ доступен. При том же сигнале динамически добавляет `` — чтобы Google не индексировал стенд.
+
+Один web-билд используется и в проде, и на стенде — секрет на проде не
+утекает: `/api/dev/quick-login/accounts` там 404, значит блока нет.
+
+## Безопасность — предохранители
+
+1. **Enabled=false по умолчанию.** Нужна явная env-переменная, чтобы
+ включить. В прод-compose её нет и не должно быть.
+2. **Issuer safety-override.** В `QuickLoginOptions.EffectivelyEnabled`:
+ если `OpenIddict:Issuer` указывает на `admin.food-market.kz` (ТОЧНО,
+ без поддоменов), быстрый вход **принудительно выключен**. Это защищает
+ от ошибки конфигурации — поставили `QUICK_LOGIN_ENABLED=true` в
+ прод-env по ошибке, но Issuer остался прод-овым → ничего не включилось,
+ в лог Warning.
+3. **Пустой или кривой Issuer тоже отбивает.** Не рискуем в неизвестном
+ окружении: если Issuer пустой/мусорный — считаем выключенным.
+4. **Rate-limit не ослаблен.** Master-password идёт через ту же защиту,
+ что обычный password-grant (`UseAuthFormUsernameKey` + `UseRateLimiter`).
+5. **Неактивные пользователи не пускаются.** Проверка `IsActive` сверху
+ `/connect/token` — master-password это не обходит.
+6. **Startup Warning.** Program.cs логирует одно решение на старте:
+ «включён для стенда» или «Enabled=true, но безопасность выключила».
+ В логах прод-инстанса всегда видно, что происходит.
+
+## Демо-аккаунты
+
+`QuickLoginDemoSeeder` при старте с `EffectivelyEnabled=true` поднимает:
+
+- Организацию **«Демо-магазин»** (идемпотентно; отдельная от «Demo Market»
+ из `DevDataSeeder`, чтобы не ломать e2e).
+- Полный demo-каталог через `DemoTenantSeeder` (50 товаров / 30 продаж /
+ приёмки / контрагенты).
+- Трио учёток с ролями:
+ - `demo-admin@food-market.local` → `Admin`
+ - `demo-cashier@food-market.local` → `Cashier`
+ - `demo-keeper@food-market.local` → `Storekeeper`
+
+Собственный пароль у них `Admin12345!` (унаследовано от шаблона), но
+мастер-пароль `123456` тоже работает.
+
+## Чистка автотестового мусора
+
+`HousekeepingJobs.PruneAutoTestEntitiesAsync` — Hangfire-recurring каждый
+день в 02:45 UTC. Удаляет автотестовые организации (regex-match по имени)
+и orphan-юзеры по email-паттерну старше 3 дней. Защищены: SuperAdmin,
+`admin@demo-market.local`, `demo-*@food-market.local`, `market@email.com`,
+«Demo Market» / «Демо-магазин».
+
+Единоразовая зачистка той же логикой — `deploy/stage-prune-autotest.sql`.
+
+## Как выключить обратно
+
+На стенде:
+
+```bash
+# на prod-vm
+nns@192.168.1.190:$ sed -i '/^QUICK_LOGIN_ENABLED=/d; /^QUICK_LOGIN_MASTER_PASSWORD=/d' \
+ ~/food-market-stage/deploy/.env
+nns@192.168.1.190:$ cd ~/food-market-stage/deploy && docker compose -p food-market-stage up -d --force-recreate api
+```
+
+API перестартует, `EffectivelyEnabled` увидит `Enabled=false`, эндпоинт
+снова 404, master-password не работает. Карточки на `/login` исчезнут —
+`QuickLoginBlock` увидит 404 и вернёт `null`.
+
+## Проверить что прод не задет
+
+```bash
+curl -s -o /dev/null -w "%{http_code}\n" \
+ https://admin.food-market.kz/api/dev/quick-login/accounts
+# ожидается: 404
+```
+
+Любой другой код (200/403/500) — проблема.
+
+## Файлы
+
+| Что | Где |
+|---|---|
+| Опции + safety-override | `src/food-market.api/Infrastructure/QuickLoginOptions.cs` |
+| Master-password в token-grant | `src/food-market.api/Controllers/AuthorizationController.cs` |
+| Список аккаунтов | `src/food-market.api/Controllers/QuickLoginController.cs` |
+| Демо-сид | `src/food-market.api/Seed/QuickLoginDemoSeeder.cs` |
+| Чистка автотестов | `src/food-market.api/Background/HousekeepingJobs.cs` + `deploy/stage-prune-autotest.sql` |
+| UI блок | `src/food-market.web/src/components/QuickLoginBlock.tsx` |
+| Плашка стенда | `src/food-market.web/src/components/StageBanner.tsx` |
+| Integration-тесты | `tests/food-market.IntegrationTests/QuickLoginTests.cs` |
+| E2E-сценарий | `tests/e2e/scenarios/stage-quick-login.{yml,steps.ts}` |
+
+## История
+
+- **2026-10-04 (Sprint 30):** первоначальный ввод на тестовый стенд.
diff --git a/src/food-market.api/Infrastructure/QuickLoginOptions.cs b/src/food-market.api/Infrastructure/QuickLoginOptions.cs
index 126d450..fab4e88 100644
--- a/src/food-market.api/Infrastructure/QuickLoginOptions.cs
+++ b/src/food-market.api/Infrastructure/QuickLoginOptions.cs
@@ -52,7 +52,10 @@ public bool EffectivelyEnabled(string? issuer, out string reason)
return false;
}
var host = uri.Host.ToLowerInvariant();
- if (host == "admin.food-market.kz" || host.EndsWith(".admin.food-market.kz"))
+ // ТОЛЬКО точное совпадение с прод-хостом. Любой поддомен (test.admin.*,
+ // dev.admin.*) — это стенд, его трогать не надо. Если в будущем
+ // появится второй прод-хост, сюда дописывается новое условие.
+ if (host == "admin.food-market.kz")
{
reason = $"OpenIddict:Issuer host={host} — это ПРОД, быстрый вход принудительно выключен";
return false;