Skip to content

Сборки: метки, метаданные и вложения ​

Реализовано в native v1 для UPack и обычных файлов. API и SDK одинаковы для CI/CD, интеграций и отдельного интерфейса. Первоначальный выпуск вложений: OpenAPI 0.8.0, 108 HTTP-операций с учётом HEAD, миграция БД 12; это историческая точка внедрения. Текущая версия контрактов и схемы — в карте API.

Что считается сборкой ​

Сборка в этом сценарии — опубликованный основной артефакт с постоянным artifactId. Его исходные байты и SHA-256 неизменяемы. Версия UPack относится к этому артефакту через существующую регистрацию пакета. Метки, дополнительные метаданные и именованные вложения изменяются отдельно и не требуют пересборки архива.

Это не новый объект pipeline/run: один запуск CI может публиковать несколько самостоятельных артефактов. Коллекции позволяют объединять их логически. Изменяемый канал вида current/app.upack по-прежнему реализуется через asset pointer, а не через метку.

МеханизмПримерСемантика
Меткиbse, test, staging, release, linuxПроизвольные, регистр значим; несколько меток могут сосуществовать. Не выдают доступ и не перемещают файл.
Метаданныеbuild.number: "42", git.commit: "abc123"Плоские строковые поля. Для дерева JSON/YAML — отдельный манифест.
Коллекцииdesktop, nightlyГруппировка артефактов, независимая от версии и назначения вложения.
Вложениеbuild.json, sbom.json, app.sig, tests.xmlИменованная ссылка на опубликованный артефакт того же репозитория.

Подсказки меток в UI не являются закрытым enum. Например, relase тоже допустима как пользовательская метка: сервер не исправляет значения и не сливает их с release.

API маркировки ​

Существующие GET /api/v1/repositories/{repository}/artifacts/{id}/annotations и PUT по тому же пути сохраняются. Ответ: revision, labels, metadata, collections. Для обновления передать {expectedRevision, value: {labels, metadata, collections}}.

Это замена всего набора по ревизии. Клиент сначала читает, изменяет нужные поля и сохраняет. Нельзя прислать только одну новую метку, предполагая автоматическое слияние остальных. Конфликт — 409: перечитать, сопоставить намерение с текущим состоянием и сохранить осознанно. SDK не повторяет CAS-записи автоматически.

Ограничения: до 32 меток и 32 коллекций, до 64 символов на значение; до 32 полей metadata, ключ до 64 символов, строковое значение до 1024. Правила ключей/меток заданы текущим descriptor parser. Поиск по метке поддерживается /search?label=staging; q ищет и по значениям актуальной metadata (подстрока без учёта регистра), а metadataKey + metadataValue задают точную пару ключ/значение; в CLI — search --query, --metadata-key и --metadata-value. Regex и wildcards не поддерживаются. Начальные labels/metadata можно передать непосредственно при создании upload.

Имена файлов и строковые поля должны содержать корректные Unicode-последовательности; NUL запрещён в metadata и описаниях вложений. Парные суррогаты (например, emoji) сохраняются без изменения. Неподдерживаемые значения отклоняются как 400 invalid_input до записи каталога.

Ревизия annotations независима от ревизии attachments. Изменение вложений не перезаписывает метки. Для annotations есть актуальная ревизия и существующий аудит операций; полные исторические снимки metadata этим этапом не добавляются.

API вложений ​

Базовый путь: /api/v1/repositories/{repository}/artifacts/{id}/attachments.

МетодПутьДействие
GET / HEADбазовыйТекущий набор; до первого изменения revision: 0, items: [], actor: null, createdAt: null
PUTбазовыйЗаменить набор по expectedRevision; добавление, удаление и переименование — изменения этого набора
GET / HEAD/history?before=21До 20 снимков от новых к старым; next — эксклюзивный cursor для следующей страницы
json
{
  "expectedRevision": 0,
  "items": [
    {
      "name": "build.json",
      "kind": "manifest",
      "artifactId": "64b42380-902d-41cf-9151-10c12e4809dc",
      "description": "Build provenance from CI"
    }
  ]
}

Это тело HTTP PUT. Файл для arkvoryctl attachments set ID --revision N --file attachments.json содержит только JSON-массив items без обёртки ([{"name": …, "kind": …, "artifactId": …, "description": …}]), а ревизия задаётся опцией --revision.

Ответ PUT/GET: {revision, items, actor, createdAt}; история: {items: [revision, ...], next}. Дата и автор устанавливаются сервером. kind принимает manifest, sbom, signature, report, file. Назначение — описание, а не признак проверенной подписи, соответствия схеме или безопасности файла.

До 32 вложений на сборку; уникальное имя без учёта регистра, до 240 символов, без путей/управляющих символов и крайних пробелов. Описание обязательно как строка (может быть пустым), до 512 символов. Все поля команды валидируются; неизвестные поля отклоняются. Собственный ID сборки не может быть её вложением. Ссылки не обходятся рекурсивно: построение графа зависимостей не входит в этот контракт.

Каждый target должен быть available в том же репозитории. Чужой, отсутствующий и pending target возвращают одинаковый 404. Неправильная команда — 400; устаревшая ревизия — 409. Набор, ссылки на targets и аудит фиксируются одной транзакцией; ошибка любого INSERT откатывает всё. Конкурирующие первые записи также защищены от перетирания.

Для восстановления клиент выбирает снимок истории и отправляет его items через обычный PUT с текущей expectedRevision. Это новая ревизия с новым автором/датой. Смена указателя не меняет исходные байты. Снятие привязки не удаляет blob; исторические ссылки сохраняются и защищены внешними ключами. Реализованные retention и online GC учитывают arkvory_attachment_targets; физическая очистка также освобождает логически удалённые артефакты после grace period. Удаление истории вложений остаётся отдельной задачей.

Байты, обрывы и повторные запросы ​

  1. Создать upload для дополнительного файла, передав idempotency key и SHA-256.
  2. Загрузить существующим multipart/resume и дождаться available. Лимит файла тот же, что у обычной загрузки (capabilities.limits.maxObjectBytes); отдельного multipart-form обработчика нет.
  3. Прочитать текущую ревизию вложений и добавить полученный artifactId.
  4. При конфликте или потере ответа перечитать набор. Если нужная ссылка уже сохранена — операция завершена; иначе сопоставить текущий набор перед новым PUT.

Загрузка и привязка — две отдельные операции: опубликованный файл не исчезает, если привязка не удалась. Самостоятельный CI сохраняет idempotency key/upload ID и восстанавливает работу после перезапуска. SDK GET не предоставляет durable workflow сам по себе.

Скачивание использует существующий /artifacts/{attachment.artifactId}/content с Bearer, Range/If-Range и проверкой SHA-256 через SDK/очередь. Информация о точном размере и хеше — /artifacts/{attachment.artifactId}. Постоянного публичного URL нет, а ключ или сессия через query не передаются. Единственное исключение — краткоживущая ссылка скачивания: POST …/artifacts/{id}/links возвращает ?token=dtl_…, который принимает только маршрут содержимого этого артефакта (ADR 0062). Манифесты не исполняются, внешние ссылки из их содержимого не запрашиваются, HTML не отображается встроенно.

Права и масштабирование ​

  • Чтение списка и истории: annotation.read, как для свойств основного артефакта.
  • Изменение: annotation.write и artifact.read в том же репозитории. Привязки являются частью описания сборки; новый глобальный admin scope не нужен.
  • Загрузка файла дополнительно требует текущих upload actions; скачивание — content.read, проверяемое отдельно. Само наличие ссылки не выдаёт доступ к байтам.
  • Managed credential и policy повторно проверяются в транзакции записи. У legacy/group principal используются прежние read/write semantics. Администратор не получает права репозитория автоматически.
  • /operations отражает новые методы в surface catalog. Reader-шлюз обслуживает GET/HEAD и отклоняет PUT. Capability: buildAttachments: true.

SQL блокирует только изменяемую сборку на короткое время. Хеширование и передача байтов выполняются до транзакции; разные сборки не сериализуются общим lock. История читается через ключ (artifact_id, revision) с LIMIT 21; API отдаёт максимум 20 строк и cursor. Ответы чтения — private, no-store. История append-only; существующий лимит ревизии — signed PostgreSQL integer.

SDK и сценарий CI ​

typescript
const repo = client.inRepository('releases');
const annotations = await repo.annotations.get(buildId);
await repo.annotations.update(buildId, annotations.revision, {
  labels: [...new Set([...annotations.labels, 'staging'])],
  metadata: { ...annotations.metadata, 'build.number': '42', 'git.commit': commit },
  collections: annotations.collections,
});

// manifestArtifactId получен после обычной завершённой загрузки.
const current = await repo.attachments.get(buildId, signal);
await repo.attachments.replace(
  buildId,
  current.revision,
  [
    ...current.items,
    { name: 'build.json', kind: 'manifest', artifactId: manifestArtifactId, description: '' },
  ],
  signal,
);
const history = await repo.attachments.history(buildId, undefined, signal);

Flat SDK: attachments(repository, id, signal?), replaceAttachments(repository, id, expectedRevision, items, signal?), attachmentHistory(repository, id, before?, signal?). Runtime parsers ограничивают размер, проверяют ссылки/назначения, порядок истории и cursor. Для повторного запуска pipeline сначала проверить существующую ссылку по имени и ID, а не вслепую дописывать её снова.

Интерфейс ​

Карточка открывается из каталога, версии UPack или истории asset. RU/EN, обе темы и токены дизайна сохраняются.

  • Метки вводятся свободно; кнопки bse, test, staging, release переключают конкретную метку, не затрагивая остальные.
  • Metadata редактируется строками «ключ — значение», JSON доступен в раскрываемом блоке. Пустые пары пропускаются, дубли ключей отклоняются. Сохранение явно отделено от ввода.
  • Вложения добавляются прямо из карточки: файл или ID опубликованного артефакта, назначение, имя, описание. Пауза сохраняет upload ID/части; повторная отправка той же формы продолжает загрузку. При ошибке привязки файл и данные формы сохраняются.
  • История показывает набор, автора и дату, позволяет скачать прежний файл или восстановить набор новой ревизией. «Убрать привязку» не удаляет файл.
  • Управление скрывается/блокируется по /operations; сервер всегда повторно авторизует запрос. Смена подключения/сборки очищает прежнее состояние и прерывает незавершённую передачу вложения.
  • Состояние формы и File остаются только в текущей вкладке. После её закрытия upload восстанавливается по сохранённому ID на обычном экране загрузки; автоматического сохранения очереди вложений между сессиями нет.

Проверки: domain/contract limits, реальный PostgreSQL и HTTP, concurrent CAS, exact repository/права, rollback, restart/history, FK-пины; браузер — поля metadata, метки, частичная загрузка/пауза/продолжение, конфликт/обновление, unlink/restore, read-only и адаптивность RU/EN/light/dark. Проверки двухсерверного HA остаются стендовыми.

Корневой UPack upack.json также поддерживает nested custom metadata с _-именами. Добавлен CLI подготовки манифеста; custom _labels не синхронизируются с Arkvory annotations автоматически. Примеры и границы неизменяемости.

Arkvory · ProAnimaStudio