This commit is contained in:
2026-10-11 13:09:34 +07:00
parent 26c65062e9
commit 74a45da402
18 changed files with 2294 additions and 82 deletions
+89 -7
View File
@@ -350,7 +350,7 @@ RustFS и создать `<marker>.continue` в течение минуты. Buc
## Пакет результатов, версия 1 (ED-029, этап 9)
Это отдельный контракт: ZIP содержит ровно `manifest.json` и `results.json`,
без сценария и файлов. Он ещё не применяется онлайн и не доступен через API/UI.
без сценария и файлов. Серверное применение реализовано этапом 10; API/UI остаются этапом 11.
`game_results_package_service.NewGameResultsPackageService(repo, mode).Prepare(ctx,
gameID, output)` доступен только в `venue`, для существующей игры из готового
@@ -396,9 +396,10 @@ SHA-256 защищает от повреждения; это не подпись
| `answers` | `team_id`, `question_code`, `answer`, nullable `score`, `created_at`, `updated_at` |
`source_id` совпадает с `id` для исходной записи и равен `0` для новой локальной.
Все ссылки прогресса используют итоговый локальный `team_id`. Локальные ID новых
команд, билетов, ходов и улик не являются свободными ID онлайн-базы: следующий
этап должен выделять ID и сохранять соответствия.
Все ссылки прогресса используют итоговый локальный `team_id`. Этап 10 уточняет
выделение ID: новые команды сохраняют venue-ID и пароль для напечатанных QR;
занятый чужой командой ID отклоняет весь возврат. Новым билетам, ходам и уликам
выделяются онлайн-ID, соответствия сохраняются в закрытом реестре.
`creator_id` и `registration_delivery_id` итоговой команды берутся только из
исходного снимка; для новой команды они отсутствуют. Текущий локальный создатель
@@ -433,6 +434,87 @@ workspace. Стенд проверяет оба формата, свежую о
сохранность прежнего ZIP, повторную подготовку после перезапуска и отсутствие
изменений исходных игр. Пакеты и тестовые ресурсы удаляются после прогона.
Применение онлайн, разрешение конфликтов и идемпотентность возврата — этап 10;
подготовка/скачивание и импорт через интерфейс — этап 11. До завершения возврата
используются только тестовые игры.
Подготовка/скачивание и возврат через интерфейс — этап 11. До завершения
интерфейса и контрольного прогона используются только тестовые игры.
## Применение результатов онлайн (ED-029, этап 10)
`game_results_package_service.NewGameResultsApplyService(repo, privateStorage,
mode, sourceServer).Apply(ctx, input io.ReaderAt, size, initiatorID)` доступен
только при `mode="prod"`. Это серверный сервис без нового API/UI и развёртывания.
Возвращается закрытая запись реестра; будущий transport должен выдавать только
публичный результат операции.
Входные байты один раз сохраняются в приватный временный файл и проверяются
кодеком этапа 9; SHA-256 всего ZIP связывает повтор с этими же байтами.
Сервис проверяет сервер-источник, игру, ID и SHA-256 исходного экспорта, читает
его ZIP из закрытого хранилища, проверяет размер, контрольную сумму и контракт.
`source` результатов сверяется с данными этого ZIP: команды, билеты, сценарий,
материалы и вопросы нельзя подменить в загруженном пакете. Формат неизменяем.
Все временные файлы/каталоги имеют права 0600/0700 и удаляются при успешном
применении, отказе и отмене.
Пакет результатов считается источником истины для возвращаемых данных:
онлайн-правки состава команд/билетов, прогресса, ответов, баллов, формата,
статуса и фактических дат перезаписываются без сравнения с прежним состоянием.
Игровой сценарий восстанавливается из сохранённого экспортного снимка с
исходными онлайн-ссылками материалов. Если текущий сценарий отличается,
создаётся отдельная копия для возвращаемой игры; сценарии других игр не меняются.
Копия сохраняет оформление и playlist текущего сценария; неизменённый сценарий
повторно не копируется. Название, описание, место, плановая дата, музыка, карта
и экраны игры сохраняются. Отсутствующая игра не восстанавливается.
Полные итоговые снимки заменяют команды/участие и билеты с учётом удалений,
ходы/MCP, материалы и выдачу, ответы и nullable баллы, статус `finish` и
фактические даты. Баллы не пересчитываются. Удаление команды очищает её связи
карты. Исходные команды/билеты сохраняют ID; новые команды сохраняют также
venue-пароль. Новые билеты получают онлайн-ID и прежний хеш токена. Коллизия
ID или хеша с чужой записью отклоняет весь пакет. Ходы и улики получают свежие
онлайн-ID в порядке пакета, включая исправленный снимок. Реестр хранит
соответствия каждого возврата; соответствия новых билетов сохраняются между
исправлениями. Последовательности продвигаются монотонным `nextval`, без `setval`
и глобальных блокировок обычных операций.
Материалы переводятся из portable-троек исходного ZIP в исходные онлайн-ссылки
через сохранённый экспортный снимок. Новых загрузок файлов и скачивания внешних
URL нет. Существующие онлайн-ссылки создателей и исполнителей и технические
почтовые поля сохраняются; их изменение не считается изменением возвращаемых
данных. У новых команд `creator_id=NULL`, у новых локально использованных
билетов `used_by=NULL`; отсутствующий онлайн-аккаунт не восстанавливается.
Локальные исполнители остаются только в закрытом реестре. Новые почтовые
задания и токены не создаются; возвращённые игры исключены из очереди новых
напоминаний об отзыве.
`game_returns` хранит ID/SHA-256 ZIP результатов, исходный экспорт, исходный ID
игры, время подготовки/применения, соответствия ID (включая сценарий),
локальных исполнителей и инициатора. Записи остаются после
удаления игры. Тот же ID с теми же байтами возвращает первоначальную запись без
изменений, даже после более нового пакета или удаления игры. Тот же ID с другими
байтами — конфликт. Новый пакет требует `created_at` строго позже последнего
применённого; время используется без поправок часов. UTC-время подготовки
сохраняется строкой с фиксированными девятью знаками долей секунды, чтобы
PostgreSQL не терял наносекунды при сравнении и повторе.
Чтение текущих ссылок пользователей, запись данных и реестра выполняются
одной `SERIALIZABLE`
транзакцией с блокировками игры, экспорта, сценария и затронутых записей.
Serialization failure/deadlock повторяются не более трёх раз, затем возвращается
безопасная повторяемая ошибка. Проверки ZIP/хранилища проходят до транзакции.
`game.export` исчезает в чтении игры и списка после успешного возврата именно
соответствующего экспорта; исходный экспорт остаётся `ready`, ZIP сохраняется
для следующих проверок. Более старый экспорт вместо скрытого не показывается.
Проверки: `go test ./...` с отдельным `TEST_DATABASE_URL`, релевантный `go test
-race`, `python3 tests/venue/flow.py --apply`. Стенд проверяет оба формата,
настоящие venue-ZIP, prod PostgreSQL/private RustFS, перезапись онлайн-редактирования,
исправление баллов, старый повтор после нового и перезапуска prod, прежние и
новые ссылки, сохранение оформления и исчезновение офлайн-отметки.
Ограничения: ZIP не имеет криптографического подтверждения подлинности.
Sequence-значения PostgreSQL не откатываются: большие разрывы venue-ID расходуют
их даже при отказе. Продвижение ограничено пятью секундами, затем данные
откатываются и возвращается повторяемая ошибка; INT32-потолок отклоняется.
Перед поддержкой площадок с такими разрывами требуется расширить типы ID.
ZIP на 1 ГиБ, 100 клиентов и Wi-Fi этим этапом не проверяются. До завершения
этапа 11 и контрольного прогона используются только тестовые игры.