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

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

应用部分 7. 规范 CI:将规范作为可执行产物

状态:建议。 在 CI 中运行规范检查是一项成熟实践。具体的网关集合(覆盖率、范围、架构、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,因为覆盖率和 JSON Schema 可以在没有 CI 的情况下本地验证。
  • capstone/ 的要求:high_memory_usage 的一条 Spec CI 记录:命令、已证明的事实和反例。
  • 第一遍的主要术语:Spec CI。gatefixtureschema-checkcoverage-check 为参考性术语,直接出现在命令和脚本注释中。
  • 可延后内容:GitHub Actions 工作流、范围网关以及从任意 validation.md 中提取固定数据。

目标

在本章中,规范网关从「检查文档」的理念转变为事件项目的可运行 GitHub Actions 流水线。每次 push 和每个拉取请求都经过强制网关。网关在以下三类违规情况下阻止合并:

  • 需求未满足,
  • 超出边界(out-of-scope),
  • JSON Schema 错误。

读者将获得一个实用仓库结构,其中 requirements.mdplan.mdvalidation.md 和 API 合约作为可执行产物而非参考文档进行验证。

主要收益——团队获得 CI 中可复现的阻塞机制。关于规范质量的争论归结为具体行、规则和修复操作。

最小教学场景

教学案例

incident payload:验证 requirements.mdplan.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 工作流,则无需转移;对于教学最小场景,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 的证明
Coveragecheck_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_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——如有需要,如果其中固化了事件流水线的领域约束。

在分支保护设置中,将最终任务 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_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 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_idseveritysourceescalation_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 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

产物与就绪标准

产物就绪条件

| 本地运行 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 诊断格式指明文件、行号、规则标识符和操作。

实践

  1. cd book2/examples/spec-ci && python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md — *预期:返回代码 0,stdout 单行 coverage ok: 3 requirements covered。*
  1. 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,因此视为成功拒绝)。*
  2. 将一条 Spec CI 记录移入 capstone/validation.md:「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

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

课程菜单

课程

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