# REVIEWS.md · 贡献审查记录

> 本文件与 `reviews.json` 是同一份记录的两种表示，由 `repo_lint` **R16** 强制一致。
> 名录（`CREDITS.md`）回答「谁被记下了」，本文件回答「凭什么记他」。

## 1. 审过才签：这一层防的是什么

2026-11-11 的决定：不逐条签 commit；也不允许「只要提交了就自动算贡献」。
两者之间需要一层闸门，就是本文件——**一条提交先被审，审出确实有贡献，它才进名录的依据、
才值得由作者本人签名锁死。**

先说清一件容易搞反的事：**维护者不能替贡献者签。**

- 签名在 git 里绑的是**作者身份**，不绑背书关系。别人签你的提交，`verify-commit` 报的是
  `Good signature for 别人`——读起来像「有人为你背书」，技术上却是「这条提交的作者是别人」。
- R15 抓的正是这件事：签名者 principal 必须是该作者登记过的邮箱，否则判红。维护者代签，
  体检立刻红。这不是配置错误，是规则在说「你正在改写作者身份」。

于是顺序是唯一的：

```
提交 → 审查（本文件） → 进名录依据（§2） → 作者本人签 → R15 锁身份
```

签名既不是审查的替代品，也不是审查的奖章；它是作者对自己那部分的主张。
审查管的是「这段东西算不算他的贡献」，签名管的是「这条提交确实出自他手」。

## 2. 基线：为什么用「范围」，不逐条列举

`reviews.json` 的 `baseline` 指向 `638f157`（含）及之前共 28 条提交，作者全部是唯一著作权人
本人（两个维护者账号按 `credits.json` 的 `accounts` 折算到同一人），无第三方提交。

不逐条补登的理由：

1. **没有争议时写记录，是把成本花在不存在的问题上。** 审查记录的价值在于「有人可能不认账」；
   一个人自己的 28 条提交不存在这个可能。
2. **记录一多，人就不看了。** 28 行「自己审自己」会让第 29 行（真正的第一条外部记录）
   淹没在里面。
3. **基线可以指认，所以不是空白。** 想知道到底覆盖了哪些提交，`git rev-list 638f157` 就是答案，
   不依赖本文件的措辞。

基线之后，规则换成硬约束：**作者不是有发布权的人，提交就必须有审查记录**（R16 ⑥）。
这一条让「新人不审就提交」在体检里无法蒙过去。

## 3. 什么算「确实有贡献」：四条判据

判据要能落到「是/否」，否则审查会退化成印象打分。

| # | 判据 | 满足的样子 | 不满足的样子 |
| --- | --- | --- | --- |
| ① | **改动可指认且非机械** | 改了一个具体行为，diff 能说出改了哪 | 只动空白 / 行尾 / 文件排序 / 全仓替换 |
| ② | **作者能解释** | 被问「为什么这么改」答得出 | 整段贴进来、说不清来源的代码 |
| ③ | **可复现** | 干净 clone 里按 `REPRODUCE.md` 跑，判据仍全绿 | 依赖只有作者本机才有的路径、口令、服务 |
| ④ | **边界守住** | 许可头或伴生文件齐；引用他人代码标明来源 | 把别人的代码写进自己名下、许可头漏写 |

四条**全部满足**才记 `accept`。任一条不满足，记 `reject` 并写清卡在哪一条——
`reject` 也是记录，它防的是「过几个月有人重新提一遍，大家从头吵」。

三个真实形态的反例，遇到了直接判 ①/②：

- **机械批量**：全仓把某个词换掉，提交说明写「优化」。这类改动有时有用，但它不是**贡献**，
  是操作；要收就单独收成一条、说明改了什么，不加进任何人的依据。
- **生成式堆量**：一次几千行、作者无法逐段解释。这类东西的著作权归属本身就不确定
  （是否构成独创性表达存疑），收进来等于把不确定性写进名录。
- **代提交**：A 写了代码，B 用 `--author` 提交。作者字段写了 A，署名权归 A，但**提交行为**是 B 的。
  这种情形必须在记录里写清，且签名只能由 A 出——B 签就触发 R15 冒名。

## 4. 一次审查做五件事

命令都在仓库根执行；`<sha>` 是待审提交。

```sh
# ① 看范围，不看摘要
git show <sha> --stat

# ② 读 diff。审查唯一不能省的一步
git show <sha>

# ③ 在干净 clone 里跑判据（确认不是「本机能跑」）
git clone https://drawcommons.org/repo.git /tmp/rv && cd /tmp/rv
python3 evidence/build_ledger.py && python3 evidence/regen_manifest.py

# ④ 查作者身份，有签名就验
git log -1 --format='%an <%ae>' <sha>
git -c gpg.ssh.allowedSignersFile=repo/allowed_signers verify-commit <sha>

# ⑤ 写记录（两处：本文件 §7 表格 + reviews.json 的 reviews 数组）
```

`--stat` 会骗人：一次「+2 −2」可能删掉了整个校验函数。凡 `--stat` 里出现判据相关文件
（`evidence/`、`spec/`、`deploy/app/`）的增删，必须读 diff。

## 5. 一条记录长什么样

| 字段 | 含义 | 约束（R16 校验） |
| --- | --- | --- |
| `sha` | 提交（7 位足够，须唯一前缀） | 必须是真实存在的提交 |
| `author` | 作者的人名录 id | 必须在 `credits.json` 的 `people` 里 |
| `reviewer` | 审阅人 id | 必须 `can_release = true` |
| `date` | 审查日期 | — |
| `verdict` | `accept` / `reject` | 只允许这两个值 |
| `basis` | 卡在哪条判据、或满足了哪几条 | `accept` 时四个判据要能对上 |
| `contribution` | `code` / `docs` / `data` | 与名录 `roles` 不冲突 |

`accept` 有一条硬连带：**该提交必须出现在该作者 `credits.json` 的 `evidence.commits` 里。**
审过了却没进依据，等于名录漏记，R16 直接红——这正是「审查」与「署名」之间要焊死的地方。

## 6. 审查不证明什么

| 不证明 | 由谁承担 |
| --- | --- |
| 写得好、没有 bug | `REPRODUCE.md` 的判据与后续修改，审查不做质量担保 |
| 著作权归了项目 | 著作权自创作完成即归作者；审查记录不转移、不许可任何权利 |
| 作者有权提交（DCO） | `DCO` 是作者自己的声明，审查是维护者确认有贡献，两件事，不能互相替代 |
| 审阅人不会造假 | 只能靠名录与历史的公开性：改一条记录要在 `CHANGELOG` 留痕，改历史被服务端两道闸拦住 |

## 7. 记录

| 提交 | 作者 | 审阅人 | 日期 | 结论 | 依据 |
| --- | --- | --- | --- | --- | --- |
| `638f157` 及之前 28 条（基线） | laofu | laofu | 2026-11-11 | accept | 全部由唯一著作权人本人提交，无第三方提交，见 §2 |

`reviews.json` 的 `reviews` 数组为空，因为基线用范围表达，不占条目。
**这不是待填的占位，是当前的事实：至今没有任何第三方提交。**

## 8. 加一条记录

1. 按 §4 走完五步；
2. 本文件 §7 表格加一行；`reviews.json` 的 `reviews` 数组加一条（字段见 §5）；
3. `accept` 的，同时在 `credits.json` 该作者的 `evidence.commits` 里加上该 `sha`；
4. 跑 `python3 evidence/repo_lint.py repo`，R12、R13、R16 必须全绿；
5. 发版时 `CHANGELOG` 记一句，说明引入了第一位/新一位贡献者。

**只改一处必然红**：只改 md 不改 json（R16 两源分叉）、只记 review 不进依据（R16 ⑤）、
只给依据不记 review（R16 ⑥ 会先放行，但下一条外部提交就会被抓）。

## 9. 为什么审查记录也进 MANIFEST

`reviews.json`、`reviews.json.license`、`REVIEWS.md` 与名录同批纳入 `MANIFEST` 校验。
理由与 `credits.json` 相同：**能被单独改掉的承诺，等于没有承诺。** 记录进了指纹，
就算有人把某一条悄悄删掉，旧 tag 里的快照哈希也对不上。
