ADR 0090 — QA 策略和 AppSec 作为第二个时刻,而不是新 agent
- 状态: 已接受
- 日期: 2026-08-17
- 背景: 产品所有者有意识地提前触发条件——不是
docs/fluxo.yml所预期的"自然触发"(hoje_absorvido_por+criterio_de_separacao),而是构建连接该 gate 之物这项工作本身 - 激活: gate
implementavel(docs/gates.yml,自 FASE 14d 起为status: planned) - 修订以下内容的一部分: ADR 0053(它把 gate
implementavel声明为planned) - 直接先例: ADR 0038(QA 区域——Lead + 子专业)、ADR 0085(
docs/fluxo.yml,早已把这两个角色预期为proposto)
背景
docs/fluxo.yml(ADR 0085)把目标模型中的两个角色记录为 proposto,每个都在文件本身中写明了分离标准:
qa-estrategia — hoje_absorvido_por: area-qa (que só atua DEPOIS, na PR). criterio_de_separacao: Quando o gate implementavel ativar — o plano-de-teste é entrada dele. Pode ser o próprio qa-lead em segundo MOMENTO, não necessariamente agente novo: a separação é de entregável.
appsec — hoje_absorvido_por: secops (que só atua na PR). criterio_de_separacao: Mesmo padrão do QA: dois MOMENTOS, não dois agentes por ora. Threat model no design ativa quando o gate implementavel existir. Agente separado só com produção real.
声明的触发条件("当 gate implementavel 激活时")从未自然触发——产品中还没有任何东西需要它。产品所有者决定提前:触发条件就是构建连接该 gate 的机制这项工作本身,而不是等待发生的使用症状。
本次交付完整覆盖 qa-estrategia。appsec 仍为 proposto,遵循同一原则(两个时刻,不是两个 agent),实现放在另一条工作线中——模式和决定是同一个,在此描述;appsec/threat-model 的代码留到那条工作线运行时再写。
决策
QA 策略就是处于第二个时刻的 qa-lead——同一个进程,独立的可交付物。 没有新 agent,名册中也没有自带名字的新 GenServer。Engine.Gates.QaLeadServer 在一贯的 run/2(PR 审查)旁边增加一个追加式入口 run_design/3:
各个部件
Engine.Gates.QaEstrategiaContext.fetch/3——轻量上下文:只有 story(来自EngineApiClient.list_backlog/1,PO 自 RN-164 起就在读的那棵树)和当前module_map(来自EngineApiClient.get_infra_context/2,Infra Lead 所消费的同一个GetInfraContextUseCase,这里只用moduleMap字段)——没有dev_state,没有worktree_path。api 中没有新路由:这两个函数早已存在,而 gateimplementavel在 PRE-DEV 运行——还没有 dev agent、worktree 或task_id。Engine.Gates.QaEstrategiaAgent——无状态模块(不是GenServer),与Engine.Gates.QaPerformanceSegurancaAgent形式相同:没有Terminal的工具注册表(ReadFile、SearchWorkspace、EmitPlanoDeTeste),运行 harness 的通用ToolLoop。三个工具都不经过Engine.Harness.Hooks.ActionPipeline(只有terminal/write_file经过)——这个 agent 从不挂起,run_design/3在自己的handle_cast内同步运行。它在调用它的会话的 event log 中发出artifact.plano_de_teste(schema 在Engine.Harness.ArtifactSchemas中校验)。Engine.Gates.QaLeadServer.run_design/3——GenServer.cast,与run/2同一风格,没有DevAgentState.find_by_task_id。通过Engine.Gates.Dispatcher.run_qa_estrategia/3调用(与run_qa/2/run_secops/2早已使用的、在测试中可替换的同一间接层——避免在轻量测试中启动真实的 GenServer)。Engine.Agents.DevLeadTools.assess_implementability——Dev Lead 的新工具。在 Dev Lead 自己会话的历史中读取该 story 最新的artifact.plano_de_teste:decide.ts增加assess_implementability,最低角色maintainer,有意放在绝对上限块之外——与propose_execution_plan同样的推理:它是会话的初始决定,不是对已授权上限的突破。docs/gates.yml,implementavel:status: planned→active,evidencia指向 event log(按actionType: assess_implementability过滤的proposed_action.created)。severidade仍然是warn——没有被触碰;登记早已写着"即使激活也以 warn 诞生"。
为什么迭代上限没有增加新子句
Engine.Harness.Iteracoes.tipo/1 把未知 agent 归类为 :conversacional(上限 8)。"qa-estrategia" 落入这个默认值,这是正确的决定——不是缺口。RN-085 的标准不是"谁干得多":而是"在迭代上限之外是什么拦住花费"。这个 agent 在没有 token_budget_micros 的情况下运行(没有 task,PRE-DEV),与 infra-workflows 的情况相同——后者即使使用工具也保持在 8,因为在底下没有预算的情况下提高上限,会把最坏情况成倍放大而没有任何东西来约束。
后果
有利
- 行为开始与
docs/fluxo.yml早已声明的一致——与 ADR 0086 同一类修正(关闭声明的流程与代码之间的分歧),只是这里的"代码"从未存在过。 - 可交付物的分离在没有新基础设施成本的情况下得到证明:零 GenServer、零新 HTTP 路由、零表。
- gate
implementavel在 event log 中获得可测量的证据,与 ADR 0054 同一精神——记录下来的决定正是让通过 gate 变得可测量的东西。
不利
- "我触发了评估"与"计划已存在"之间的等待窗口是异步且没有回调的。 Dev Lead 只有在模型决定再次调用
assess_implementability时才会发现计划已就绪——没有自动恢复(与 ADR 0086 中由事件驱动的审批挂起不同)。之所以接受,是因为Engine.Gates.QaEstrategiaAgent在一个独立的进程(qa-lead)中运行,而 Dev Lead 不能在不耦合两者的情况下,阻塞一个同步的run_turn/2去等待另一个进程的结果——而 14 次迭代的上限给了模型足够的余量在同一个轮次内再试。 appsec仍为proposto。 本次交付没有解决docs/fluxo.yml同一段中声明的第二个角色——有意识的范围决定,记录在此,以便下一条工作线复用同一模式而无需重新打开决定。
考虑过的替代方案
新的 qa-estrategia agent,自己的 GenServer。 被拒绝:docs/fluxo.yml 本身早已反对——"不一定是新 agent:分离的是可交付物"。一个新 GenServer 会复制 QA 区域的 Wake/Registry 连线而毫无收益,因为这条路径从不挂起,也不需要那套证明独立进程合理的恢复机制。
assess_implementability 阻塞等待测试计划(端到端同步调用)。 被拒绝——这需要 Dev Lead 在自己的进程中直接调用 QaEstrategiaAgent.run/4,并在 Dev Lead 的轮次内部运行 QA 策略的 ToolLoop。技术上可行(两个工具循环都不挂起),但会耦合两个 agent——QA 策略中的一次读取错误会拖垮 Dev Lead 的整个轮次,而一方的迭代上限会开始吃掉另一方的。保持进程分离、让结果经由 event log 传递,是 api↔engine 之间一切通信早已采用的同一设计。
复用 Engine.Dev.ContextBuilder.fetch/3(QaPerformanceSegurancaAgent 早已使用的构建器)而不是新的构建器。 被拒绝:那个构建器按 task_id 工作——它假定已有 dev agent、worktree 以及已提交到某个分支的代码。为 PRE-DEV 的评估强塞一个虚构的 task_id,会颠倒 gate 存在所要保证的顺序(在花 token 写代码之前评估)。
参考
docs/fluxo.yml— 角色qa-estrategia(现为active)和appsec(仍为proposto)docs/gates.yml— gateimplementavel(现为active)- ADR 0053 — 把 gate
implementavel声明为planned - ADR 0038 — QA 区域(Lead + 子专业),本设计的结构先例
- ADR 0086 —
assess_implementability所重复的三结局proposed_action模式 apps/engine/lib/engine/gates/qa_estrategia_agent.ex、qa_estrategia_context.ex、qa_lead_server.ex、dispatcher.exapps/engine/lib/engine/agents/dev_lead_tools.ex、dev_lead_server.exapps/api/src/domain/actions/decide.ts