阅读材料: 应用部分 7. Specification CI:规范作为可执行制品

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

应用部分 7. Specification CI:作为可执行工件的规范

状态:建议。 在 CI 中运行规范检查是一项稳定的实践。具体的关卡集合(coverage、scope、schema、spec_gate)以及 JSON 诊断格式是大多数团队都会适配的推荐框架。对来自 validation.md 的固件进行 JSON Schema 检查是该工具的标准用法。

在本章中,「规范关卡」是对「像普通 CI 检查代码一样检查规范」这一环节的简称。该检查本身由常规步骤组成:解析 requirements.mdplan.md、根据 JSON Schema 校验示例、检查文件之间的对应关系。其余术语——gatefixtureschema-checkcoverage-check——将在它们实际出现在命令或脚本的相应位置时引入,无需在一句开篇定义中把它们全部塞进去。

规范关卡正是对 第一卷第 9 部分 凭肉眼执行的那套流程的自动化,也是 第 16 部分 在拉取请求中以团队形式完成的那套流程的自动化。在教学用 AgentClinic 中,来自 第 12 部分 的反馈所对应的 REQ 标识符和负载模式还可以停留在约定层面。在生产环境中,这种信任余量并不存在。必须把同样的关联关系转成强制关卡,让绿色单元测试也无法绕过。

阅读前

  • 第一卷的基础:第 9 部分将 validation.md 与事实关联,第 16 部分展示证据包评审。
  • 本地教学用例:incident payload,因为 coverage 和 JSON Schema 可以在本地、脱离 CI 验证。
  • capstone/ 的落点:为 high_memory_usage 写一条 Spec CI 行:命令、被证明的事实和反例。
  • 第一遍阅读的主要术语:Spec CI。gatefixtureschema-checkcoverage-check 属于参考性术语,会直接出现在命令和脚本注释中。
  • 暂时搁置的内容:GitHub Actions workflow、scope-gate,以及从任意 validation.md 中抽取固件。

目标

在本章中,规范关卡从「检查文档」这一理念转变为事件响应项目可运行的 GitHub Actions 流水线。每次 push 和每个拉取请求都必须通过该关卡。当出现以下三类违规时,关卡会阻止合并:

  • 需求未实现,
  • 超出范围(out-of-scope),
  • JSON Schema 错误。

读者将获得一个实用的仓库蓝图,其中 requirements.mdplan.mdvalidation.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.pyvalidate_schema.py

步骤

  1. cd book2/examples/spec-ci预期:你已位于可运行示例的目录中。
  2. python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md。*预期:返回码为 0,所有 REQ-* 都与计划存在关联。*
  3. python3 scripts/validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures预期:合法固件通过,非法固件按预期失败。
  4. 查看针对非法固件的报错信息。预期:能直观看出缺失哪个字段,以及应当修改哪个文件。
  5. validation.md 中记下关卡具体阻塞的内容:覆盖率、范围还是模式。

验收事实

一次本地运行就能呈现两类真值:需求与计划存在关联,且数据符合契约。如果 CI 报错没有指明文件、规则和动作,那它对团队而言尚未就绪。

如何落到 capstone/

把一行 Spec CI 写入 capstone/validation.md:运行了哪条命令、它证明了什么、被阻塞的反例是什么。如果还没有创建完整的 GitHub Actions workflow,就不要硬塞进去;对于教学最低要求而言,examples/spec-ci 中的可运行版本已经足够。

最小片段:

| Spec CI | `python3 scripts/check_coverage.py ...` | 所有 REQ-* 与计划存在关联 | PASS |
| Schema negative | `python3 scripts/validate_schema.py ...` | 缺失 incident_id 被阻塞 | PASS |

迁移到 high_memory_usage

教学示例运行在 incident payload 上,但 capstone/ 中需要为 high_memory_usage 写一行。把相同的两类检查套到自己的需求上:

检查项命令(教学用)high_memory_usage 证明了什么
Coveragecheck_coverage.py --requirements requirements.md --plan plan.md需求 REQ-HM-01「在未确认 RSS 在 5 分钟内持续 > 90% 时不重启 pod」与 plan.md 中的一项任务存在关联

| Schema negative | validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures | 缺少 incident_idseverity: "P0" 但缺少 backup_verified 的固件被阻塞 |

如果无法为 high_memory_usage 写出 Coverage 和 Schema negative 这两行,说明 requirements.md 中还没有可验证的需求,或者模式还无法区分缺少备份的 P0 事件。

可评审的落点

在教学包中,保留对 requirements.mdplan.mdvalidation.md 或模式的改动。若仅为本地诊断而创建的临时固件,只要未纳入回归集,就不必保留。

核心思想

此处「可执行工件」并不意味着把 Markdown 当作程序运行,而是用普通 CI 脚本检查需求、计划和示例。

pull_request 和到受保护分支的 push 都要运行强制 GitHub Actions 关卡。为什么要同时设置两个触发条件。规范违规可能通过两条路径溜进来:常规的拉取请求,或者对支撑性文件的直接更新。

最小可追踪工件集合:

  • requirements.md
  • plan.md
  • validation.md
  • contracts/**
  • constitution.md——视需要,若其中固化了事件流水线的领域约束。

在分支保护(branch protection)设置中,务必把最终任务 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

范围(scope)检测器用于处理形式上可追踪、但步骤内容超出事件域的情况。将 plan.md 中的动作与领域模型 incident-response 比对。允许的操作例如:

  • acknowledge
  • escalate
  • annotate
  • rollback
  • notify_on_call

任意业务动作(如 notify_financeclose_customer_contractforce_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.jsonpagerduty_webhook.schema.jsongrafana_alert.schema.json

关注两个方向:合法示例必须无错通过;特意构造的非法示例必须按预期失败。如果非法负载通过校验,说明模式过于宽松,无法保护契约。

在合并到受保护分支之前,校验严格度与应用测试一致。错误的 incident_id、非法的 severity 或为空的 source 都会破坏整个修复链路。

> [project script] —— scripts/spec_ci/extract_fixtures.pyscripts/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 钩子]
B[本地快速运行与 push 前的轻量校验]
C[PR 推送]
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 中放入一个 webhook 负载示例,字段为 incident_idseveritysourceescalation_target

检查项:若 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 requirementrogue taskschema 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.pyexamples/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

面向智能体计划的 AoT 关卡

Spec CI 检查的不仅是文本层面的需求。如果智能体把计划构建为一组原子动作,该计划本身也成了可校验工件。格式可以很简单:atoms[],其中每个原子包含 idkindnameinputdependsOn

最低规则:LLM 有权提议原子动作,但在校验通过前无权执行。本地关卡应拒绝以下情况:

  • 未知工具名;
  • 缺少必需参数;
  • 引用了不存在原子的结果;
  • 出现依赖环;
  • 动作超出 incident-response 领域模型。

对于 high_memory_usage,形如:

{
  "atoms": [
    {"id": 1, "kind": "tool", "name": "read_metrics", "input": {"window": "10m"}, "dependsOn": []},
    {"id": 2, "kind": "tool", "name": "check_readiness", "input": {"incident_id": "HM-2026-05-17-01"}, "dependsOn": [1]},
    {"id": 3, "kind": "tool", "name": "dry_run", "input": {"action": "restart_pod"}, "dependsOn": [2]}
  ]
}

这样的计划不能凭文本说服力被接受。它必须被当作图来校验:拓扑排序、允许的 name 列表、每个工具 input 对应的 JSON Schema,以及对影响半径的独立检查。这与 requirements.md → plan.md 的原则一致:规范只有在能被机器拒绝时才真正有用。

工件与就绪标准

工件就绪条件
book2/examples/spec-ci 本地运行smoke-pass,无外部依赖
requirements → plan 覆盖率检查每条 REQ-* 都有实现任务,每个任务都带 implements
JSON Schema 检查合法固件通过,非法固件按预期失败
原子计划检查未知工具、依赖环、超出领域的动作在执行前被阻塞
validation.md 记录关卡报错信息指明文件、规则和修复动作

完整轨道还会加入 .github/workflows/spec-ci.yml 或其项目等价物、out/spec-ci/coverage-report.json(用于 requirements → plan 图)、out/spec-ci/scope-violations.ndjson(领域模型违规)、out/spec-ci/schema-audit.json(基于 validation.md 中的固件),以及一个本地快速运行封装。其就绪条件是:范围检查能阻塞 incident-response 模型之外的自治动作;spec_gate 任务在受保护分支中为必需项;CI 诊断格式同时给出文件、行号、规则标识和动作。

练习

  1. cd book2/examples/spec-ci && python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md —— *预期:返回码 0,stdout 为一行 coverage ok: 3 requirements covered。*
  2. python3 scripts/validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures —— *预期:返回码 0;stdout 包含 valid-incident.json: validinvalid-missing-incident-id.json: expected invalid, rejected: missing required property incident_id(非法固件带有 _expected_invalid: true 标记,因此视为成功被拒)。*
  1. capstone/validation.md 中记入一行 Spec CI:「coverage ok: 3/3, schema ok: 2/2(非法样例因 missing required property incident_id 被拒)」。预期:在下次回归出现时,该行足以还原合并被阻塞的原因,而不必翻阅 CI 日志。

思考题

  1. 为什么基于相同词汇的覆盖率弱于 requirements → plan 图?
  2. 范围检查应当捕获哪些违规,又应放过哪些?
  3. 是什么让一条 CI 错误无需排查就能被修复?
  4. 规范关卡因 REQ-ID 不匹配而阻塞合并。程序员打算给现有计划项加上 REQ-ID 然后合并。这种做法的问题在哪里?
我的笔记
0 / 10000

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

课程菜单

课程

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