Сборки: метки, метаданные и вложения
Реализовано в 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 для следующей страницы |
{
"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. Удаление истории вложений остаётся отдельной задачей.
Байты, обрывы и повторные запросы
- Создать upload для дополнительного файла, передав idempotency key и SHA-256.
- Загрузить существующим multipart/resume и дождаться
available. Лимит файла тот же, что у обычной загрузки (capabilities.limits.maxObjectBytes); отдельного multipart-form обработчика нет. - Прочитать текущую ревизию вложений и добавить полученный
artifactId. - При конфликте или потере ответа перечитать набор. Если нужная ссылка уже сохранена — операция завершена; иначе сопоставить текущий набор перед новым 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отражает новые методы в surfacecatalog. 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
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 автоматически. Примеры и границы неизменяемости.