14 KiB
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_DRRKboard_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.jsonversion = 6.9(илиtrue, илиsha256короче 64 символов), хаб получает 100claim - THEN в
claimagent_release = null,GET /agent/release— 404; в логе одно предупреждение о манифесте
Scenario: Агент без запускателя
- WHEN опубликована v6, агент v5 онлайн, в последнем
claimlauncher_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.pyv14 и манифестом безrelease/jira/запущен на новом коде - THEN публикация v14 лежит в
release/jira/,claimагентаjiraполучаетagent_release {version: 14, …}