Что это за задача
StoryChainWorkflow — следующий срез фабрики: проходит все задачи одной стори по очереди. Сейчас DevPipeline умеет сделать одну задачу от спеки (ТЗ) до кода. StoryChain берёт целую стори — несколько задач — и гонит их последовательно.
Зависит от: CH-1, SA-3.
Главное правило: не перепрыгивать
Цепочка строгая: ТЗ следующей задачи не генерируется, пока предыдущая не смёржена.
- Задачи стори идут строго по очереди, в заданном порядке.
- Следующая задача не начинается, пока предыдущая не закрыта (мерж MR).
- Закрытие стори — следствие прохождения всех задач, а не отдельное действие.
Зачем так строго: задачи стори часто зависят друг от друга — следующая строится на коде предыдущей. Если гнать параллельно, они конфликтуют; если перепрыгивать — следующая не имеет базы.
Остановка несёт причину
Цепочка останавливается не «просто так», а с явной причиной. Классы остановки (четыре из среза CH-2-S2 плюс пятый из решения Р-8):
| Причина | Что произошло | Дальше |
|---|---|---|
| сбой доставки | инфраструктура упала (агент не стартовал, сеть, стенд) | это не решение агента — отдельный класс, не смешивать с провалом работы |
| провал агента | агент выполнил шаг, но результат не прошёл проверку | остановка с именем шага и причиной |
| исчерпание max_rework | спека (ТЗ) или код возвращались слишком много раз | эскалация ЧЕЛОВЕК — дальше решает человек |
| закрытие MR человеком | MR задачи закрыт без мержа | остановка с причиной «закрыт человеком» (CH-3) |
| эскалация (человек не ответил) | гейт ТЗ ждал вердикта человека, тот не ответил (human_verdict_timeout) | остановка со своей причиной, rework_count не растёт, спека-агент не диспатчится (Р-8) |
Различие важно: «сервер упал» и «агент сделал плохо» — разные события, и цепочка обязана называть, какое из них случилось.
Откуда запускается
Снаружи цепочку стартуют по номеру стори — через новый endpoint
(его пишем в срезе 1). Вход — идентификатор стори (артефакт kind=user_story), а не
список задач: порядок задач workflow узнаёт сам. story_id детерминирован
(uuid5 от координаты + заголовка, решение 2026-08-17) — поэтому workflow_id
цепочки детерминирован от story_id, и повторный запуск той же стори не создаёт
второй цепочки.
# вход извне — один идентификатор стори
handle = await client.start_workflow(
StoryChainWorkflow.run,
"story-uuid-…", # id стори (артефакт user_story)
id="story-chain-1",
task_queue="dev-factory",
)
Как это ложится на уже готовое
StoryChain не строит механизм заново — он переиспользует готовые части:
DevPipeline
одна задача: спека (ТЗ) → код → MR. Его запускает StoryChain на каждую задачу.
MR + сигнал
DevPipeline открывает MR; вебхук Forgejo сигналит «смёржено» → переход к следующей задаче.
потолок rework
общий max_rework для ТЗ и кода — цепочка останавливается на нём.
Тесткейсы
Кейсы, привязанные к этой задаче в листе «Тесткейсы» (колонка «Единица бэклога»):
| TC-ID | Дано | Когда | Тогда |
|---|---|---|---|
TC-CH-2.1-a | цепочка стори запущена | повторный запуск той же стори | второй workflow не создаётся — workflow_id детерминирован от story_id |
TC-CH-2.2-a | задача N смержена не была | наблюдается состояние цепочки | SpecStep задачи N+1 не стартовал — ТЗ вне состояния базы не генерируется |
TC-CH-2.3-a | остановки четырёх классов: сбой доставки, провал агента, max_rework, закрытие MR человеком | конструируется причина остановки из каждого источника | четыре причины различимы и не пересекаются |
TC-CH-2.4-a | все задачи стори смержены | наблюдается состояние стори | стори закрыта следствием прохождения задач, без отдельного решения человека |
TC-CH-2.5-a | стори зарегистрирована, цепочка не запущена | вызов facade-эндпоинта, затем повторный вызов | цепочка стартовала; оба ответа несут один chain_id, второй workflow не создан |
Срезы CH-2
Задача разбита на три среза — каждый делает свой шаг к цепочке. Статус ToDo: это план, а не результат.
Срез 1 · запуск извне
Что нужно сделать: написать новый endpoint, которого сейчас нет. Он принимает
story_id, стартует StoryChainWorkflow и отвечает: chain_id (= workflow_id)
и первая задача по порядку. Сейчас вход — одна задача, после среза вход — целая стори.
Как делаем: добавляем маршрут в существующий FastAPI-процесс — тот самый, что
принимает вебхуки Forgejo (webhook_receiver.py, compose-сервис
ci-webhook-receiver). Новый процесс запускать не нужно. Worker остаётся чистым
поллером.
Критерий: запустил дважды на ту же стори — получил тот же chain_id,
второй цепочки не появилось. Идемпотентность видна и через endpoint, и при прямом запуске
в Temporal.
Зависит: CH-1-S1, SA-3-S1 · подзадача CH-2.5
Срез 2 · барьер мержа
Что нужно сделать: научить цепочку ждать мержа. Задача N+1 не начинается, пока не пришёл
сигнал о мерже MR задачи N. Спека (ТЗ) следующей задачи генерируется только после этого —
ТЗ вне определённого состояния базы (base_sha) не существует.
Как делаем: заводим причину остановки цепочки — четыре непересекающихся класса:
сбой доставки, провал агента, исчерпание max_rework, закрытие MR человеком
(расширение failure_reason.py с шага на цепочку). Причина видна снаружи, в состоянии
workflow. Сигнал о мерже приходит из приёмника вебхука (CH-3).
Критерий: до сигнала о мерже задачи N следующая спека не стартовала; остановка всегда называет свой класс; классификация шага не меняется по смыслу.
Зависит: CH-2-S1, SA-3-S2 · подзадачи CH-2.2, CH-2.3
Срез 3 · закрытие стори
Что нужно сделать: стори закрывается сама, когда пройдены все задачи. После мержа последней задачи фабрика сама переводит стори в «закрыто» — следствием прохождения задач, отдельного вердикта человека не запрашивается (стори — поперечная проверка, решение 2026-09-09).
Как делаем: проверяем оба исхода цепочки на поднятом стенде — довели до конца (стори закрыта) и остановились на середине (причина названа). Прогон закрепляем в репозитории; после мержа среза реестр тесткейсов CH-2 прогоняется целиком.
Критерий: после мержа последней задачи стори закрыта без вердикта человека; остановка на любом шаге несёт корректную причину из четырёх классов; ни один исход не оставляет цепочку без наблюдаемого состояния.
Зависит: CH-2-S1, CH-2-S2 · подзадача CH-2.4 · замыкающий
Связанные задачи
Полный контекст CH-2 по всем листам таблицы: задачи-соседи, подзадачи, срезы, решения, UX-сценарии и UX-контракты. Всё, чтобы сделать CH-2 целиком по всем репозиториям.
Задачи-соседи (лист «Задачи»)
| Задача | Статус | Что делает | Связь с CH-2 |
|---|---|---|---|
CH-1 | Done | Внесение пакета стори: регистрация стори, задач с порядком, подзадач и тесткейсов в artifacts-service (kinds user_story/task/subtask/test_case), связи parent/child, валидация полноты, идемпотентность. | Вход CH-2: пакет стори и порядок задач, которые цепочка проходит. |
SA-3 | Done | Гейт ТЗ: пауза на pending_review, approve через decide_transition(actor=human) → диспетч dev-agent на base_sha, reject с фидбеком → новая версия ТЗ. | Зависимость CH-2: каждая задача проходит гейт ТЗ до разработки. |
CH-3 | ToDo | Сигнал о судьбе MR: приёмник вебхука (CI-6) транслирует merged → переход к следующей задаче, closed без merge → остановка с причиной «закрыт человеком». | Потребитель CH-2: барьер мержа (CH-2-S2) ждёт именно этот сигнал. |
CI-6 | Done | Приёмник webhook Forgejo (путь Б): человеческий push/MR стартует/сигналит VerificationWorkflow. | База CH-3; приёмник — тот же FastAPI-процесс, куда добавляем facade-эндпоинт CH-2-S1. |
GW-1 | ToDo | Chat-gateway, синхронные команды: /submit, /run → CH-2.5, /report, /stats. | Вызывает facade-эндпоинт старта цепочки (UXC-3). |
GW-2 | ToDo | Push-канал событий боту: остановка цепочки с причиной (UXC-9), закрытие стори (UXC-10). | Читает исход CH-2 (остановка/закрытие). |
MT-1 | ToDo | Метрики стори: выгрузка telemetry, first-pass, реворки, минуты внимания. | Работает на незакрытой стори (UX-11). |
PL-1 | ToDo | Пилотный сквозной прогон стори через бот-поверхность. | Зависит от CH-3, RA-3, CM-3, CI-8, MT-1, GW-1..3 — после CH-2. |
Подзадачи CH-2 (лист «Подзадачи»)
| ID | Слой | Изменение | Критерий готовности | Зависит от |
|---|---|---|---|---|
CH-2.1 | Orchestration | Workflow цепочки стори: задачи в заданном порядке, workflow_id детерминирован от story_id — повторный запуск не порождает вторую цепочку. | Идемпотентность подтверждена повторным запуском. | — |
CH-2.2 | Orchestration | Барьер мержа: SpecStep следующей задачи стартует только по сигналу о мерже MR предыдущей — ТЗ вне состояния базы не определено (base_sha). | До сигнала мержа следующая ТЗ не генерируется. | CH-2.1 |
CH-2.3 | Домен | Причина остановки цепочки: тип различает сбой доставки, провал агента, исчерпание max_rework и закрытие MR человеком — расширение классификации NM-6 с шага на цепочку. | Четыре класса причин непересекающиеся, видимы в состоянии workflow. | — |
CH-2.4 | Тест | Закрытие стори и остановка с причиной. | После мержа последней задачи стори закрыта следствием, не решением человека; остановка несёт корректную причину. | CH-2.2, CH-2.3 |
CH-2.5 | Orchestration | Facade-эндпоинт старта цепочки по story_id — точка входа извне (UXC-3): стартует StoryChainWorkflow, возвращает chain_id (= workflow_id) и первую задачу; повторный вызов возвращает тот же chain_id. | Внешний вызов запускает цепочку; идемпотентность доступна через эндпоинт. | CH-2.1 |
Подзадачи CH-3: CH-3.1 (приёмник парсит pull_request и маршрутизирует сигнал: merged → переход, closed без merge → остановка «закрыт человеком»), CH-3.2 (оба исхода MR руками в Forgejo).
Срезы CH-2 и соседей (лист «Срезы», репозитории)
| Срез | Родитель | Репозиторий | Критерий готовности | Зависит от | Замыкающий |
|---|---|---|---|---|---|
CH-1-S1 | CH-1 | nikell28/artifacts-service | Фабрика принимает пакет стори одним синхронным вызовом; повторный — тот же story_id. | — | да · Done |
SA-3-S1 | SA-3 | nikell28/mr_meeseeks | Approve запускает диспетч dev-agent на base_sha, тело ТЗ — task prompt. | — | — · NeedSpec |
SA-3-S2 | SA-3 | nikell28/mr_meeseeks | Reject с фидбеком → новая версия ТЗ; rework_count растёт, max_rework ограничивает. | SA-3-S1 | — · NeedSpec |
SA-3-S3 | SA-3 | nikell28/mr_meeseeks | Оба вердикта гейта в одном прогоне на стенде; реестр тесткейсов SA-3 целиком. | SA-3-S2 | да · NeedSpec |
CH-2-S1 | CH-2 | nikell28/mr_meeseeks | Внешний вызов по story_id запускает цепочку и отдаёт chain_id + первую задачу; повторный — тот же chain_id, второй workflow нет. Эндпоинт — маршрут в существующий webhook_receiver (CI-6), worker чистым поллером. | CH-1-S1, SA-3-S1 | — · Done |
CH-2-S2 | CH-2 | nikell28/mr_meeseeks | До сигнала о мерже N SpecStep N+1 не стартовал; 4 класса причины непересекающиеся, читаются из состояния workflow; классификация шага не меняется. | CH-2-S1, SA-3-S2 | — · Done |
CH-2-S3 | CH-2 | nikell28/mr_meeseeks | На стенде: после мержа последней задачи стори закрыта следствием без вердикта человека; остановка несёт корректную причину; реестр тесткейсов CH-2 целиком зелёный. | CH-2-S1, CH-2-S2 | да · Done |
CH-3-S1 | CH-3 | nikell28/mr_meeseeks | Каждый из двух исходов MR даёт ровно один сигнал нужному workflow; исходы не смешиваются; маршрут CI-6 не меняется. | CH-2-S1, CH-2-S2 | да · NeedSpec |
Решения, на которые опирается CH-2 (лист «Решения»)
| Решение | Суть |
|---|---|
| 2026-09-09 | Стори — поперечная проверка, а не звено цепочки. Стори закрыта, когда закрыты все задачи, у которых она в рамке поведения; состояние по цепочке не наследуется. |
| 2026-08-17 | Ключ идентичности стори: story_id = uuid5(namespace, project + title). workflow_id цепочки детерминирован от story_id → повторный запуск не создаёт вторую цепочку. |
| 2026-08-17 | Правило «один срез = одно ТЗ = одна ветка = один PR» — декомпозиция CH-2 на три среза и их PR. |
| Р-8 | Исходы гейта ТЗ: PASS → approve; FAIL → реворк с потолком; ESCALATE (human_verdict_timeout) → остановка с собственной причиной, без роста rework_count и без диспатча спека-агента. |
| Р-4 | Форма доставки base_sha — верхнеуровневое поле артефакта spec, не ключ внутри body_json. |
| Р-9 | Промпт спека-агента расширен контрактом финализации: create_artifact (kind=spec, parent_id=task_id, base_sha из git rev-parse), затем task_result.json. |
UX-сценарии и UX-контракты
| ID | Сценарий | Контракт (UX-контракты) |
|---|---|---|
| UX-3 / UXC-3 | Автор шлёт /run <story_id> либо жмёт кнопку «Запустить». StoryChainWorkflow стартует, workflow_id детерминирован от story_id. | callback_data run:<story_id>; ответ — chain_id (= workflow_id) и первая задача по порядку; backend — CH-2.5, вызов синхронный. Повторный вызов идемпотентен на уровне CH-2.1: тот же chain_id, второй workflow не создаётся. |
| UX-8 / UXC-8 | Дев-ревьюер мержит/закрывает MR нативно в Forgejo, не в боте. | CH-3.1: merge → сигнал перехода к следующей задаче (или UXC-10, если задач не осталось); close без merge → UXC-9 с причиной «закрыт человеком». |
| UX-9 / UXC-9 | StoryChainWorkflow остановился — push-уведомление с причиной. | CH-2.3: один из четырёх классов причины (сбой доставки / провал агента / исчерпание max_rework / закрытие MR человеком) + куда возвращаться; канал GW-2 (шаблон UXC-4). |
| UX-10 / UXC-10 | Последняя задача стори смержена — стори закрыта. | CH-2.4: минимальный текст «стори закрыта»; закрытие — следствие прохождения задач, вердикт бот не запрашивает; канал GW-2. |
Репозитории
nikell28/mr_meeseeks — основная работа CH-2: StoryChainWorkflow, domain/failure_reason.py (4 класса причины), facade-эндпоинт в webhook_receiver.py (CI-6), тесты. nikell28/artifacts-service — входные данные CH-2: пакет стори, порядок задач (CH-1-S1), spec с base_sha (SA-3). meeseeks/chat-gateway — потребитель исхода CH-2 (GW-1: /run, GW-2: push об остановке/закрытии), заводится IC-9.
Открытые вопросы CH-2-S1
Решения, которых нет в таблице и на сайте — обсуждаем здесь, пока не станет понятно.
Что уже решено (из таблицы и сайта)
- Срез стартует и берёт первую задачу стори → запускается DevPipeline (спека → код → MR).
- Эндпоинт — маршрут в существующий
webhook_receiver(ci-webhook-receiver, порт 8300), не новый процесс. - Ответ:
chain_id(=workflow_id) + первая задача. Вызов синхронный. - Повторный вызов → тот же
chain_id, второй цепочки не создаётся. - Порядок задач читается из пакета стори (artifacts-service), однозначно.
Вопрос 1. Как эндпоинт узнаёт первую задачу?
Сначала — что уже есть и чего ещё нет, чтобы вопрос был понятен.
- Задачи стори уже упорядочены и лежат в базе. CH-1 записал стори и её задачи в artifacts-service; у каждой задачи есть поле
order(1, 2, 3…). Сервис умеет их отдать по порядку (list_story_tasks). - DevPipeline сейчас делает одну задачу, а не стори. Его вход —
spec_id/task_idодной задачи: спека (ТЗ) → код → MR. Про стори и порядок он не знает. - Кода, который читает задачи стори и гонит их по очереди, сейчас нет. Это и есть CH-2: новый workflow
StoryChainWorkflowпринимаетstory_id, читает задачи по порядку из artifacts-service и берёт первую.
Вопрос в одном месте: внешний вызов POST /story-chains должен в ответе вернуть «первая задача» — откуда эндпоинт возьмёт её, чтобы ответить?
Решено: A — эндпоинт стартует workflow и спрашивает у него через query. Эндпоинт workflow создаёт — он же у него и читает. Сам в сервис не ходит.
A · query к workflow — решено
Эндпоинт стартует цепочку и спрашивает у самого workflow через Temporal-query: «какая у тебя первая задача?». Workflow уже прочитал задачи из artifacts-service и знает ответ.
Плюс: весь доступ к сервису — внутри воркера, эндпоинт ничего не читает и логику порядка не дублирует.
Минус: сразу после старта workflow может ещё не успеть прочитать задачи → query вернёт пусто. Нужно короткое ожидание (ретрай).
B · эндпоинт сам читает сервис — отклонено
Эндпоинт идёт в artifacts-service (GET /artifacts/{story_id}/children), отбирает задачи по порядку и сам берёт первую.
Плюс: ответ готов сразу, гонки нет.
Минус: эндпоинту нужен адрес и токен сервиса, и правило «какие из детей — задачи и в каком порядке» придётся держать в двух местах (эндпоинт + воркер).
Вопрос 2. По какому адресу дёргать эндпоинт?
Конкретики в таблице нет — берём вариант по рекомендации: POST /story-chains.
- A —
POST /story-chains(рекомендация) - B —
POST /story-chains/start - C —
POST /run
Вопрос 3. Как выкладывать на дев-стенд?
Решено: для задачи CH-2 заводим одну большую ветку с именем вида ch2-implementation — в каждый затронутый репозиторий пушим свою копию этой ветки с изменениями. Когда будем тестировать на дев-стенде, подтягиваем все эти ветки, а на мастере разбираемся, как возводить контейнеры.
Баги
Известные проблемы, найденные при проверке поведения на дев-стенде. Не блокируют CH-2-S2/S3 (барьер работает от сигнала, а не от самого MR), фиксируются отдельно.
Реальный MR не создаётся — гонка goose / entrypoint dev-agent
Что. create_mr в workflow срабатывает от поля mr_title в result.json. А result.json собирает entrypoint dev-agent: читает task_result.json и переносит MR-поля. Но goose пишет task_result.json поздно — после того, как bridge уже прочитал файл, поэтому mr_title не попадает в result.json и create_mr не вызывается. Реальный MR не создаётся, хотя dev-шаг завершается exit 0.
Где. гонка в goose / entrypoint dev-agent (entrypoint.sh), отдельная проблема — не в цепочке. Барьер мержа (CH-2-S2) от этого не зависит: он ждёт сигнал о мерже, а не сам MR.
Спека-агент создавал spec с workflow_id=null — гейт ТЗ не будился автоматически (исправлено)
Что. spec-агент создавал артефакт spec через MCP create_artifact без поля workflow_id (остаётся null). Вердикт-нотификатор artifacts-service шлёт сигнал human_verdict воркфлоу только если current.workflow_id is not None. Человек апрувил спеку через POST /status — сигнал не уходил, гейт ТЗ (HumanVerifier, таймаут 24h) висел.
Фикс. DevPipeline проставляет workflow.info().workflow_id на spec через новую activity attach_workflow_id (эндпоинт POST /artifacts/{id}/workflow) перед submit_spec_for_review. Проверено на стенде: после апрува human_verdict приходит автоматически, ручной сигнал не нужен.
Связи
Вход
id стори (user_story), её задачи и порядок.
Выход
стори закрыта (все задачи смёржены) или остановка с причиной.
Перед
стори и задачи заведены (CH-1), гейт ТЗ работает (SA-3).
Дальше
CH-3 — приёмник вебхука шлёт цепочке ровно один сигнал (мерж → следующая задача; закрытие без мержа → остановка «закрыт человеком»); GW-2 — уведомления об остановке (UXC-9) и закрытии стори (UXC-10); MT-1 — метрики стори.