На Pi при каждом `docker compose up` контейнеры пишут «Your kernel does not
support memory limit capabilities…». Причина — прошивка Raspberry Pi добавляет
ядру cgroup_disable=memory: контроллера memory нет, и Docker молча игнорирует
mem_limit (app 512m, tunnel 64m, backup 384m).
deploy/pi/README.md: проверка, дописывание cgroup_enable=memory в cmdline.txt с
бэкапом, перезагрузка и контроль; строка в «Если что-то не так». Пояснения
у mem_limit в compose и в .env.example. #67
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Решение владельца: в партии все выбывшие проиграли и одинаково слабы, миров у них
нет, поэтому пара двух выбывших в сумму не входит — ни S − E, ни множитель отрыва.
Пары с невыбывшими считаются как прежде, нормировка на N − 1 тоже: при равных
рейтингах результат не меняется. Слабый выбывший больше не получает рейтинг за счёт
сильных выбывших.
Эталон — флаг skip_eliminated_pairs у предложенной системы; документ: 4.3, 4.4,
4.9, пример 5, итоги 7.3 пересчитаны (сдвиг в третьем знаке), решение 8 в разделе 9;
справка. #91
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Рейтинг с #80 один на всё приложение: завершённая или удалённая партия двигает
топ, историю и профили всех, кто играл после неё, и страницы других групп.
Событие match по-прежнему идёт группе партии, остальным активным игрокам —
ratings без подробностей о партии. Фронт по обоим сбрасывает все рейтинговые
витрины (invalidateRatingViews); participant_ids больше не нужен. #88
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
parseCount и граница 99 — в domain/matchCounts.ts вместо двух копий в PlaceEditor
и AdminMatchEdit. В MatchDetailPage места по раскладке считает placeRows, из него
строятся и предупреждения, и строки для API. Поведение не меняется. #89
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Справка и краткое изложение документа обещали, что победитель не теряет рейтинг,
а последнее место его не приносит. Внутри общего места (ничья за 1-е, несколько
выбывших) это не так — раздел 4.9 уже говорил «без ничьей». #87
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Объявления выпускаются в main раньше рейтинга, а в main последняя миграция —
0013. Если бы 0015 оставалась после 0014, прод, получивший объявления,
встал бы на 0015, и при будущем переходе на dev alembic счёл бы базу
актуальной — рейтинговая 0014 не применилась бы никогда.
Теперь 0015 отходит от 0013 (как и 0014), а пустая 0016 сводит обе ветки
в одну голову: прод на 0015 докатит 0014 и 0016, база на 0014 — 0015 и 0016.
Базы, уже прогнанные прежней версией (0015 после 0014), повторно получат
идемпотентную 0014 — проверено на копии dev-БД, на пустой базе и на такой
базе. #84
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Окно — вариант E из макетов: заголовок в плашке-шапке, текст, точки очереди
и «Понятно», крестика нет. Очередь идёт от старого к новому; закрытое сразу
уходит из кэша, следующее открывается без ожидания сети. У повторно
показанного объявления под заголовком — красная пометка «обновлено».
AppShell показывает окно на любой странице и только когда пароль задан:
обязательное окно пароля всегда первое. Новые и изменённые объявления
приходят SSE-событием announcements, отложенное начало показа — перезапросом
раз в 5 минут.
Админка — вкладка «Объявления» с редактором R1: contenteditable и панель
(жирный, курсив, золотой и красный акцент, эмодзи, снятие оформления),
вставка и перетаскивание только простым текстом, счётчик символов, период
показа по МСК, переключатели «показывать новым игрокам» и «показать заново».
Предпросмотр — тем же окном, что у игрока. Список со статусом, счётчиком
«закрыли N из M» и действиями: изменить, снять с показа, дублировать,
удалить. Выделение текста в форме — полупрозрачный оранжевый вместо синего.
schema.d.ts пересобран из OpenAPI. #84
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Объявление администрации показывается игроку окном в свой период, пока игрок
не закроет его («Понятно»). Отметка о закрытии хранится на сервере с номером
версии: правка с «показать заново» поднимает версию, и закрывшие прежнюю
увидят объявление снова — ответ помечен updated («обновлено»). Флаг
show_to_new_players=false прячет объявление от зарегистрировавшихся после
начала показа. Пересекающиеся объявления идут от старого к новому.
Текст приходит HTML-ом из редактора админки и сохраняется только после
очистки по белому списку (b, em, mark и mark.red, p, br): атрибуты
отбрасываются, script/style/svg — вместе с содержимым, текст экранируется
заново. Фронт вставляет только этот HTML.
API: GET /api/announcements/pending, POST /api/announcements/{id}/ack;
админка — список со статусом и счётчиком «закрыли N из M», создание, правка,
«снять с показа», удаление, всё в аудит. SSE-событие announcements активным
игрокам. Миграция 0015 идемпотентная.
Тесты: очистка (XSS-попытки, вложенные div), права, период и порядок,
«новые игроки», повторный показ, снятие, удаление, валидация. #84
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AGENTS.md — такой же локальный гайд для ассистента, как CLAUDE.md, только
для другого инструмента. Он лежал в рабочем дереве незакоммиченным и висел
в git status; теперь он в блоке «AI-ассистенты» рядом с CLAUDE.md
и .claude/, так что случайный `git add .` его не подхватит. #84
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
В профиле Ivan висели две «партии» с одним участником, которые не
открывались. Dev-удаление аккаунта (dev_admin) вычёркивает игрока из партий,
не трогая сами партии, и 17.06 в dev-БД так опустели партии 2, 3, 6 и 7.
Они засчитывались оставшемуся в число игр с ΔR 0.0, показывались в истории
и списке группы, а бэкфилл 0014 проставил им last_standing.
Партия меньше чем с двумя участниками больше не считается игрой: её нет
в рейтинге и счётчиках (load_history), в истории игрока и в списке партий
группы; админка её по-прежнему видит. Бэкфилл last_standing в 0014 теперь
требует хотя бы двух участников (на уже мигрированных БД такие партии и так
скрыты).
«Партия не найдена» при переходе из чужого профиля в партию группы, где
зритель не состоит, — это 403; страница партии теперь так и пишет.
Тест: партия, опустевшая после dev-удаления, не попадает в историю, список
и счётчик группы, рейтинг считается только по настоящей партии. #80
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Групповая цепочка убрана: рейтинг у игрока один, по всем партиям приложения
(решение владельца). load_history всегда грузит всю историю, одно
проигрывание на запрос. Страница группы: игры, победы, винрейт и среднее
место — по партиям группы, рейтинг — общий; «Новичок» тоже по общему числу
партий (новое поле rating_confirmed), поэтому опытный игрок в новой группе
ранжирован. Участники, не игравшие в группе, показывают общий рейтинг.
Главная и профиль — общие показатели; в блоке активной группы игры по
группе, рейтинг общий. У profile_stats убран неиспользуемый group_id.
Фронт: цвет рейтинга в списках берётся из rating_confirmed; справка —
«Один рейтинг на всё приложение». Документ рейтинга: раздел 8 и решение 7
в разделе 9.
Тесты: рейтинг в группах равен общему при групповых играх и победах,
главная и блок активной группы, ветеран в новой группе ранжирован,
«Ещё не играли» с общим рейтингом. #80
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
GET /users/{id}/matches отдаёт у каждой партии rating_delta — изменение
общего рейтинга владельца истории за неё (один знак после запятой), в обоих
режимах, включая «лучшую партию»; история и рейтинг считаются одним
проигрыванием. В списке партий группы поле пустое: там непонятно, чья это
дельта.
Фронт: компактная карточка истории — второй строкой «Рейтинг: +12.3»,
подробная (MatchListView) — последней строкой, только когда дельта есть.
Рост — зелёным, падение — красным, минус типографский (formatRatingDelta).
schema.d.ts перегенерирован.
Тест: дуэль новичков ±32 в обеих историях, реванш с обратными знаками,
сумма изменений сходится с рейтингом, в списке группы дельты нет, режим
«лучшая» её отдаёт. #77
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Удалённый из группы игрок больше не остаётся в «Списке игроков»: leaderboard
получает набор текущих участников, и group_stats фильтрует по нему вывод до
сортировки, так что места нумеруются без пропусков. Расчёт не меняется —
групповая цепочка проигрывает все партии группы, и партии с ушедшим по-прежнему
влияют на рейтинг оставшихся; счётчик партий и статистика фракций группы тоже
прежние. Общий топ и главная без изменений. После удаления участника фронт
сразу перезапрашивает статистику группы.
Тесты: удалённый пропадает из группы, но остаётся в общем топе, рейтинг
оставшегося не меняется, после возврата игрок снова в списке; ранги после
удаления перенумерованы. #76
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Общий топ: столбец «Поб» и сортировка по победам убраны, чтобы четырёхзначный
рейтинг помещался в строку; «Очки» → «Рейтинг» (и в карточке профиля).
Справка переписана под Elo: шкала 1500/400, ожидание и формула ΔR, K новичка,
вес стола, множитель отрыва и близость по типу победы, необязательность
раунда, целей и миров, «последний выживший», лимит 8/9 раундов, примеры
из документа, общий и групповой рейтинг, лучшая партия по приросту.
В документе рейтинга статус — утверждено и реализовано. #23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Форма завершения и правка результатов (MatchDetailPage): у каждого игрока
в PlaceEditor поля «цели» и «миры» на конец партии (у выбывшего миры
заблокированы нулём), общий блок «Итог партии» — причина победы и раунд
окончания из max_rounds партии. Всё новое уходит в общий черновик.
Причина «последний выживший» ставится сама при одном невыбывшем, выбор
заблокирован; вернули второго — причина сбрасывается и её нужно выбрать.
То же в админской правке партии.
Предупреждения (domain/finishWarnings.ts, отправку не блокируют): тип победы
и цели/миры лидеров не согласуются, досрочный конец без N целей у победителя,
у соперника целей больше, чем у победителя. Настройки группы: переключатель
«9 раундов при 5–6 игроках» через PATCH /groups/{id}. schema.d.ts
перегенерирован. #23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
SQL-агрегат League Points (SCORED_CTE, сглаженное среднее) заменён
проигрыванием завершённых партий по порядку played_at, finished_at, id:
load_history — один запрос на партии с участниками, scoring.replay считает
рейтинг. Цепочки две: общая (все партии) и групповая (только партии группы,
K — по партиям внутри группы). Правка или удаление прошлой партии
пересчитывает всё после неё без отдельной логики.
Топ, профиль, главная и статистика группы берут игры, победы, винрейт
и среднее место из той же истории; score — рейтинг целым числом, сортировка
по неокруглённому. Главная грузит историю один раз. Лучшая партия — наибольший
ΔR в общей цепочке, лучшая/худшая фракция — средний S − E. League Points
из scoring.py удалён.
Тесты: дуэль новичков ±32, ранжирование после 10 партий, пересчёт после
правки прошлой партии, отдельная групповая цепочка, лучшая партия по ΔR,
одна загрузка истории на главной. #23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Модели и миграция 0014: groups.nine_rounds_rule, снимок правила в matches,
matches.end_round, match_participants.objectives/worlds. CHECK причины победы
расширяется, только если он в БД есть (старые БД без CHECK таблицу не
пересоздают); пересоздание matches отказывает при PRAGMA foreign_keys=ON,
иначе DROP унёс бы участников каскадом. Бэкфилл: last_standing у завершённых
партий с одним невыбывшим. Проверено на копии dev-БД и на схеме origin/dev
с CHECK: строки, CHECK, FK и индексы на месте, повтор и downgrade работают.
API и валидация: end_round от 1 до лимита раундов партии (9 при хоумруле
и 5+ игроках), у выбывшего миров 0, last_standing ровно при одном невыбывшем —
в завершении и правке (игрока и админа); черновик хранит раунд, цели и миры
без проверки правила. MatchRead отдаёт end_round, снимок правила и max_rounds.
PATCH /groups/{id} принимает nine_rounds_rule вместе с названием. #23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
scoring.py получает движок из docs/rating/rating-system.md: ожидание пары,
K 64 → 16 за 20 партий, вес стола G(N), множитель отрыва (темп, цели, миры,
clamp [0.5, 2]) и близость по типу победы, включая last_standing. rate_match
и replay — чистые функции без БД; replay отдаёт рейтинги без округления,
ΔR и результат относительно ожидания по каждой партии.
Тесты: 15 примеров раздела 6 с числами документа, совпадение констант
и всех ΔR сезона с эталоном simulate.py (полные партии и история без деталей),
монотонность и сумма-ноль. League Points пока остаётся в модуле — витрины
переводятся отдельным коммитом. #23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Поле дуэли 2×3 в таблице правил и примерах; шкала 1500 в формулах, примерах
и пределах изменений; таблицы 7.3 и 7.4 — из новых прогонов simulate.py.
Раздел 8 для #23: правило last_standing (автоматически при одном невыбывшем,
проверка на сервере, бэкфилл истории), рейтинг целым числом, столбцы общего
топа; устаревший абзац про CLAUDE.md заменён отсылкой к HelpPage.tsx.
Раздел 9 «Открытые вопросы» стал «Решениями владельца». #22
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Решения владельца по #22: у дуэли поле 2×3 (6 тайлов, 6.6 мира на игрока),
шкала — классический Elo (R0 = 1500, D = 400, K 64 → 16; чистый Elo — 96 → 16).
Примеры и assert переведены в новую шкалу, сетки K в --grid умножены на 10,
|ΔR| League Points в сравнении приводится к шкале 1500. Добавлена проверка,
что шкала не влияет на точность, Brier и ρ. #22
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
- README: test без порта на хосте (только через домен, Secure-cookie), прод из реестра
(build-push + docker compose up -d), лимиты перебора и регистраций, dev_admin.py в списке
dev-кода, структура репозитория, раздел о бэкапах, отличия test от prod; отмечены
известные проблемы (#69, #71, #72, #73).
- deploy/README.md: три сервиса (app + tunnel + backup), источники ключа туннеля для Pi,
test и dev-туннеля, слот 9000 у временного прода.
- deploy/pi/README.md: контейнер backup, fail-fast по секретам, ADMIN_PASSWORD только при
первом создании админа (#73), порядок обновления.
- deploy/vps/README.md: туннель-контейнер вместо autossh, сниппет (edge), единые имена
файлов в примере сборки сертификатов, дописывать authorized_keys через >>.
- deploy/backup/README.md: первый бэкап на новом Pi, выбор снимка с данными при
восстановлении (#74), метка keep, --no-pre-restore, служебные команды, причины unhealthy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
deploy/backup/README.md — подробная пошаговая инструкция по всему, что делается вручную:
пароль шифрования, SSH-ключ, VPS (пользователь fsbackup только для SFTP, проверки sshd),
сборка образов, включение на Pi (с разбором каждой переменной и контрольными проверками),
доступ с ПК и выгрузка, учебное восстановление на тест-клоне, восстановление прода и из
архива, катастрофа «Pi умер», повседневные действия, таблица неполадок, справочник, чек-лист.
Команды разделов VPS и Pi прогнаны на локальном стенде.
deploy/pi, deploy/vps §8, deploy/README — ссылки на новую схему. Удалены scripts/backup.sh и
scripts/restore.sh (restore.sh ещё и оставлял БД root-овой: docker cp пишет файлы с uid 0);
их архивы fs_*.tar.gz восстанавливаются через fs-backup import. #64
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jBxs9nBCk5nBdTLzcGz91
run отказывается бэкапить БД без игроков и партий, если последний снимок в репозитории был
с данными: новый Pi до восстановления иначе отправил бы пустой снимок на VPS, он стал бы
latest, а keep-daily мог вытеснить настоящий снимок того же дня. Обход — run --allow-empty.
Очистка дополнительно всегда оставляет 3 последних снимка. Проверено сценарием «Pi умер»:
отказ бэкапа → restore latest --repo vps → следующий бэкап проходит. #64
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jBxs9nBCk5nBdTLzcGz91
scripts/fs-backup.ps1 (ASCII, Windows PowerShell 5.1) и scripts/fs-backup.sh (Linux/macOS/
Git Bash) — одинаковые команды к контейнеру backup прода на Pi по SSH или к локальному
тест-клону (-Target test / --test): status, list, now [--tag], verify;
pull — export в файл на Pi, scp в backups/, сверка sha256 и проверка содержимого tar
(бинарные данные не идут через пайпы PowerShell — они их портят);
restore-test — учебное восстановление архива (в т.ч. старого fs_*.tar.gz) в тест-клон.
Настройки BACKUP_PI_SSH / BACKUP_PI_DIR из .env, переменная окружения важнее.
Вызовы нативных команд устойчивы к перенаправлению stderr в PS 5.1. #64
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jBxs9nBCk5nBdTLzcGz91
docker-compose.yml: сервис backup (образ из реестра, явный environment только с BACKUP_*,
тома данных + backup-data), предупреждение про down -v. docker-compose.test.yml: тот же
образ локальной сборкой, только локальный репозиторий, без расписания, BACKUP_VPS_HOST
принудительно пуст. .env.example: новый блок BACKUP_* (пароль, расписание, хранение,
сжатие, SFTP-пользователь fsbackup — системный backup в Debian/Ubuntu уже занят, ключ
base64, адрес Pi для скриптов ПК); удалены BACKUP_VPS_KEY и BACKUP_KEEP_LOCAL/REMOTE.
build-push: сообщения про третий образ (bake подхватывает сервис сам). #64
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jBxs9nBCk5nBdTLzcGz91
deploy/backup: образ на restic/restic:0.19.1 (+ sqlite, supercronic, tini), работает от
uid 10001, как appuser. Скрипт fs-backup:
- run: консистентная копия БД (VACUUM INTO + integrity_check), снимок в локальный
репозиторий и на VPS (SFTP), теги players/matches, GFS-очистка (дни/недели/месяцы;
именованные keep и pre-restore не удаляются), restic check;
- list / status / verify / export (tar без сжатия; сжатие — только restic, репозиторий v2);
- restore / import через промежуточную директорию: разворачивание в .restore-new внутри
каждого тома, проверка (integrity_check, таблицы, число файлов, целостность tar),
страховочный снимок pre-restore, двухфазная замена rename с файлом фазы и откатом;
recover разбирает прерванное восстановление. Отказ при работающем app.
Точка входа: без BACKUP_PASSWORD простой без рестарт-петли, первый снимок при пустом
репозитории, расписание supercronic, healthcheck по возрасту последнего успеха. #64
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jBxs9nBCk5nBdTLzcGz91
docs/rating/rating-system.md:
- проблемы текущего League Points;
- правила игры как исходные данные: тай-брейки, хоумрул 9 раундов, миры;
- выбор модели среди альтернатив, формулы и коэффициенты;
- трассировка требований 1–5 и пошаговые примеры;
- результаты симуляции и анализ чувствительности;
- последствия для реализации: поля, миграция, пересчёт истории, что
ломается;
- открытые вопросы. #22
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
docs/rating/simulate.py — только stdlib, фиксированные seed. Содержит
многопользовательский Elo с множителем отрыва (темп, цели, миры, тип победы,
размер стола, K новичка) и пошаговые примеры документа с assert. Синтетическая
лига в сценариях «сигнал», «шум», «клубы» и «рост» сравнивает текущий League
Points, чистый Elo и предложенную систему. Флаг --grid перебирает K и веса
на отдельных сезонах. #22
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
entrypoint.sh: --forwarded-allow-ips сужен с "*" до loopback + приватных сетей
compose (переопределяемо FORWARDED_ALLOW_IPS) — uvicorn сканирует XFF справа и
берёт реальный адрес, подставленное клиентом левое значение игнорируется.
Caddyfile: reverse_proxy перезаписывает X-Forwarded-For реальным пиром
(header_up {remote_host}) вместо добавления. Итог — достоверный IP для throttle
и аудита. Инфра-часть проверяется на test-клоне. #58
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
main.py: openapi.json/docs/redoc отдаются только в dev/test (нужны для gen:api),
в production отключены. Caddyfile (edge): HSTS, X-Content-Type-Options, X-Frame-Options,
Referrer-Policy, Permissions-Policy, скрыт Server; CSP подготовлена, но выключена до
проверки на test-клоне (строгая политика ломает SPA/Telegram-виджет/SSE). #61
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
model_validator в Settings: при APP_ENV=production приложение не стартует, если
SECRET_KEY дефолтный/короче 32 символов или (при включённом бутстрапе) ADMIN_PASSWORD
дефолтный/пустой. dev/test не затронуты — там дефолты остаются нормой. #59
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
throttle_register ограничивает частоту POST /api/auth/register с одного IP
через тот же LoginThrottle (считаются все попытки). Enumeration ников через
409 не закрываем — ники и так публичны в топе (отмечено в отчёте). #62
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
Вход игрока и админа получают лимит login-user/admin-login-user, не зависящий
от IP: ротация X-Forwarded-For (#58) больше не снимает защиту полностью.
Успешный вход сбрасывает счётчики аккаунта. В docstring ratelimit — про сброс
при рестарте и необходимость внешнего стора при нескольких воркерах. #60
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
admin_login проходит через LoginThrottle (пара IP+логин, IP и сам аккаунт),
как вход игрока; исчерпание лимита -> 429. Неудачные попытки пишутся в аудит
(без пароля). Пароль админа — прямой путь к полному контролю, лимиты строже. #56
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
logout отзывает предъявленный токен по jti (in-memory denylist до exp);
смена и сброс пароля инкрементят users.token_version (claim ver в JWT,
сверка в auth/deps) — все прежние сессии отзываются. Своё устройство при
смене пароля остаётся в сессии (перевыдача cookie). Миграция 0013. #57
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
Многие тосты повторяли то, что пользователь и так видит сразу после действия:
новую фракцию или аватар в профиле, переход на страницу партии, исчезнувшую
карточку приглашения, закрывшийся редактор. Такой шум приучает не читать
всплывашки, в том числе ошибки. Убраны 19 таких тостов в профиле, группах,
партиях и админке.
Остались ошибки, предупреждения (лимит фото, повторы фракций, конфликт правок,
«вы не в группе») и сообщения о действиях без видимого следа: приглашение
отправлено, дополнения сохранены, вступление в группу, пароль задан. Сохранение
текстового поля, где заметна только погасшая кнопка, тоже осталось.
По просьбе владельца оставшиеся тосты показываются не нижней «таблеткой», а
всплывашкой сверху, как входящие уведомления. ToastProvider держит одну стопку
на всё приложение: show — обычное сообщение, error — ошибка с красной полосой
(держится 5 с), notify — уведомление с переходом по клику. Одинаковое сообщение
не копится, а заменяет прежнее; клик по карточке закрывает её.
NotificationToaster больше не рисует свою стопку, а отдаёт новые уведомления в
общую, поэтому они не наезжают на сообщения. Все вызовы с ApiError и отказы
(лимит фото, партия изменилась на другом устройстве) переведены на toast.error.
Нижний класс .toast удалён.
#41
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6