WEB-307 · Дефект · Инфраструктура · web
Boot-контракт валит прод из-за телефонии, которой нет: TELEGRAM_BOT_TOKEN тянет требование TELEGRAM_CALL_ADAPTER_SECRET
Закрыт
P0 · горит
ведёт: backup-opus
Суть
## Найдено 21.08 попыткой посадки тридцатой — **посадка остановлена, прод откачен**
Новый релиз (`51d5b74f`) отказался поднимать HTTP-порт:
```
[boot-env-contract] missing_required name=APP_URL group=app-origin
[boot-env-contract] missing_conditional name=TELEGRAM_CALL_ADAPTER_SECRET group=telegram-call
[boot-env-contract] bad_secret_format name=BLUEBUBBLES_SERVER_PASSWORD
[boot-env-contract] bad_secret_format name=DATABASE_URL
[boot-env-contract] bad_secret_format name=SOCIAL_ENCRYPTION_KEY_PREVIOUS
[boot-env-contract] fatal: refusing to bind HTTP port (2 fatal issue(s))
```
Фатальны **только** `missing_required` и `missing_conditional` (`src/lib/boot/envContract.ts:40-41`, `FATAL_ISSUE_CODES`). Три `bad_secret_format` — предупреждения; в частности претензия к `DATABASE_URL` **не** блокирует.
### Блокер 1 — APP_URL: настоящий пробел, **закрыт**
`REQUIRED_GROUPS` содержит `{ group: 'app-origin', names: ['APP_URL'] }` (`envContract.ts:57`). На проде переменной не было вовсе, хотя `NEXTAUTH_URL=https://nb.wool2.online` присутствует.
Добавлено в `/home/pi/note-clone/shared/.env` тем же значением, бэкап `/home/pi/prelanding-backup/env.20260821T022055Z.bak`. Прод не перезапускался — переменная нужна только новому релизу.
(Группа `auth-secret` — `['AUTH_SECRET','NEXTAUTH_SECRET']`, достаточно любого; `AUTH_SECRET` есть, поэтому она не падала.)
### Блокер 2 — дефект самого контракта, **НЕ закрыт**
```ts
{ group: 'telegram-call',
triggers: ['TELEGRAM_CALL_ADAPTER_SECRET','TELEGRAM_BOT_TOKEN','TELEGRAM_API_ID','TELEGRAM_API_HASH'],
required: ['TELEGRAM_CALL_ADAPTER_SECRET'] }
```
На проде есть `TELEGRAM_BOT_TOKEN` — **им ходят уведомления владельцу**, никакой телефонии. Проверено по окружению живого процесса: `TELEGRAM_API_ID` нет, `TELEGRAM_API_HASH` нет, `TELEGRAM_CALL_ADAPTER_SECRET` нет.
Обычный бот-токен **не является** свидетельством того, что задействован call-адаптер (тот работает по MTProto и требует API_ID/API_HASH). Триггер слишком широк.
**Чинить нужно триггер, а не окружение.** Выдумать `TELEGRAM_CALL_ADAPTER_SECRET` ради прохождения гейта — это соврать контракту: он начнёт считать call-адаптер настроенным, хотя его нет. Ровно тот класс, который мы ловим весь день («самодостаточность, которой нет»; «расписка, доказывающая форму, а не существо»).
Предлагаемая правка: убрать `TELEGRAM_BOT_TOKEN` из `triggers`, оставить `TELEGRAM_CALL_ADAPTER_SECRET`, `TELEGRAM_API_ID`, `TELEGRAM_API_HASH`. Аналогично проверить соседние группы (`whatsapp-call` триггерится от `TWILIO_WHATSAPP_NUMBER` — вероятно та же болезнь).
**Негативный тест обязателен:** окружение с одним лишь бот-токеном обязано загружаться; окружение с `TELEGRAM_API_ID` и без адаптерн��го секрета обязано падать.
⚠️ Граница зоны: телефония — изолированная зона внешнего инженера (WEB-15/17/45-56). Правка касается **boot-контракта** (WEB-070), а не кода телефонии, но перед слиянием проговорить.
### Что гейт при этом сделал правильно
Он **не пустил** релиз с неполной конфигурацией и напечатал, чего именно не хватает. Это ровно поведение fail-closed, ради которого WEB-070 и делался. Дефект не в строгости, а в широте одного триггера.
## 21.08 — ПОЧИНЕНО, КОММИТ `f994d515`, СЛИТО В landing30b → HEAD `504af77f`
Волна `bootfix`, отчёт `/home/ubuntu/waves/BOOTFIX-REPORT.md`.
### Правило, по которому принималось каждое решение (записано и в коде)
> Имя попадает в `triggers`, только если код, который его читает, **не может** работать ради какой-либо ДРУГОЙ фичи. Общий с соседней подсистемой credential — не свидетельство.
**Удалено 4 триггера из 4 групп.** Ни один список `required` не тронут — там, где подсистема действительно включена, контракт требует ровно то же. `FATAL_ISSUE_CODES` не тронут. Фиктивные секреты не проставлены.
Главный: `telegram-call` лишился `TELEGRAM_BOT_TOKEN` — токен читают обычный Bot API клиент (`src/lib/telegram/bot.ts:11`) и мессенджер-вебхук (`.../telegram/webhook/route.ts:2191,2570`), то есть он свидетельствует о переписке, а не о звонках.
### Доказательство на РЕАЛЬНОМ окружении инцидента
| прогон | результат |
|---|---|
| **A. контракт `51d5b74f`, точное прод-окружение** | `mode=enforce halt=true`; обе строки **дословно совпали** с боевым логом посадки |
| **B. эта ветка, тот же env** | осталась одна претензия — `missing_required APP_URL`; `rc=1` |
| **C. эта ветка + APP_URL (то, что добавил координатор)** | `no contract issues` · `fatal: none` · **`VERDICT: boot proceeds`** · `rc=0` |
### Инструмент сухого прогона — недостающий шаг чеклиста
`scripts/boot-contract-dry-run.ts` + 12 тестов; `pnpm boot:contract:dry-run`.
**Требование существа выполнено:** зовёт ту же `validateBootEnv()` из `src/lib/boot/envContract`, что и боевой путь через `src/instrumentation.ts:22-23`. Своей копии правил нет, и это **закреплено тестом** `no second copy of the rules`: он падает, если в исходнике скрипта появится литерал любого кода issue или имени переменной из таблиц контракта. Это ровно защита от того класса, который мы чиним — верификатор, разошедшийся с проверяемым.
Умеет читать systemd drop-in каталогом (в алфавитном порядке, как systemd) и env-файлы с префиксом `-` для необязательных.
Шаг вписан в `docs/core/DEPLOYMENT_RUNBOOK.md` — раздел «1a) Boot Env Contract Dry Run», запускать **на Pi** и **до** `systemctl restart`.
### Гигиена
В код телефонии (`src/lib/telephony/**`, `infra/sip/**`, `scripts/*-call-adapter/**`) не входил — читал только чтобы установить, кто читает переменную; ни одного файла оттуда не изменено. Граница изолированной зоны (WEB-15/17/45-56) соблюдена.
## 21.08 03:53 — ПРОВЕРЕНО НА ПРОДЕ ✅
После посадки `504af77f` в журнале службы: фатальных строк `boot-env-contract] fatal` — **0**, осталось три предупреждения `bad_secret_format` (не блокируют, как и задумано). Служба поднялась с первого раза, `NRestarts=0`.
Дефект широкого триггера закрыт на живом окружении: `TELEGRAM_BOT_TOKEN` больше не требует секрета звонков.
## 2026-08-23 BOARDSWEEP: STALE-NOT-DEPLOYED
- Evidence: Canonical contains boot contract code/tests at src/lib/boot/envContract.ts and scripts/boot-contract-dry-run.ts, but buildFixed is empty; the board does not bind the fixed boot contract to v4-081ff4fb5. Unauth GET /api/health=200 only.
- buildFixed: <empty>; canonical: v4-081ff4fb5 / 081ff4fb5f6110f8001daec7ed910ccef2e395dc.
- Status preserved by sweep: review.
Доказательства
[2026-08-24 STALEREV против 61bc04462; в проде с посадки 45 v4-5a4edbac3] Вердикт SERVED: реализация+тесты доказаны в target tree; принято по приёмке 45-тикетного разбора. Отчёт: A1 /home/ubuntu/waves/STALEREVIEW-REPORT.md (STALEREVIEW_DONE).
Починено в
v4-5a4edbac3
Воркер
не привязан — привязать:
curl -X POST https://bugs.wool2.online/api/web/assign -H 'content-type: application/json' \
-d '{"issueId":"WEB-307","session":"<имя tmux-сессии>","host":"m4"}'
Обновлён
2026-08-24T14:28:58.991Z