应用部分 2. 规范缺陷诊断
状态:建议。 向规范中注入一个受控缺陷——这是一种接近变异测试(mutation testing)的教学技术。具体的缺陷类别(cycle、priority_conflict、hidden_out_of_scope)在项目中有所应用,但尚未统一。卡滞指标(ask_storm、stage_regress)属于前沿探索。
该技术的工程名称是受控缺陷规范:你故意引入一个缺陷来检验诊断能力。文中有时使用简称"毒规范",但它不应掩盖核心规则:一次变异、一个症状、一个恢复标准。
本章延续第一卷的两个基本理念:第7部分的否定性需求,以及第20部分的SDD反模式。区别在于,现在的缺陷是故意引入的,并且事先受到限制。不要试图在此处检验整个分类流程:教学最小集是 poisoned/fixed 配对和 validation.md 中的一行恢复记录。
阅读前准备
- 第一卷支撑:第7部分提供否定性需求,第20部分提供SDD反模式。
- 本地教学案例:
appointment_latency,因为优先级冲突无需外部基础设施即可显现。 capstone/的跟踪项:high_memory_usage的 poisoned/fixed 配对和validation.md中的一行恢复记录。- 第一遍的核心术语:受控缺陷。
- 可延后内容:
ask_storm、stage_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.md和fixed-spec.md。 - 一个预期症状:
ask_storm、stage_regress或phase_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.md 和 fixed-spec.md 中。若最终考核基于 high_memory_usage,则仅将缺陷类别和下方恢复行转入 capstone/。每次只变更一个缺陷:此处为优先级冲突。
步骤
- 在
poisoned-spec.md中记录两条相同priority的冲突规则。预期:缺陷在数据中可见,而非隐藏在注释中。 - 启动分析前记录预期症状:例如
priority_conflict=true && escalation_path_resolved=false。 - 进行手动审查或对 Qwen Code 发起 Plan Mode 请求,不修改文件。预期:模型指出冲突或在争议点失去进展。
- 在
fixed-spec.md中添加p0_time_critical_override,将人工检验转为事后审计。 - 在
validation.md中固定两个事实:原始冲突已找到,修复路径保留human_audit_required=true。 - 若需自动检验需求形式和计划,可将结果与 [
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_usage:restart_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— 阶段上下文丢失,例如Plan与Implement混淆。
这些特征在 Qwen Code 形式上继续回答但实际不推进解决方案时特别有用:再次询问负责人、重新构建相同计划或建议使用规范中未允许的工具。实用检验行可能形如:ask_storm >= 4 || stage_regress >= 2 || phase_context_loss=true。触发后,将对话作为诊断产物而非失败对话来分析。
> 教学实践中如何计算这些指标。 这些是启发式而非 CI 指标:第一遍只需在 validation.md 中铅笔标记。 > > - ask_storm:代理的每条新消息请求当前会话先前消息中已提及的数据。计+1。当你在 requirements.md 或 clarifications.md 中至少添加一个新字段时重置。 > - stage_regress:SDD 当前阶段(specify/plan/tasks/implement)无明确原因记录于 validation.md 而回退到前一阶段。每次回退计+1。 > - phase_context_loss:当且仅当代理在新阶段引用当前 requirements.md 或 plan.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 中引用同一层级,而非引入并行解释;
- 检验不变式 固定转换安全性:升级前固定
severity、deadline和owner_state,升级后固定channel、audit_record和reason_code。否则系统可能形式上"解决"冲突,但丢失可追溯性。
重构需以完整 Specify → Plan → Tasks → Implement 循环的回退测试来闭合。否则修复只是局部猜测。跟踪中寻找:
- 若补丁后
Plan稳定,但Tasks产生不兼容动作——意味着缺陷从规则迁移到了分解; - 若
Implement通过但验收测试失败——可接受行为边界描述不完整,或模式未覆盖操作效应。
仅当结果可复现时才视为可靠:同一事件日志、同一规范、连续两次运行无新的 ask_storm、stage_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.py 和 check_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_storm、stage_regress、phase_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 重放不返回原始冲突,则视为就绪。
实践
- 复制一份现有功能规范,向其中恰好引入一个缺陷:优先级冲突、循环或隐藏越界。*预期:得到两个版本——
poisoned-spec.md和fixed-spec.md,恰好相差一个变异;你能在启动代理前用一个词命名缺陷类别。*
- 在启动代理前描述预期失败症状:什么应该循环、什么应该变得不明确、哪个事实应该失败。*预期:症状具体记录(第三次澄清后的
ask_storm、plan → specify 的stage_regress、validation.md中Then失败),而非"代理搞不定"。* - 修复缺陷,使补丁变更需求、计划和检验,而非仅文本解释。*预期:diff 至少触及
requirements.md、plan.md、validation.md之一;Specify → Plan → Tasks → Implement回退测试不返回原始冲突。*
检验问题
- 为什么毒规范不能同时引入多个缺陷?
- 优先级冲突与隐藏越界有何区别?
- 完整
Specify → Plan → Tasks → Implement回退测试证明了什么? - 你引入了"升级循环"缺陷,但 Qwen Code 输出"无需澄清"并开始实现。这说明你的规范有什么问题,下一步诊断步骤是什么?