## 1. Ревью дизайна (чекпоинт ДО кода) - [x] 1.1 Согласовать design.md: стратегию миграции/backfill, снятие `DEFAULT`, секцию `[general]` с `timezone`. Правки внести до реализации. ## 2. Хелперы формата времени (store) - [x] 2.1 `internal/store/download.go`: убрать `sqliteTimeLayout`; `FormatTime` → `t.UTC().Format(time.RFC3339)`, `ParseTime` → `time.Parse(time.RFC3339, s)`. Обновить комментарии (формат `...Z`, не `datetime('now')`), включая коммент про `SourceAddedAt` на `download.go:118` (ссылается на `sqliteTimeLayout`). - [x] 2.2 Добавить `store.Now()` (обёртка над `time.Now().UTC()`) как единую точку получения «сейчас» в слое store. - [x] 2.3 Проверить `migrations/0006_ulid_identity.go:204` (`parseCreatedAt` парсит старый формат) — это историческая миграция, формат менять не нужно; убедиться, что она не ломается и не пересекается с новой. ## 3. Убрать DEFAULT и писать время из Go - [x] 3.1 Каждый INSERT-**сайт** со временем явно передаёт `created_at`/`updated_at` (= `FormatTime(store.Now())`). Полный список сайтов (не методов!): `download.go` — `CreateDownloadIfNoActive` (вставка download :219 + вставка хешей :228), dedup top-up хешей :203 (`INSERT OR IGNORE`), `AddInfohashes` :306 (`INSERT OR IGNORE`); `recognition.go` — `CreateRecognition` :72, `AddHint` :108, `SetOverride` :130, `AddFileLink` :185, `AddMetadataCandidate` :332. Для `INSERT OR IGNORE` в `download_infohash` `created_at` обязателен на новой строке (после снятия `DEFAULT` + `NOT NULL`); при конфликте PK строка не вставляется — ок. - [x] 3.2 `download.go:510`: инлайн `updated_at = datetime('now')` → параметр `FormatTime(store.Now())` (sortability-ловушка — проверить, что не осталось сырых `datetime('now')` в SQL: `grep -rn "datetime('now')" internal`). ## 4. Миграция БД (goose) - [x] 4.1 Новая миграция (стиль `0006`): backfill каждой timestamp-колонки `strftime('%Y-%m-%dT%H:%M:%SZ', col)` (7 таблиц: download.created_at/updated_at/ source_added_at, download_infohash/recognition/hint/override/metadata_candidate/ file_link .created_at) + rebuild таблиц без `DEFAULT` на timestamp-колонках. - [x] 4.2 Down-миграция: обратный backfill `strftime('%Y-%m-%d %H:%M:%S', col)` + восстановление `DEFAULT`. - [x] 4.3 Тест миграции: строки со старым форматом → новый формат; хронология сортировки сохранена; `source_added_at IS NULL` не затронут; обратный backfill (down) на строке с `T`/`Z` даёт старый формат. - [x] 4.4 Обновить существующий `TestUlidMigration` (`migration_test.go:100-101`): ассерт ждёт старый формат `2026-01-01 10:00:00`, после новой миграции метки станут `...T...Z` — поправить ожидаемое (заодно канарейка, что backfill отработал). ## 5. Настройка таймзоны отображения - [x] 5.1 `internal/config`: секция `[general]` (`General struct`) с полем `Timezone string` (дефолт `UTC`) + распарсенный `*time.Location`; жёсткая валидация на старте через `time.LoadLocation`, невалидная зона → ошибка конфигурации (без рантайм-фолбэка). - [x] 5.1a Встроить zoneinfo: `import _ "time/tzdata"` в точке входа (`cmd/jellybit`). Тогда `LoadLocation` падает только на битом имени. - [x] 5.2 `internal/httpapi`: пробросить `*time.Location` до `fmtDate`/`humanizeAge` (свободные функции — тянуть параметром через `toView`, вызовы `httpapi.go:288,545`, либо сделать методами `server`); заменить зашитый `mskLoc` (`live.go:189-201`) целиком (после встраивания tzdata рантайм-фолбэк на UTC внутри `mskLoc` не нужен). Guard: `t.In(nil)` паникует — при незаданной зоне подставлять `time.UTC`; тест-хелпер `testRouterLive` тоже задаёт loc. - [x] 5.3 Обновить пример конфига (`config.example.toml`/деплой): секция `[general]` первой, `timezone`; для этого сервера — `Europe/Moscow`. ## 6. Документация - [x] 6.1 `docs/conventions/database.md:42-43`: формат меток → RFC 3339 (UTC, `Z`), генерация только из Go (`store.Now`/`FormatTime`), без `DEFAULT`. - [x] 6.2 `docs/conventions/config.md`: зона отображения конфигурируема (дефолт UTC); хранение и логи — UTC. - [x] 6.3 `docs/specs/database.md`: комментарий формата timestamp-колонок в ER. ## 7. Проверка и ревью кода (чекпоинт до archive) - [x] 7.1 `task test` / `task lint` зелёные; тест сортировки списка на смешанных и однотипных метках. - [x] 7.2 Ревью кода реализации; затем `openspec validate --strict` и archive.