ADR 0095 — gate necessidade-validada 由单独的人工确认关闭,而不是由 Criativo 自我验证
- 状态: 已接受
- 日期: 2026-08-17
- 背景: fluxo.yml × 代码审计(第 6 波/最后一波,B2 项,
docs/explanation/auditoria-fluxo-vs-codigo.md,D 节) - 扩展: ADR 0054(
docs/gates.yml设计的所有者) - 参考:
docs/explanation/modelo-de-time.md——促成本决策的反模式已作为待办事项记录在那里
背景
docs/fluxo.yml(角色 criativo)自 ADR 0085(fluxo v3)起声明了 gate_saida: { id: necessidade-validada, status: proposto }。该 gate 从未真正存在:docs/gates.yml 中没有条目,代码中也没有任何机制。docs/explanation/modelo-de-time.md 已在“待决定的提议”列表中记录了它停滞的原因:
Anti-padrão do Criativo como validação real do gate
necessidade-validada.
反模式是这样的:Criativo 是产出业务需求的 agent(通过 emit_artifact/confirm_readiness,汇总为
artifact.product_brief)。如果由模型自己决定这个需求何时“已验证”——例如从对话中推断就绪——那么 gate 就是作者在自我评估,而不是真正的关口。一个没有独立验证的 gate 什么也测不了;它只是假装在测。
已经存在但不是这个的东西
confirm_readiness/RN-142——结构性下限:当会话中捕获的业务规则为零时拒绝(CriativoServer,handle_call(:confirm_readiness, ...))。这是对形式(有内容吗?)的检查,而不是对实质(内容对吗?)的检查。它继续完全照原样有效——本 ADR 不触碰它。AcceptHandoffUseCase——PO 接受 handoff 是结构性的:把 handoff 转为accepted并启用目标 agent。它不评判所接受内容的内容;这是每个 handoff 都使用的同一通用机制(QA 从 dev 接受、Dev Lead 从 Arquiteto 接受等)。把这次接受当作“需求验证”会悄悄复用一个含义不同的事件。
两者都不是实质性的 gate。缺少的是第三个刻意设计的机制。
决策
gate 由用户明确且单独的一次点击关闭——与“Confirmar arquitetura pronta”(RN-160)同一交互模式:SessionPage.tsx 中一个专用按钮,一条专用 HTTP 路由
(POST .../agents/criativo/validate-necessity),一个专用用例
(ValidateNecessityUseCase),写入领域事件
necessity.validated(RN-406)。
串联:在 confirm_readiness 之后,而不是与之并行
有两种顺序都合理:(a) 验证按钮只在 confirm_readiness 已运行之后(product_brief 已存在)才启用;或 (b) 两个按钮并存,各有自己的前置条件,彼此没有顺序关系。
选择 (a)。决定性的论据:“验证”一份尚未汇总的 product_brief 毫无意义——necessidade-validada 在 docs/fluxo.yml 中是 Criativo 的出口 gate,即他的工作已经产出一个具体工件供用户评判的时刻。选项 (b) 将允许在代表需求的执行摘要存在之前验证需求,这会颠倒流程本身的顺序(需求就是汇总后的 product_brief——而不是之前的自由对话)。hasProductBrief(events.some(e => e.type === 'artifact.product_brief'))是新按钮的前置条件,与已经守护两个相邻按钮的 hasBusinessRule/hasPromotedStory 同一模式。
为什么不通知 engine
不同于 OfferInfraHandoffUseCase(RN-160/ADR 0086),它调用 engineClient.offerInfraHandoff/offerDevHandoff,因为确认正是启动下一步的东西;这里 Criativo→PO 的 handoff 已经在 confirm_readiness 内部发生了
(CriativoServer.executar_confirm_readiness/1 在这个 gate 存在之前就已调用
EngineApiClient.create_handoff)。没有任何 agent 在等待 necessity.validated 来行动——该事件只是记录有人确认了已交付内容的实质。ValidateNecessityUseCase 的构造函数中刻意没有通往 engine 的 port:只有 SessionEventRepository(读取最新的 product_brief)和 AppendSessionEventUseCase(写入)。
docs/gates.yml:warn,而不是 block
aprovacao_humana: true(字面上就是一次人工点击)和 verificacao: script(验证脚本已经可以从 event log 中提取 necessity.validated)。但 severidade: warn:今天产品中没有任何机制在让 PO 继续之前查询这个 gate 是否通过——不同于 story-promovida/plano-de-adocao(block,因为真实的代码锁阻止它们被跳过),这个 gate 是测量,而不是关口。与 implementavel 中已使用的注释同一精神:“即使启用也以 warn 诞生”。如果以后出现阻断机制(例如 PO 在没有验证时拒绝工作),提升为 block 是新的产品决定,需要它自己的 ADR——不隐含在本次交付中。
后果
有利
- 关闭 fluxo.yml × 代码审计计划六个波次中的最后一个
(
docs/explanation/auditoria-fluxo-vs-codigo.md,D 节)——六个都已关闭(第 3、4、5 波已由 ADR 0089/0090 提前、不按顺序关闭;第 1、2 波在之前的 PR 中关闭;这是第 6 个也是最后一个)。 docs/gates.yml不再有一个没有登记的active角色 gate——审计指出的这类缺口中的最后一个。- 机制是可辨认的:读过“Confirmar arquitetura pronta”(RN-160)的人可以零成本读懂这个——同样的专用按钮 + 前置条件 + 专属事件模式。
不利
- Criativo 的流程中多了一次点击,而
confirm_readiness已经独自产出product_brief和 handoff。接受:正是这多出的一次点击区分了“模型决定了”与“人验证了”——没有它,就根本没有 gate,只是一贯的机制换了个新名字。 warngate 不会阻止任何东西在没有它的情况下继续——一个项目可以永远不触发necessidade-validada,PO 照常工作。刻意接受:在产品中没有任何人要求硬关口的情况下发明一个阻断,将是 ADR 0041/0042/0077 已经针对凭空捏造的 capability/说明所拒绝的同一类错误——把 gate 声明为诚实的测量,好过假装它锁住了它并不锁住的东西。
考虑过的备选方案
复用 confirm_readiness——一个已经同时算作两者的单一按钮。 被产生本 ADR 的请求明确拒绝:
confirm_readiness/RN-142 是结构性下限(“至少捕获了 1 条规则”),而不是对需求内容的实质判断。合并两者会让浅层下限伪装成新的 gate。
由 Criativo(模型)通过新工具(作为 tool call 的 confirm_necessity_validated)自行决定。 被拒绝:这正是本 ADR 存在所要避免的反模式——产出 product_brief 的同一个 agent 不能是证明它正确的那个。
把 AcceptHandoffUseCase(PO 接受 handoff)当作验证。 被拒绝:这次接受是结构性的,对产品中的每个 handoff 都是通用的——仅为这一来源改变它的含义,会打破 QA、Infra 和 Dev Lead 同样依赖的“接受 = 我开始工作了”这一解读。
从一开始就 severidade: block,在需求被验证之前锁住 PO。 被拒绝:这需要发明一个阻断机制(例如 ActivateAgentUseCase 在没有 necessity.validated 时拒绝启用 PO),而没有人要求过,今天对任何等价的 gate 也不存在(arquitetura-pronta/RN-160 除了对已晋升故事的重新验证之外,在后端也不阻断任何东西)。作为未来的可能性登记,而不是本次交付的一部分。
参考
apps/api/src/application/use-cases/agents/validate-necessity.use-case.tsapps/api/src/interfaces/http/agents/agents.controller.ts(validateNecessityHandoff)apps/web/src/routes/SessionPage.tsx(hasProductBrief、necessidadeJaValidada、handleValidateNecessity)docs/gates.yml——gatenecessidade-validadadocs/fluxo.yml——角色criativo,gate_saida- RN-406
- RN-160——被复制的交互模式
docs/explanation/modelo-de-time.md——促成该决策的反模式(本 ADR 将其从待办列表中移除)docs/explanation/auditoria-fluxo-vs-codigo.md——发现 B2,D 节 (第 6 波,计划的最后一波)