Skip to content

Удаление артефактов и retention ​

Реализован законченный сценарий логического удаления неиспользуемого опубликованного артефакта: API, SDK, предварительный просмотр очистки, выборочная обработка и действие в карточке консоли. OpenAPI 0.11.0, 127 HTTP-операции; миграции 13 и 14. Этот документ описывает ручной сценарий. Автоматизация описана в STORAGE_POLICIES; фоновая физическая очистка — в ONLINE_CLEANUP.

Права и API ​

Новый managed permission artifact.delete выдаётся на точный репозиторий. Старые read/write, пользовательская группа, administrator и bootstrap-флаг не дают удаления автоматически; парольные сессии, персональные токены, файловые ключи и ключ восстановления это право не получают вовсе — только управляемый сервисный ключ. Bootstrap-оператор может выдать отдельный managed credential через уже существующий service API. Для полноценного просмотра карточки консоли ключу дополнительно нужны обычные права каталога и аннотаций. Право удаления само по себе разрешает только инспекцию жизненного цикла, без bytes, публикации и управления ключами.

МетодURL относительно /api/v1/repositories/{repository}Назначение
GET/HEAD/artifacts/{id}/deletionПроверить опубликованный артефакт и блокирующие зависимости
DELETE/artifacts/{id}Удалить конкретный артефакт; JSON body {expectedAnnotationRevision} обязателен
POST/retention/previewПрочитать ограниченную страницу кандидатов, без изменений
POST/retention/applyПовторно проверить и обработать только перечисленные IDs

GET /operations показывает новые действия только ключу с соответствующим правом. Reader разрешает GET/HEAD инспекции, остальные методы возвращают 405. Поэтому preview с JSON-фильтрами выполняется на writer, несмотря на отсутствие изменений. CORS уже поддерживает DELETE с JSON и preflight. В capabilities: artifactDeletion=true, retentionPreview=true, onlineGarbageCollection=true.

Все параметры проходят runtime validation. Неизвестные поля и query-параметры отклоняются. Размеры — десятичные строки. Для несуществующей инспекции — 404. DELETE/apply возвращают явно различимые результаты, поэтому HTTP 200 не означает, что каждый файл удалён.

Предварительный просмотр ​

json
{
  "criteria": {
    "publishedBefore": "2026-09-01T00:00:00.000Z",
    "protectedLabels": ["bse", "release"]
  },
  "limit": 50
}

Дата обязательна: канонический UTC с миллисекундами, не в будущем. Сравнение строгое publishedAt < publishedBefore; это время публикации, а не начала загрузки. Список защищённых меток обязателен (до 32, точное сравнение регистра). Пустой список явно отключает защиту меток для этой команды. Метки test, staging, release не имеют скрытых системных прав.

Ответ: {items, next}; элемент содержит id, name, size, publishedAt, annotationRevision, blockers. До 100 элементов, по умолчанию 50. next — exclusive UUID cursor; следующая страница должна сохранять те же критерии. Заблокированные кандидаты тоже видны, чтобы оператор понимал причину. Пагинация не создаёт снимок и не резервирует право удаления.

Блокирующие причины:

  • reference: артефакт закреплён внешним сервисом; снять свою ссылку может её владелец через reference API.
  • asset_history: артефакт используется текущей либо любой исторической ревизией файла.
  • attachment_history: артефакт используется любой сохранённой ревизией вложений.
  • protected_label: текущие аннотации, либо исходные метки при отсутствии аннотаций, совпадают с protectedLabels.

Удаление ссылки из текущего набора вложений не уничтожает её историю и не освобождает исторический target. Удаление основного build также сохраняет его snapshots и пины. Опубликованный UPack binding сам по себе не блокирует удаление; его immutable identity сохраняется как tombstone.

Применение и повтор запроса ​

ts
const repository = client.inRepository('releases');
const criteria = {
  publishedBefore: '2026-09-01T00:00:00.000Z',
  protectedLabels: ['bse', 'release'],
};
const page = await repository.retention.preview({ criteria, limit: 50 }, signal);
// Интерфейс показывает кандидатов; оператор выбирает конкретные строки.
const selectedIds = new Set(reviewedIds);
const items = page.items
  .filter((entry) => selectedIds.has(entry.id) && entry.blockers.length === 0)
  .map((entry) => ({ id: entry.id, expectedAnnotationRevision: entry.annotationRevision }));
if (items.length) {
  const result = await repository.retention.apply({ criteria, items }, signal);
  // Проверить outcome каждой строки, а не только HTTP status.
}

Apply принимает 1–100 уникальных IDs с ожидаемой ревизией аннотаций, в одной короткой транзакции. Критерии, ссылки и актуальность ключа проверяются заново. Состав выборки не расширяется сервером. Результаты:

outcomeЗначение
deletedМетаданные удаления и аудит зафиксированы
already_deletedЭтот immutable ID уже удалён; повтор не добавляет аудит
protectedПоявилась/сохранилась блокирующая зависимость или метка; причины в blockers
changedИзменилась ревизия аннотаций; нужен повторный просмотр
not_eligibleВремя публикации не соответствует критерию
not_foundВ данном репозитории нет такого опубликованного артефакта

Непредвиденная SQL/аудит-ошибка откатывает весь batch, включая receipts; защищённые строки являются обычным результатом и не отменяют удаление остальных выбранных строк. Утрата HTTP-ответа допускает повтор той же команды: immutable ID и receipt предотвращают повторное действие. SDK не повторяет mutation скрыто. Истечение/отзыв ключа закрывает новую команду и проверяется повторно внутри транзакции.

Прямое repository.artifacts.delete(id, expectedAnnotationRevision, signal) не применяет retention-фильтр меток: оператор явно выбрал конкретный объект. Зависимости и CAS обязательны всегда, обхода защиты истории нет. Консоль объясняет это, показывает blockers и требует вставить ID. Обновление темы/языка сохраняет подтверждение, переключение объекта/credential сбрасывает его. Устаревшая ревизия требует новой проверки.

Байты, скачивания и восстановление ​

После commit артефакт исчезает из artifact/search/package listings. Exact download получает 404, latest выбирает оставшуюся опубликованную версию. Уже открытый поток продолжает читать сохранённые байты. Upload status старого ID становится cancelled; complete не может воскресить его. Package group/name/version остаются занятыми: повторная публикация той же версии с другими байтами запрещена.

HTTP удаление не освобождает резерв ёмкости. Online cleanup после настраиваемого grace period (по умолчанию 24 часа) удаляет байты и помечает резерв reclaimed без остановки сервисов. Активные читатели и writers откладывают конкретный объект. Политика включается отдельно; UI не обещает немедленного уменьшения занятого диска. Прежний npm run gc сохранён как offline repair tool.

Восстановление удалённого артефакта в API не реализовано. Grace period нужен для обслуживания, а не является контрактом корзины. Backup PostgreSQL и blob storage следует восстанавливать согласованно. Receipts, immutable package identities, audit и исторические пины сохраняются; автоматической компактации tombstones пока нет, они учитываются в пределе строк каталога.

Обновление и границы ​

Миграция 13 добавляет published_at, публикационный trigger и deletion receipts. Для старых published rows baseline времени равен времени миграции — старые данные не становятся кандидатами задним числом. Trigger фиксирует переход pending → available и сохраняет время при replay complete. Миграция 14 строит индексы ссылок assets через существующий resumable CREATE INDEX CONCURRENTLY. Readiness требует обе миграции.

Перед выдачей нового permission обновить writer, readers, workers и SDK: старые permission parsers не знают artifact.delete, старые package listings не фильтруют tombstones. Существующая инструкция обновления со stop/backup/migrate/start сохраняется. Откат на runtime без защиты удаления после начала использования этого API не поддерживается.

Сериализация коротких catalog mutations выполняется по репозиторию; транзакции не держат byte streams. Pin, annotation, asset, package и attachment записи повторно проверяют published state под тем же gate. Обоснование: ADR 0025.

Следующие самостоятельные этапы: архивирование/удаление asset paths и управляемая политика хранения истории, восстановление удалённых объектов, реестр репозиториев. Репликация и глобальная очередь не добавлены этим изменением; стенд двух серверов остаётся отложенным.

Автоматизация последних N и общие квоты реализованы отдельной revisioned storage policy: STORAGE_POLICIES. Ручной preview/apply сохраняет прежний контракт.

Arkvory · ProAnimaStudio