Возобновление сетевых передач
Для конечных клиентов доступен arkvoryctl: самостоятельная установка, сохранение upload idempotency/checkpoints, продолжение download и проверка SHA-256. Приведённые ниже SDK-примеры и прежний npm run upload остаются доступны для интеграций.
Реализованный профиль: standalone API + portable TypeScript SDK. Дополнительно работают read gateways с фиксированными общими квотами; два физических сервера и replication/fencing остаются отдельным этапом. Решение: ADR 0007.
Ограниченные повторы
const client = new ArkvoryClient(baseUrl, () => token, {
maxAttempts: 5, // включая первую попытку одного запроса, диапазон 1..10
maxRetries: 20, // общий бюджет повторов create/resume/downloadVerified, 0..100
attemptTimeoutMs: 120_000, // запрос + чтение ответа, 1..1_800_000
baseDelayMs: 500, // 1..60_000
maxDelayMs: 60_000, // baseDelayMs..60_000, включает Retry-After
});Показаны значения по умолчанию. Backoff растёт экспоненциально с jitter; Retry-After поддерживает секунды и HTTP-date. Повторяются транспортные сбои и HTTP 408/429/502/503/504. Ошибки прав, конфликты, повреждение содержимого/контракта и прочие HTTP-ошибки завершают операцию. Ответы busy (503) и rate_limited (429) с Retry-After — не сбой, а «приходи позже» (ADR 0076): клиент ждёт указанное время плюс до половины сверху случайно, не расходуя бюджет повторов и без предела maxDelayMs. Общее время таких ожиданий одной операции ограничивает waitLimitMs (по умолчанию без предела), о каждом ожидании сообщает onWait; оба задаются для вызова или для клиента. Для прочих повторов Retry-After дольше maxDelayMs по-прежнему завершает операцию. Внешний AbortSignal завершает запрос или ожидание; отмена не расходует новые попытки.
Общий бюджет не сбрасывается на каждом диапазоне. После исчерпания частичная загрузка остаётся на сервере, клиент может явно начать новый resume. Фиксированное окно каждой попытки также останавливает зависшие ответы; на очень медленном канале увеличьте attemptTimeoutMs. Синхронному complete отведено 30 минут на попытку для сборки больших файлов; proxy тоже должен допускать это ожидание. Для фонового завершения доступны complete-async/worker. Общий deadline операции задаётся внешним AbortSignal, SDK не навязывает лимит времени всему файлу.
Автоповторы включены у create, внутренних шагов resume, downloadVerified. Самостоятельные низкоуровневые status, parts, complete, download и каталоговые операции выполняют один вызов. Изменения metadata/assets с CAS автоматически не повторяются.
Upload
const upload = await client.create(repository, persistentKey, descriptor, signal);
await client.resume(repository, upload.id, file, {
signal,
onProgress: (confirmedBytes) => updateProgress(confirmedBytes),
onRetry: ({ attempt, delayMs }) => reportRetry(attempt, delayMs),
});Сохраняйте ID сессии и исходный файл. resume заново читает список принятых частей и сверяет их хеши с выбранным файлом. Повтор одной части безопасен; прогресс учитывает подтверждённые байты один раз. SHA-256 всего файла проверяется сервером при публикации. Повтор complete после потери успешного ответа возвращает опубликованный объект. Отмена сетевого вызова не равна DELETE сессии.
CLI после npm run build:
npm run upload -- C:\packages\large.upack releases
npm run upload -- C:\packages\large.upack releases <saved-upload-id>CLI использует multipart SDK, выводит ID, поддерживает Ctrl+C и проверяет хеш выбранного файла даже при уже опубликованной сессии. Токен передаётся через ARKVORY_TOKEN_FILE или ARKVORY_TOKEN; адрес через ARKVORY_BASE_URL. Чтобы восстановить резервирование после полной потери ответа, задайте ARKVORY_IDEMPOTENCY_KEY равным выведенному ключу. Повтор ключа с другим descriptor запрещён. Истёкшую/отменённую сессию заменяют новой с новым ключом.
Сервер разделяет ожидание отправителя (ARKVORY_UPLOAD_IDLE_TIMEOUT_MS, 30 секунд) и общий срок принятого PUT/complete (ARKVORY_UPLOAD_DEADLINE_MS, 30 минут). Паузы записи и ограничения скорости не расходуют idle-бюджет. После timeout клиент сверяет статус и повторяет безопасный шаг; сессия автоматически не удаляется. Короткое чтение blob/Range прерывает ответ, поэтому скачивание нельзя считать успешным до проверки длины и SHA-256. Гарантии, диагностика и ограничения.
Verified download
const stream = await client.downloadVerified(repository, artifactId, { signal });
await stream.pipeTo(stagingWritable, { signal });
// Только успешный pipeTo разрешает commit/переименование локального файла.Web-консоль уже использует этот путь. Размер, диапазон, ETag и отсутствие преобразования содержимого проверяются до выдачи блока; SHA-256 всего файла — перед успешным завершением. Нельзя передавать поток непосредственно в необратимый deployment: используйте временный файл и commit после проверки. ArkvoryIntegrityError означает отказ проверки, ArkvoryNetworkError — сетевой сбой/тайм-аут после исчерпания повторов. ArkvoryHttpError сохраняет status/code/requestId и retryAfterMs, а также reason/details/serverMessage (ADR 0051); ответы proxy и секреты не отражаются в сообщениях. Локальные несоответствия файла и загрузки — ArkvoryClientError (size_mismatch, file_changed).
Чтобы продолжить после перезапуска своего клиента, передайте сохранённый префикс:
const suffix = await client.downloadVerified(repository, artifactId, {
prefix: immutableSavedPrefixBlob,
signal,
});
await suffix.pipeTo(stagingWritableAtPrefixEnd, { signal });Поток содержит только суффикс. SDK хеширует prefix заново и сверяет итог с SHA-256 artifact. Prefix должен быть неизменяемым снимком начала файла; sink должен начинать запись ровно с prefix.size. Долговечность локального checkpoint, отсутствие параллельной записи и атомарный commit обеспечиваются клиентом. При неверном сохранённом начале операция завершится ошибкой проверки целого файла, частично записанный target нельзя публиковать. Переносимый SDK не создаёт дисковый журнал сам: CLI сохраняет staging/checkpoint рядом с результатом, а web использует приватный OPFS-журнал.
На сервере content pin проверяется после получения метаданных, до коротких ответов HEAD/304/416, при чтении и после ожидания полосы перед выдачей каждого блока. Потеря защиты прерывает текущий поток; слот очереди и pin освобождаются. При здоровом writer новый запрос может получить новую pin-сессию после освобождения старых handles. Закрытие ограничителя скорости запрещает выдачу уже разрешённого, но ещё не переданного блока. Уже отправленные в HTTP/socket байты отозвать нельзя; клиенту нужна проверка полного результата.
Если поток отказал до отправки HTTP-заголовков, клиент получает обычную JSON-ошибку (для потери защиты — 503/unavailable с Retry-After). Заголовки подготовленного файла, включая ETag, Content-Range и Content-Disposition, удаляются. После начала ответа заменить байты JSON-ошибкой невозможно: соединение прерывается, а клиент сверяет длину/хеш и продолжает по прежним правилам.
Проверка
tests/sdk-transfer.test.mjs: реальные локальные HTTP-сокеты, разрыв внутри блока, восстановление новым клиентом с prefix, неверные ETag/Range/размер/encoding/hash, отмена, stalled body, Retry-After и общий бюджет. tests/integration/transfer-recovery.test.mjs: fault proxy перед настоящими API/PostgreSQL/local storage, потеря ответа после create/part/complete, разрыв отправки, отсутствие дубликатов и пустой файл.
Большой профиль: npm run gate -- large-multipart, требует ARKVORY_TEST_DATABASE_URL и отдельную тестовую БД. Передаёт 5 GiB, убивает API на середине multipart и после публикации, рвёт download socket и проверяет SHA-256/RSS клиента и сервера. Не запускайте одновременно другие интеграционные тесты на этой БД.
После reload консоль позволяет восстановить незавершённую очередь из OPFS: подключитесь к тому же серверу с той же identity, восстановите задания на паузе и заново выберите файл назначения при продолжении. Сохраняются закрытые сегменты, а не незавершённые записи. Закрытие вкладки с потерей sessionStorage или удаление данных браузера не гарантирует восстановление. В CLI повтор той же команды использует .arkvory-part и .arkvory-download.json; существующий конечный файл не перезаписывается. После аварийного завершения процесса lock снимают только после проверки, что прежний процесс остановлен.