docs: ADR-009/010, карта деплоя, деплой-памятка фронта, задачи фронтендеру

Приёмка и починка 15.09.2026:
- ADR-009: один бэк на оба домена — прод-домен smart-updown.binom.pw переведён на тот же
  TS-шлюз (:8895), что и testfront; Kotlin-бэк (3NWEK) выключен обратимо. Разобраны расхождения
  API игры и шлюза (их должны править во фронте).
- ADR-010: две ловушки деплоя фронта — (1) чарт рендерит только path '/', поэтому helm-релиз
  сносит /api,/ws → вынесены в отдельный ingress smart-updown-api вне helm;
  (2) захардкоженный values.image.tag бьёт Chart.AppVersion → helm upgrade НЕ поднимает новый
  образ (новый RS не создаётся), на домене остаётся старый билд при 'Upgrade complete'.
  Приведена правильная команда (--reuse-values --set image.tag=<N>) и способ ПРОВЕРКИ по кубу
  и домену, а не по helm. Отмечен остаточный риск: Unity-ассеты immutable 7 суток при
  неизменяемых именах файлов → кэш браузера держит старый билд.
- README: карта деплоя (кто где живёт), статус проекта на 15.09, актуализирован блок
  безопасности (SEC-16 — утечка SOL, блокер перед mainnet), Kotlin-бэк помечен выключенным,
  добавлен TS-шлюз как боевой бэк, ссылки на tools/ и k8s/.
- TASKS.md: деплой-памятка фронта + задачи фронтендеру (убрать image.tag, хэш в именах
  ассетов, привести API игры к контракту шлюза).
This commit is contained in:
Hermes Agent
2026-09-15 12:13:23 +03:00
parent 101a4b60a9
commit 21a3a37a66
3 changed files with 138 additions and 13 deletions
+71
View File
@@ -132,3 +132,74 @@
дороже и требует фидов; (в) заглушка — отклонено как единственный вариант, оставлена
возможность (`setPriceOverride`) для тестов.
- **Детали и диаграммы:** `docs/PRICE-NATS.md`, `docs/diagrams/price-flow.puml`.
## ADR-009 — Один бэк на оба домена: прод-домен переведён на TS-шлюз (2026-09-15)
- **Дата:** 2026-09-15
- **Статус:** принято, реализовано
- **Контекст:** на `smart-updown.binom.pw` (Unity-игра) стоял **канонический Kotlin-бэк**
`smart-updown-server` с programId `3NWEK…`, а на `testfront.binom.pw` — **TS-шлюз**
`updown-relayer` (:8895), кастодиальный, на контракте `9ALs…`. Два бэка = два контракта =
разные цепочки состояния: у Kotlin-бэка вообще НЕТ WebSocket, а его программа в цепи
отсутствовала (`getAccountInfo` → `null`), поэтому `/api/state` отдавал
`global state is not initialized`. Игра при этом читала баланс напрямую из цепи через свой
nginx-прокси `/solana-rpc/getBalance` — то есть жила в третьей реальности.
- **Решение:** **не деплоить `3NWEK` под Kotlin**, а повесить игровой домен на **тот же** TS-шлюз,
что и тестовый: `/api` и `/ws` → `testfront-gateway-smart-updown-relayer:8895`. Формулировка
владельца: «тестовый фронт мы для этого и делали»; игру переделают под API шлюза — расхождение
контрактов API не блокер.
- Kotlin-бэк выключен **обратимо**: `replicas=0` + его ingress удалён (иначе два ingress'а на
один хост+path = недетерминированный роутинг Traefik).
- Бэкап состояния до правки — `/root/bak-smart-updown-15.09/` на 192.168.76.120.
- **За:** один бэк = одно состояние на оба стенда (счётчик 55=55, касса 29320); у игры
появляется живой WS; тестовый фронт больше не «отдельный мир»; откат — две команды.
- **Против:** API шлюза и ожидания игры расходятся (`minAmount`/`maxAmount` строкой против
`minAmountLamports`/`entryPrice`; тело ставки `{address, side, amountUnits}` против
`{side, amount, entryPrice, betId, signedTx}`; `GET /api/bet/{id}` у шлюза нет) — игру надо
править. Принято осознанно.
- **Что осталось не сделано:** программа `3NWEK` в цепь так и не задеплоена (ключ
`/root/game_program_kp.json`, `.so` 217496 б лежат в LXC 151; на адрес аирдропнуто 5 SOL).
Это задел на будущее, не потеря.
## ADR-010 — `/api`,`/ws` вынесены в ingress вне helm; образ фронта поднимается явным `--set image.tag` (2026-09-15)
- **Дата:** 2026-09-15
- **Статус:** принято, реализовано
- **Контекст — две независимые ловушки, обе пойманы живьём:**
1. Чарт `fun-game-front2` (`helm/templates/ingress.yaml`) рендерит **ровно один** path `/`
(жёстко, host из `values.yaml`) — значений для `/api`,`/ws` в чарте НЕТ. Значит **любой**
`helm upgrade` игры перерисовывает ingress и **сносит** `/api`,`/ws`: домен начинает отдавать
HTML игры на `/api/*` (nginx 405 на `POST /wallet`, `/api/state` = HTML Unity вместо JSON).
2. Шаблон деплоя объявляет образ как
`image: "{{ Values.image.name }}:{{ default Chart.AppVersion Values.image.tag }}"`, а в
`helm/values.yaml` **захардкожен `image.tag`** (был `26`). Непустой `Values.image.tag`
побеждает `appVersion`, поэтому `helm upgrade` на чарт `28` отрендерил под со **старым**
тегом `26`: спека не изменилась, новый ReplicaSet **не создался**, старый под (от 8 сентября)
продолжал жить. Внешне — «на домене открывается старый билд» при зелёном CI и
`Upgrade complete` в истории helm.
- **Решение:**
1. `/api` и `/ws` живут в **отдельном ingress `smart-updown-api`** (манифест
`k8s/smart-updown-api-ingress.yaml`), front2-ingress остаётся `paths: [/]`. Релиз игры
больше не может их снести.
2. Фронт игры поднимать **всегда** явным тегом и с сохранением values:
`helm upgrade fun-game-front2 binom/fun-game-front2 -n game --reuse-values --set image.tag=<N> --wait`.
Без `--reuse-values` затираются `imagePullSecrets: regcred` и `ingress.host`.
- **Проверка результата — по КУБУ и домену, а не по helm** (helm отрапортует «успех» и в случае 2):
```bash
kubectl get pod -n game -l app.kubernetes.io/name=fun-game-front2 \
-o custom-columns='NAME:.metadata.name,IMAGE:.spec.containers[0].image,CREATED:.metadata.creationTimestamp'
kubectl exec -n game <pod> -- ls -la /usr/share/nginx/html/Build/ # дата файлов = дата билда
curl -sI https://smart-updown.binom.pw/Build/WebGL.data | grep -i last-modified
kubectl -n game get ingress \
-o custom-columns='NAME:.metadata.name,HOST:.spec.rules[0].host,PATHS:.spec.rules[0].http.paths[*].path'
```
- **Против / остаточный риск (не забыть):** в `nginx.conf` все Unity-ассеты объявлены
`Cache-Control: public, max-age=604800, immutable`, а имена файлов (`WebGL.data`, `WebGL.wasm`,
`WebGL.framework.js`) между сборками **НЕ меняются**. Браузер, уже игравший, будет до 7 суток
отдавать старые ассеты из кэша, игнорируя новый билд (`index.html` спасает `no-store`, ассеты — нет).
Лечится сбросом кэша/инкогнито у игрока. **Правильно на будущее** — хэш содержимого в имени
файла на шаге сборки (CI), тогда `immutable` становится корректным.
- **Альтернативы:** (а) патчить чарт фронта (добавить в него `/api`,`/ws`) — чужой репозиторий и
чужой релизный цикл; (б) держать образ на `latest` — нет воспроизводимости; (в) убрать `image.tag`
из `values.yaml` и опираться только на `Chart.AppVersion` — правильнее, но это правка чужого репо
(отдать фронтендеру, см. `TASKS.md`).
---