|
| 1 | +# Gap Remediation Plan — Disciplined Research & Design |
| 2 | + |
| 3 | +**Date**: 2026-06-29 |
| 4 | +**Gaps identified**: 3 active, 1 already fixed |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## Gap 1: BlockerKind Classification (PR #2588, Issue #2465) |
| 9 | + |
| 10 | +### Research |
| 11 | + |
| 12 | +**What**: Add `BlockerKind` enum to distinguish CI failures (`ci_failed`, `ci_pending`, `ci_no_status`) from policy holds (`not_mergeable`) in merge coordinator logs. |
| 13 | + |
| 14 | +**Problem**: Operators cannot tell whether a blocked PR failed CI (needs code fix), is pending CI (needs patience), or hit a policy gate (needs human review). Example: PR can be held by CI failure alone, but logs just say "not mergeable". |
| 15 | + |
| 16 | +**Current state on main**: No `BlockerKind` enum exists. `evaluate_one` is synchronous, no CI status querying. `PrSummary` has no `sha` field for commit lookup. |
| 17 | + |
| 18 | +**PR #2588 branch analysis**: 44 files, 27K lines — 90%+ noise (3 session dumps at 9K lines, 2 reconcile snapshots, handoff files). Core code is ~200 lines in `types.rs` (BlockerKind enum), `gitea.rs` (CommitCombinedStatus, get_commit_status, sha field), `evaluator.rs` (async evaluate_one with classification), and `lib.rs` (poll_pending_reviews). |
| 19 | + |
| 20 | +**Essential Questions**: |
| 21 | +| Question | Answer | |
| 22 | +|----------|--------| |
| 23 | +| Energising? | Yes — operators need this daily | |
| 24 | +| Leverages strengths? | Yes — core merge coordinator code | |
| 25 | +| Meets real need? | Yes — Issue #2465 has real-world examples | |
| 26 | + |
| 27 | +### Design |
| 28 | + |
| 29 | +**Approach**: Cherry-pick only the BlockerKind core from the branch. Create a clean branch from main with 4 files touched. |
| 30 | + |
| 31 | +**Scope**: |
| 32 | +| File | Change | |
| 33 | +|------|--------| |
| 34 | +| `types.rs` | Add `BlockerKind` enum + `Display` impl | |
| 35 | +| `gitea.rs` | Add `PrSummary.sha`, `CommitCombinedStatus` struct, `get_commit_status()` | |
| 36 | +| `evaluator.rs` | Make `evaluate_one` async, classify blocker via CI status | |
| 37 | +| `lib.rs` | Add `poll_pending_reviews()` or enrich existing poll with blocker_kind | |
| 38 | + |
| 39 | +**API Design**: |
| 40 | +```rust |
| 41 | +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] |
| 42 | +pub enum BlockerKind { |
| 43 | + CiFailed, |
| 44 | + CiPending, |
| 45 | + CiNoStatus, |
| 46 | + NotMergeable, |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +**Test Strategy**: |
| 51 | +| Test | Purpose | |
| 52 | +|------|---------| |
| 53 | +| `test_blocker_kind_ci_failed` | CI status "failure" → CiFailed | |
| 54 | +| `test_blocker_kind_ci_pending` | CI status "pending" → CiPending | |
| 55 | +| `test_blocker_kind_no_status` | No CI status → CiNoStatus | |
| 56 | +| `test_blocker_kind_not_mergeable` | Mergeable=False, CI green → NotMergeable | |
| 57 | + |
| 58 | +**Estimated effort**: 1 hour |
| 59 | + |
| 60 | +**Verdict**: IMPLEMENT on clean branch. Close PR #2588 with comment linking to new PR. |
| 61 | + |
| 62 | +--- |
| 63 | + |
| 64 | +## Gap 2: FirecrackerExecutor::ensure_pool() (PR #2590, #2521, Issue #2521) |
| 65 | + |
| 66 | +### Research |
| 67 | + |
| 68 | +**What**: Implement Firecracker VM pool warming so `ensure_pool()` doesn't always return Err. |
| 69 | + |
| 70 | +**Current state on main**: `ensure_pool()` IS implemented. The function: |
| 71 | +1. Checks if pool already exists (returns clone) |
| 72 | +2. Creates PoolConfig from RLM config |
| 73 | +3. Initialises pool with min/max/target sizes |
| 74 | + |
| 75 | +The function signature exists and compiles. Issue #2521 reported "always returns Err" but the current implementation shows it CAN return Ok. |
| 76 | + |
| 77 | +**PR #2590**: Closed. 42 files, 27K lines (similar noise pattern to #2588). |
| 78 | + |
| 79 | +**Verdict**: **ALREADY FIXED**. The `ensure_pool()` stub was replaced with a real implementation during the merge sprint (likely via PR #2902 or executor changes). Issue #2521 should be closed. |
| 80 | + |
| 81 | +**Close comment**: "`ensure_pool()` is now implemented in main (firecracker.rs) with real PoolConfig from RlmConfig. Verify: `grep -A20 'async fn ensure_pool' crates/terraphim_rlm/src/executor/firecracker.rs`" |
| 82 | + |
| 83 | +--- |
| 84 | + |
| 85 | +## Gap 3: Path::starts_with in validate_path (PR #2898, Issue #2884) |
| 86 | + |
| 87 | +### Research |
| 88 | + |
| 89 | +**What**: Use `Path::starts_with` to prevent sibling-directory bypass in `validate_path()`. String-based `starts_with` allows `/safe/a` to match `/safe-attack/x` — Path-based comparison doesn't. |
| 90 | + |
| 91 | +**Current state on main**: `validate_path()` does NOT exist in `gitea.rs`. There's no path validation function in the merge coordinator. This gap is larger than expected — the entire path validation is missing, not just the Path::starts_with fix. |
| 92 | + |
| 93 | +**PR #2898**: Closed (mergeable=True when closed). 1 file, +48/-9 lines. The PR adds `validate_path()` to `gitea.rs` with `Path::starts_with` and tests. |
| 94 | + |
| 95 | +**Verdict**: **NEEDS REOPENING + REBASE**. The PR was closed as "already merged" but the code is not in main. The branch `task/2884-path-starts-with-fix` likely has the changes. Rebase and re-merge. |
| 96 | + |
| 97 | +**Essential Questions**: |
| 98 | +| Question | Answer | |
| 99 | +|----------|--------| |
| 100 | +| Energising? | Medium — security fix for path traversal | |
| 101 | +| Leverages strengths? | Yes — simple Rust standard library usage | |
| 102 | +| Meets real need? | Yes — Issue #2884 describes a real sibling-dir bypass | |
| 103 | + |
| 104 | +**Estimated effort**: 15 minutes (rebase + merge) |
| 105 | + |
| 106 | +--- |
| 107 | + |
| 108 | +## Gap 4: Contamination Gate Pagination (Issue #2409) |
| 109 | + |
| 110 | +### Research |
| 111 | + |
| 112 | +**What**: `list_pr_files` doesn't paginate — returns only the first page of files. PRs with >50 changed files have their contamination gate silently skipped because files from page 2+ are missed. |
| 113 | + |
| 114 | +**Current state on main**: |
| 115 | +- `list_pr_files()` exists in `gitea.rs` — calls Gitea API but without `?page=2` pagination |
| 116 | +- Contamination gate in `evaluator.rs` does NOT exist (empty grep) |
| 117 | +- The entire contamination check is missing from main |
| 118 | +- Issue #2409 is open |
| 119 | + |
| 120 | +**More complex than initially thought**: The gap has TWO parts: |
| 121 | +1. Add pagination to `list_pr_files()` (loop through pages) |
| 122 | +2. Wire the contamination gate into `evaluate_all()` (check file lists for banned patterns) |
| 123 | + |
| 124 | +**Verdict**: **MEDIUM EFFORT**. Split into two sub-tasks: |
| 125 | +- Sub-task A: Paginate `list_pr_files()` (simple API loop) |
| 126 | +- Sub-task B: Wire contamination gate into evaluation (new logic) |
| 127 | + |
| 128 | +**Estimated effort**: 2-3 hours total |
| 129 | + |
| 130 | +--- |
| 131 | + |
| 132 | +## Summary |
| 133 | + |
| 134 | +| # | Gap | Status | Effort | Next Step | |
| 135 | +|---|-----|--------|--------|-----------| |
| 136 | +| 1 | BlockerKind classification | NOT in main | 1h | Extract from PR #2588, create clean PR | |
| 137 | +| 2 | ensure_pool() | ALREADY FIXED | 0 | Close issue #2521 | |
| 138 | +| 3 | Path::starts_with validate_path | PR closed, code missing | 15min | Rebase PR #2898, re-merge | |
| 139 | +| 4a | list_pr_files pagination | NOT in main | 1h | Add page loop to API call | |
| 140 | +| 4b | Contamination gate wiring | NOT in main | 2h | New feature in evaluator.rs | |
| 141 | + |
| 142 | +### Priority Order |
| 143 | +1. **Gap 3** (15 min) — Quickest win, PR already written |
| 144 | +2. **Gap 2** (0 min) — Already fixed, just close issue |
| 145 | +3. **Gap 1** (1h) — Most operator-facing value |
| 146 | +4. **Gap 4** (3h) — Largest scope, split into A/B |
0 commit comments