Andrey Epifancev 0a64a5b1d1 design: решения исполнения fact-status-removal (#104)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:23:04 +04:00

130 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Context
Основа — `main` после sdlc-rename (#97) и web-modules (#98), `5a21825`. Схема базы — v7 (`core/db.py`,
`SCHEMA_VERSION = 7`). Issue #104, ветка `feat/104-fact-status-removal`.
Где живёт статус по факту (разведка 24.10.2026):
- база: `issue_note.fact_status` (`modules/jira/schema.py`); `mirror.NOTE_FIELDS`, фильтр `fact_status` в
`mirror.list_issues`, колонка в выборках `list_issues` и `board_snapshot` (`modules/jira/mirror.py`);
- API: `ISSUE_FILTERS` (`modules/jira/api.py`); `PUT /issues/{key}/note` принимает поле через `NOTE_FIELDS`;
- MCP: `issues` (параметр и колонка таблицы), `notes_set` (параметр), `issue` (строка заметок) — `modules/jira/mcp.py`;
- настройки: `status_labels`, `transitions_map` в `DEFAULTS` и `AGENT_KEYS` (`modules/jira/settings.py`), перенос из
конфига в `init --config` (там же);
- импорт: колонка «Статус по факту» в `tasks.csv`/`new_tasks.csv` (`modules/jira/importer.py`);
- веб: типов с `fact_status` уже нет (#57); остались фикстуры (`board/__fixtures__/hub-basic.board.json`,
`settings.json`), тест-страж терминов (`boundary.test.ts`), тест «параметр из адреса не уходит в запрос»
(`IssuesPage.test.tsx`) и сидер тестового хаба `web/scripts/seed-hub.py` — он пишет `fact_status` в заметки
через `PUT /issues/{key}/note` и после change получит 422;
- тесты хаба и эталоны: `tests/fixtures/baseline/{mcp_tools,settings,schema}.expected.json`, `test_db`, `test_mirror`,
`test_api`, `test_mcp`, `test_importer`, `test_settings`, `test_e2e_hub`, `test_cli`, `test_queue`, `test_agents`;
`tests/fixtures/{settings_v6,schema_v6,config.example}.json` — входы миграционных тестов, исторические.
jira-agent (`../jira-agent/src/agent.py`) ни `status_labels`, ни `transitions_map` не читает: `ensure_status` получает
путь статусов в параметре `target`. Потребители MCP в `~/projects` поле не упоминают.
## Goals / Non-Goals
**Goals:** в хабе нет ни поля, ни настроек, ни параметров статуса по факту; живая база v7 мигрирует при старте без
ручных шагов; прочие заметки целы.
**Non-Goals и Anti-Goals:** см. proposal.md.
## Decisions
1. **Колонка удаляется, а не обнуляется** (решение владельца 24.10.2026: «удаляй безвозвратно»). Шаг миграции v7 → v8
— `ALTER TABLE issue_note DROP COLUMN fact_status`, только если колонка есть (новая база v8 собирается схемой без
неё). Сам шаг живёт в модуле jira (функция в `modules/jira/`, ядро вызывает её ленивым импортом из `MIGRATIONS[7]`,
как `_project_fields_v7` вызывает `projects.migrate_v6`): термины jira в ядро не попадают (`test_core_has_no_jira_terms`).
SQLite хаба — 3.50 (Python 3.13 от uv), `DROP COLUMN` поддерживается с 3.35. Копию базы делает общий механизм
миграции (`backups/<дата>-schema-v7.sqlite`), отдельной выгрузки нет.
2. **Ключи `status_labels` и `transitions_map` удаляются из `setting` той же миграцией** и уходят из `DEFAULTS` и
`AGENT_KEYS`, иначе `ensure_defaults` создаст их снова. Если удалён хотя бы один ключ — `settings_version` + 1:
набор ключей агента изменился, агент перечитает настройки (на работу агента это не влияет). Строки
`settings_version` может не быть (база без `ensure_defaults`) — тогда версия не поднимается и не создаётся.
3. **`PUT /issues/{key}/note` с `fact_status` — 422** «неизвестные поля заметки: fact_status» — общее правило
`mirror.set_note` для полей вне `NOTE_FIELDS`, отдельного кода нет.
4. **`GET /issues?fact_status=…` — параметр игнорируется**, как любой неизвестный: `h_issues` читает только
`ISSUE_FILTERS`. Веб его уже не шлёт, а старые закладки с параметром не ломаются.
5. **`init --config` не переносит `status_labels` и `transitions`**, даже если они есть в конфиге.
6. **Импорт пропускает колонку «Статус по факту»**; остальные колонки — как раньше.
7. **Веб: только тесты, фикстуры и сидер.** `seed-hub.py` перестаёт писать `fact_status` в заметки. Из
`hub-basic.board.json` и `settings.json` поле и ключ убираются; golden-эталоны доски (`*.expected.json`) MUST NOT
измениться — доска поле не читает. `fact_status` в `MODULE_TERMS` теста-стража остаётся: ядро веба по-прежнему не
должно его содержать. Тест `IssuesPage.test.tsx` про параметр из адреса остаётся (поведение то же).
8. **Исторические фикстуры (`*_v6.json`, `config.example.json`) не правятся**: это входы миграций и `init`, на них
проверяется, что поле и ключи пропадают.
9. **Спека `data-model`: «текущая — 8»**, сценарии миграций v1–v5 вместо числа говорят «текущая» — число «6» в них
отстало от кода ещё в wiki-module (v7) и ломалось бы при каждой новой версии.
10. **Документация и тексты в коде правятся вместе с кодом:** README (описание `import`, строки `GET /issues` и
`GET /board`), docstring `mirror.board`, `mcp.issues`, `mcp.notes_set`, обе строки чтения CSV в
`importer.py`. Исторические планы `docs/superpowers/plans/*.md` и архив `openspec/changes/archive/` не правятся;
итоговая проверка — `grep -rn "fact_status\|status_labels\|transitions_map\|по факту" src web/src web/scripts README.md
ARCHITECTURE.md openspec/specs` даёт только намеренные остатки (страж `MODULE_TERMS`, тест параметра из адреса,
текст спек «статуса по факту нет»).
11. **Устаревшие ключи настроек объявляет модуль** (`Module.settings_obsolete`, у jira — `status_labels`,
`transitions_map`); `core.settings.ensure_defaults` удаляет их при каждом вызове, `settings_version` не поднимает
(агенту ключи не передаются). Ядро имён ключей не знает.
## Справка: пути переходов из `transitions_map`
Значение настройки на живом хабе 24.10.2026 — пути по workflow AURORA (статус Jira, в который переводили задачу,
по виду работы; `default` — для видов, которых нет в строке; «—» — пути нет). Ключи — статусы по факту, сами пути пригодятся для спринтов/бэклога.
| Статус по факту | default | аналитика / решение / актуализация | организационная |
|---|---|---|---|
| Готово — закрыть | Разработка → Готово к тестированию | Разработка → Готово | Разработка → Готово |
| В работе | Разработка | Ожидает анализа → Анализ | Разработка (default) |
| Ожидает решения владельца/заказчика | Ожидает анализа | Ожидает анализа | Ожидает анализа |
| К актуализации аналитики | Ожидает анализа | Ожидает анализа | Ожидает анализа |
| Ожидание разработки | — | Ожидает анализа | — |
| Частично — переформулировать | — | Ожидает анализа | — |
| Снято | Снято | Снято | Снято |
`status_labels` сопоставлял статусам по факту служебные метки `fact-*` (`fact-done`, `fact-partial`,
`fact-in-progress`, `fact-decision`, `fact-docs`, `fact-open`, `fact-roadmap`, `fact-cancelled`).
## Risks / Trade-offs
- **Значения статуса по факту теряются** — решение владельца; восстановить можно только из `backups/`.
- **Новый код до kickstart.** MCP-сервер и CLI открывают базу сами (`HubClient.from_home()` → `db.connect` +
`ensure_defaults`). Новая сессия Claude, `/mcp` → reconnect или `sdlc-hub tokens` между `git pull` и `kickstart`
мигрируют базу до v8, а хаб на прежнем коде отвечает 500 на `GET /issues`, `GET /board`, `PUT /issues/{key}/note`
(нет колонки). Порядок выкатки — `git pull` → сразу `kickstart` (README, ARCHITECTURE.md, раздел о выкатке схемы).
- **Старый код после kickstart.** Долгоживущие MCP-сессии со старым кодом при каждом вызове открывают базу и своим
`ensure_defaults` снова вставляют `status_labels`/`transitions_map` (`{}`); миграция уже прошла и не повторится, а
удалить ключ через API нельзя (I-1 финального ревью). Поэтому ключи объявлены устаревшими модулем
(`Module.settings_obsolete`), и `ensure_defaults` нового кода удаляет их при каждом вызове — при старте хаба и в каждой
новой MCP-сессии (design #11). Пока жива старая сессия, ключи могут появляться между её вызовами и вызовами нового кода;
агенту они не уходят (`AGENT_KEYS` нового кода).
- **Сессии MCP со старым кодом** до `/mcp` → reconnect показывают параметр `fact_status`; вызов с ним вернёт пустой
фильтр (хаб параметр игнорирует), `notes_set` с ним — 422.
## Decisions made without discussion
- 24.10.2026 — R-1 (design #2): `transitions_map` и `status_labels` удаляются вместе с полем; пути переходов сохранены
в разделе «Справка» этого документа. Обоснование: ключи карты — статусы по факту, читателей нет. Цена ошибки —
вернуть настройку из справки одной правкой `PUT /settings`.
- 24.10.2026 — R-2: jira-agent не меняется, design-only change в агенте не заводится (агент ключи не читает). Цена
ошибки — отдельный PR в агенте.
- 24.10.2026 — R-3 (design #2): `settings_version` поднимается, если миграция удалила ключ. Цена ошибки — один лишний
перечит настроек агентом.
- 24.10.2026 — R-4 (design #3, #4): запись поля в заметку — 422, фильтр в `GET /issues` — игнорируется. Обоснование:
общие правила хаба для неизвестных полей тела и параметров. Цена ошибки — правка одного обработчика.
- 24.10.2026 — R-5: M-3, M-4, M-5 из #60 в change не входят и остаются в #60. Цена ошибки — отдельная мелкая задача.
- 24.10.2026 — R-7 (design #11): после I-1 финального ревью — устаревшие ключи удаляет каждый `ensure_defaults`, без
подъёма `settings_version`. Обоснование: старые MCP-сессии возвращают ключи, а удалить их иначе нечем. Цена ошибки —
поле контракта модуля, которое придётся убрать.
- 24.10.2026 — R-6 (design #9): в `data-model` исправлено отставание номера версии схемы. Цена ошибки — нет.
- 24.10.2026 — R-8 (исполнение, Task 1): проверки «`fact_status` нет в заметке» в `test_api`/`test_importer` на время
Task 1 ослаблены до `.get(...) is None` (`get_note` читает `SELECT *`, колонка уходила в Task 2), в Task 2 возвращены.
Цена ошибки — нет.
- 24.10.2026 — R-9 (исполнение, Task 2): `test_v14_agent_over_http_on_v6_base` вычитает `status_labels` и
`transitions_map` из ключей агента исторического `settings_v6.json`; фикстура не правится (design #8). Цена ошибки —
одна строка теста.
- 24.10.2026 — R-10 (исполнение, Task 2): `migrate_v7` — в `modules/jira/settings.py`, ядро вызывает его ленивым
импортом (`_note_fields_v8`). Цена ошибки — перенос функции.
- 24.10.2026 — R-11 (финальное ревью): M-1 (ARCHITECTURE «v7») и M-4 (неточности proposal/design) исправлены вместе с I-2
— те же документы. Цена ошибки — нет.