应用部分 7. 规范 CI:将规范作为可执行产物
状态:建议。 在 CI 中运行规范检查是一项成熟实践。具体的网关集合(覆盖率、范围、架构、spec_gate)和 JSON 诊断格式是建议性框架,大多数团队会在此基础上进行适配。来自 validation.md 的固定数据 JSON Schema 检查属于该工具的标准用法。
在本章中,「规范网关」是检查规范的流水线的简称,其方式与普通 CI 检查代码相同。检查本身由常规步骤组成:解析 requirements.md 和 plan.md,针对 JSON Schema 验证示例,检查文件之间的一致性。所有其他术语——gate、fixture、schema-check、coverage-check——将在它们实际出现在团队或脚本中的位置下方引入;无需将其汇总到一行介绍中。
规范网关正是 第一卷第 9 部分 通过人工审查执行的流程,以及 第 16 部分 在拉取请求中由团队执行的流程的自动化。在 AgentClinic 教学案例中,来自 第 12 部分 的评论负载的 REQ 标识符和架构可能仍是一种约定。在生产环境中,没有这种信任余地。同样的关联需要转化为强制网关,绿色单元测试无法绕过。
阅读前
- 第一卷的基础:第 9 部分将
validation.md与事实关联,第 16 部分展示了证据包审查。 - 本地教学案例:
incident payload,因为覆盖率和 JSON Schema 可以在没有 CI 的情况下本地验证。 - 对
capstone/的要求:high_memory_usage的一条 Spec CI 记录:命令、已证明的事实和反例。 - 第一遍的主要术语:Spec CI。
gate、fixture、schema-check、coverage-check为参考性术语,直接出现在命令和脚本注释中。 - 可延后内容:GitHub Actions 工作流、范围网关以及从任意
validation.md中提取固定数据。
目标
在本章中,规范网关从「检查文档」的理念转变为事件项目的可运行 GitHub Actions 流水线。每次 push 和每个拉取请求都经过强制网关。网关在以下三类违规情况下阻止合并:
- 需求未满足,
- 超出边界(out-of-scope),
- JSON Schema 错误。
读者将获得一个实用仓库结构,其中 requirements.md、plan.md、validation.md 和 API 合约作为可执行产物而非参考文档进行验证。
主要收益——团队获得 CI 中可复现的阻塞机制。关于规范质量的争论归结为具体行、规则和修复操作。
最小教学场景
教学案例
incident payload:验证 requirements.md 与 plan.md 关联,且 JSON 固定数据(我们从 validation.md 中提取的输入示例)包含必需的 incident_id。目标是了解 Spec CI 是一个小型本地网关(没有它合并就会被阻塞),而非大型 GitHub Actions 流程。
准备
book2/examples/spec-ci/requirements.md。book2/examples/spec-ci/plan.md。
book2/examples/spec-ci/fixtures/valid-incident.json。book2/examples/spec-ci/fixtures/invalid-missing-incident-id.json。- 脚本
check_coverage.py和validate_schema.py。
步骤
cd book2/examples/spec-ci。 预期:您已进入可运行示例目录。python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md。 *预期:返回代码 0,所有REQ-*均与计划关联。*python3 scripts/validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures。 预期:有效固定数据通过,反例按预期失败。- 打开反例固定数据的错误消息。 预期:清楚了解缺少哪个字段以及需要修复哪个文件。
- 在
validation.md中记录网关阻塞的具体内容:覆盖率、范围还是架构。
验证事实
一次本地运行展示两种事实类型:需求与计划关联,数据符合合约。如果 CI 错误未指明文件、规则和操作,则它尚未准备好供团队使用。
如何进入 capstone/
将一条 Spec CI 记录移入 capstone/validation.md:运行了什么命令、它证明了什么、哪个反例被阻塞。如果尚未创建完整的 GitHub Actions 工作流,则无需转移;对于教学最小场景,examples/spec-ci 中的可运行等效物已足够。
最小片段:
| Spec CI | `python3 scripts/check_coverage.py ...` | 所有 REQ-* 与 plan 关联 | PASS |
| Schema negative | `python3 scripts/validate_schema.py ...` | missing incident_id 被阻塞 | PASS |
迁移到 high_memory_usage
教学示例适用于 incident payload,但 capstone/ 中需要 high_memory_usage 的记录。将相同的两类检查应用于您的需求:
| 检查内容 | 命令(教学用) | 对 high_memory_usage 的证明 |
|---|---|---|
| Coverage | check_coverage.py --requirements requirements.md --plan plan.md | 需求 REQ-HM-01「在 5 分钟内确认 RSS > 90% 之前不重启 pod」与 plan.md 中的任务关联 |
| Schema negative | validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures | 无 incident_id 或 severity: "P0" 但无 backup_verified 的固定数据被阻塞 |
如果无法为 high_memory_usage 编写 Coverage 和 Schema negative 记录——则意味着 requirements.md 中尚无可验证的需求,或架构尚无法区分无备份的 P0。
可审查痕迹
在教学包中,将更改保存到 requirements.md、plan.md、validation.md 或架构中。仅为本地诊断创建的临时固定数据无需保留,除非它们已成为回归套件的一部分。
核心思想
这里的「可执行产物」并非指将 Markdown 作为程序运行。而是指通过常规 CI 脚本检查需求、计划和示例。
在 pull_request 和向受保护分支的 push 上都运行强制 GitHub Actions 网关。为什么是两个触发器。规范违规可能通过两种途径进入:常规拉取请求或直接更新服务文件。
最小跟踪产物集:
requirements.md,plan.md,validation.md,contracts/**,
constitution.md——如有需要,如果其中固化了事件流水线的领域约束。
在分支保护设置中,将最终任务 spec_gate 标记为必需。否则绿色单元测试将绕过语义检查。此方案符合 SDD 方法,其中需求、计划和任务成为可验证层而非静态文本(GitHub Spec Kit)。
> [project script] — .github/workflows/spec-ci.yml 调用项目脚本 scripts/spec_ci/*.py。
name: spec-ci
on:
pull_request:
paths:
- 'requirements.md'
- 'plan.md'
- 'validation.md'
- 'contracts/**'
- 'constitution.md'
push:
branches: [main]
paths:
- 'requirements.md'
- 'plan.md'
- 'validation.md'
- 'contracts/**'
- 'constitution.md'
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: python3 scripts/spec_ci/check_coverage.py --requirements requirements.md --plan plan.md --out out/spec-ci/coverage-report.json
scope:
runs-on: ubuntu-latest
needs: [coverage]
steps:
- uses: actions/checkout@v4
- run: python3 scripts/spec_ci/check_scope.py --domain models/incident-response.yaml --plan plan.md --contracts contracts/api.md --out out/spec-ci/scope-violations.ndjson
schema:
runs-on: ubuntu-latest
needs: [coverage, scope]
steps:
- uses: actions/checkout@v4
- run: python3 scripts/spec_ci/extract_fixtures.py --from validation.md --out out/spec-ci/fixtures
- run: python3 scripts/spec_ci/validate_schema.py --schemas schemas --fixtures out/spec-ci/fixtures --out out/spec-ci/schema-audit.json
spec_gate:
runs-on: ubuntu-latest
needs: [coverage, scope, schema]
steps:
- run: echo "Specification gate passed"
覆盖率检查从 requirements → plan 图开始,而非搜索匹配词。我们引入追踪规则:
requirements.md中的每个用户故事获得稳定的REQ-*标识符;plan.md中的每个任务或步骤必须通过类似implements: [REQ-014]的字段引用一个或多个此类标识符。
什么算作错误。如果故事没有可追踪的任务,以 fail 结束进程:团队在开始实现之前就已经丢失了对用户的承诺。反向违规同样重要。没有 implements 的任务变成游离任务(rogue task)。这意味着计划开始添加未经需求确认的功能。
> [project script] — scripts/spec_ci/check_coverage.py;可运行等效物——[examples/spec-ci/scripts/check_coverage.py](examples/spec-ci/)。
python3 scripts/spec_ci/check_coverage.py \
--requirements requirements.md \
--plan plan.md \
--out out/spec-ci/coverage-report.json
边界检测器用于形式追踪存在但步骤内容超出事件领域的情况。将 plan.md 中的操作与 incident-response 领域模型匹配。允许的操作例如:
acknowledge,escalate,annotate,rollback,notify_on_call。
任意业务操作(如 notify_finance、close_customer_contract 或 force_resolve_without_operator)不属于此范围。
不仅检查动词。同时检查:
- 行为者,
- 端点(endpoint),
- 触发条件。
为何如此。resolve incident 可能允许人工值班操作员执行,但禁止自主代理执行。实用规则很简单:如果步骤无法通过事件模型和允许的 API 合约解释,则阻塞拉取请求。
> [project script] — scripts/spec_ci/check_scope.py。没有现成等效物;请基于领域模型和 API 合约自行实现。
python3 scripts/spec_ci/check_scope.py \
--domain models/incident-response.yaml \
--plan plan.md \
--contracts contracts/api.md \
--out out/spec-ci/scope-violations.ndjson
JSON Schema 检查覆盖固定数据和负载示例层,此处常出现静默集成回归。需要执行:
- 从
validation.md提取所有 JSON 块; - 将其转换为独立固定数据;
- 针对
schemas/**中的架构进行验证——例如incident_payload.schema.json、pagerduty_webhook.schema.json或grafana_alert.schema.json。
关注两个方向。有效示例应无错误通过。专门构造的反例应按预期失败。如果反例负载通过,则架构过于宽松,无法保护合约。
在合并到受保护分支之前,此项检查与应用测试同样严格。错误的 incident_id、不正确的 severity 或空的 source 可能破坏整个修复流程。
> [project script] — scripts/spec_ci/extract_fixtures.py 和 scripts/spec_ci/validate_schema.py;架构检查的可运行等效物——[examples/spec-ci/scripts/validate_schema.py](examples/spec-ci/)。提取步骤请根据项目 validation.md 的格式自行实现。
python3 scripts/spec_ci/extract_fixtures.py \
--from validation.md \
--out out/spec-ci/fixtures
python3 scripts/spec_ci/validate_schema.py \
--schemas schemas \
--fixtures out/spec-ci/fixtures \
--out out/spec-ci/schema-audit.json
将 CI 拒绝的诊断格式设计为便于快速修复,而非需要调查进程日志。
差: > Coverage failed: missing REQ
问题:审查者不知道哪个需求成为孤儿,以及查看何处。不经过单独调查无法修复错误。
好: > requirements.md:42: REQ-014 在 plan.md 中没有引用。请在 plan.md 中添加带有 implements: REQ-014 的任务,或删除该需求。
每条错误应指明四个要素:
- 清晰的原因,
- 文件和行号引用,
- 违反规则的标识符,
- 规范团队的具体操作。
错误类型示例如下。对于覆盖率,可能是 REQ-021 has no implementing plan item; add implements: [REQ-021] to plan.md or remove the requirement。对于范围,是 plan.md:48 uses force_resolve without domain permission。对于架构,是 validation.md:72 missing required property incident_id。
此格式减轻审查者负担。人员检查修改的语义,而非重建 CI 的损坏内容。
{
"status": "failed",
"check": "scope",
"file": "plan.md",
"line": 48,
"rule": "IR-SCOPE-007",
"reason": "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"
}
示例与应用
flowchart LR A[pre-commit hook] B[本地快速运行和 push 前的轻量对决] C[PR push] D[变更文件检测] E[check_coverage 需求 计划 任务 图] F[check_scope 领域模型和 contracts/api] G[check_schema validation 和反例] H[网关报告和 PR 状态] A --> B --> C --> D --> E --> F --> G --> H
教学事件仓库中的典型拉取请求修改三个文件:
requirements.md,plan.md,validation.md。
作者描述故事 REQ-014:作为值班(on-call)工程师,我希望收到升级确认。然后在计划中添加任务 TASK-033 并带有 implements: [REQ-014]。在 validation.md 中放置包含字段 incident_id、severity、source 和 escalation_target 的 webhook 负载示例。
检查内容。如果存在 REQ-014 → TASK-033 关联,则覆盖率检查通过。如果操作符合领域模型,则范围检查通过。如果负载符合合约,则架构检查通过。如果三层中任意一层损坏,spec_gate 返回红色状态,GitHub 不允许合并。
典型失败:作者试图「加速」处理,在 plan.md 中添加步骤 POST /pagerduty/force-resolve,但没有单独需求和领域模型许可。如果步骤形式上关联到现有故事,覆盖率可能保持绿色。但范围检查将阻塞拉取请求:未经值班操作员确认的自主关闭事件不属于约定操作。
如果同一拉取请求在 validation.md 中添加带有 event_code 而非必需 incident_id 的负载,架构检查给出独立阻塞器。团队获得两类不同错误:
- 语义越界,
- 数据结构违规。
push 前的本地快速运行节省时间,并使规范网关成为工作循环的常规部分。在 pre-commit 中仅运行变更文件。将完整流程留给 GitHub Actions,以免冗长的全部固定数据检查拖慢开发者。
对于事件项目,一个执行三件事的命令已足够:
- 构建覆盖率图,
- 按差异(diff)检查范围,
- 验证受影响的 JSON 块。
如果本地报告已显示 orphan requirement、rogue task 或 schema mismatch,作者在创建拉取请求前修复规范,而非在远程 CI 出现红色状态后。
> [project script] — scripts/spec_ci/*.py 的本地包装器示例。
#!/usr/bin/env bash
set -euo pipefail
python3 scripts/spec_ci/check_coverage.py \
--requirements requirements.md \
--plan plan.md \
--out out/spec-ci/coverage-report.json
python3 scripts/spec_ci/check_scope.py \
--domain models/incident-response.yaml \
--plan plan.md \
--contracts contracts/api.md \
--out out/spec-ci/scope-violations.ndjson
python3 scripts/spec_ci/extract_fixtures.py \
--from validation.md \
--out out/spec-ci/fixtures
python3 scripts/spec_ci/validate_schema.py \
--schemas schemas \
--fixtures out/spec-ci/fixtures \
--out out/spec-ci/schema-audit.json
总结
规范网关使规范成为仓库的可执行仲裁者。GitHub Actions 在以下三类违规情况下阻塞拉取请求:
- 未覆盖的用户故事,
- 计划中的无关场景,
- 验证示例中的 JSON Schema 错误。
对团队而言,这改变了审查性质。取代关于需求完整性的主观争论,出现带有文件、行号、规则和操作的诊断报告。
在事件自动化中,这种严格性尤为重要。错误的范围或宽松的负载合约可能导致三种后果:
- 误报升级,
- 危险的自动操作,
- 对修复流程的信任丧失。
接下来,此流水线将成为争议变更的文件仲裁基础。
本章的最小可运行集位于 examples/spec-ci/。在部署完整 GitHub Actions 流程之前完成它。首先确保本地网关绿色通过。然后将相同命令迁移到 CI。
> [runnable] — 可运行示例:examples/spec-ci/scripts/check_coverage.py 和 examples/spec-ci/scripts/validate_schema.py。
cd book2/examples/spec-ci
python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md
python3 scripts/validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures
产物与就绪标准
| 产物 | 就绪条件 |
|---|
| 本地运行 book2/examples/spec-ci | 无外部依赖的 smoke-pass | | 覆盖率检查 requirements → plan | 每个 REQ-* 有实现任务,每个任务有 implements | | JSON Schema 检查 | 有效固定数据通过,反例按预期失败 | | validation.md 记录 | 网关错误消息指明文件、规则和修复操作 |
完整追踪添加 .github/workflows/spec-ci.yml 或其项目等效物、用于 requirements → plan 图的 out/spec-ci/coverage-report.json、含领域模型违规的 out/spec-ci/scope-violations.ndjson、基于 validation.md 固定数据的 out/spec-ci/schema-audit.json 以及本地快速运行包装器。当其就绪条件为:范围检查阻塞事件模型外的自主操作、spec_gate 任务在受保护分支中为必需、CI 诊断格式指明文件、行号、规则标识符和操作。
实践
cd book2/examples/spec-ci && python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md— *预期:返回代码 0,stdout 单行coverage ok: 3 requirements covered。*
python3 scripts/validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures— *预期:返回代码 0;stdout 包含valid-incident.json: valid和invalid-missing-incident-id.json: expected invalid, rejected: missing required property incident_id(反例固定数据标记_expected_invalid: true,因此视为成功拒绝)。*- 将一条 Spec CI 记录移入
capstone/validation.md:「coverage ok: 3/3, schema ok: 2/2(反例因missing required property incident_id被拒绝)」。 预期:下次回归时,该记录允许在不阅读 CI 日志的情况下恢复合并阻塞内容。
检查问题
- 为什么基于词的覆盖率弱于
requirements → plan图? - 范围检查应捕获哪些违规,不应捕获哪些?
- 什么使 CI 错误无需调查即可修复?
- 规范网关因
REQ-ID不匹配阻塞合并。程序员希望向现有计划项添加REQ-ID并合并拉取请求。此方法有何危险?