阅读材料: 应用部分 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 路由的延迟增长,即 Hono JSX 代理页面,该页面已在第一卷第11部分中出现。同一领域,但处于压力之下。经典错误目录(变异所依据)见第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

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

课程菜单

课程

在 Qwen Code CLI 开发中使用 SDD。应用课程
进度 0 / 95