Files
playbook/skills/commit-message/references/ci-wiring.md
T
cshandClaude Fable 5 7408c532f0 🐛 fix(commit-message): enforce one rule owner across skill and CI
CI ran .gitea/ci/commit_message_lint.py, an independent reimplementation
that parsed the type/emoji mapping out of docs/common/commit_message.md.
Its scope pattern accepted fix(-a), fix(a-), fix(a--b) and fix(_), and it
had no subject length check, so the gate that actually blocks merges
enforced weaker rules than the Validation this skill reports.

The CI entry is now a wrapper that locates the skill validator and
delegates to it with no arguments; commit_policy.json becomes the only
rule source and explanatory docs stop being a machine policy input.

The validator also learns the workflow_run event, whose payload carries
neither a PR title nor a before/after range. An upstream pull_request now
validates <integration-branch>..head_sha instead of HEAD alone, taking
the branch name from COMMIT_LINT_MAIN_BRANCH, and degrades to the
upstream head commit with a WARN rather than guessing a base.

Alongside: --help now documents the no-argument CI mode it had always
supported silently, CI wiring detail moves to references/ci-wiring.md,
and the description gains negative boundaries.

test/test_commit_message_policy.py asserts policy/spec-table equality and
uses ast to assert the CI entry imports no regex and reads no file, so a
second implementation cannot reappear unnoticed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 15:21:37 +08:00

74 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CI 接线(CI Wiring
配置或排查 CI 上的提交信息校验时读本文件。交互式起草提交信息不需要它。
## 唯一实现
`scripts/validate_commit_message.py` 是提交信息规则的唯一实现,策略来自
`references/commit_policy.json`。CI 入口只负责定位并调用它:
```bash
python <skill-root>/scripts/validate_commit_message.py
```
无参数即进入 CI 输入校验模式。退出码原样透传:`0` 全部通过、`1` 存在不合规主题、
`2` 策略/参数/输入错误。
CI 入口不得自行实现校验,不得从 `docs/common/commit_message.md` 之类的说明性文档
解析 type/emoji 映射。说明性文档是给人读的;把它当机器策略来源会让 CI 与
`commit_policy.json` 各自演进,而 validator 报告的 `Validation` 就不再代表实际
拦合并的那道门。本仓库的 `.gitea/ci/commit_message_lint.py` 是薄 wrapper 范例,
其纪律由 `test/test_commit_message_policy.py` 机器保证。
## 事件覆盖范围
`validate_commit_message.py --help` 的当前输出为权威;下表是形状说明。
| 事件 | 取回的主题 |
| --------------- | ----------------------------------------------------------------- |
| `pull_request*` | `pull_request.title``base.sha..head.sha` 的完整提交范围 |
| `push` | `before..after` 完整范围;`before` 为零对象时按分支图计算新增提交 |
| `workflow_run` | 见下节 |
| 其它 / 本地 | 回退 `HEAD` 一条 |
已识别事件缺失或损坏 payload 时失败;shallow repository 一律失败。不能用可能截断的
payload `commits` 数组或计数字段证明范围完整。
## workflow_run 编排
`workflow_run` payload 不含 PR 标题,也不含 `before`/`after``workflow_run.head_sha`
是唯一锚点。因此:
- **上游为 `pull_request*`**:取 `<集成分支>..head_sha` 的完整范围。集成分支名读
`$COMMIT_LINT_MAIN_BRANCH`(默认 `main`),依次尝试
`refs/remotes/origin/<名字>``refs/heads/<名字>`
- **其它上游事件**:只校验 `head_sha` 一条。
- 集成分支无法解析、上游 `head_branch` 就是集成分支、或计算出的范围为空时,退化为
只校验 `head_sha` 并打印 `WARN`,**不猜测 base**。默认分支的 push 因此只校验被推
上去的那一条,这是可接受的:进入默认分支的路径由 PR 那道门守。
该架构下 PR 标题不参与校验,这是 payload 决定的,不是配置项。完整提交范围校验取代
它,且强度更高——标题一直只是历史的代理,范围校验管的是真正留在历史里的东西。若确实
需要校验 PR 标题,只能在直接响应 `pull_request` 的那个 workflow 里做。
## 可信编排
workflow 编排必须来自可信 base/default branch。两条路:
1. 直接触发用 `pull_request_target`
2. 先由 `pull_request` 准备输入,再由默认分支的 `workflow_run` 执行。
普通 `pull_request` 中来自 PR head 的 workflow 即使切到 base worktree,仍可被待审改动
删除或绕过。可信 workflow 必须从可信 base 运行 wrapper、validator 和 policy;只能读取
PR head 的 Git 对象,不能在持有 secret 的步骤 checkout 或执行 PR head 内容。
## 排查
| 现象 | 处理 |
| ----------------------------------------- | ------------------------------------------------------------- |
| `rc=2``ERROR: ...policy...` | 部署或配置问题,不是提交信息不合规;检查 policy 路径与 schema |
| `rc=2``ERROR: ...payload...` | 事件 payload 缺失或损坏;核对 `GITHUB_EVENT_PATH` |
| `rc=2``requires complete ... history` | checkout 是 shallow;改为完整 fetch |
| `checks: 1 subject(s)` 但期望是一个范围 | 读 stderr 的 `WARN`:集成分支未解析,或上游不是 PR |
| `validator: ... not found` | wrapper 的候选路径与实际部署布局不符 |