up
This commit is contained in:
+6
-3
@@ -27,13 +27,13 @@
|
||||
|
||||
### ED-029 — Автономная площадка и панель организатора
|
||||
|
||||
- Статус: `IN_PROGRESS` — этап 3 принят пользователем без WAN; этапы 4–7 завершены; автоматическая часть этапа 8 завершена, ожидает аппаратной приёмки.
|
||||
- Статус: `IN_PROGRESS` — этап 3 принят пользователем без WAN; этапы 4–9 завершены; аппаратная приёмка этапа 8 подтверждена пользователем.
|
||||
- Репозитории: корневой, `evening_detective_server`, `evening_detective_frontend`.
|
||||
- Цель: реализовать принятый пользователем план из 15 последовательных этапов: автономное окружение; локальные организаторы; прежние адреса через Cudy; пакет игры; экспорт со статусом офлайн-режима; локальный импорт; интерфейс передачи; полный билетный и игровой flow; пакет результатов; возврат онлайн; интерфейс возврата; устройства; панель состояния; сбор данных Cudy; Wi-Fi и испытание площадки.
|
||||
- Критерии: проверяемый результат каждого этапа; цикл «онлайн → ноутбук → проведение → онлайн» через интерфейс с сохранением QR, билетов и связей; повторные импорты без дублей; мониторинг устройств и Wi-Fi; полный прогон без WAN, 100 клиентов и проверка покрытия.
|
||||
- Ограничения: только тестовые игры до завершения возврата; существующие правила игры и формат `offline` сохраняются; без несогласованных новых зависимостей и отклонений от стандартов; без персональных списков игроков, CPU/памяти Cudy и автоматического переключения серверов; настройки роутера менять только по явному запросу пользователя; секреты и пакеты вне Git; commit/push выполняет пользователь.
|
||||
- Проверка: Go-стандарт и `go test ./...`; PostgreSQL-интеграционные тесты для атомарности; frontend-стандарт, type-check, lint, lint:css и build; штатная генерация protobuf; ручные контрольные точки после этапов 3, 8, 11, 15.
|
||||
- Прогресс: этапы 1–2 реализованы и проверены в локальном Docker-окружении. Добавлены отдельный Compose, подготовка образов заранее, постоянные тома, миграции перед стартом, healthchecks и прокси. Явный `APP_MODE=venue` отключает SMTP, фоновые письма и внешний webhook; почтовые операции аккаунта возвращают безопасную ошибку до изменения данных. Локальные аккаунты создаются штатным `dev-user.sql`, роли и правила автоматического подтверждения команд сохраняются. Этап 3 принят пользователем после проверки без WAN; формат и код пакета игры (этап 4) реализованы и проверены. Экспорт со статусом офлайн-режима (этап 5) и локальный импорт (этап 6) реализованы и проверены. Этап 7 реализован и проверен; автоматическая часть этапа 8 проверена на изолированном стенде; аппаратная приёмка ожидается, этапы 9–15 ещё не завершены.
|
||||
- Прогресс: этапы 1–2 реализованы и проверены в локальном Docker-окружении. Добавлены отдельный Compose, подготовка образов заранее, постоянные тома, миграции перед стартом, healthchecks и прокси. Явный `APP_MODE=venue` отключает SMTP, фоновые письма и внешний webhook; почтовые операции аккаунта возвращают безопасную ошибку до изменения данных. Локальные аккаунты создаются штатным `dev-user.sql`, роли и правила автоматического подтверждения команд сохраняются. Этап 3 принят пользователем после проверки без WAN; формат и код пакета игры (этап 4) реализованы и проверены. Экспорт со статусом офлайн-режима (этап 5) и локальный импорт (этап 6) реализованы и проверены. Этап 7 реализован и проверен; этап 8 проверен на изолированном стенде и принят пользователем на аппаратной контрольной точке; этап 9 реализован и проверен; этапы 10–15 ещё не завершены.
|
||||
- Дополнение по запросу пользователя: структура `venue` приведена к `prod` (`backend/`, `frontend/dist/`, `migrations/`, `Dockerfile.backend`, `docker-compose.yml`, `deploy.sh`); имена основных Compose-сервисов совпадают. Общая генерация `make build` / `make prod-build` готовит артефакты обеих папок; frontend собирается отдельно с настройками своего окружения. Текущие файлы `prod` при этой работе не изменяются; сборка проверяется с production-выводом в `/tmp`.
|
||||
- Упрощение запуска: отдельные `init.sh` и `prepare.sh` удалены, всё собрано в `deploy.sh`. Единственная команда запуска — `make venue`: создаёт `.env` только при его отсутствии, при необходимости готовит зависимости/образы и автоматически создаёт организатора. Повторный готовый запуск не собирает и не скачивает образы. `make venue-update` явно пересобирает окружение с интернетом. Аккаунт организатора синхронизируется со значениями `.env` при каждом запуске. Первый запуск, повторный запуск без интернета и обновление проверяются `python3 tests/venue/deploy.py` с подставными командами; реальный готовый запуск проверен через Docker и API-smoke.
|
||||
- Настройки площадки: по запросу пользователя необязательный `venue/.env.example` удалён; настройки создаются автоматически через `make venue`. Убраны ссылка и проверка примера; `python3 tests/venue/deploy.py` прошёл. Существующая `.env` не изменялась.
|
||||
@@ -68,7 +68,10 @@
|
||||
- Результат автоматической части этапа 8: добавлены повторяемые `tests/venue/flow.py` и `flow_browser.py`, команды и ручной контрольный список в README проверок. Актуальный код собирается в `/tmp`; стенд запускает два настоящих backend prod/venue с отдельными PostgreSQL/RustFS в Docker internal-сети без WAN, SMTP направлен в локальный sink. API проверяет оба формата, экспорт/импорт ZIP, исходные ссылки/ID/пароли команд и билетов, статусы участия, QR/PDF, обычный/повторный/конкурентный приём билетов, отказ без авторизации и неверные ссылки, локальные команды/билеты, старт/ходы/выдачу материалов/паузу/продолжение/завершение, ответы и баллы, результаты, перезапуск всех venue-сервисов и повторный импорт без потери прогресса. Проверена независимость источника и площадки; ограничения формата `offline` сохранены. После проверки удаляются только ресурсы стенда; ошибка очистки одного ресурса не останавливает попытки очистить остальные.
|
||||
- Исправления этапа 8: общий gameplay-сервис проверяет `run` до записи хода и подтверждения участия, одинаково для API/MCP; безопасная ошибка отображается как FailedPrecondition. Кнопка хода блокируется вне `run` и при отправке; отказ показывается пользователю и сохраняет код. Кнопкам lifecycle добавлены доступные имена. Конкурентный приём билета теперь перечитывает текущее состояние и сообщает «билет уже использован», а не ошибочно «игра уже завершена». PostgreSQL-тест конкурентности синхронизирует запросы блокировкой строки: до исправления воспроизводил ошибку, после исправления проходит.
|
||||
- Проверки этапа 8: `python3 tests/venue/flow.py --browser` прошёл с реальными API/хранилищем и отдельным браузерным стендом. Firefox проверил полный UI-flow на 1440 px (`online`) и 390 px (`offline`), реальное скачивание/загрузку ZIP, билеты, ходы и polling, паузу с введённым кодом, двойную отправку, отказ с сохранением ввода, выдачу материала, оценивание/результаты, завершённый экран игрока, неверный пароль и клавиатурный focus в модалке. Внутри стенда прошли PostgreSQL-регрессии gameplay/lifecycle, tickets, MCP и app; отдельно прошёл полный `go test ./...`. Frontend type-check, lint, lint:css и сборка в `/tmp` прошли; синтаксис Python, `gofmt`, diff-проверки, ручное ревью по Go/frontend-стандартам и независимое ревью затронутого flow выполнены. API/protobuf, миграции и зависимости не менялись. Работающие окружения и Cudy не изменялись; существующие пользовательские изменения бинарника и `prod` сохранены.
|
||||
- Следующий шаг: ручная аппаратная приёмка этапа 8 по `tests/venue/README.md` — прежние ссылки, билеты/QR, игровой flow на Android/iPhone с отключёнными WAN и мобильными данными. После подтверждения — этап 9, пакет результатов. Обновление реальной площадки, камера/реальные телефоны, нагрузка на 100 клиентов, покрытие Wi-Fi, ZIP на 1 ГиБ и возврат результатов этим прогоном не проверялись. До реализации возврата использовать только тестовые игры. Общая задача остаётся `IN_PROGRESS` и не переносится в архив.
|
||||
- Приёмка этапа 8: пользователь подтвердил «Этап 8 принят» при согласовании следующего этапа. Отдельный повтор аппаратной проверки агентом не выполнялся.
|
||||
- Результат этапа 9: добавлен серверный сервис подготовки и проверки ZIP результатов версии 1, без новых API/UI, protobuf, миграций и зависимостей. Read-only PostgreSQL-снимок `REPEATABLE READ` связывает завершённую импортированную venue-игру с ID/SHA-256 исходного пакета. Сохраняются итоговые команды/участие, исходные и новые билеты, явные удаления, пароли QR/хеши билетов, порядок и время ходов/MCP, материалы и их выдача, ответы и nullable баллы. Исходные ссылки пользователей восстанавливаются из реестра; локальные исполнители вынесены в отдельное пространство. Материалы ссылаются на исходный ZIP, без локальных адресов RustFS. Изменённый игровой сценарий/формат и неизвестные материалы отклоняются. Повторная подготовка создаёт новый снимок с актуальными оценками и не меняет игру; кодек проверяет состав, версию, размеры/CRC/SHA-256, лимиты JSON/ZIP, уникальность и связи. Контракт описан в backend `docs/GAME_PACKAGE.md`.
|
||||
- Проверки этапа 9: полный `go test ./...`, unit-тесты кодека/снимка и `python3 tests/venue/flow.py --results` прошли. Изолированный PostgreSQL/RustFS-стенд проверил обе игры, связь с исходным ZIP, новые локальные записи, прогресс/выдачу/ответы/баллы, свежий снимок после оценивания, конкурентное оценивание, сохранность предыдущего пакета, повтор после перезапуска и отсутствие изменений исходных игр. Unit-тесты проверяют удаления, пустые наборы, повторные/неизвестные ходы/MCP, исходных и локальных исполнителей, повреждённые ZIP и связи, лимиты, отмену и ошибки записи. `gofmt`, Python-синтаксис, diff-проверки, ручное ревью по Go-стандарту и независимое read-only ревью выполнены. Frontend-код не менялся; его штатная сборка в `/tmp` прошла в составе стенда. Тестовые пакеты/контейнеры удалены; работающие окружения и Cudy не менялись. Commit/push и развёртывание не выполнялись.
|
||||
- Следующий шаг: этап 10 — безопасное применение результатов онлайн с проверкой исходного экспорта, конфликтами, соответствиями локальных ID и идемпотентностью возврата. API/UI передачи результатов остаются следующим этапам. ZIP на 1 ГиБ, нагрузка на 100 клиентов и покрытие Wi-Fi этим прогоном не проверялись. До завершения возврата использовать только тестовые игры. Общая задача остаётся `IN_PROGRESS` и не переносится в архив.
|
||||
|
||||
### ED-024 — Общая дизайн-система фронтенда
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ python3 tests/venue/dns.py --server 192.168.10.155
|
||||
```sh
|
||||
python3 tests/venue/flow.py
|
||||
python3 tests/venue/flow.py --browser
|
||||
python3 tests/venue/flow.py --results
|
||||
```
|
||||
|
||||
`flow.py` собирает актуальный Go/Vue-код во временном каталоге, поднимает
|
||||
@@ -62,6 +63,14 @@ python3 tests/venue/flow.py --browser
|
||||
backend, имитируя смену DNS; API-ответы не подменяются. Тестовые ссылки, ZIP,
|
||||
профиль Firefox и сборки остаются во временном каталоге до конца прогона.
|
||||
|
||||
`--results` дополнительно проверяет этап 9: серверный сервис собирает ZIP
|
||||
результатов обеих завершённых игр из реальной тестовой БД. Проверяются привязка
|
||||
к исходному ZIP, прогресс/материалы/баллы, новый снимок после оценивания,
|
||||
конкурентное оценивание, сохранность предыдущего пакета, повтор после
|
||||
перезапуска и отсутствие изменений исходных игр. Локальные ссылки RustFS
|
||||
в пакеты не попадают. ZIP находятся только во временном каталоге с правами
|
||||
0600; новые API/UI для возврата и применение онлайн не выполняются.
|
||||
|
||||
### Ручная контрольная точка этапа 8
|
||||
|
||||
Автоматический прогон не заменяет проверку телефонов и камеры. После отдельного
|
||||
|
||||
@@ -19,6 +19,7 @@ import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import uuid
|
||||
import zipfile
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -371,6 +372,7 @@ def exercise(stand, data):
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--results', action='store_true', help='Also verify stage 9 result packages before/after restart')
|
||||
parser.add_argument('--browser', action='store_true', help='Also run Firefox against the real APIs (Firefox required)')
|
||||
args = parser.parse_args()
|
||||
with tempfile.TemporaryDirectory(prefix='ed029-flow-') as directory:
|
||||
@@ -389,6 +391,30 @@ def main():
|
||||
assert imported['gameID'] == data['game'] and not imported['repeated']
|
||||
for data in fixtures:
|
||||
exercise(stand, data)
|
||||
if args.results:
|
||||
source_before = [stand.apis['prod'].game(data['game']) for data in fixtures]
|
||||
venue_before = [stand.apis['venue'].game(data['game']) for data in fixtures]
|
||||
for attempt in range(2):
|
||||
if attempt:
|
||||
stand.restart()
|
||||
for data in fixtures:
|
||||
with zipfile.ZipFile(stand.directory / f'game-{data["game"]}.zip') as archive:
|
||||
manifest = json.loads(archive.read('manifest.json'))
|
||||
result = run('env', f'TEST_DATABASE_URL=host={pg_address} user=test password=test-only dbname=test sslmode=disable',
|
||||
f'ED029_RESULTS_GAME_ID={data["game"]}', f'ED029_RESULTS_PACKAGE_ID={manifest["package_id"]}',
|
||||
f'ED029_RESULTS_DIR={stand.directory}', 'go', 'test', './internal/services/game_results_package_service',
|
||||
'-run', 'TestVenueFlowResults', '-count=1', '-v', cwd=ROOT / 'evening_detective_server')
|
||||
assert '--- PASS: TestVenueFlowResults' in result
|
||||
with zipfile.ZipFile(stand.directory / f'results-{data["game"]}.zip') as archive:
|
||||
result_manifest = json.loads(archive.read('manifest.json'))
|
||||
payload = archive.read('results.json')
|
||||
assert set(archive.namelist()) == {'manifest.json', 'results.json'}
|
||||
assert result_manifest['source_sha256'] == hashlib.sha256(data['package']).hexdigest()
|
||||
assert result_manifest['sha256'] == hashlib.sha256(payload).hexdigest()
|
||||
assert b'game-import-' not in payload
|
||||
assert [stand.apis['prod'].game(data['game']) for data in fixtures] == source_before
|
||||
assert [stand.apis['venue'].game(data['game']) for data in fixtures] == venue_before
|
||||
print('PASS: stage 9 real result packages, fresh grading, concurrent snapshots, restart and unchanged games', flush=True)
|
||||
finally:
|
||||
stand.close()
|
||||
if args.browser:
|
||||
|
||||
Reference in New Issue
Block a user