Задача · CH-2 · СРЕЗ 1 · P0 · Done

StoryChainWorkflow

Работа идёт не одной задачей, а цепочкой задач стори: следующая задача не начинается, пока предыдущая не смёржена. Это «внешняя рамка» вокруг DevPipeline.

Статус: Done · зависит от SA-3 (гейт ТЗ) · все задачи · на главную

Что это за задача

StoryChainWorkflow — следующий срез фабрики: проходит все задачи одной стори по очереди. Сейчас DevPipeline умеет сделать одну задачу от спеки (ТЗ) до кода. StoryChain берёт целую стори — несколько задач — и гонит их последовательно.

Зависит от: CH-1, SA-3.

Простая аналогия: DevPipeline — это один рабочий день по одной задаче. StoryChainWorkflow — это вся неделя: день за днём, по очереди, ничего не пропуская.
Стори — поперечная проверка, а не звено цепочки (решение 2026-09-09): она стоит в рамке поведения задач и закрыта, когда закрыты все её задачи. Цепочка лишь проходит задачи по очереди — закрытие стори от этого не зависит.

Главное правило: не перепрыгивать

Цепочка строгая: ТЗ следующей задачи не генерируется, пока предыдущая не смёржена.

Зачем так строго: задачи стори часто зависят друг от друга — следующая строится на коде предыдущей. Если гнать параллельно, они конфликтуют; если перепрыгивать — следующая не имеет базы.

Остановка несёт причину

Цепочка останавливается не «просто так», а с явной причиной. Классы остановки (четыре из среза 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",
)
Это первый срез CH-2: сейчас вход — задача (run(spec_id, task_id)), а цепочки стори ещё нет. После CH-2 вход — стори целиком.

Как это ложится на уже готовое

StoryChain не строит механизм заново — он переиспользует готовые части:

DevPipeline

одна задача: спека (ТЗ) → код → MR. Его запускает StoryChain на каждую задачу.

Гейт ТЗ (SA-3)

спека задачи проходит ЧЕЛОВЕК до разработки — уже готово.

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-1DoneВнесение пакета стори: регистрация стори, задач с порядком, подзадач и тесткейсов в artifacts-service (kinds user_story/task/subtask/test_case), связи parent/child, валидация полноты, идемпотентность.Вход CH-2: пакет стори и порядок задач, которые цепочка проходит.
SA-3DoneГейт ТЗ: пауза на pending_review, approve через decide_transition(actor=human) → диспетч dev-agent на base_sha, reject с фидбеком → новая версия ТЗ.Зависимость CH-2: каждая задача проходит гейт ТЗ до разработки.
CH-3ToDoСигнал о судьбе MR: приёмник вебхука (CI-6) транслирует merged → переход к следующей задаче, closed без merge → остановка с причиной «закрыт человеком».Потребитель CH-2: барьер мержа (CH-2-S2) ждёт именно этот сигнал.
CI-6DoneПриёмник webhook Forgejo (путь Б): человеческий push/MR стартует/сигналит VerificationWorkflow.База CH-3; приёмник — тот же FastAPI-процесс, куда добавляем facade-эндпоинт CH-2-S1.
GW-1ToDoChat-gateway, синхронные команды: /submit, /run → CH-2.5, /report, /stats.Вызывает facade-эндпоинт старта цепочки (UXC-3).
GW-2ToDoPush-канал событий боту: остановка цепочки с причиной (UXC-9), закрытие стори (UXC-10).Читает исход CH-2 (остановка/закрытие).
MT-1ToDoМетрики стори: выгрузка telemetry, first-pass, реворки, минуты внимания.Работает на незакрытой стори (UX-11).
PL-1ToDoПилотный сквозной прогон стори через бот-поверхность.Зависит от CH-3, RA-3, CM-3, CI-8, MT-1, GW-1..3 — после CH-2.

Подзадачи CH-2 (лист «Подзадачи»)

IDСлойИзменениеКритерий готовностиЗависит от
CH-2.1OrchestrationWorkflow цепочки стори: задачи в заданном порядке, workflow_id детерминирован от story_id — повторный запуск не порождает вторую цепочку.Идемпотентность подтверждена повторным запуском.—
CH-2.2OrchestrationБарьер мержа: 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.5OrchestrationFacade-эндпоинт старта цепочки по 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-S1CH-1nikell28/artifacts-serviceФабрика принимает пакет стори одним синхронным вызовом; повторный — тот же story_id.—да · Done
SA-3-S1SA-3nikell28/mr_meeseeksApprove запускает диспетч dev-agent на base_sha, тело ТЗ — task prompt.—— · NeedSpec
SA-3-S2SA-3nikell28/mr_meeseeksReject с фидбеком → новая версия ТЗ; rework_count растёт, max_rework ограничивает.SA-3-S1— · NeedSpec
SA-3-S3SA-3nikell28/mr_meeseeksОба вердикта гейта в одном прогоне на стенде; реестр тесткейсов SA-3 целиком.SA-3-S2да · NeedSpec
CH-2-S1CH-2nikell28/mr_meeseeksВнешний вызов по story_id запускает цепочку и отдаёт chain_id + первую задачу; повторный — тот же chain_id, второй workflow нет. Эндпоинт — маршрут в существующий webhook_receiver (CI-6), worker чистым поллером.CH-1-S1, SA-3-S1— · Done
CH-2-S2CH-2nikell28/mr_meeseeksДо сигнала о мерже N SpecStep N+1 не стартовал; 4 класса причины непересекающиеся, читаются из состояния workflow; классификация шага не меняется.CH-2-S1, SA-3-S2— · Done
CH-2-S3CH-2nikell28/mr_meeseeksНа стенде: после мержа последней задачи стори закрыта следствием без вердикта человека; остановка несёт корректную причину; реестр тесткейсов CH-2 целиком зелёный.CH-2-S1, CH-2-S2да · Done
CH-3-S1CH-3nikell28/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-9StoryChainWorkflow остановился — 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

Решения, которых нет в таблице и на сайте — обсуждаем здесь, пока не станет понятно.

Что уже решено (из таблицы и сайта)

Вопрос 1. Как эндпоинт узнаёт первую задачу?

Сначала — что уже есть и чего ещё нет, чтобы вопрос был понятен.

Вопрос в одном месте: внешний вызов 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.

Вопрос 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 — метрики стори.