Andrey Epifancev 70eaf5dc20 Архив OpenSpec: attachment-fetch (#83)
Спека agent: вложение без имени тоже отбрасывается (R-50 агента).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:06:20 +04:00

36 KiB
Raw Blame History

commands Specification

Purpose

Контракт команд моста: каждая команда — желаемое состояние одной задачи Jira (ensure_*) или чтение состояния (refresh). Здесь типы команд и их параметры, временные ключи внутри пакета, порядок выполнения, политика темпа, обработка ошибок, повторная выдача и подтверждение по состоянию зеркала.

Requirements

Requirement: Команда — желаемый результат на одной задаче

Каждая команда SHALL описывать желаемое состояние одной задачи Jira и выполняться агентом как «прочитать → сравнить → записать только разницу». Повторное выполнение любой команды с тем же результатом в Jira SHALL завершаться done без записи. fetch_attachment в Jira не пишет: она читает файл вложения и передаёт его хабу; повтор безопасен. Типы команд и их контракт:

Тип Параметры Что читает Что пишет
ensure_issue temp_key (new:<n>), issue_type (task/epic/bug), summary, description, priority, duedate, planned_start, planned_end, labels, epic_key, assignee поиск project = <proj> AND reporter = currentUser() AND created >= -7d, точное сравнение summary при отсутствии — POST /issue с типом из issue_types.<issue_type> (для эпика — с epic_name_field_id); результат key
ensure_fields key, любое подмножество: summary, description, priority, duedate, planned_start, planned_end, labels_add, labels_remove, epic_key, assignee GET /issue/{key} PUT /issue/{key} только с отличающимися полями; эпик — по epic_link.mode
ensure_comment key, marker, body GET /issue/{key}/comment комментарий с marker в начале: PUT если текст отличается, POST если нет
ensure_status key, target (имя статуса или список-путь) GET /issue/{key} (статус), GET /issue/{key}/transitions на каждом шаге POST /transitions по шагам пути; при отсутствии перехода — failed с перечнем доступных
ensure_link source, target, link_type (логическое имя из link_types), replace (bool) GET /issue/{source} (issuelinks) нет нужной и нет конфликта — POST /issueLink; конфликт (другой тип из link_types или обратное направление) и replace — DELETE лишней, затем POST; конфликт без replace — failed с описанием
ensure_sprint key, sprint (имя или backlog); board_id хаб дописывает при постановке (от клиента — 422) GET /issue/{key} (поле спринта), список спринтов доски board_id (активные и будущие) POST /rest/agile/1.0/sprint/{id}/issue или POST /rest/agile/1.0/backlog/issue
ensure_attachment key, file (sha256 файла в хранилище хаба, 64 hex-символа), filename (имя вложения в Jira, без /, \, " и управляющих символов); size хаб дописывает при выдаче GET /issue/{key}?fields=attachment вложения с тем же filename и размером нет — файл у хаба (GET /agent/files/{file}), затем POST /issue/{key}/attachments (multipart); есть — ничего; результат attachment_id
refresh keys (список) или jql; boards ([{id, type}] — доски проектов, type — scrum, kanban или null) хаб дописывает при выдаче search с полями зеркала, по 100 на страницу; плюс дочитывание соседей по issuelinks; доски с неизвестным типом — GET /rest/agile/1.0/board/{id} ничего в Jira; состояние уходит в хаб; результат — `boards: {"": "scrum"
fetch_attachment key, attachment_id (id вложения Jira, строка из цифр) GET /issue/{key}?fields=attachment (имя и размер вложения) ничего в Jira; содержимое — GET <base_url>/secure/attachment/{id}/{имя}, затем хабу POST /agent/files; результат {attachment_id, file, size}

Доску для ensure_sprint хаб SHALL определять при постановке: проект задачи — проекты, чьи метки есть у задачи в зеркале (для временного ключа — в labels её ensure_issue в пакете, после дописывания метки проекта); board_id — доска этого проекта. 422 при постановке: задача ни в одном проекте; в нескольких проектах с разными досками; у проекта нет доски; тип доски kanban («у проекта P канбан-доска, спринтов нет»); тип доски ещё не известен («тип доски проекта P ещё не известен — дождитесь обновления из Jira»). boards у refresh хаб SHALL дописывать при каждой выдаче (в базе не хранится): все доски проектов без повторов. По результату refresh хаб SHALL записать присланные типы scrum и kanban в проекты с этой доской; null тип не меняет.

Семантика ensure_link: source блокирует/ссылается на target; агент в POST /issueLink SHALL ставить source в inwardIssue, target в outwardIssue (проверено на Jira банка 11.10.2026).

Текст описания (ensure_fields) и тело комментария (ensure_comment) агент SHALL сравнивать с текущим значением в Jira без различия переводов строк \r\n и \n; записывается текст команды как есть. Jira хранит присланный текст, а правленный в веб-интерфейсе — с \r\n (проверено на Jira банка 13.10.2026).

Scenario: Повтор уже выполненной команды

  • WHEN ensure_fields {key: AURORA-1533, duedate: 2026-10-23} выполняется, а в Jira срок уже 23.10
  • THEN агент не делает PUT, отчёт done, jira_calls = 1

Scenario: Описание отличается только переводами строк

  • WHEN ensure_fields {key: X, description: "а\nб"}, а в Jira описание X — "а\r\nб"
  • THEN агент не делает PUT, отчёт done с changed = []

Scenario: Замена связи

  • WHEN ensure_link {source: AURORA-1537, target: AURORA-1538, link_type: "связанные", replace: true} и в Jira между ними «блокирует начало» 1537→1538
  • THEN DELETE старой связи, пауза по политике, POST новой; отчёт done с обеими операциями

Scenario: Связь-конфликт без replace

  • WHEN та же команда с replace: false
  • THEN failed, в error найденная связь, Jira не изменена

Scenario: Переход по пути

  • WHEN ensure_status {key: X, target: ["Разработка", "Готово"]} и X в «Ожидание разработки»
  • THEN два POST /transitions с паузой между ними; если X уже «Готово» — done без записи

Scenario: Создание ошибки

  • WHEN ensure_issue {temp_key: new:1, issue_type: bug, summary: "ОСВ 60 не распознана", labels: [AWPCA]}, в настройках issue_types.bug = {name: "Ошибка"} без id
  • THEN агент создаёт задачу с issuetype = {name: "Ошибка"}; отчёт done с ключом

Scenario: Вложение уже есть

  • WHEN ensure_attachment {key: X, file: <sha>, filename: "ОСВ 60.xlsx"}, у X в Jira есть вложение «ОСВ 60.xlsx» того же размера
  • THEN агент не скачивает файл у хаба и не загружает его в Jira; отчёт done с attachment_id найденного, jira_calls = 1

Scenario: Вложение с тем же именем, другого размера

  • WHEN та же команда, у X есть «ОСВ 60.xlsx» другого размера
  • THEN агент загружает файл вторым вложением с тем же именем (Jira это допускает); отчёт done с новым attachment_id

Scenario: Создание с проверкой дубля

  • WHEN ensure_issue повторяется после падения агента, а задача с той же темой от нас уже создана час назад
  • THEN агент не создаёт вторую, отчёт done с ключом найденной

Scenario: Спринт задачи проекта

  • WHEN у AURORA-1538 метка AWPCA, проект AWPCA с доской 4216 типа scrum; ensure_sprint {key: AURORA-1538, sprint: "Спринт 2"}
  • THEN пакет создан, в параметрах команды board_id = 4216

Scenario: Спринт на канбане

  • WHEN у AURORA-1 метка STOCKS_DRRK, доска проекта — kanban; ensure_sprint {key: AURORA-1, sprint: "Спринт 2"}
  • THEN 422 «у проекта STOCKS_DRRK канбан-доска, спринтов нет»

Scenario: Тип доски определён

  • WHEN проект STOCKS_DRRK с доской 5120 и board_type = null; выдаётся refresh, агент вернул boards: {"5120": "kanban"}
  • THEN при выдаче в параметрах boards есть {id: 5120, type: null}; после отчёта у проекта board_type = kanban

Requirement: Временные ключи и ссылки внутри пакета

Параметр-ключ любой команды MAY быть временным new:<n>. Хаб SHALL выдавать агенту такую команду только после done команды ensure_issue с тем же temp_key в том же пакете, подставив настоящий ключ. Если ensure_issue завершилась failed или cancelled, зависимые команды SHALL получить skipped с ошибкой «создание задачи new: отменено», если ensure_issue отменена, иначе «задача new: не создана». Временный ключ вне пакета с его ensure_issue SHALL отвергаться при создании пакета (422).

Scenario: Создать и привязать

  • WHEN пакет: ensure_issue {temp_key: new:60, …}, ensure_link {source: new:60, target: AURORA-1537, …}
  • THEN вторая команда выдаётся агенту с source = AURORA-1590 после завершения первой

Scenario: Создание отменено

  • WHEN пакет: ensure_issue {temp_key: new:901, …} (отменена с force: true), ensure_comment {key: new:901, …}
  • THEN ensure_comment — skipped с ошибкой «создание задачи new:901 отменено»

Scenario: Создание упало

  • WHEN ensure_issue {temp_key: new:902, …} завершилась failed, зависимая ensure_link {source: new:902, …}
  • THEN ensure_link — skipped с ошибкой «задача new:902 не создана»

Requirement: Пакет и порядок выполнения

Пакет SHALL создаваться одним вызовом с автором, заголовком и списком команд; хаб SHALL назначить seq по порядку списка и добавить в хвост refresh по всем ключам пакета (кроме пакетов, состоящих только из refresh и fetch_attachment). Хаб SHALL проверять типы параметров: текстовые параметры и ключи — строки (null у необязательного параметра — «не задан»; агент трактует его по контракту типа, для ensure_fields — очистка поля), target команды ensure_status — строка или список строк, labels, labels_add, labels_remove, keys — списки строк, replace — логическое значение; нарушение — 422 с именем параметра. Пакет с двумя и более ensure_issue с одинаковой summary (после обрезки пробелов по краям) SHALL отвергаться (422). Очередь SHALL выдавать команды в порядке (batch.created_at, seq), одну в полёте. Пакет и его queued команды MAY быть отменены; claimed команда MUST NOT отменяться автоматически.

Scenario: Оценка длительности

  • WHEN создаётся пакет из 3 ensure_fields и 2 ensure_comment
  • THEN estimate_seconds = сумма пауз по политике для последовательности типов + пауза refresh

Scenario: Две задачи с одной темой

  • WHEN пакет содержит ensure_issue {temp_key: new:1, summary: "Отчёт"} и ensure_issue {temp_key: new:2, summary: " Отчёт "}
  • THEN 422 с темой «Отчёт», пакет не создан

Scenario: Параметр не того типа

  • WHEN пакет содержит ensure_fields {key: X, labels_add: "AWPCA"}
  • THEN 422 с указанием labels_add, пакет не создан

Requirement: Политика темпа и пауза на команде

Хаб SHALL хранить pacing как минимальные зазоры в секундах по типу команды (по умолчанию: ensure_issue и ensure_fields 120, ensure_comment, ensure_status, ensure_link, ensure_sprint, ensure_attachment 30, refresh и fetch_attachment 2; тип, которого нет в сохранённой настройке pacing, получает зазор по умолчанию). При выдаче команды хаб SHALL сообщить pause_seconds = зазор по типу выдаваемой команды; агент SHALL выдержать его от времени своего последнего вызова Jira. Внутри одной команды с несколькими записями (ensure_link с replace, путь переходов) агент SHALL выдерживать зазор типа команды между записями.

Scenario: Нет следующей команды — нет паузы

  • WHEN очередь пуста
  • THEN claim возвращает пустой ответ, агент не спит сверх долгого запроса

Requirement: Ошибки и подтверждение по состоянию

Ошибка команды SHALL записываться в error (HTTP-код, текст Jira); хаб SHALL хранить не больше 1500 символов error, обрезая присланное. Пакет после ошибки продолжается.

Повторная выдача. Агент запрашивает новую команду, только когда у него нет незавершённой (см. agent), поэтому команда в claimed к моменту следующего claim считается не доставленной или потерянной агентом. При claim хаб SHALL сначала выдать снова самую старую (по claimed_at, затем id) команду в claimed: тот же id и параметры, pause_seconds по текущей политике темпа, новый claimed_at, deliveries + 1. Если у неё уже deliveries >= 3, хаб SHALL перевести её в failed с error = "агент не отчитался после 3 выдач" и перейти к следующей (довыдача другой claimed или следующая queued). Довыдача — доставка той же выдачи, а не повтор команды: новых команд хаб MUST NOT создавать сам, повтор после failed — новая команда с теми же параметрами по запросу интерфейса. Если агент всё же отчитался done с ключом по ensure_issue, переведённой в failed по пределу, хаб SHALL записать ключ в результат и перенести заметку, не меняя статус (как для отменённой, см. hub-api).

Подтверждение по состоянию. Если после любого refresh желаемый результат команды в claimed виден в зеркале, хаб SHALL перевести её в confirmed_by_state: ensure_fields — все поля совпадают (строки сравниваются без различия \r\n и \n; null и пустая строка равны); ensure_status — статус равен последнему в пути; ensure_link — связь есть и конфликтов нет; ensure_sprint — спринт совпадает. ensure_issue, ensure_comment, ensure_attachment, fetch_attachment и refresh по состоянию не подтверждаются (зеркало не показывает ни загрузку вложения, ни скачанный файл): потерянный отчёт по ним закрывается повторной выдачей (агент находит созданную задачу поиском дубля) или отменой человеком.

Scenario: Отчёт потерян, факт на месте

  • WHEN ensure_sprint {key: X, sprint: "Спринт 2"} в claimed, после выполненного refresh зеркало показывает X в «Спринт 2»
  • THEN команда confirmed_by_state

Scenario: Описание с CRLF в зеркале

  • WHEN ensure_fields {key: X, description: "a\nb"} в claimed, после refresh в зеркале у X description = "a\r\nb"
  • THEN команда confirmed_by_state

Scenario: Ответ на claim потерян

  • WHEN команда 40 выдана (deliveries = 1), ответ до агента не дошёл, агент снова прислал claim
  • THEN claim отдаёт команду 40, deliveries = 2; в очереди есть queued команды, но выдаётся 40

Scenario: Предел выдач

  • WHEN команда 41 в claimed с deliveries = 3, агент снова прислал claim, в очереди есть команда 42
  • THEN 41 — failed «агент не отчитался после 3 выдач»; claim отдаёт 42

Scenario: Потерянный отчёт о создании задачи

  • WHEN ensure_issue {temp_key: new:5, summary: "Отчёт"} выполнена агентом, отчёт не дошёл, агент прислал claim
  • THEN команда выдаётся снова; агент находит созданную задачу поиском дубля и отчитывается done с её ключом; второй задачи нет, заметка new:5 перенесена на ключ

Requirement: Описание параметров и ошибки валидации

Описание параметров каждого типа команды (обязательные, необязательные, формат значения, условие на набор) SHALL храниться в коде хаба в одном месте с правилами валидации; описание инструмента MCP batch_create SHALL собираться из него (см. mcp). Ошибка валидации «нет <параметры>» и «лишние параметры <параметры>» SHALL заканчиваться ожидаемым набором параметров типа: «ожидаются <обязательные>» и, если у типа есть необязательные, «необязательные <список>».

Scenario: Неверное имя параметра

  • WHEN POST /batches с ensure_status {key: X, status: "Готово"}
  • THEN 422 «команда 1 (ensure_status): нет target; ожидаются key, target»

Scenario: Лишний параметр у типа с необязательными

  • WHEN POST /batches с ensure_link {source: X, target: Y, link_type: "связанные", force: true}
  • THEN 422 «команда 1 (ensure_link): лишние параметры force; ожидаются source, target, link_type; необязательные replace»

Requirement: Проверки ensure_issue с типом bug и ensure_attachment при постановке

При постановке пакета хаб SHALL отвергать 422 без создания пакета: ensure_issue с issue_type = bug, если в настройках нет issue_types.bug с непустым name или id («нет настройки issue_types.bug»); ensure_attachment, если file — не 64 hex-символа в нижнем регистре, файла с таким sha256 нет в хранилище хаба («файл не загружен: POST /files»), filename пустой (или из одних пробелов) или содержит /, \, " или управляющие символы (код < 32 и 127, в том числе CR и LF): такие имена ломают multipart/form-data. Клиент size не передаёт (лишний параметр — 422). При выдаче агенту (claim, в том числе повторной) хаб SHALL добавить к параметрам ensure_attachment size — текущий размер файла в хранилище, или size = null, если файла уже нет; в базе параметры команды остаются без size, поэтому повтор команды (POST /commands/{id}/retry) проходит проверку как новая.

Scenario: Ошибка без настройки типа

  • WHEN POST /batches с ensure_issue {issue_type: bug, …}, в issue_types только task и epic
  • THEN 422 «команда 1: нет настройки issue_types.bug», пакет не создан

Scenario: Вложение без загруженного файла

  • WHEN POST /batches с ensure_attachment {key: X, file: <64 hex>, filename: "a.xlsx"}, файла нет в хранилище
  • THEN 422 «команда 1: файл не загружен: POST /files», пакет не создан

Scenario: Ошибка с вложением одним пакетом

  • WHEN пакет [ensure_issue {temp_key: new:1, issue_type: bug, …}, ensure_sprint {key: new:1, sprint: "Спринт 2"}, ensure_attachment {key: new:1, file: <sha>, filename: "ОСВ 60.xlsx"}], файл загружен
  • THEN пакет создан; ensure_attachment выдаётся после завершения ensure_issue с подставленным ключом и size файла

Requirement: Пакет проекта

Пакет, поставленный с project = P (см. hub-api), SHALL проходить при постановке, до записи, проверки проекта; любая неудача — 422 с ключом задачи и причиной, пакет не создаётся:

  • P нет среди проектов — 422;
  • ensure_issue: если в labels нет P, хаб SHALL дописать P в конец labels (в базе параметры хранятся уже с ней; без labels — labels = [P]); метка, совпадающая с P без учёта регистра, но не точно, SHALL заменяться на P; метка другого проекта в labels — 422;
  • ensure_fields: P в labels_remove или метка другого проекта в labels_add — 422;
  • ensure_fields, ensure_comment, ensure_status, ensure_sprint, ensure_attachment: key SHALL быть временным ключом ensure_issue этого пакета или задачей зеркала с меткой P, иначе 422 « не в проекте P»;
  • ensure_link: хотя бы один из source, target — временный ключ этого пакета или задача зеркала с меткой P;
  • refresh и fetch_attachment — без проверок проекта. «Метка другого проекта» и «P в labels_remove» SHALL сравниваться без учёта регистра; принадлежность задачи проекту (метка P у задачи зеркала) — точным совпадением, как в разрезе проекта. Пакет без project эти проверки не проходит, метки хаб в нём не дописывает.

Scenario: Метка проекта дописана

  • WHEN пакет с project = STOCKS_DRRK, ensure_issue {temp_key: new:1, summary: "Сверка остатков", labels: ["kind-dev"]}
  • THEN пакет создан; в параметрах команды labels = ["kind-dev", "STOCKS_DRRK"]

Scenario: Чужая метка проекта

  • WHEN пакет с project = STOCKS_DRRK, ensure_issue {…, labels: ["awpca"]}, есть проект AWPCA
  • THEN 422 «метка другого проекта: awpca», пакет не создан

Scenario: Задача другого проекта

  • WHEN пакет с project = STOCKS_DRRK, ensure_fields {key: AURORA-1538, duedate: …}, у AURORA-1538 метка только AWPCA
  • THEN 422 «AURORA-1538 не в проекте STOCKS_DRRK»

Scenario: Связь с соседом

  • WHEN пакет с project = STOCKS_DRRK, ensure_link {source: AURORA-1 (метка STOCKS_DRRK), target: AURORA-1538 (метка AWPCA), …}
  • THEN пакет создан

Requirement: Журнал изменений в refresh

Отметка журнала — started_at последнего sync_run с history_at IS NOT NULL (любой query). JQL стартовой загрузки — (<базовая JQL>) AND sprint in openSprints() AND statusCategory != Done. Хаб SHALL при каждой выдаче refresh (первой и повторной) дописывать в параметры history (в базе params без него, как boards):

  • полный прогон (refresh по базовой JQL), отметка есть — {since: <отметка − 15 минут, ISO-8601 UTC с Z>}; если query прогона-отметки не равна текущей базовой JQL — также jql: <JQL стартовой загрузки>;
  • полный прогон, отметки нет — {jql: <JQL стартовой загрузки>};
  • refresh {keys} и refresh по небазовой JQL — {since: …}, если отметка есть; иначе history не дописывается;
  • без базовой JQL (нет проектов) — history не дописывается.

Результат refresh MAY содержать history (см. agent, «Журнал изменений»); хаб SHALL принимать результат и без него. Отметку (sync_run.history_at) хаб SHALL проставлять по правилам data-model, «Отметка журнала в журнале синхронизаций».

Scenario: Первый refresh после выкатки

  • WHEN проект AWPCA, прогонов с history_at нет; выдаётся полный refresh
  • THEN в выданных параметрах history = {jql: '(project = AURORA AND labels in ("AWPCA")) AND sprint in openSprints() AND statusCategory != Done'}; в базе params без history

Scenario: Регулярный refresh

  • WHEN последний полный прогон с history_at начат в 2026-10-22T09:00:00+00:00 по текущей базовой JQL; выдаётся автообновление
  • THEN history = {since: "2026-10-22T08:45:00Z"}

Scenario: Журнал не дочитан

  • WHEN прогон в 10:00 завершился done с history.error, прогон в 09:00 — с принятым журналом; выдаётся следующий полный refresh
  • THEN history.since — от прогона 09:00

Scenario: Новый проект

  • WHEN прогон с history_at в 09:00 по JQL только с AWPCA; затем создан проект STOCKS_DRRK; выдаётся полный refresh
  • THEN history = {since: "…T08:45:00Z", jql: <стартовая по новой базовой JQL>}

Scenario: Хвост пакета

  • WHEN выдаётся хвостовой refresh {keys: [X]}, отметка есть
  • THEN history = {since: …}; по завершении done с result.history у его sync_run history_at = NULL

Scenario: Хвост пакета до первой отметки

  • WHEN выдаётся хвостовой refresh {keys: [X]}, прогонов с history_at нет
  • THEN в выданных параметрах нет history

Requirement: Скачивание вложения по запросу

fetch_attachment {key, attachment_id} SHALL ставиться только хабом по запросу интерфейса (POST /attachments/{id}/fetch, см. hub-api) — отдельным пакетом из одной команды, без хвостового refresh и без проекта (batch.project = NULL), заголовок «Скачать вложение ()»; через POST /batches её MAY поставить и клиент, с теми же проверками. При постановке хаб SHALL проверять: attachment_id — непустая строка из цифр; вложение с этим id есть в зеркале у задачи key (иначе 422 «у нет вложения # в зеркале — обновите задачу»); его размер в зеркале не больше 10 МБ (10 485 760 байт) — иначе 413 too_large «файл больше 10 МБ (<размер>) — скачайте в Jira»; размер неизвестен (NULL) — ставится, предел проверяет агент. Агент SHALL выполнять её так:

  1. GET /issue/{key}?fields=attachment; вложения с этим id нет — failed «вложение # удалено из в Jira»;
  2. размер по Jira больше 10 МБ — failed «файл больше 10 МБ (<размер>) — скачайте в Jira», содержимое не качается; размер неизвестен — качается, но чтение ответа SHALL обрываться на 10 485 761-м байте с тем же failed;
  3. содержимое — GET по адресу, собранному из base_url агента: <base_url>/secure/attachment/<id>/<имя в URL-кодировке> (адрес content из ответа Jira MUST NOT использоваться: учётные данные уходят только на base_url), таймаут не меньше 120 с; ответ Jira 429/503 — повтор по политике агента; перенаправление (3xx) агент MUST NOT выполнять с учётными данными на другой хост (схема, хост и порт не как у base_url) — failed «Jira перенаправила скачивание на <хост>»; длина полученного не равна известному размеру из шага 1 — failed «файл пришёл не целиком (<получено> из <размер>)»; пустое вложение (0 байт) — допустимо;
  4. POST /agent/files {content} (base64) хабу; ответ {file, size}; ошибка хаба — с префиксом «хаб: », недоступность хаба повторяется, как при отправке состояния;
  5. результат done {attachment_id, file, size}. По отчёту done хаб SHALL записать скачанный файл вложения (см. data-model, «Скачанные вложения»).

Scenario: Скачивание скриншота

  • WHEN у AURORA-1650 в зеркале вложение #31337 «скрин 1.png» 240 000 байт; поставлена fetch_attachment {key: AURORA-1650, attachment_id: "31337"}
  • THEN агент читает задачу, качает <base_url>/secure/attachment/31337/%D1%81%D0%BA%D1%80%D0%B8%D0%BD%201.png, отправляет содержимое хабу; отчёт done {attachment_id: "31337", file: <sha256>, size: 240000}; в хвосте пакета refresh нет

Scenario: Вложение удалили в Jira

  • WHEN вложение #31337 в зеркале есть, в Jira его уже нет
  • THEN команда failed «вложение #31337 удалено из AURORA-1650 в Jira»; хаб файл не записывает

Scenario: Слишком большой файл

  • WHEN в зеркале у вложения #40000 размер 25 000 000 байт; интерфейс просит его скачать
  • THEN 413 too_large «файл больше 10 МБ (23,8 МБ) — скачайте в Jira», пакет не создан

Scenario: Размер неизвестен

  • WHEN у вложения #50 в зеркале size = NULL, в Jira размера тоже нет, содержимое — 12 МБ
  • THEN постановка проходит; агент обрывает чтение после 10 МБ, команда failed «файл больше 10 МБ (больше 10,0 МБ) — скачайте в Jira»; хабу ничего не отправлено

Scenario: Перенаправление на другой хост

  • WHEN Jira на GET <base_url>/secure/attachment/31337/… ответила 302 на https://files.example/…
  • THEN агент туда не ходит, команда failed «Jira перенаправила скачивание на files.example»