## MODIFIED Requirements ### Requirement: Раздача веб-приложения Хаб SHALL отдавать собранное веб-приложение (спека `web-app`) из каталога пакета `src/sdlc_hub/web/app/`, читая файлы с диска на каждый запрос: - `GET /app/assets/<путь>` — файл из `assets/` сборки; путь, который после разрешения выходит за `assets/`, не существует или содержит нулевой байт, — 404; `Content-Type` по расширению (`.js` — `text/javascript; charset=utf-8`, `.css` — `text/css; charset=utf-8`, `.svg` — `image/svg+xml`, `.png` — `image/png`, `.ico` — `image/x-icon`, `.woff2` — `font/woff2`, `.map` — `application/json`, прочее — `application/octet-stream`); `Cache-Control: public, max-age=31536000, immutable`; - любой другой `GET /app` или `GET /app/…` — `index.html` сборки, `text/html; charset=utf-8`, `Cache-Control: no-cache`; - если `index.html` сборки нет — на любой `GET /app…` 503 `text/html; charset=utf-8` с текстом, что приложение не собрано и нужно выполнить `make web` в каталоге sdlc-hub; - `GET /` и `GET /links` — 302 с `Location: /app/` (главная страница приложения — «Обзор»). Сборка приложения не хранится в git; её создаёт `make web`. #### Scenario: Файл сборки - **WHEN** в сборке есть `assets/index-abc123.js`, запрошен `GET /app/assets/index-abc123.js` - **THEN** 200, `Content-Type: text/javascript; charset=utf-8`, `Cache-Control` с `immutable`, тело — содержимое файла #### Scenario: Путь приложения - **WHEN** запрошен `GET /app/issues/AURORA-1620` - **THEN** 200, тело — `index.html` сборки, `Cache-Control: no-cache` #### Scenario: Выход за каталог сборки - **WHEN** запрошен `GET /app/assets/../../api.py` (в том числе в виде `%2e%2e`) - **THEN** 404, содержимое файлов вне `assets/` не отдаётся #### Scenario: Приложение не собрано - **WHEN** каталога сборки нет, запрошен `GET /app/board` - **THEN** 503, тело — HTML с подсказкой `make web` #### Scenario: Старый адрес доски - **WHEN** запрошен `GET /links` или `GET /` - **THEN** 302, `Location: /app/` #### Scenario: Нулевой байт в пути - **WHEN** запрошен `GET /app/assets/%00` или `GET /app/assets/a%00.js` - **THEN** 404 `not_found`, а не 500 ### Requirement: Маршруты проектов Хаб SHALL предоставлять под клиентским токеном: - `GET /projects` → список проектов по `created_at`, затем `label`; каждый — плоский объект из полей ядра (`label`, `name`, `created_at`) и полей всех модулей (`modules`, «Поля проекта от модулей»). Модуль jira даёт `board_id`, `board_type`, `issues_count` (задачи зеркала с меткой проекта), `jira_synced_at` (время завершения последнего полного обновления зеркала, общее для всех проектов; `null`, если его не было), модуль wiki — `wiki_root`, `wiki_root_title`, `wiki_pages`, `wiki_synced_at`, `wiki_sync_error`. - `POST /projects` `{label, name, author?, <поля модулей>}` → 201 `{project, batch_id, batches}`: в одной транзакции проект записывается, модули записывают свои поля и ставят свои пакеты; `batches` — список `{module, id}` всех поставленных пакетов в порядке модулей; `batch_id` — пакет первого модуля, вернувшего пакет (у jira — «Новый проект <метка>» из одного `refresh` по базовой JQL, уже включающей новую метку). Автор пакетов — `author` (по умолчанию `api`). Неизвестное поле, неверные поля ядра (см. `data-model`, «Проекты») или модуля — 422 с указанием поля; метка, совпадающая с существующей без учёта регистра, — 409. При ошибке ни проекта, ни пакетов нет. - `PATCH /projects/{label}` `{<изменяемые поля модулей>, author?}` → 200 `{project, batch_ids, batches}`: модули правят свои поля и ставят пакеты в одной транзакции; `batches` — список `{module, id}`. Неизменяемое поле (метка, название, `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`; `batches` — `[{module: "jira", id: }, {module: "wiki", id: }]` #### 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`, `batches` — `[{module: "wiki", id: <он же>}]`; `GET /projects` отдаёт `AWPCA` с `wiki_root = DOC-000777` #### Scenario: Доску не меняют - **WHEN** `PATCH /projects/AWPCA {board_id: 1}` - **THEN** 422 «board_id после создания не меняется», доска прежняя #### Scenario: Свежесть Jira в проекте - **WHEN** последний полный пакет `refresh` завершён в 11:07, `GET /projects` - **THEN** у каждого проекта `jira_synced_at` — это время; до первого полного обновления — `null` ## ADDED Requirements ### Requirement: Модуль пакета `GET /batches` и `GET /batches/{id}` SHALL отдавать у пакета поле `module` — имя модуля, которому принадлежит тип первой команды пакета (по описаниям модулей в реестре); пакет без команд или с типом, которого не знает ни один модуль, — `module = null`. Фильтр `GET /batches?module=<имя>` SHALL отдавать только пакеты модуля; неизвестный модуль — 404. #### Scenario: Модуль у пакетов - **WHEN** в очереди пакет «Автообновление» (`refresh`, автор `hub`) и пакет `wiki_sync` проекта `AWPCA`; `GET /batches` - **THEN** у первого `module = "jira"`, у второго `module = "wiki"` #### Scenario: Фильтр по модулю - **WHEN** `GET /batches?module=wiki&project=AWPCA` - **THEN** только пакеты модуля wiki проекта `AWPCA` #### Scenario: Неизвестный модуль - **WHEN** `GET /batches?module=nope` - **THEN** 404