2026-10-24 00:18:31 +04:00

14 KiB
Raw Blame History

MODIFIED Requirements

Requirement: Процесс и доступ

jira-hub serve SHALL запускать один процесс: HTTP на 0.0.0.0:<port> (по умолчанию 8790), база и настройки в ~/.local/share/jira-hub/ (переопределяется JIRA_HUB_HOME). Все маршруты API SHALL требовать Authorization: Bearer <token>: токен агента модуля открывает только /agent/* (маршруты ядра — от имени этого агента, маршруты модуля со scope agent — только агенту своего модуля, см. modules, «Агенты модулей»), клиентский — всё остальное. jira-hub tokens SHALL печатать клиентский токен и токены агентов с именами модулей. Исключение — раздача веб-приложения (GET /app, GET /app/…) и перенаправления GET /, GET /links: это статические файлы без данных, они отдаются без токена. Ответы API — JSON, ошибки — {error, detail} с кодами 400 (неверное тело или Content-Length), 401, 404, 409, 413 (тело больше предела), 422, 500 (внутренняя ошибка); реле общей папки дополнительно отвечает 502. Методы, кроме GET, POST, PUT и PATCH, отвечают 501 стандартной страницей http.server, не JSON.

Scenario: Чужой токен

  • WHEN токеном агента jira вызван GET /issues
  • THEN 401

Scenario: Неподдерживаемый метод

  • WHEN DELETE /issues/AURORA-1
  • THEN 501, тело не JSON; база не меняется

Scenario: Страница без токена

  • WHEN GET /app/board без заголовка Authorization, сборка приложения есть
  • THEN 200, Content-Type: text/html; charset=utf-8, тело — index.html сборки; данных зеркала в нём нет

Scenario: Токены после миграции

  • WHEN база v6 с агентским токеном T мигрировала в v7, запущен jira-hub tokens
  • THEN вывод содержит клиентский токен, jira: T и новый токен wiki

Requirement: Маршруты проектов

Хаб SHALL предоставлять под клиентским токеном:

  • GET /projects → список проектов по created_at, затем label; каждый — плоский объект из полей ядра (label, name, created_at) и полей всех модулей (modules, «Поля проекта от модулей»). Модуль jira даёт board_id, board_type, issues_count (задачи зеркала с меткой проекта), модуль wiki — wiki_root, wiki_root_title, wiki_pages, wiki_synced_at, wiki_sync_error.
  • POST /projects {label, name, author?, <поля модулей>} → 201 {project, batch_id}: в одной транзакции проект записывается, модули записывают свои поля и ставят свои пакеты; batch_id — пакет первого модуля, вернувшего пакет (у jira — «Новый проект <метка>» из одного refresh по базовой JQL, уже включающей новую метку). Автор пакетов — author (по умолчанию api). Неизвестное поле, неверные поля ядра (см. data-model, «Проекты») или модуля — 422 с указанием поля; метка, совпадающая с существующей без учёта регистра, — 409. При ошибке ни проекта, ни пакетов нет.
  • PATCH /projects/{label} {<изменяемые поля модулей>, author?} → 200 {project, batch_ids}: модули правят свои поля и ставят пакеты в одной транзакции. Неизменяемое поле (метка, название, board_id) — 422 «<поле> после создания не меняется»; неизвестное поле или неверное значение — 422 с указанием поля; проекта нет — 404.

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

  • WHEN POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК", board_id: 5120, author: "viewer"}, есть проект AWPCA
  • THEN 201; GET /projects отдаёт два проекта, у STOCKS_DRRK board_type = null, wiki_root = null; в очереди пакет автора viewer с refresh {jql: 'project = AURORA AND labels in ("AWPCA", "STOCKS_DRRK")'}

Scenario: Проект без доски

  • WHEN POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК"}
  • THEN 201, board_id = null

Scenario: Проект с корнем вики

  • WHEN POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК", wiki_root: "DOC-001234"}
  • THEN 201; в очереди пакет refresh и пакет wiki_sync {label: "STOCKS_DRRK", root: "DOC-001234"}; batch_id — пакет refresh

Scenario: Повтор метки

  • WHEN POST /projects {label: "AWPCA", name: "…"}, проект AWPCA есть
  • THEN 409, пакет не поставлен

Scenario: Без названия

  • WHEN POST /projects {label: "STOCKS_DRRK", name: " "}
  • THEN 422 с указанием поля name

Scenario: Неизвестное поле

  • WHEN POST /projects {label: "X", name: "X", board: 5}
  • THEN 422 с указанием поля board, проекта нет

Scenario: Корень вики у существующего проекта

  • WHEN PATCH /projects/AWPCA {wiki_root: "DOC-000777"}
  • THEN 200, batch_ids — один пакет wiki_sync; GET /projects отдаёт AWPCA с wiki_root = DOC-000777

Scenario: Доску не меняют

  • WHEN PATCH /projects/AWPCA {board_id: 1}
  • THEN 422 «board_id после создания не меняется», доска прежняя

Requirement: Публикация версии агента

jira-hub agent publish [--module <модуль>] PATH SHALL публиковать файл агента модуля (по умолчанию jira; неизвестный модуль или модуль без команд — код 2 и список модулей с агентами в stderr): текст в UTF-8 не больше 1 МБ, ровно одна строка AGENT_VERSION = <целое>, версия больше опубликованной для этого модуля. Файл SHALL записываться в <JIRA_HUB_HOME>/release/<модуль>/agent.py (каталоги 0700), затем manifest.json {version, sha256, size, published_at}; оба — через временный файл с fsync и переименование, манифест последним. Прежний agent.py на время публикации SHALL переименовываться в agent.py.prev и удаляться после записи манифеста; если запись нового файла или манифеста не удалась (OSError), хаб SHALL вернуть agent.py.prev на место agent.py, удалить временные файлы и завершить команду сообщением «не удалось записать публикацию: <ошибка>» и кодом 1 — опубликованной остаётся прежняя версия. Команда SHALL работать без запущенного serve и печатать «опубликована v, sha256 <первые 12 символов>…, байт»; при ошибке — сообщение и код 1, опубликованное не меняется. При старте хаб SHALL один раз перенести прежнюю публикацию release/agent.py и release/manifest.json в release/jira/, если release/jira/ ещё нет; ошибка переноса — предупреждение в лог, хаб стартует. Хаб SHALL читать манифест модуля агента при каждом claim, GET /agent/release и GET /agent; манифест годен, только если это объект, version — целое число (не логическое) не меньше 1, sha256 — строка из 64 символов [0-9a-f]; негодный или нечитаемый манифест означает «публикации нет» и предупреждение в лог — одно на каждый новый текст ошибки. Файл agent.py, не декодируемый как UTF-8 при совпавшем sha256, означает «публикации нет» (GET /agent/release — 404 no_release). GET /agent?module=<модуль> (без параметра — jira) и каждый элемент GET /agents SHALL дополняться полями release ({version, sha256, published_at} или null), update_state, launcher_version, agent_reported (был ли claim этого агента с момента запуска хаба) и update_hint: «агент запущен без run.py — обновится только вручную» (опубликована версия выше версии агента, агент онлайн, agent_reported = true, launcher_version = null), «откат с v на v: » (rolled_back), «v отвергнута: » (rejected), иначе null.

Scenario: Публикация новой версии

  • WHEN опубликована v5, jira-hub agent publish jira-agent/src/agent.py с AGENT_VERSION = 6
  • THEN в release/jira/ — новый agent.py и манифест с version = 6, agent.py.prev нет; вывод «опубликована v6, sha256 …»; код 0

Scenario: Публикация eva-agent

  • WHEN jira-hub agent publish --module wiki eva-agent/src/agent.py с AGENT_VERSION = 1, опубликована jira v14
  • THEN в release/wiki/ — v1; claim агента wiki получает agent_release {version: 1, …}, claim агента jira — {version: 14, …}

Scenario: Неизвестный модуль

  • WHEN jira-hub agent publish --module nope agent.py
  • THEN код 2, в stderr «неизвестный модуль nope» и список модулей с агентами; release/ не изменён

Scenario: Версия не выше опубликованной

  • WHEN опубликована v6, публикуется файл с AGENT_VERSION = 6
  • THEN «опубликована v6, новая должна быть больше», код 1; release/ не изменён

Scenario: Нет строки версии

  • WHEN публикуется файл без строки AGENT_VERSION = <целое> или с двумя такими строками
  • THEN сообщение об ошибке, код 1; release/ не изменён

Scenario: Диск полон при записи манифеста

  • WHEN опубликована v8, публикуется v9, запись manifest.json завершилась OSError
  • THEN «не удалось записать публикацию: …», код 1, без трейсбека; agent.py и манифест — прежние v8, GET /agent/release отдаёт v8; временных файлов и agent.py.prev в release/jira/ нет

Scenario: Негодный манифест

  • WHEN в manifest.json version = 6.9 (или true, или sha256 короче 64 символов), хаб получает 100 claim
  • THEN в claim agent_release = null, GET /agent/release — 404; в логе одно предупреждение о манифесте

Scenario: Агент без запускателя

  • WHEN опубликована v6, агент v5 онлайн, в последнем claim launcher_version = null
  • THEN GET /agent → agent_reported = true, update_hint = «агент запущен без run.py — обновится только вручную»

Scenario: Хаб перезапущен, агент ещё не выходил на связь

  • WHEN опубликована v10, агент v9 под запускателем, хаб перезапущен, claim после запуска ещё не было
  • THEN GET /agent → agent_reported = false, launcher_version = null, update_hint = null; после первого claim — agent_reported = true, launcher_version из claim

Scenario: Перенос прежней публикации

  • WHEN хаб с release/agent.py v14 и манифестом без release/jira/ запущен на новом коде
  • THEN публикация v14 лежит в release/jira/, claim агента jira получает agent_release {version: 14, …}