5.1 KiB
5.1 KiB
Task 04: Разблокировка SNAPSHOT_LOCKED → READY Report
Date: 2026-08-21
Base Commit SHA: fb6ae616285aebe4ef6ac1436cd0861664f3ba0d
Status: COMPLETED / PASS
Assigned Agent: Antigravity (Implementation Orchestrator)
1. Executive Summary
Реализован механизм безопасной и атомарной разблокировки розыгрыша (SNAPSHOT_LOCKED → READY), устраняющий проблему невозвратной блокировки на шаге 4 визарда.
Ключевые свойства реализации:
- Атомарность и защита от Seed Grinding: При вызове разблокировки поле
giveaway.seedиseedCommitmentпринудительно сбрасываются вnullв рамках единой транзакции с условным переходом статуса (updateMany where status = 'SNAPSHOT_LOCKED'). - Новый CSPRNG seed при повторной фиксации: При повторном вызове
/api/giveaways/[id]/snapshotгенерируется новый криптостойкий seed и новый commitment SHA-256. - Обоснование стратегии версионирования снапшотов: Предыдущие записи
ParticipantSnapshotсохраняются в базе данных, а полеversionинкрементируется (version = max(version) + 1). Это оживляет версионирование и сохраняет полную историю условий розыгрыша, в то время какdrawResultиauditRecordсвязываются исключительно с финальнымsnapshotId. - Безопасность и авторизация: Новый эндпоинт
POST /api/giveaways/[id]/unlockзащищен CSRF-guard (validateCsrfOrigin), проверкой владения организатором (requireGiveawayOwner), лимитером частоты (expensiveApiRateLimiter) и поддержкойIdempotency-Key. - UI Integration: На шаге 4 визарда добавлена кнопка возврата к шагу 3 с вызовом
/api/giveaways/[id]/unlockи сбросом клиентского состояния commitment.
2. Modified Files
| File | Type | Description |
|---|---|---|
src/lib/repository/giveaway-repository.ts |
Interface | Добавлен метод unlockSnapshot(id: string): Promise<GiveawayWithRelations>. |
src/lib/repository/memory-repository.ts |
Repository | Реализован unlockSnapshot с атомарным сбросом seed, seedCommitment, latestSnapshot и переходом в READY. |
src/lib/repository/prisma-repository.ts |
Repository | Реализован unlockSnapshot через транзакционный условный updateMany (SNAPSHOT_LOCKED → READY, seed: null). |
src/lib/giveaway-store.ts |
Store | Добавлен фасад GiveawayStore.unlockSnapshot(id). |
src/app/api/giveaways/[id]/unlock/route.ts |
API Route (NEW) | Защищенный HTTP-эндпоинт разблокировки с CSRF, auth, rate limit и идемпотентностью. |
src/app/giveaways/new/page.tsx |
UI | Обработчик handleUnlockAndReturnToStep3 и кнопка возврата с шага 4 к шагу 3. |
tests/snapshot-unlock.test.ts |
Tests (NEW) | 6 тестов на полный жизненный цикл, IDOR, терминальные состояния, конкурентность и идемпотентность. |
tests/storage-driver.test.ts |
Tests | Обновлен mock-объект failingDbRepo интерфейса IGiveawayRepository. |
3. Architecture & Security Invariants
- Terminal State Protection: Из статусов
DRAWNиPUBLISHEDразблокировка строго запрещена — возвращается409 Conflict. - Ownership (IDOR): Запросы разблокировки чужого розыгрыша возвращают
403 Forbidden. - Single Flight / Concurrency: Конкурентные запросы разблокировки гарантируют ровно один переход
200 OK, все остальные получают409 Conflict. - Core Randomizer Invariant: Криптографический алгоритм
HMAC_SHA256_FY_V1и формат proof сохранены в строгом соответствии сAGENTS.md.
4. Verification Evidence & Test Gate
npx prisma generate -> EXIT 0 (Prisma Client v5.22.0)
npx tsc --noEmit -> EXIT 0 (Clean TypeScript check, 0 errors)
npm test -> EXIT 0 (52 test files, 306 tests passed, 0 failed)
npm run lint -> EXIT 0 (0 errors, 6 warnings on no-img-element)
npm run build -> EXIT 0 (All 16 routes compiled and static pages generated)
npm audit --omit=dev -> EXIT 0 (0 vulnerabilities)