randomayzer/agents/antigravity/done/TASK-2026-08-21-04-snapshot-unlock.md

56 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 визарда.
Ключевые свойства реализации:
1. **Атомарность и защита от Seed Grinding:** При вызове разблокировки поле `giveaway.seed` и `seedCommitment` принудительно сбрасываются в `null` в рамках единой транзакции с условным переходом статуса (`updateMany where status = 'SNAPSHOT_LOCKED'`).
2. **Новый CSPRNG seed при повторной фиксации:** При повторном вызове `/api/giveaways/[id]/snapshot` генерируется новый криптостойкий seed и новый commitment SHA-256.
3. **Обоснование стратегии версионирования снапшотов:** Предыдущие записи `ParticipantSnapshot` сохраняются в базе данных, а поле `version` инкрементируется (`version = max(version) + 1`). Это оживляет версионирование и сохраняет полную историю условий розыгрыша, в то время как `drawResult` и `auditRecord` связываются исключительно с финальным `snapshotId`.
4. **Безопасность и авторизация:** Новый эндпоинт `POST /api/giveaways/[id]/unlock` защищен CSRF-guard (`validateCsrfOrigin`), проверкой владения организатором (`requireGiveawayOwner`), лимитером частоты (`expensiveApiRateLimiter`) и поддержкой `Idempotency-Key`.
5. **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
```text
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)
```