阅读材料: 应用部分 2. 规格缺陷诊断

模块「应用部分 2. 规格缺陷诊断」中第 1 / 5 节课
您正在未登录状态下查看课程。 请登录,以保存进度并参加测试。

应用部分 2. 规范缺陷诊断

状态:建议。 向规范中注入一个受控缺陷——这是一种接近变异测试(mutation testing)的教学技术。具体的缺陷类别(cyclepriority_conflicthidden_out_of_scope)在项目中有所应用,但尚未统一。卡滞指标(ask_stormstage_regress)属于前沿探索。

该技术的工程名称是受控缺陷规范:你故意引入一个缺陷来检验诊断能力。文中有时使用简称"毒规范",但它不应掩盖核心规则:一次变异、一个症状、一个恢复标准。

本章延续第一卷的两个基本理念:第7部分的否定性需求,以及第20部分的SDD反模式。区别在于,现在的缺陷是故意引入的,并且事先受到限制。不要试图在此处检验整个分类流程:教学最小集是 poisoned/fixed 配对和 validation.md 中的一行恢复记录。

阅读前准备

  • 第一卷支撑:第7部分提供否定性需求,第20部分提供SDD反模式。
  • 本地教学案例:appointment_latency,因为优先级冲突无需外部基础设施即可显现。
  • capstone/ 的跟踪项:high_memory_usage 的 poisoned/fixed 配对和 validation.md 中的一行恢复记录。
  • 第一遍的核心术语:受控缺陷。
  • 可延后内容:ask_stormstage_regress 指标、完整回退测试和自动循环检测。

与相邻技术的边界很简单。本章——一个手动引入的缺陷和一个卡滞症状。第4章——一个针对形式化 Then 的最小反例。第5章——多个确定性变异体用于检验验证器。第8章——正式的争议、证据和先例协议。

本章的场景是 appointments-api 路由的延迟增长,即第一卷第11部分中已出现的 Hono JSX 代理页面。同一领域,但在压力下运行。经典错误目录——变异所依据的基础——见第20部分 SDD反模式

目标

完成本章后,你将能够故意破坏事件分类规范,发现 Qwen Code 的卡滞点,并将规范推进到稳定、可复现的状态。

教学价值不在于立即获得完美的分类流程。目标是学会可控地制造故障、读取其痕迹并修复需求中的根本原因。结果将是一项实用技术:

  • 每次迭代一个缺陷;
  • 可测量的死锁诊断;
  • 形式化的矛盾消解;
  • 完整 SDD 回路 Specify → Plan → Tasks → Implement 的回退测试。

最小教学场景

教学案例

事件 appointment_latency:规范同时要求"30秒内升级P0"和"任何升级前需人工确认"。需要固定一个优先级冲突并用例外规则修复。

准备工作

  • book2/examples/templates/validation.md — 检验记录模板。
  • 两个简短文件或章节:poisoned-spec.mdfixed-spec.md
  • 一个预期症状:ask_stormstage_regressphase_context_loss

第一遍的最小 poisoned/fixed 配对:

poisoned:

REQ-LAT-01: latency_p95 >= 2s 且 severity=P0 要求30秒内升级。priority=100
REQ-LAT-02: 任何升级都需要预先人工审批。priority=100

fixed:
REQ-LAT-01: severity=P0 适用 p0_time_critical_override。
REQ-LAT-02: 当 p0_time_critical_override 时,升级立即允许,但 human_audit_required=true。
REQ-LAT-03: 对于 P1-P3,预先人工审批仍保持阻塞性。

这些行可放入本地案例 appointment_latency 的教学用 poisoned-spec.mdfixed-spec.md 中。若最终考核基于 high_memory_usage,则仅将缺陷类别和下方恢复行转入 capstone/。每次只变更一个缺陷:此处为优先级冲突。

步骤

  1. poisoned-spec.md 中记录两条相同 priority 的冲突规则。预期:缺陷在数据中可见,而非隐藏在注释中。
  2. 启动分析前记录预期症状:例如 priority_conflict=true && escalation_path_resolved=false
  3. 进行手动审查或对 Qwen Code 发起 Plan Mode 请求,不修改文件。预期:模型指出冲突或在争议点失去进展。
  1. fixed-spec.md 中添加 p0_time_critical_override,将人工检验转为事后审计。
  2. validation.md 中固定两个事实:原始冲突已找到,修复路径保留 human_audit_required=true
  3. 若需自动检验需求形式和计划,可将结果与 [examples/spec-ci/](examples/spec-ci/README.md) 中 Spec CI 的可运行类比进行比对。

检验事实

修复变更的是可检验规则,而非仅解释文字。validation.md 中有恢复行:priority_conflict=false && escalation_path_resolved=P0 && audit_required=true

如何进入 capstone/

capstone/poisoned-spec.md 转入恰好一个缺陷,向 capstone/fixed-spec.md 转入恰好一个修复。在 capstone/validation.md 中添加恢复行。不要转入冗长的 Plan Mode 跟踪:考核看重的是缺陷类别、补丁和冲突不再复现的事实。

最小片段(同一 priority_conflict 类别从 appointment_latency 迁移到主考核案例 high_memory_usagerestart_pod 许可与相同优先级的人工审批要求相冲突):

- defect_class: priority_conflict
- poisoned: memory_percent >= 90 持续10分钟允许 restart_pod,但任何 restart_pod 需要相同优先级的预先人工审批。
- fixed: restart_pod 仅对 stateless pod 作为预批准动作允许,而首次生产运行需要 human_review_for_first_run=true。
- validation: priority_conflict=false && action=restart_pod && human_review_for_first_run=true

可审查痕迹

在教学包中保留 poisoned-spec.md / fixed-spec.md 配对和 validation.md 记录。若 out/* 仅由本地项目脚本生成,则不需要。

核心思想

每次迭代恰好引入一种缺陷类型。此处"缺陷"指三种受控规范变异之一:

  • 循环 — 状态之间的循环依赖(例如 WAIT_APPROVAL → VALIDATE_ESCALATION → WAIT_APPROVAL);
  • 优先级冲突 — 两条相同优先级规则导致互斥动作(如"30秒内升级P0"和"等待人工确认");
  • 隐藏越界(hidden out-of-scope)— 需求强制执行的动作被 constraints 禁止(例如验收测试中的 Jira 工单,而约束中禁止 Jira)。

若同时添加递归依赖、争议升级规则和禁止集成,Qwen Code 的跟踪将显示总体混乱。无法判断哪个元素破坏了行为。

将变异保持在最小半径:规范的一个变更片段、一个预期症状和一个恢复标准。

通过聊天指标而非"奇怪"回答的印象来定位模型卡滞。引入三个诊断特征:

  • ask_storm — 重复澄清请求而无新数据出现;
  • stage_regress — 返回同一任务或阶段;
  • phase_context_loss — 阶段上下文丢失,例如 PlanImplement 混淆。

这些特征在 Qwen Code 形式上继续回答但实际不推进解决方案时特别有用:再次询问负责人、重新构建相同计划或建议使用规范中未允许的工具。实用检验行可能形如:ask_storm >= 4 || stage_regress >= 2 || phase_context_loss=true。触发后,将对话作为诊断产物而非失败对话来分析。

> 教学实践中如何计算这些指标。 这些是启发式而非 CI 指标:第一遍只需在 validation.md 中铅笔标记。 > > - ask_storm:代理的每条新消息请求当前会话先前消息中已提及的数据。计+1。当你在 requirements.mdclarifications.md 中至少添加一个新字段时重置。 > - stage_regress:SDD 当前阶段(specify/plan/tasks/implement)无明确原因记录于 validation.md 而回退到前一阶段。每次回退计+1。 > - phase_context_loss:当且仅当代理在新阶段引用当前 requirements.mdplan.md 中不存在的规则时为真。 >

> 完整跟踪可通过 Qwen Code 会话转录解析器自动化(qwen --output-format json + 聚合脚本)。教学最小集在会话时刻肉眼计数。

用带优先级的显式冲突需求来设定缺陷,而非 YAML 注释。比较两种方式。

不良做法:

# TODO: P0应在30s内升级,但人工审批是强制的——
# 不清楚哪个优先,以后解决。
rules:
  - id: escalate_p0
    when: severity == "P0"
    then: { escalation: critical_phone }

问题:缺陷坐在注释里。Linter 和 JSON Schema 不检查它,Qwen Code 可能读到 # TODO,但不必将注释视为可执行契约。因此冲突将留在形式检验之外。

良好做法:

rules:
  - id: escalate_p0
    when: severity == "P0"
    then: { escalation: critical_phone }
    priority: 100
  - id: human_approval_required
    when: severity == "P0"
    then: { require_human_approval: true }
    priority: 100   # 相同优先级上的故意冲突

现在 check_rule_priority.py(见下方[project script])通过 priority 捕获碰撞,而非依赖人的记忆。

将争议需求转为 Given/When/Then 和 JSON Schema。自然语言善于传达意图,但难以保持可接受行为的边界。"关键事件需要快速升级"的表述给模型留下猜测空间。场景 Given severity=P0 and owner_unresponsive=true / When escalation_deadline expires / Then use critical_phone and record human_audit_required 设定了可检验分支。

JSON Schema 解决另一半问题。它不仅描述期望路径,还禁止不可接受状态。例如 P0 时缺少 auto_escalation_channel,或使用 forbidden_integrations 列表中的集成。这种组合对应 SDD 方法:规范应包含完整开发循环中的成功标准、约束和可检验验收测试。GitHub Spec Kit Quickstart 将这些阶段描述为 Specify → Plan → Tasks → Implement 的序列。

通过形式化策略解决冲突。策略包含三部分:

  • 例外规则(override)定义时间边缘哪个需求获胜(例如 time_critical_override 高于 manual_gate_for_noncritical);
  • 唯一真相源 消除规范文本、模式和测试之间的分歧——若优先级在 YAML 中声明,则在验收测试和 JSON Schema 中引用同一层级,而非引入并行解释;
  • 检验不变式 固定转换安全性:升级前固定 severitydeadlineowner_state,升级后固定 channelaudit_recordreason_code。否则系统可能形式上"解决"冲突,但丢失可追溯性。

重构需以完整 Specify → Plan → Tasks → Implement 循环的回退测试来闭合。否则修复只是局部猜测。跟踪中寻找:

  • 若补丁后 Plan 稳定,但 Tasks 产生不兼容动作——意味着缺陷从规则迁移到了分解;
  • Implement 通过但验收测试失败——可接受行为边界描述不完整,或模式未覆盖操作效应。

仅当结果可复现时才视为可靠:同一事件日志、同一规范、连续两次运行无新的 ask_stormstage_regress 和优先级冲突。

示例与应用

采用不同于前述案例的场景:生产环境 appointments-api 的延迟激增。在毒规范版本中,同时设定两条需求:"所有 P0 在30秒内升级"和"任何升级需要人工确认(human approval)"。

会发生什么。若负责人不可用,Qwen Code 陷入循环 ESCALATE_EVENT → CHECK_OWNER → WAIT_APPROVAL → VALIDATE_ESCALATION → ESCALATE_EVENT。截止时间要求动作。人工屏障禁止动作。退出规则未定义。诊断运行可如下安排:

> [project script] — 以下命令描述毒规范回路的预期检验;基础规范网关(Spec CI)的可运行近似见 examples/spec-ci/README.md

qwen -p "在规划模式下分析 @specs/appointment-latency-poisoned.yaml。

找出循环、优先级冲突和隐藏越界(hidden_out_of_scope)。不要修改文件。" \
  --approval-mode plan \
  --output-format json \
  > out/appointment-latency-plan-review.json

python3 scripts/spec_ci/find_spec_loops.py \
  --spec specs/appointment-latency-poisoned.yaml \
  --out out/appointment-loop.dot

失败检验行:cycle_count > 0 && ask_storm >= 4 && escalation_path_resolved=false

flowchart TD
    Specify[Specify]
    Plan[Plan]
    Tasks[Tasks]
    WaitApproval[WAIT_APPROVAL]
    Deadlock[优先级死锁]
    Specify -->|SDD| Plan
    Specify -->|SDD| Tasks
    Plan -->|SDD| WaitApproval
    Tasks -->|SDD| WaitApproval
    WaitApproval -->|SDD 回退弧| Deadlock
    Deadlock -->|优先级阻塞| Specify
    classDef danger fill:#ffcccc,stroke:#b00020,stroke-width:2px,color:#5a0000
    class Deadlock danger

修复时不要从删除人工确认开始,而是先明确其作用域。对 P0 引入例外规则,其中响应时间优先于预先人工确认。将人工检验转为事后审计。

对 P1–P3 保留人工屏障为阻塞性——那里没有同等时间风险。最小补丁可能如下:

rules:
  - id: p0_time_critical_override
    when: severity == "P0" && owner_unresponsive == true
    then:
      escalation: critical_phone
      human_audit_required: true
    priority: 100

  - id: human_gate_noncritical
    when: severity in ["P1", "P2", "P3"]
    then:
      require_human_approval: true
    priority: 10

然后用模式固定争议点。这防止模型通过邻近步骤回到隐藏协商。在 JSON Schema 中,要求 P0 且负责人不可用时必须有自动升级通道,同时保留强制审计痕迹。这样你不仅设定了"做什么",还设定了"什么不能算作成功完成":

{
  "if": {
    "properties": {
      "severity": { "const": "P0" },
      "owner_unresponsive": { "const": true }
    },
    "required": ["severity", "owner_unresponsive"]
  },
  "then": {
    "required": ["auto_escalation_channel", "human_audit_required", "reason_code"],
    "properties": {
      "auto_escalation_channel": { "const": "critical_phone" },

"human_audit_required": { "const": true },
      "reason_code": { "const": "time_critical_override" }
    }
  }
}

最终检验必须跑完整个回路,而非仅新模式:

> [project script]lint_spec.pycheck_rule_priority.py 需在你的项目中实现;简单模式网关和覆盖度的可运行近似见 examples/spec-ci/README.md

python3 scripts/spec_ci/lint_spec.py \
  --spec specs/appointment-latency-fixed.yaml \
  --atomicity

python3 scripts/spec_ci/check_rule_priority.py \
  --spec specs/appointment-latency-fixed.yaml \
  --expect-json-schema

qwen -p "阅读 @specs/appointment-latency-fixed.yaml 和 @validation.md。
将 specify/plan/tasks/implement 阶段作为审查重放:哪些通过,
哪些未检验,哪些事实需要脚本。" \
  --approval-mode plan \
  --output-format json \
  > out/appointment-latency-replay-review.json

成功恢复行:priority_conflict=false && cycle_count==0 && escalation_path_resolved=P0 && audit_required=true

总结

毒规范只有在其毒性事先受限时才有用:一个缺陷、可测量症状、形式补丁和完整回退测试。

循环、优先级冲突和隐藏越界在两种条件下从 Qwen Code 的随机失败变为可控实验室变异。第一,你通过 ask_stormstage_regressphase_context_loss 读取跟踪。第二,你通过 Given/When/Then、JSON Schema、例外规则和升级前后不变式检验修复。

经过这种训练,规范不再是一组愿望,而成为稳定契约。契约可被复现地破坏、修复和防止再次失败。下一章我们将把这些规则形式化为 constitution.md,作为首个项目公投。

产物与就绪标准

产物就绪条件
poisoned-spec.md(或 specs/appointment-latency-poisoned.yaml恰好引入一个受控缺陷,来自单一类别:循环、优先级冲突或隐藏越界
预期症状记录启动代理前命名 ask_storm / stage_regress / phase_context_loss 之一

| fixed-spec.md(或修复后的 YAML) | 补丁变更可检验规则,而非仅文本解释 | | validation.md 中的恢复行 | 解释修复后哪个具体事实不再复现 |

完整跟踪额外添加:Qwen Code 诊断的 out/appointment-latency-plan-review.json、禁止回到隐藏人工确认的 JSON Schema 片段、以及回退后的 out/appointment-latency-replay-review.json。若本地 Spec CI 可运行类比显示可修复失败和通过,且 Specify → Plan → Tasks → Implement 重放不返回原始冲突,则视为就绪。

实践

  1. 复制一份现有功能规范,向其中恰好引入一个缺陷:优先级冲突、循环或隐藏越界。*预期:得到两个版本——poisoned-spec.mdfixed-spec.md,恰好相差一个变异;你能在启动代理前用一个词命名缺陷类别。*
  1. 在启动代理前描述预期失败症状:什么应该循环、什么应该变得不明确、哪个事实应该失败。*预期:症状具体记录(第三次澄清后的 ask_storm、plan → specify 的 stage_regressvalidation.mdThen 失败),而非"代理搞不定"。*
  2. 修复缺陷,使补丁变更需求、计划和检验,而非仅文本解释。*预期:diff 至少触及 requirements.mdplan.mdvalidation.md 之一;Specify → Plan → Tasks → Implement 回退测试不返回原始冲突。*

检验问题

  1. 为什么毒规范不能同时引入多个缺陷?
  2. 优先级冲突与隐藏越界有何区别?
  3. 完整 Specify → Plan → Tasks → Implement 回退测试证明了什么?
  4. 你引入了"升级循环"缺陷,但 Qwen Code 输出"无需澄清"并开始实现。这说明你的规范有什么问题,下一步诊断步骤是什么?
我的笔记
0 / 10000

笔记保存在当前浏览器中。在其他设备上将不会显示。

课程菜单

课程

Production SDD for Qwen Code CLI. Part 2
进度 0 / 100