应用部分 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 路由的延迟增长,即 Hono JSX 代理页面,该页面已在第一卷第11部分中出现。同一领域,但处于压力之下。经典错误目录(变异所依据)见第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 输出"无需澄清"并继续实现。这说明您的规范有什么问题,下一步诊断步骤是什么?