0064. Raw-файлы по пути одним запросом
Статус: accepted. Owner: Ian Panaev. Дата: 2026-10-04. Аддитивное расширение /api/v1, SDK и CLI поверх истории путей (assets).
Контекст
Основные потребители — сборочные конвейеры игр (Unity, Unreal) и программ разных стеков: сборки, установщики exe, архивы, логи. Хранение по пути с историей ревизий уже есть (PUT …/asset). Но положить файл можно было только за три шага: создать загрузку, передать байты, привязать путь. Для curl, PowerShell и сборочных скриптов без SDK это неудобно, а в CLI загрузки по пути не было вовсе. Анализ форматов выделил «generic/raw» как основу, которой не хватает; npm, Maven, PyPI, S3-фасад и WebDAV отложены или отклонены.
Решение
HTTP. PUT /api/v1/repositories/{repository}/raw/{path} сохраняет тело запроса как новую ревизию пути; GET|HEAD того же адреса отдаёт текущую ревизию (Range, ETag, X-Arkvory-Artifact-Id).
- Путь — как у assets: сегменты через
/, без пустых,.,.., обратной косой черты и двоеточия, до 1024 символов. - Тело любого типа хранится как байты (JSON-файл не разбирается). Без
Content-Typeтоже работает, как отправляетcurl -T. - Если есть
X-Checksum-Sha256иContent-Length, байты идут прямо в хранилище артефактов за один проход, и хранилище проверяет сумму и размер. Несовпадение —422 integrity_mismatch, путь не меняется. - Без суммы или без длины (chunked) байты сначала пишутся во временный файл
<data>/raw-stagingс подсчётом SHA-256. Это второй проход и до двух объёмов файла на диске. Запас свободного места хранилища проверяется каждые 64 MiB. Остатки после сбоя убирает обслуживание пишущего шлюза через сутки. - Если путь уже указывает на те же байты (SHA-256 и размер), ничего не сохраняется:
200,created: false, ревизия прежняя. Повтор шага CI безопасен (retry: idempotent). При известной сумме тело в этом случае не читается, а соединение закрывается. - Новые байты —
201, новая ревизия, артефакт назван по последнему сегменту пути. If-None-Match: *— сохранить, только если пути ещё нет, иначе409 already_exists.- Двое пишут в один путь: позже пришедший получает
409 revision_mismatch(CAS ревизии пути). Его артефакт остаётся без пути, пока его не удалит retention. - Права: запись —
upload.create/write/complete,asset.read/write,artifact.read(файловые ключи —write); чтение —content.read. Шлюз чтения принимает только GET и HEAD.
SDK. Пространство имён client.raw: putRawFile(repository, path, blob, {sha256, createOnly}) — один запрос; downloadRawFile(repository, path, range?). В клиенте репозитория — assets.put и assets.download.
CLI.
arkvoryctl put FILE PATH— загрузка частями с чекпойнтом (какupload), затем следующая ревизия пути. Если по пути уже те же байты, ничего не загружается. Изменённый кем-то путь даётrevision_mismatchи не перезаписывается.arkvoryctl get PATH OUTPUT— текущая ревизия через проверяемое скачивание с докачкой.
Для файлов в десятки ГБ правильный путь — CLI или SDK с загрузкой частями: одиночный PUT не продолжается после обрыва.
Последствия
- Удаления пути нет, как и у assets: история хранится. Листинг каталога не добавлен:
GET …/assets/page?prefix=уже отдаёт пути по префиксу. - В зеркала raw-файлы попадают как assets: изменения путей уже в ленте.
- Отклонены: фасад S3 (большая поверхность и опасная частичная совместимость), WebDAV (артефакты неизменяемы, а WebDAV ждёт перезаписи и блокировок), отдельный формат Helm (Helm 3 хранит чарты в OCI-реестре, ADR 0063). npm, Maven, PyPI, NuGet, APT/YUM — только при появлении конкретного потребителя.
- Следующие форматы для игровых проектов — Git LFS с блокировками и реестр пакетов Unity (UPM по протоколу npm). Каждый получит отдельное решение.
Проверки
tests/integration/raw-files.test.mjs, настоящийcurl -T:- загрузка без суммы и с ней, повтор без новой ревизии, неверная сумма;
- скачивание целиком, Range, HEAD,
If-None-Match: *, история пути; - JSON-файл как байты, chunked-тело, права чтения и записи, недопустимые пути,
404; - SDK одним запросом;
arkvoryctl putдважды (второй раз без загрузки) иget.
tests/api-contract.test.mjsиtests/sdk-compatibility.test.mjs: новые операции и пространство имёнrawдобавлены аддитивно.