7.8 KiB
Восстановление операций и проверка mock-сценариев
Дополнительные эксплуатационные инструкции: Windows kiosk setup, матрица доступа к настройкам и длительный soak-тест.
Назначение
Документ фиксирует безопасное поведение Exchanger при сбое приложения или потере связи до появления спецификации реального контроллера. Описанные правила не предполагают существование wire-команд, идентификаторов транзакций или запроса статуса, которых ещё нет в документации.
Локальный идентификатор операции
Каждая ветка оплаты получает уникальный OperationId. Это локальный диагностический идентификатор приложения, а не идентификатор банковской транзакции и не будущий идентификатор команды контроллера. После получения протокола эти значения можно будет связать отдельным полем, но смешивать их нельзя.
Журнал операций
Жизненный цикл записывается построчно в JSONL-файл:
user://logs/operations.jsonl
Запись содержит время UTC, тип события Started/Finished, локальный OperationId, способ оплаты, сумму, расчётное число жетонов, итог и типизированный код ошибки. В журнал запрещено записывать номер карты, данные магнитной полосы/чипа, платёжные токены, сырые ответы оборудования и персональные данные.
Повреждённая строка не мешает чтению остальных записей. При достижении текущим файлом настроенного лимита он переносится в operations-<timestamp>.jsonl. Архивы exchanger, operations и settings-audit автоматически очищаются по сроку хранения и общему объёму; активные файлы при очистке защищены.
Правила восстановления после перезапуска
При старте приложение ищет Started без соответствующего Finished и закрывает такую запись итогом UnknownAfterRestart. Автоматическая повторная оплата или выдача не запускается.
Для операции, прерванной в CardTerminalWait:
- Не считать платёж успешным или отклонённым без подтверждения терминала/контроллера.
- Не запускать новую оплату автоматически.
- Вернуть пользовательский интерфейс на домашний экран.
- Оператор сверяет время и
OperationIdс журналом терминала и будущими журналами контроллера.
Для операции, прерванной в Dispensing:
- Считать результат выдачи неизвестным.
- Не повторять команду выдачи и не предлагать пользователю повторную оплату.
- Оператор проверяет физически выданные жетоны, остаток и журнал контроллера.
- Автоматическое восстановление допускается только после документированного запроса статуса либо подтверждённой идемпотентности команды.
До получения протокола приложение не может автоматически различить «команда не дошла» и «команда выполнена, но подтверждение потеряно». Это намеренное безопасное ограничение.
Ручной mock-чек-лист
Панели эмуляции отображаются только в debug-сборке. Перед проверкой выполнить dotnet test Exchanger.sln, затем запустить главную сцену.
| Сценарий | Действия | Ожидаемый результат |
|---|---|---|
| Наличные: успех | Открыть оплату наличными, добавить тестовые купюры, нажать выдачу | Сначала «Идёт выдача», затем подтверждённый успех; бинарный уровень запаса не изменяется без отдельного сигнала датчика |
| Карта: успех | Выбрать сумму, нажать УСПЕХ (DEBUG) |
Выдача запускается один раз, успех показывается только после mock-подтверждения |
| Карта: отказ | Нажать ОТКАЗ (DEBUG) |
Без выдачи показывается безопасная ошибка оплаты |
| Ошибка выдачи | Нажать ОШИБКА ВЫДАЧИ |
Показывается типизированная ошибка; сырая строка mock-контроллера не выводится |
| Частичная выдача | Выбрать сумму более одного жетона, нажать ЧАСТИЧНАЯ ВЫДАЧА |
Показывается обращение в поддержку; повтор выдачи недоступен |
| Потеря связи до выдачи | Нажать ПОТЕРЯ СВЯЗИ на экране ожидания карты |
Операция завершается ConnectionLost, пользователь направляется в поддержку |
| Потеря связи во время выдачи | Задать увеличенную DispenseDelaySeconds, подтвердить карту и вызвать SimulateConnectionLoss() через разрешённый godot-ai |
Итог DispenseStatusUnknown; интерфейс предупреждает не повторять оплату |
| Таймаут | Не подтверждать оплату до истечения настроенного времени | Выдача не запускается, итог PaymentTimeout |
| Повторный ввод | Быстро нажать действие несколько раз | Создаётся одна операция и не более одной выдачи |
| Перезапуск | Завершить приложение с активной операцией, затем запустить снова | Незакрытая запись получает UnknownAfterRestart; автоматического retry нет |
После каждого сценария проверить user://logs/exchanger.log, user://logs/operations.jsonl, а также game/editor logs. В них не должно быть платёжных секретов, необработанных исключений и дублированных итогов одной операции.