Перейти к основному содержимому

ADR 0185 — «Estou pronto — a necessidade está validada»: один клик закрывает готовность, gate necessidade-validada и принятие PO

Статус​

Accepted. 2026-09-30 (AT-311 и AT-312, история HS-079, эпик EP-031, раунд 32). Решение владельца, с двумя вопросами из исследования AT-309, на которые дан ответ ДА:

«Может ли один клик 'Estou pronto — a necessidade está validada' закрыть оба gate, даже если product_brief создаётся после клика?»

«Может ли принятие PO быть неявным в 'Estou pronto'?»

Пересматривает дизайн ADR 0095 (gate necessidade-validada закрывается ОТДЕЛЬНЫМ кликом) и RN-406. Не редактирует 0095: фиксирует, что меняется и что из него продолжает действовать.

Контекст​

Измерено в origin/dev (94e5dc721d), на основе исследования AT-309:

  • «Validar necessidade» ничего не блокировало. Кнопка включалась только при artifact.product_brief в сессии (hasProductBrief, session-readiness.ts), шла ПОСЛЕ «Estou pronto para produzir», записывала только necessity.validated (ValidateNecessityUseCase), а gate — warn в docs/gates.yml: сама RN-406 говорит, что ничто в продукте не ПРОВЕРЯЕТ прохождение.
  • Принять PO было ещё одним кликом сразу после «Estou pronto». Handoff Criativo→PO рождается внутри хода, который запускает «Estou pronto» (CriativoServer.executar_confirm_readiness/1 → EngineApiClient.create_handoff → POST /internal/sessions/:id/handoffs), а карточка принятия вызывала AcceptHandoffUseCase, который записывает переход в accepted, handoff.accepted (актор — человек), agent.activated (актор — человек) и, по RN-635, handoff.superseded других предложений PO в проекте.
  • Три клика — готов, проверить, принять — чтобы выразить одно намерение: «то, что собрал Criativo, верно, можно продолжать».

0095 явно отказался сливать проверку с confirm_readiness, с аргументом, который остаётся верным: confirm_readiness сам по себе — это структурный НИЖНИЙ ПОРОГ («≥1 зафиксированное правило», RN-142), а не суждение по СУЩЕСТВУ, и слияние двух «сохранило бы мелкий порог, замаскированный под новый gate». На это возражение здесь отвечает МЕТКА: клик перестаёт быть «Estou pronto para produzir» и становится «Estou pronto — a necessidade está validada». Человек объявляет обе вещи словами обеих; структурный порог по-прежнему проверяется engine, а суть по-прежнему объявляется человеком.

Решение​

1. Один POST, два события (RN-657)​

POST .../sessions/:id/readiness (ConfirmReadinessUseCase) записывает readiness.confirmed, вызывает engine и, только если engine принял ход, записывает necessity.validated с тем же человеком в качестве актора, productBriefId: null, via: 'readiness.confirmed' и readinessEventId. Нулевой productBriefId — ответ на вторую половину вопроса владельца: brief рождается из хода, который запускает клик, и событие указывает на КЛИК, а не на ещё не существующий brief.

Отказ engine (409 ход идёт, 422 нет правила) поднимается до второго события: без принятого хода нет brief в пути, и потребность не становится проверенной. readiness.confirmed по-прежнему записывается ДО команды, как всегда (ADR 0163).

Gate остаётся warn. Ничто не начало его проверять, и его повышение остаётся продуктовым решением с собственным ADR, как сказал 0095.

2. Принятие PO неявное, с ТЕМИ ЖЕ событиями (RN-658)​

readiness.confirmed нового клика несёт метку { necessidadeValidada: true, aceiteImplicitoDoPo: true }. Когда engine создаёт предложение Criativo→PO по внутреннему маршруту, api спрашивает (decidirAceiteImplicitoDoPo, чистое правило), является ли оно тем предложением, которое запросил этот клик, — criativo → po, offered, этой сессии, несущее product_brief, родившийся ПОСЛЕ самого свежего помеченного readiness.confirmed, — и, если да, принимает его через AcceptHandoffUseCase, ТОТ ЖЕ сценарий использования, что у карточки, от имени того, кто кликнул.

Проверяемый след — как у карточки, без нового события: handoff.accepted и agent.activated с человеком в качестве актора и метка implicito: { via: 'readiness.confirmed', readinessEventId } в payload обоих. Из двух вариантов, оставленных владельцем (новое событие или те же с человеком-актором и меткой), выбран второй, потому что каждый сегодняшний читатель — canActivateAgent, список handoffs, проекция графа, экран — уже понимает handoff.accepted; новое событие потребовало бы научить каждого, а забытый прочитал бы PO как никогда не принятый. Ни одно событие не редактируется.

Принятие происходит во внутреннем маршруте, а не в POST клика, потому что предложения в момент клика не существует. Сбой принятия не поднимается к engine (Criativo рассказал бы «не смог предложить handoff», что ложно): он становится устойчивым agent.error, актор system/aceite-implicito, с источником. 400 «не offered» — не сбой: другая вкладка решила предложение в промежутке, и это уже в log.

3. Engine не меняется​

Измерено: Criativo ничего не ждёт от принятия (ответ внутреннего маршрута лишь сопоставляется с {:ok, _}), а PO включается тем же привычным startAgent, чей kickoff — GenServer.cast. Единственное отличие — МОМЕНТ: PO поднимается внутри вызова создания предложения, ещё в ходе Criativo, а не после клика человека.

4. Экран​

Кнопка называется «Estou pronto — a necessidade está validada» (en: «I'm ready — the need is validated»), заголовок во включённом состоянии говорит, что закрывает клик и что PO входит сам, а уведомление об успехе повторяет это. Кнопка «Confirmar necessidade validada» ушла вместе с handleValidateNecessity и validateNecessity клиента. Клик также делает PO получателем composer — это жест его вызова, тот же, что принятие через карточку (RN-631).

Последствия​

За

  • С трёх кликов до одного, не трогая ни одного потолка: принятие PO не подготавливает репозиторий (только Arquiteto и Dev Lead, RN-582), не делает ни merge, ни push.
  • Проверка по-прежнему принадлежит ЧЕЛОВЕКУ, никогда Criativo, — антипаттерн, ради избежания которого существовал 0095, по-прежнему избегается.
  • След принятия тот же, что у карточки, читаемый каждым существующим потребителем, и метка говорит, из какого клика он пришёл.

Против, объявлено

  • necessity.validated перестаёт указывать на brief, который он проверил: связь — через readinessEventId, а brief — это artifact.product_brief, следующий за тем кликом. Кому нужна точная пара, читает log по порядку.
  • Проверка объявляется ДО того, как человек прочитает резюме. Так решил владелец; gate — warn и измеряет объявление, а не чтение.
  • POST .../agents/criativo/validate-necessity остаётся в api, без потребителя на экране: это путь для сессии, чей «Estou pronto» предшествует этому ADR (там карточка принятия тоже остаётся, потому что у старого события нет метки). Его удаление — нарушение контракта API, и оно остаётся на время, когда не будет старой сессии, которая его использует.
  • PO поднимается без клика человека в момент подъёма. Человек решил раньше, в клике, и именно он записан как актор.

Рассмотренные альтернативы​

Новое событие (handoff.accepted_implicitly). Отвергнуто: см. пункт 2 — каждому читателю handoff.accepted пришлось бы изучить второй тип.

Принимать в POST клика, предварительно создав предложение. Отвергнуто: предложение несёт product_brief как артефакт и рождается из хода Criativo; создать его раньше потребовало бы изменить engine и предложить handoff без артефакта, который его обосновывает.

Позволить engine вызывать человеческий маршрут принятия. Отвергнуто: человеческий маршрут требует аутентифицированного пользователя, а агент, включающий агента, — это ровно то, от чего отказывается описание внутреннего маршрута. Здесь решает api, по метке, которую записал ЧЕЛОВЕК.

Ссылки​

  • ADR 0095 — дизайн, который этот пересматривает
  • ADR 0163 — клик отвечает при принятии
  • ADR 0182 — superseded, сохранено
  • RN-406, RN-657, RN-658, RN-633, RN-635
  • apps/api/src/domain/sessions/estou-pronto.ts
  • apps/api/src/application/use-cases/agents/confirm-readiness.use-case.ts
  • apps/api/src/application/use-cases/agents/aceite-implicito-do-po.use-case.ts