学习指南: 应用部分 7. Specification CI:规范作为可执行产物

模块「应用部分 7. Specification CI:规范作为可执行产物」中第 3 / 5 节课
您正在未登录状态下查看课程。 请登录,以保存进度并参加测试。

主题:实践部分 7. Specification CI:将规范作为可执行工件

难度级别:中级

预计学习时间:4-6 小时(理论 + 可运行示例实践)

前置条件: 基本理解 Git 和 GitHub Actions

具有 Markdown 需求文档(requirements.md、plan.md)的工作经验

熟悉 JSON Schema

完成第一卷第 9 部分(事实验证)和第 16 部分(团队评审)

基础 Python 用于运行检查脚本

学习目标: 配置本地规范网关(Spec CI),在覆盖范围、作用域或数据模式违规时阻止合并

构建可复现的 requirements → plan 追溯图,使用 REQ-* 标识符和 implements 字段

制定 CI 诊断消息,包含文件、行号、规则标识符和具体的修复操作

将 spec_gate 集成到 GitHub 分支保护中,使绿色单元测试无法绕过语义检查

为 JSON Schema 创建否定性夹具,并解释其可预测的失败如何保护契约

概述:本章将规范从静态文本转变为仓库的可执行仲裁者。规范网关(Spec CI)是一个 GitHub Actions 工作流,在每次 push 和 pull_request 时检查三个层次:计划对需求的覆盖(coverage)、计划与领域模型的匹配(scope)、示例数据的正确性(schema)。核心原则:关于规范质量的争议归结为具体行、规则和操作。团队获得可复现的阻断机制,评审者检查修改的语义而非调查 CI 日志。本章从 incident payload 的本地可运行示例开始,然后扩展到具有分支保护的生产工作流。

核心概念: Spec ci(规范网关):持续集成工作流,将 requirements.md、plan.md、validation.md 和 API 契约作为可执行工件进行检查。在三种违规情况下阻止合并:需求未满足、超出边界(out-of-scope)、JSON Schema 错误。第一遍的主要术语;其他术语随脚本出现而引入。

Gate(网关):CI 中的强制检查,未通过则无法合并。与普通测试不同,spec_gate 检查语义完整性,而非仅代码正确性。必须在分支保护设置中标记为强制。

Coverage-check(覆盖检查):通过稳定标识符 REQ-* 和 implements 字段构建 requirements → plan 追溯图。每个用户故事必须有实现任务,每个任务必须有反向链接。错误类型:orphan requirement(无任务的需求)和 rogue task(无需求的任务)。

Scope-check(作用域检查):超出领域模型边界的检测器。检查 plan.md 中的操作是否在 incident-response 领域中允许:acknowledge、escalate、annotate、rollback、notify_on_call。考虑执行者、端点和触发条件。阻止 force_resolve_without_operator 等自主操作。

Schema-check(模式检查):将 validation.md 中的 JSON 夹具与 JSON Schema 进行验证。两个方向:有效示例通过,否定示例可预测地失败。若否定夹具通过,则模式过于宽松。

Fixture(夹具):从 validation.md 提取的输入数据示例,用于检查契约。有效夹具确认正常工作,否定夹具防止模式回归。

Spec gate(CI 最终任务):工作流中的最终 job,要求 coverage、scope 和 schema 成功完成。此任务必须在分支保护中标记为强制,否则绿色单元测试将绕过语义检查。

Json-诊断:CI 拒绝消息的格式,设计用于无需调查即可快速修复。四个必需元素:明确原因、文件和行号引用、规则标识符、具体操作。

Runnable-示例:基于 incident payload 的本地教学案例,无需 GitHub Actions 即可运行。包含 check_coverage.py 和 validate_schema.py,用于在部署完整 CI 之前演示网关原理。

练习: 标题:本地运行覆盖网关

问题:进入 book2/examples/spec-ci 目录并运行覆盖检查脚本。然后故意破坏链接:在 requirements.md 中添加没有 plan.md 中实现任务的 REQ-999 历史。再次运行脚本并分析诊断信息。

解答:1. cd book2/examples/spec-ci

  1. python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md → 预期返回码 0,消息 coverage ok
  2. 在 requirements.md 中添加:'## REQ-999: 作为管理员,我想要自动删除事件'
  3. 再次运行脚本 → 预期失败,消息为:'requirements.md:42: REQ-999 在 plan.md 中没有链接。请在 plan.md 中添加 implements: REQ-999 的任务,或删除该需求。'
  4. 修复方法:在 plan.md 中添加 implements: [REQ-999] 的任务,或删除 REQ-999

难度:初级

标题:否定夹具与模式保护

问题:在 book2/examples/spec-ci 目录中运行 validate_schema.py。然后创建新的否定夹具:格式有效,但 severity 为 'P0' 且没有 backup_verified。检查模式是否拒绝它。如果通过,则模式过于宽松。

解答:1. python3 scripts/validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures → 返回码 0,valid-incident.json: valid,invalid-missing-incident-id.json: expected invalid

  1. 创建 fixtures/invalid-p0-no-backup.json,包含 _expected_invalid: true,incident_id: 'INC-003',severity: 'P0',但没有 backup_verified
  2. 再次运行 validate_schema.py
  3. 如果得到 'invalid-p0-no-backup.json: expected invalid, rejected: missing required property backup_verified' → 模式正确
  4. 如果通过验证 → 修改模式:添加 'if severity == P0 then backup_verified required' 并重复测试

难度:中级

标题:将 Spec CI 迁移至 high_memory_usage 的毕业项目

问题:在毕业项目中为 high_memory_usage 场景创建 Spec CI 流程。需求:REQ-HM-01「未经确认的 RSS > 90% 持续 5 分钟,不得重启 pod」。证明与 plan.md 的链接,并创建没有 incident_id 的否定夹具。

解答:1. 确保 requirements.md 中有 REQ-HM-01,条件明确(RSS > 90%,5 分钟)

  1. 在 plan.md 中添加 implements: [REQ-HM-01] 的任务
  2. 运行:python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md → 预期 coverage ok
  3. 创建 high_memory_usage 负载的夹具,但没有 incident_id,标记 _expected_invalid: true
  4. 运行 validate_schema.py → 预期因 missing required property incident_id 而被拒绝
  5. 在 capstone/validation.md 中记录:

| Spec CI | check_coverage.py | REQ-HM-01 与 plan.md 关联 | PASS | | Schema negative | validate_schema.py | missing incident_id 被阻止 | PASS |

难度:中级

标题:作用域检查:阻止 rogue 操作

问题:在教学 plan.md 中添加步骤 'TASK-ROGUE: 通过 POST /incidents/{id}/force-resolve 自主关闭事件,无需操作员确认,implements: [REQ-014]'。检查 scope-check 是否阻止此操作,尽管覆盖保持绿色。

解答:1. 在 plan.md 中添加 TASK-ROGUE,implements: [REQ-014],端点 POST /incidents/{id}/force-resolve

  1. 运行 check_coverage.py → 绿色(形式链接存在)
  2. 运行 check_scope.py(或根据 incident-response 领域模型手动分析)
  3. 预期失败:'plan.md:48 uses force_resolve without domain permission'
  4. 替换为 POST /incidents/{id}/ack 或添加单独的需求和领域规则
  5. 重复直至绿色状态

难度:高级

标题:可修复诊断的格式化

问题:获得「错误」错误消息:'Coverage failed: missing REQ'。按照四个元素将其转换为「良好」消息:原因、文件/行号、规则标识符、操作。以 JSON 格式记录。

解答:原始(错误):'Coverage failed: missing REQ'

转换后(良好): { 'status': 'failed', 'check': 'coverage', 'file': 'requirements.md', 'line': 42, 'rule': 'REQ-COV-014', 'reason': 'REQ-014 在计划中没有实现任务', 'action': '请在 plan.md 中添加 implements: [REQ-014] 的任务,或从 requirements.md 中删除该需求' }

验证:消息允许无需打开文件和阅读进程日志即可修复

难度:中级

案例研究: 标题:事件流水线:否定夹具如何阻止 P0 的虚假升级

场景:金融科技公司的 SRE 团队自动化了 Grafana → PagerDuty 告警处理。validation.md 包含带有 incident_id、severity、source 字段的 webhook 示例。incident_payload.schema.json 模式要求 severity: 'P0' 时必须提供 backup_verified。规范已连接到具有三个层次的 Spec CI:coverage、scope、schema。

挑战:开发者「优化」了模式,删除了 P0 的 backup_verified 条件,认为「总是有备份」。一周后另一位开发者在 plan.md 中添加了 P0 自动升级步骤,未修改需求。Coverage 保持绿色(implements 存在),scope 也保持绿色(升级在领域内)。但否定夹具 invalid-p0-no-backup.json 突然通过了验证——模式变得过于宽松。

解决方案:Schema-check 层的 Spec CI 检测到标记为 _expected_invalid: true 的否定夹具通过了验证。Spec_gate 状态变为红色,合并被阻止。诊断指出:'validation.md:72 schema mismatch: negative fixture invalid-p0-no-backup.json passed, expected reject by missing backup_verified. Action: restore if-then requirement in incident_payload.schema.json: severity P0 requires backup_verified'。团队恢复了模式的严格性,并在 requirements.md 中添加了明确需求 REQ-BACKUP-01。

结果:阻止了 severity P0 且未确认备份的事件在凌晨 3 点自动升级到值班人员,尽管恢复不可能的情况。模式回归的反应时间:分钟而非小时或天。团队确立了规则:任何模式变更都需要成对夹具——有效和否定的。

经验教训: 否定夹具是模式本身的回归测试;它们的通过是警报信号,而非成功

不改变夹具集的模式变更——Spec CI 必须阻止的经典反模式

三个检查层次(coverage、scope、schema)相互独立:一层的绿色状态不能补偿另一层的红色

相关概念: schema-check

fixture

spec_gate

JSON-诊断

标题:Rogue task 与自主关闭:为什么覆盖检查不足够

场景:在事件项目中,团队部署了具有覆盖检查的 Spec CI。每个 REQ-* 都有 implements,每个任务都引用需求。评审时这看起来是完美的追溯。

挑战:产品经理要求「加速」事件解决。开发者在 plan.md 中添加了 TASK-AUTO-RESOLVE 任务,implements: [REQ-014](通用需求「值班人员收到升级确认」),端点 POST /incidents/{id}/force-resolve。形式上有链接,但内容——无操作员的自主关闭——超出了 incident-response 领域模型范围。Coverage-check 通过绿色。

解决方案:Scope-check 在领域模型层面触发:force_resolve 不在允许的操作中(acknowledge、escalate、annotate、rollback、notify_on_call)。检查不仅考虑了动词,还考虑了执行者(自主代理 vs 值班人员)和端点。诊断:'plan.md:48: IR-SCOPE-007 — Autonomous force resolve is outside the incident-response domain model. Action: Replace with POST /incidents/{id}/ack or add an approved requirement and domain rule'。

结果:Pull request 被阻止。团队讨论并拒绝了自主关闭作为风险操作。取而代之的是添加了明确需求 REQ-MANUAL-ACK 和人工确认任务。领域模型保持不变,对修复的信任得以维持。

经验教训: 按标识符的覆盖图比按词搜索更强,但没有内容检查则不足够

Scope-check 捕获语义漂移:形式结构保留,意义改变

领域模型是团队与系统之间的契约;违反它比缺少测试更危险

相关概念: scope-check

coverage-check

rogue task

domain model

学习建议: 从 book2/examples/spec-ci 的本地可运行示例开始,而非 GitHub Actions。先在本地实现绿色网关,再迁移到 CI

仅对修改的文件使用 pre-commit hook,完整运行留给 CI——这节省时间并使网关成为循环的习惯部分

练习「错误」诊断:采用 'Coverage failed' 等消息,重写为包含文件、行号、规则和操作的格式

与正面夹具并行创建否定夹具——这防止模式的「软性」回归

维护「违规日志」:记录 spec_gate 捕获的错误,用于团队培训

视觉风格:在纸上绘制 requirements → plan → domain → schema 图,标记每个网关触发位置

听觉风格:向同事口头解释为什么 main 的 push 触发器与 pull_request 同样重要(直接更新服务文件)

动觉风格:物理删除 plan.md 中的 implements 行,运行脚本,感受阻止,然后恢复

额外资源: Github spec kit: https://github.com/github/spec-kit — SDD 方法的参考实现,需求、计划和任务成为可检查层次

本章可运行示例:book2/examples/spec-ci/scripts/check_coverage.py 和 validate_schema.py — 无外部依赖的本地冒烟测试

Json schema validation: https://json-schema.org/understanding-json-schema/ — 创建严格夹具契约的规范

第一卷第 9 部分:part-09-feature-validation.md — validation.md 与事实的关联,理解夹具的基础

第一卷第 16 部分:part-16-team-code-review.md — 证据包团队评审,自动化的背景

第一卷第 12 部分:part-12-mvp.md — 教学项目 AgentClinic 中的 REQ 标识符和有效负载模式

总结:规范网关(Spec CI)将 requirements.md、plan.md、validation.md 和 API 契约从参考文档转变为阻止合并的可执行工件。三个检查层次——coverage(REQ-* → implements 图)、scope(与 incident-response 领域模型的匹配)、schema(带否定示例的 JSON 夹具)——相互独立并补充单元测试。关键收益:关于规范的争议归结为包含文件、行号、规则和操作的诊断,减轻评审者负担。部署步骤:先本地可运行示例,然后 GitHub Actions 在分支保护中设置强制 spec_gate,始终在 push 和 pull_request 上触发。在事件自动化中,这种严格性阻止虚假升级、危险自动操作和对修复信任的丧失。

我的笔记
0 / 10000

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

课程菜单

课程

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