0006 — История файлов и восстановление
Статус: accepted. Дата: 2026-09-22.
Контекст
Текущий asset — изменяемый указатель на неизменяемый artifact. Ревизии уже сохранялись, но публичного чтения и восстановления истории не было. Для безопасного восстановления нужны защита от параллельной записи, исходные байты и объяснимая история действий.
Решение
Native API возвращает историю в убывающем порядке по revision, страницами до 50. Курсор before исключает указанную ревизию. Запрос читает 51 строку, поэтому next появляется только при наличии следующей страницы. Новые записи не сдвигают уже начатый обход; их видно после обновления первой страницы. История пока не удаляется.
Отдельный lookup возвращает точную ревизию и artifactId. Содержимое скачивается существующим API артефактов с теми же ACL, Range и ETag. Исторические метаданные артефакта не создаются: annotations остаются текущими, независимо от даты asset-ревизии.
Restore требует sourceRevision и expectedRevision. Он создаёт ревизию expectedRevision + 1, указывающую на прежний artifact, а не изменяет старую запись или копирует файл. CAS указателя, добавление истории и asset.restore в аудит составляют одну транзакцию PostgreSQL. Конкурирующая запись или повтор уже выполненного запроса получает 409. SDK не повторяет restore автоматически: после потери ответа клиент перечитывает указатель/историю, затем принимает решение о новом действии.
Миграция 4 добавляет actor, created_at и source_revision. Существующие записи сохраняют null для неизвестных автора и даты: в старом аудите нет пути, поэтому однозначно восстановить их нельзя. Новые записи содержат principal и время транзакции БД. Внешний ключ источника ограничен тем же repository/path; источник должен быть старше созданной ревизии. Путь чувствителен к регистру, как существующий native asset API.
Чтение истории требует read scope репозитория. Восстановление требует read + write и доступный опубликованный artifact того же репозитория. Это не обход ACL через старую версию.
Последствия и границы
Перед обновлением остановить API/worker, сделать согласованный backup, выполнить npm run migrate, запустить новый код. Readiness требует миграцию 4. Возврат на старый код без отдельного плана не поддерживается: старый writer не записывает новые поля истории.
Страницы ограничены, история в БД растёт с количеством изменений. Retention, удаление опубликованного содержимого, history endpoints внешнего протокола и HA не входят в это решение. В будущем очистка обязана учитывать живые указатели, историю, source_revision и внешние ссылки; текущий GC сохраняет все published blobs.
Проверка
PostgreSQL/HTTP тесты проверяют конкурирующие restore, rollback при отказе записи истории/аудита, повтор после потери ответа, перезапуск, ACL, миграцию с существующими записями, пагинацию при параллельном append, SDK и чтение исходных байтов. Внешние клиенты и два сервера проверяются отдельно.