Skip to content

Commit f9ad75d

Browse files
author
Test User
committed
docs: structural PR review for BlockerKind + contamination gate (P1: retry-on-404, P2: contains-pattern)
1 parent 4e007d7 commit f9ad75d

1 file changed

Lines changed: 93 additions & 0 deletions

File tree

.docs/pr-review-gaps-2026-06-29.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Structural PR Review: BlockerKind Classification + Contamination Gate
2+
3+
**PRs**: Ref #2465, Ref #2409
4+
**Author**: AI Agent
5+
**Reviewed**: 2026-06-29
6+
**Commit range**: 218d9e2a3..4e007d737
7+
**Files**: 6 changed, +395/-52 lines
8+
9+
## Summary
10+
11+
This PR adds two independently valuable features to the merge coordinator:
12+
1. **BlockerKind classification**: queries Gitea's CI status API to distinguish CI failures (`ci_failed`, `ci_pending`) from policy/confidence holds (`not_mergeable`), so operators can triage blocked PRs at a glance.
13+
2. **Contamination gate**: scans PR file lists for artefact contamination (`.sessions/`, `.review_tmp/`, `.handoff/`, `.beads/`) before mergeability evaluation, preventing ADF agent noise from entering main.
14+
15+
The implementation is clean and well-structured. The `PrEvaluation` struct gains a backward-compatible `Option<BlockerKind>` field. `evaluate_one` is now async to support CI status lookups, with an `Option<&GiteaClient>` parameter for testability. The contamination check runs first (blocking artefact PRs immediately), followed by CI-status-based blocker classification.
16+
17+
**What was done well**:
18+
- Clean separation of concerns: `classify_blocker`, `check_contamination`, and `evaluate_one` each have single responsibilities.
19+
- Testability: `evaluate_one` accepts `Option<&GiteaClient>` so unit tests can pass `None` and skip network calls.
20+
- Pagination: `list_pr_files` correctly uses `X-Total-Count` header with a page loop and guards against infinite loops via `page_len == 0`.
21+
- Test coverage: 35 tests (34 existing + 1 new pattern-match test) all pass.
22+
23+
**What remains problematic**:
24+
- One P1 finding: `list_pr_files` retry-with-backoff on non-retryable 404/4xx responses may waste time.
25+
- One P2 finding: `check_contamination` pattern matching uses `contains` which could produce false positives on filenames that coincidentally contain pattern strings.
26+
27+
## Confidence Score: 4/5
28+
29+
Safe to merge with awareness of one P1 retry-noisiness issue. The retry-on-404 behaviour pre-exists in the `send_with_retry` helper (not introduced here), so it is consistent with the rest of the codebase. The contamination pattern false-positive risk is theoretical (no known collision in practice). Tests pass, clippy clean, fmt clean. No security or data-loss risks.
30+
31+
## Important Files Changed
32+
33+
| Filename | Overview |
34+
|----------|----------|
35+
| `types.rs` | Added `BlockerKind` enum with `Display` + `Serialize`, 2 new tests. Clean. |
36+
| `gitea.rs` | Added `head_sha` to PrSummary, `CommitCombinedStatus` struct, paginated `list_pr_files`, `get_commit_status`. One P1 finding (retry on 404). |
37+
| `evaluator.rs` | Core changes: `evaluate_one` async, `check_contamination`, `classify_blocker`. Updated all 5 existing tests + 1 new test. `#[allow(clippy::collapsible_match)]` on `evaluate_one`. |
38+
| `lib.rs` | Extended `extract_fixes` to match `closes/close/fixes/fix/resolves/resolve` (was only `fixes`). Deduplication via `BTreeSet`. |
39+
| `main.rs` | Minor doc/import sync. |
40+
| `pid_lock.rs` | Minor doc sync. |
41+
42+
## Diagram
43+
44+
```mermaid
45+
%%{init: {'theme': 'neutral'}}%%
46+
flowchart TD
47+
A[evaluate_all: fetch open PRs] --> B[evaluate_one: per PR]
48+
B --> C{head_sha + GiteaClient?}
49+
C -->|Yes| D[check_contamination]
50+
C -->|No / test mode| G[Skip contamination]
51+
D --> E{file list has artefacts?}
52+
E -->|Yes: .sessions/ etc| F[Hold: contaminated]
53+
E -->|No| G
54+
G --> H{pr.mergeable?}
55+
H -->|No| I[classify_blocker]
56+
H -->|Yes| J[Merge]
57+
I --> K[GET /commits/{sha}/status]
58+
K --> L{CI state?}
59+
L -->|failure/error| M[Hold: ci_failed]
60+
L -->|pending| N[Hold: ci_pending]
61+
L -->|no data| O[Hold: ci_no_status]
62+
L -->|success| P[Hold: not_mergeable]
63+
64+
style D fill:#d4edda,stroke:#28a745
65+
style I fill:#d4edda,stroke:#28a745
66+
style F fill:#fff3cd,stroke:#ffc107
67+
style M fill:#fff3cd,stroke:#ffc107
68+
style N fill:#fff3cd,stroke:#ffc107
69+
style O fill:#fff3cd,stroke:#ffc107
70+
style P fill:#fff3cd,stroke:#ffc107
71+
```
72+
73+
## Inline Findings
74+
75+
**P1 `gitea.rs`, line 175**: **`list_pr_files` retries on 4xx/non-retryable errors**
76+
77+
The `get_with_retry` helper (called by `list_pr_files` to fetch each page) retries with backoff on ANY non-success status, including 404 (PR deleted), 403 (token expired), and 422 (malformed). A 404 on page 1 will retry 4 times (1s+2s+4s = 7s wasted) before giving up. A 404 on page N (after successful page 1..N-1) will also retry. This behaviour is inherited from `send_with_retry` (pre-existing in the codebase), so it is consistent, but it still adds latency to failure paths.
78+
79+
**Suggestion**: Consider adding a `non_retryable` status check to `send_with_retry` for 400-499 range, or wrap the page-loop in code that handles 404 specially (break early, return accumulated files). Alternatively, accept as-is since merge coordinator runs are infrequent and 7s latency on failure is negligible.
80+
81+
**P2 `evaluator.rs`, line 77**: **`check_contamination` uses `contains` which risks false positives**
82+
83+
The pattern match uses `file.starts_with(pattern) || file.contains(pattern)`. The `contains` check could match a path like `src/sessions_parser.rs` against `.sessions/` (though the `/` in the pattern makes this extremely unlikely). A more precise check would use `file.starts_with(pattern) || file.contains(&format!("/{}", pattern.trim_end_matches('/')))`.
84+
85+
**Impact**: Theoretical only. No known collision in practice. The check is a hold, not a permanent block — a human can override it. Acceptable as-is for initial implementation.
86+
87+
## Comments Outside Diff
88+
89+
*No findings on unchanged code.*
90+
91+
---
92+
93+
*Last reviewed commit: 4e007d737 | Reviews (1)*

0 commit comments

Comments
 (0)