# 裁决记录 — REQUEST_CHANGES（硬门禁失败 + 1 个 major）

> 模拟 `gitlink-gatekeeper` 的完整执行记录：采集 → 逐维算分 → 硬门禁 → 裁决 → 渲染评分卡 → dry-run 回写命令。
> 策略：默认 `gatekeeper.yaml`（权重 40/20/15/15/10，severity_penalty blocker=100/major=25/minor=5/nit=1，thresholds pass=85 / request_changes=60，max_changed_files=80）。
> 命令均来自 `REFERENCE.md` 第 7 节 CLI 映射，已对照真实 shortcut 核验。

**场景**：`Gitlink/forgeplus` 仓库 PR #305，重构订单结算逻辑，改动了 4 个源码文件但**未附带任何测试**，其中一处折扣计算有边界缺陷（major）。CI 通过。

---

## ① 采集上下文

```bash
gitlink-cli pr +view  -i 305 --owner Gitlink --repo forgeplus --format json
gitlink-cli pr +files -i 305 --owner Gitlink --repo forgeplus --format json
gitlink-cli pr +diff  -i 305 --owner Gitlink --repo forgeplus --format json
gitlink-cli api GET /Gitlink/forgeplus/pulls/305/commits --format json
gitlink-cli ci +builds --owner Gitlink --repo forgeplus --format json
```

**采集结果摘要**

| 信号 | 值 |
|------|----|
| 标题 | `refactor(billing): rework settlement pipeline` |
| 描述 | 非空，长度 88 字符（≥30），**无** `#<n>` 关联 |
| `changed_files` | 4 |
| `changed_src` | 4（`settlement.go` / `discount.go` / `invoice.go` / `tax.go`） |
| `changed_tests` | 0 |
| commits | 3 条，符合 Conventional Commits 的 2 条（1 条为 `wip: fix`）→ conforming=2 |
| CI | passing |
| AI 审查发现 | 0 blocker / 1 major / 1 minor / 2 nit |

---

## ② 逐维算分（公式代入，可复算）

**review_findings（权重 40）** — §3.1
```
penalty = 25(major) + 1×5(minor) + 2×1(nit) = 32
score   = 40 × max(0, 1 − 32/40) = 40 × (1 − 0.8) = 40 × 0.2 = 8
```

**test_coverage（权重 20）** — §3.2
```
changed_src=4 > 0, changed_tests=0
→ score = 0
```

**pr_hygiene（权重 15）** — §3.3
```
描述≥30 ✓ (+1/3)
关联 Issue ✗ (0)
体量 changed_files=4 ≤ max_changed_files/2 = 40 ✓ (+1/3)
命中比例 = 2/3
score = round(15 × 2/3) = round(10.0) = 10
```

**commit_quality（权重 15）** — §3.4
```
conforming/total = 2/3
score = round(15 × 2/3) = round(10.0) = 10
```

**ci_status（权重 10）** — §3.5
```
CI passing → score = 10
```

**总分**
```
total = 8 + 0 + 10 + 10 + 10 = 38
```

---

## ③ 硬门禁判定（§4）

| 门禁 | 配置 | 命中？ |
|------|------|:------:|
| `forbid_blocker_findings` | true | 否（0 blocker） |
| `require_ci_pass` | true | 否（passing） |
| `require_tests_for_src_changes` | true | **是**（changed_src=4>0 且 changed_tests=0） |
| `require_linked_issue` | false | —（未启用） |
| `max_changed_files` | 80 | 否（4 ≤ 80） |

`hard_gate_failed = true`。

---

## ④ 裁决（§5）

```
hard_gate_failed = true
→ verdict = REQUEST_CHANGES   （无视总分；本例 total=38 < request_changes(60) 也指向 REQUEST_CHANGES，结论一致）
```

---

## ⑤ 渲染的评分卡

```markdown
## 🛡️ Gatekeeper Report — PR #305 refactor(billing): rework settlement pipeline

**Verdict: ❌ REQUEST_CHANGES**  ·  Score: 38/100  ·  policy: gatekeeper.yaml@v1

| Dimension | Weight | Score | Notes |
|-----------|:------:|:-----:|-------|
| Review findings | 40 | 8/40 | 0 blocker / 1 major / 1 minor / 2 nit |
| Test coverage   | 20 | 0/20 | 4 src / 0 test files |
| PR hygiene      | 15 | 10/15 | desc ✓ / linked issue ✗ / size ✓ |
| Commit quality  | 15 | 10/15 | 2/3 conventional |
| CI status       | 10 | 10/10 | passing |

### ⛔ Hard gate failures (1)
- `require_tests_for_src_changes`: 改动了 4 个源码文件，但本 PR 未包含任何测试文件

### 🔴 Must fix (1)
- [major] 折扣金额未做下限保护，满减叠加时可算出负数总价 — discount.go:72

### 🟡 Should fix (1)
- [minor] `settle()` 吞掉了 tax 计算的 error，应向上传播 — settlement.go:118

### 🔵 Nits (2)
- [nit] commit `wip: fix` 不符合 Conventional Commits，建议 rebase 整理 — (commit)
- [nit] 导出结构体 `Invoice` 字段缺少注释 — invoice.go:14

### ✅ Strengths
- 改动聚焦、体量适中（4 文件），CI 全绿

### Next steps
1. **补测试**（最高优先级）：为 4 个改动源码文件补单元测试，解除 `require_tests_for_src_changes` 硬门禁
2. 修复折扣下限的 major 缺陷，建议补一条针对负数总价的回归测试
3. 整理 `wip: fix` commit、补充 error 传播后，重新触发 gatekeeper
---
*Generated by gitlink-gatekeeper · policy-as-code PR gate · re-run after changes*
```

---

## ⑥ 将执行的回写命令（dry-run 展示）

> 默认 dry-run，不传 `--apply` 时仅打印。REQUEST_CHANGES 永不涉及合并。

```bash
# [dry-run] 1) 评分卡作为评论回写（与 workflow 脚本一致，走 pr +comment）
gitlink-cli pr +comment -i 305 -b "$(cat scorecard.md)" --owner Gitlink --repo forgeplus
#   裁决=REQUEST_CHANGES 不直接用平台的强语义 rejected（留给人工）；裁决靠评分卡标题 + 标签表达

# [dry-run] 2) 打 needs-changes 标签（labels.request_changes = "gatekeeper:needs-changes"）
gitlink-cli label +create -n "gatekeeper:needs-changes" -c "#D1242F" \
  -d "Gatekeeper verdict: REQUEST_CHANGES" --owner Gitlink --repo forgeplus
#   标签挂到 PR 背后的 issue.id（非 PR 号）：
ISSUE_ID=$(gitlink-cli pr +view -i 305 --owner Gitlink --repo forgeplus --format json | jq -r '.data.issue.id')
gitlink-cli api POST /Gitlink/forgeplus/issues/$ISSUE_ID --body '{
  "issue_tag_ids": [<gatekeeper:needs-changes 的 tag_id>],
  "done_ratio": 0,
  "subject": "<PR 原标题>",
  "description": "<PR 原描述>"
}'

# [dry-run] 3)（子题三善后）创建 tracking issue 汇总必修项并关联 PR #305
#   注意：用 $'...' 让 \n 成为真实换行（双引号里的字面 \n 不会换行）
gitlink-cli issue +create --owner Gitlink --repo forgeplus \
  -t "[gatekeeper] PR #305 必修项跟踪" \
  -b $'由 gatekeeper 裁决 REQUEST_CHANGES 触发。\n硬门禁: require_tests_for_src_changes。\nMust fix: discount.go:72 折扣下限。\n关联 PR: #305'
```

> 实际写回需追加 `--apply`，Agent 会先复述「将回写 1 条评论 + 打 gatekeeper:needs-changes 标签 + 建 1 条 tracking issue，不合并」并等待确认。
