# Result Migration — Sub-Track 5 (Baseline Cleanup) Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use `mma-tier3-worker` (recommended) or `mma-tier2-tech-lead` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Migrate all 88 migration-target sites across the 3 baseline files (`mcp_client.py`, `ai_client.py`, `rag_engine.py`) to the data-oriented `Result[T]` convention, making the baseline 100% convention-compliant.

**Architecture:** Per-site `_result` helper convention (matches sub-track 3 Phase 2 and sub-track 4 patterns). The 3 baseline files are backend services; the drain is the caller (MMA worker, mcp_client tool invocation, API hook). No new render functions needed. The existing `Result[T]` return type is the data plane.

**Tech Stack:** Python 3.11+, pytest, pydantic. Existing infrastructure: `Result[T]` from `src/result_types.py:91-105`, audit script at `scripts/audit_exception_handling.py` (with 5 regression-guard tests at `tests/test_audit_heuristics.py`).

---

## Anti-Sliming Protocol (MANDATORY for every phase)

This is the same template as sub-track 4 (which was "the first to not need error correction" per the user). Every phase:

1. **Pre-phase styleguide re-read** (commit 1 of the phase): Read `conductor/code_styleguides/error_handling.md` end-to-end. Commit message MUST include "TIER-2 READ conductor/code_styleguides/error_handling.md end-to-end before Phase N."

2. **Audit pre-check** (per site, before migration): Capture the site's category BEFORE migration. Capture in commit body.

3. **Red** (1 commit per site): Write the unit test in `tests/test_baseline_result.py`. Run test — MUST FAIL. Commit.

4. **Green** (1 commit per site): Migrate the site. Use the `_result` helper convention. Run test — MUST PASS. Commit.

5. **Audit post-check** (per site, after migration): Same command. Confirm the site moved out of the violation category. Capture in commit body.

6. **Phase invariant test** (1 commit at end of phase): `test_phase_N_<file>_<phase>_invariant` verifies the per-phase count drop.

7. **If a site "resists migration":** DO NOT invent a heuristic. Report to the user (Tier 1). The user decides whether to fix forward or defer.

8. **Per-file atomic commits:** 1 site = 1 commit (per `workflow.md` "ATOMIC PER-TASK COMMITS").

---

## File Structure

**Files modified (3):**
- `src/mcp_client.py` — 46 migration sites (40 broad-catch + 5 silent-swallow + 1 UNCLEAR)
- `src/ai_client.py` — 33 migration sites (17 broad-catch + 9 silent-swallow + 7 rethrow)
- `src/rag_engine.py` — 9 migration sites (5 broad-catch + 1 silent-swallow + 3 rethrow)
- `conductor/tracks.md` — new track row (Phase 0)
- `conductor/tracks/result_migration_baseline_cleanup_20260620/state.toml` — task statuses

**Files created (5):**
- `tests/test_baseline_result.py` — 88 site tests + 14 invariant tests = ≥102 tests
- `docs/reports/TRACK_COMPLETION_result_migration_baseline_cleanup_20260620.md` — end-of-track report (Phase 14)
- `tests/artifacts/PHASE1_AUDIT_BASELINE.json` — baseline audit JSON
- `tests/artifacts/PHASE1_SITE_INVENTORY_mcp_client.md` — 46-row inventory
- `tests/artifacts/PHASE1_SITE_INVENTORY_ai_client.md` — 33-row inventory
- `tests/artifacts/PHASE1_SITE_INVENTORY_rag_engine.md` — 9-row inventory

**Files NOT modified:**
- `scripts/audit_exception_handling.py` — the audit heuristic is correct (sub-track 3 Phase 7 + sub-track 4 Phase 11/12); do not change
- `tests/test_audit_heuristics.py` — the 8 regression-guard tests are correct; do not change
- `src/result_types.py` — the `Result[T]` dataclass is the convention reference; do not change
- `src/app_controller.py` — the data plane is correct from sub-track 3 Phase 6; this track only consumes the convention

---

## Migration Pattern (used by Phases 3-13)

Every migration follows this pattern. The `_result` helper convention (matches mcp_client + ai_client + rag_engine existing style):

```python
# BEFORE (in src/mcp_client.py, src/ai_client.py, or src/rag_engine.py)
def _do_x(...):
 try:
  result = do_something()
  return result
 except Exception as e:
  sys.stderr.write(f"Error: {e}\n")  # SLIMING: logging-only, NOT a drain
  return None  # or return default

# AFTER
def _do_x_result(...) -> Result[T]:
 """Drain-aware variant of _do_x. Returns Result[T] so caller can check .ok."""
 try:
  result = do_something()
  return Result(data=result)
 except Exception as e:
  return Result(data=<zero-value>, errors=[ErrorInfo(
 kind=ErrorKind.INTERNAL, message=str(e),
 source="<file>._do_x_result", original=e,
 )])

def _do_x(...):
 """Legacy wrapper. Checks .ok; caller decides how to handle the error."""
 result = _do_x_result(...)
 if not result.ok:
  # Caller-specific error handling:
  # - mcp_client tools: return the error in the tool's result
  # - ai_client providers: return Result(data=fallback) or propagate
  # - rag_engine: append to controller's _last_request_errors or similar
  return <caller-specific-fallback>
 return result.data
```

The unit test pattern:

```python
def test_<site>_returns_result_on_success():
 """Migrated helper returns Result.ok=True on success."""
 from src.<file> import _<site>_result
 # Build mock inputs that make the inner call succeed
 result = _<site>_result(<args>)
 assert result.ok
 assert result.data == <expected>
 assert result.errors == []


def test_<site>_returns_result_with_error_on_failure():
 """Migrated helper returns Result.ok=False with ErrorInfo on failure."""
 from src.<file> import _<site>_result
 # Build mock inputs that make the inner call fail
 result = _<site>_result(<args>)
 assert not result.ok
 assert result.errors
 assert result.errors[0].kind == ErrorKind.INTERNAL
 assert result.errors[0].source == "<file>._<site>_result"


def test_<site>_legacy_wrapper_handles_error():
 """Legacy wrapper handles Result.ok=False correctly."""
 from src.<file> import _<site>
 result = _<site>(<args>)
 # Assert the wrapper returns the expected fallback (or propagates the error)
 assert result == <expected_fallback_or_None>
```

---

## Phase 0: Setup + Styleguide Re-Read (3 tasks)

**Focus:** Initialize the track, update tracks.md, Tier 2 reads the styleguide end-to-end, acknowledge in commit message.

### Task 0.1: Update `conductor/tracks.md`

**Files:**
- Modify: `conductor/tracks.md` (add new row after sub-track 4 row 6d-4)

- [ ] **Step 1: Find the sub-track 4 row**

```bash
grep -n "result_migration_gui_2_20260619" conductor/tracks.md | head -3
```

- [ ] **Step 2: Add the new row after sub-track 4**

Insert in the "Active Tracks (Current Queue)" table (between row 6d-4 and row 6e):

```
| 6d-5 | A | [Result Migration Sub-Track 5: Baseline Cleanup](#track-result-migration-baseline-cleanup-20260620) | spec ✓, plan pending, **ready to start** | `result_migration_gui_2_20260619` (sub-track 4, SHIPPED 2026-06-20) |
```

- [ ] **Step 3: Commit**

```bash
git add conductor/tracks.md
git commit -m "conductor(tracks): add result_migration_baseline_cleanup_20260620 row"
```

### Task 0.2: Tier 2 reads the styleguide end-to-end

**Files:** (no file changes; verification is the commit message)

- [ ] **Step 1: Read `conductor/code_styleguides/error_handling.md` end-to-end** (989 lines)

All sections: 5 Patterns + Data Model + Decision Tree + Anti-Patterns + Examples + Hard Rules + When to Use + Boundary Types + **Drain Points (lines 356-516)** + Broad-Except Distinction (lines 520-540) + Constructors Can Raise + **Re-Raise Patterns (lines 625-690)** + Audit Script + Migration Playbook + AI Agent Checklist (lines 809-940).

- [ ] **Step 2: Acknowledge the read in an empty commit**

```bash
git commit --allow-empty -m "chore: TIER-2 READ conductor/code_styleguides/error_handling.md end-to-end before Phase 0"
```

### Task 0.3: Phase 0 checkpoint

- [ ] **Step 1: Create empty commit marking Phase 0 complete**

```bash
git commit --allow-empty -m "conductor(plan): mark Phase 0 complete (setup + styleguide re-read)"
```

- [ ] **Step 2: Update state.toml Phase 0 status** (created in metadata task at end of track init; for now just leave as pending)

- [ ] **Step 3: Commit the state.toml + tracks.md changes together at end of track initialization**

---

## Phase 1: 3-File Inventory + Classification (4 tasks)

**Focus:** Run the audit on all 3 baseline files; walk every finding; classify each of the 88 migration-target sites into 3 inventory docs.

### Task 1.1: Run the audit + capture JSON

- [ ] **Step 1: Run the audit and save JSON**

```bash
uv run python scripts/audit_exception_handling.py --include-baseline --json > tests/artifacts/PHASE1_AUDIT_BASELINE.json
```

- [ ] **Step 2: Verify the JSON was generated and the counts match the spec**

```bash
uv run python -c "
import json
data = json.load(open('tests/artifacts/PHASE1_AUDIT_BASELINE.json'))
for f in data['files']:
    if 'mcp_client' in f.get('filename', ''):
        print(f'mcp_client.py: V={f[\"violation_count\"]} S={f[\"suspicious_count\"]} ?={f[\"unclear_count\"]}')
    elif 'ai_client' in f.get('filename', ''):
        print(f'ai_client.py: V={f[\"violation_count\"]} S={f[\"suspicious_count\"]} ?={f[\"unclear_count\"]}')
    elif 'rag_engine' in f.get('filename', ''):
        print(f'rag_engine.py: V={f[\"violation_count\"]} S={f[\"suspicious_count\"]} ?={f[\"unclear_count\"]}')
"
```

Expected: `mcp_client.py: V=45 S=0 ?=1` / `ai_client.py: V=26 S=7 ?=0` / `rag_engine.py: V=6 S=3 ?=0`

### Task 1.2: Walk the audit + write the 3 inventory docs

**Files:**
- Create: `tests/artifacts/PHASE1_SITE_INVENTORY_mcp_client.md`
- Create: `tests/artifacts/PHASE1_SITE_INVENTORY_ai_client.md`
- Create: `tests/artifacts/PHASE1_SITE_INVENTORY_rag_engine.md`

- [ ] **Step 1: Extract migration-target sites per file**

```bash
uv run python -c "
import json
data = json.load(open('tests/artifacts/PHASE1_AUDIT_BASELINE.json'))
for fname in ['mcp_client', 'ai_client', 'rag_engine']:
    f = next((x for x in data['files'] if fname in x.get('filename', '')), None)
    if not f: continue
    findings = f['findings']
    migration = [x for x in findings if x.get('category') in ('INTERNAL_BROAD_CATCH', 'INTERNAL_SILENT_SWALLOW', 'INTERNAL_RETHROW', 'UNCLEAR')]
    print(f'=== {fname}.py: {len(migration)} migration targets ===')
    for m in migration:
        print(f\"L{m['line']}: [{m['category']}]\")
" > tests/artifacts/PHASE1_MIGRATION_TARGETS.txt
```

- [ ] **Step 2: Verify the counts are 46 + 33 + 9 = 88**

```bash
grep "migration targets" tests/artifacts/PHASE1_MIGRATION_TARGETS.txt
```

Expected: 3 lines with counts 46, 33, 9.

- [ ] **Step 3: For each file, write the inventory entry**

For each migration-target site, read the code around the line and write to the per-file inventory doc. Use the format:

```markdown
# Phase 1 Site Inventory — mcp_client.py
# (or ai_client.py / rag_engine.py)

| Line | Category | Current code (5 lines around) | Target migration | Drain point |
|---|---|---|---|---|
| L<line> | <category> | <code excerpt> | <pattern> | <caller> |
| ... |
```

For "Target migration", reference the per-phase pattern (e.g., "Batch A tool broad-catch" for Phase 3-7 sites, "silent-swallow → Result[T]" for Phase 8/11 sites, "Pattern 1/2/3 classification or migrate" for Phase 12 sites).

For "Drain point" (backend services), specify the caller:
- `MMA worker` (multi-agent conductor)
- `mcp_client tool caller` (MCP tool invocation)
- `AI client SDK boundary` (the vendor SDK's caller)
- `RAG engine caller` (the controller's RAG state)

- [ ] **Step 4: Commit the inventory**

```bash
git add tests/artifacts/PHASE1_AUDIT_BASELINE.json tests/artifacts/PHASE1_MIGRATION_TARGETS.txt tests/artifacts/PHASE1_SITE_INVENTORY_*.md
git commit -m "conductor(plan): Phase 1 site inventory — 88 migration-target sites classified across 3 baseline files"
```

### Task 1.3: Phase 1 invariant test + checkpoint

**Files:**
- Create: `tests/test_baseline_result.py` (initial creation; will be extended each phase)
- Modify: `conductor/tracks/result_migration_baseline_cleanup_20260620/state.toml`

- [ ] **Step 1: Create the test file with Phase 1 invariant tests**

```python
"""Tests for baseline Result[T] migration (sub-track 5 of result_migration_20260616).

Per the anti-sliming protocol, each phase has an invariant test that locks
the per-phase progress. Per-site tests are added per phase.
"""
import json
import subprocess
from pathlib import Path


def _load_baseline_audit() -> dict:
    """Re-run the audit and return the baseline findings."""
    audit_json = Path("tests/artifacts/PHASE1_AUDIT_BASELINE.json")
    if not audit_json.exists():
        subprocess.run(
            ["uv", "run", "python", "scripts/audit_exception_handling.py",
             "--include-baseline", "--json"],
            check=True, capture_output=True,
        )
    return json.loads(audit_json.read_text())


def test_phase_1_invariant_mcp_client_inventory_has_46_rows():
 """Phase 1 invariant: the mcp_client inventory file has 46 rows."""
 inventory = Path("tests/artifacts/PHASE1_SITE_INVENTORY_mcp_client.md")
 assert inventory.exists(), "PHASE1_SITE_INVENTORY_mcp_client.md must exist"
 content = inventory.read_text()
 import re
 row_count = len(re.findall(r"^\| L\d+", content, re.MULTILINE))
 assert row_count == 46, f"Expected 46 sites in mcp_client inventory, found {row_count}"


def test_phase_1_invariant_ai_client_inventory_has_33_rows():
 """Phase 1 invariant: the ai_client inventory file has 33 rows."""
 inventory = Path("tests/artifacts/PHASE1_SITE_INVENTORY_ai_client.md")
 assert inventory.exists(), "PHASE1_SITE_INVENTORY_ai_client.md must exist"
 content = inventory.read_text()
 import re
 row_count = len(re.findall(r"^\| L\d+", content, re.MULTILINE))
 assert row_count == 33, f"Expected 33 sites in ai_client inventory, found {row_count}"


def test_phase_1_invariant_rag_engine_inventory_has_9_rows():
 """Phase 1 invariant: the rag_engine inventory file has 9 rows."""
 inventory = Path("tests/artifacts/PHASE1_SITE_INVENTORY_rag_engine.md")
 assert inventory.exists(), "PHASE1_SITE_INVENTORY_rag_engine.md must exist"
 content = inventory.read_text()
 import re
 row_count = len(re.findall(r"^\| L\d+", content, re.MULTILINE))
 assert row_count == 9, f"Expected 9 sites in rag_engine inventory, found {row_count}"


def test_phase_1_invariant_baseline_counts_captured():
 """Phase 1 invariant: the audit JSON captures the expected baseline counts."""
 data = _load_baseline_audit()
 files = {f["filename"]: f for f in data["files"]}
 mcp = files.get("src\\mcp_client.py") or files.get("src/mcp_client.py")
 assert mcp and mcp["violation_count"] + mcp["suspicious_count"] + mcp["unclear_count"] >= 46
 ai = files.get("src\\ai_client.py") or files.get("src/ai_client.py")
 assert ai and ai["violation_count"] + ai["suspicious_count"] + ai["unclear_count"] >= 33
 rag = files.get("src\\rag_engine.py") or files.get("src/rag_engine.py")
 assert rag and rag["violation_count"] + rag["suspicious_count"] + rag["unclear_count"] >= 9
```

- [ ] **Step 2: Run the test — it should PASS (the inventory was committed in Task 1.2)**

```bash
uv run python -m pytest tests/test_baseline_result.py -v
```

Expected: 4 PASSED

- [ ] **Step 3: Update state.toml Phase 1**

```toml
phase_1 = { status = "completed", checkpointsha = "<commit_sha>", name = "3-file inventory + classification (88 sites)" }
```

- [ ] **Step 4: Commit**

```bash
git add tests/test_baseline_result.py conductor/tracks/result_migration_baseline_cleanup_20260620/state.toml
git commit -m "conductor(plan): mark Phase 1 complete (88-site inventory + 4 invariant tests)"
```

---

## Phase 2: Audit Gate Baseline (2 tasks)

**Focus:** Capture the baseline audit counts in 3 Phase 2 invariant tests. These tests will be REUSED (with relaxed assertions) in each phase to verify the per-phase count drop.

### Task 2.1: Add Phase 2 invariant tests (baseline count capture)

**Files:**
- Modify: `tests/test_baseline_result.py`

- [ ] **Step 1: Append Phase 2 invariant tests**

```python
def test_phase_2_invariant_mcp_client_baseline_captured():
 """Phase 2 invariant: mcp_client baseline violation count is captured (>= 45 V + 0 S + 1 ?)."""
 data = _load_baseline_audit()
 files = {f["filename"]: f for f in data["files"]}
 mcp = files.get("src\\mcp_client.py") or files.get("src/mcp_client.py")
 assert mcp["violation_count"] >= 45, f"mcp_client baseline V should be >= 45, got {mcp['violation_count']}"


def test_phase_2_invariant_ai_client_baseline_captured():
 """Phase 2 invariant: ai_client baseline violation count is captured (>= 26 V + 7 S + 0 ?)."""
 data = _load_baseline_audit()
 files = {f["filename"]: f for f in data["files"]}
 ai = files.get("src\\ai_client.py") or files.get("src/ai_client.py")
 assert ai["violation_count"] >= 26, f"ai_client baseline V should be >= 26, got {ai['violation_count']}"
 assert ai["suspicious_count"] >= 7, f"ai_client baseline S should be >= 7, got {ai['suspicious_count']}"


def test_phase_2_invariant_rag_engine_baseline_captured():
 """Phase 2 invariant: rag_engine baseline violation count is captured (>= 6 V + 3 S)."""
 data = _load_baseline_audit()
 files = {f["filename"]: f for f in data["files"]}
 rag = files.get("src\\rag_engine.py") or files.get("src/rag_engine.py")
 assert rag["violation_count"] >= 6, f"rag_engine baseline V should be >= 6, got {rag['violation_count']}"
 assert rag["suspicious_count"] >= 3, f"rag_engine baseline S should be >= 3, got {rag['suspicious_count']}"
```

- [ ] **Step 2: Run all tests (Phase 1 + Phase 2)**

```bash
uv run python -m pytest tests/test_baseline_result.py -v
```

Expected: 7 PASSED

- [ ] **Step 3: Update state.toml Phase 2**

```toml
phase_2 = { status = "completed", checkpointsha = "<commit_sha>", name = "Audit gate baseline (3 files; counts captured)" }
```

- [ ] **Step 4: Commit**

```bash
git add tests/test_baseline_result.py conductor/tracks/result_migration_baseline_cleanup_20260620/state.toml
git commit -m "conductor(plan): mark Phase 2 complete (audit gate baseline + 3 invariant tests)"
```

---

## Phases 3-7: mcp_client.py Batches A-E (40 broad-catches, 5 batches × ≤8 sites)

**Focus:** Each batch migrates ≤8 mcp_client.py broad-catch sites using the standard `_result` helper pattern. Use the Phase 1 inventory to find the line numbers.

### Task 3.0: Phase 3 styleguide re-read + ack

- [ ] **Step 1: Re-read `error_handling.md` lines 462-540 (logging NOT a drain + Broad-Except table)**

- [ ] **Step 2: Ack commit**

```bash
git commit --allow-empty -m "chore: TIER-2 READ conductor/code_styleguides/error_handling.md lines 462-540 (logging NOT a drain) before Phase 3"
```

### Task 3.1-3.8: Migrate Batch A sites (≤8 mcp_client broad-catch sites)

For each site in the batch (use the Phase 1 inventory for line numbers):

- [ ] **Step 1: Write failing test** (with site name + line number; see migration pattern above)

- [ ] **Step 2: Run test, verify FAIL**

- [ ] **Step 3: Migrate** (extract `_result` helper + legacy wrapper per the migration pattern)

- [ ] **Step 4: Run test, verify PASS**

- [ ] **Step 5: Audit pre/post check** (capture in commit body)

- [ ] **Step 6: Commit** (one per site; format: `refactor(mcp_client): migrate L<line> _<feature> to Result[T] (Phase 3)`)

If a batch has fewer than 8 sites, the remaining tasks are skipped (not "filled in" with made-up sites).

### Task 3.9: Phase 3 invariant test + checkpoint

- [ ] **Step 1: Add Phase 3 invariant test** (Batch A mcp_client broad-catch count dropped)

```python
def test_phase_3_invariant_mcp_client_batch_a_dropped():
 """Phase 3 invariant: Batch A sites moved out of INTERNAL_BROAD_CATCH in mcp_client."""
 data = _load_baseline_audit()
 files = {f["filename"]: f for f in data["files"]}
 mcp = files.get("src\\mcp_client.py") or files.get("src/mcp_client.py")
 # Replace <BATCH_A_LINES> with the actual list (e.g., [123, 456, 789])
 batch_a_lines = <BATCH_A_LINES>
 remaining_in_v = [
 f for f in mcp["findings"]
 if f.get("line") in batch_a_lines and f.get("category") == "INTERNAL_BROAD_CATCH"
 ]
 assert not remaining_in_v, (
 f"Phase 3 Batch A sites still in INTERNAL_BROAD_CATCH: {[(f['line'], f['category']) for f in remaining_in_v]}"
 )
```

- [ ] **Step 2: Update state.toml Phase 3 + commit**

```toml
phase_3 = { status = "completed", checkpointsha = "<commit_sha>", name = "mcp_client Batch A (<=8 sites)" }
```

```bash
git add tests/test_baseline_result.py conductor/tracks/result_migration_baseline_cleanup_20260620/state.toml
git commit -m "conductor(plan): mark Phase 3 complete (mcp_client Batch A)"
```

### Tasks 4.0-4.9 / 5.0-5.9 / 6.0-6.9 / 7.0-7.9: Phases 4-7 (Batches B-E)

Same structure as Phase 3. Each phase:
- Styleguide re-read (ack commit)
- ≤8 site migrations (per-site: test, migrate, audit, commit)
- Phase invariant test
- Phase checkpoint

---

## Phase 8: mcp_client.py Silent-Swallow + UNCLEAR (5 + 1 = ≤6 sites)

**Focus:** The 5 INTERNAL_SILENT_SWALLOW sites (logging-only except bodies) and 1 UNCLEAR site. Per the user's principle (2026-06-17), logging is NOT a drain. NO narrowing+logging; full `Result[T]` propagation.

### Task 8.0: Phase 8 styleguide re-read (CRITICAL anti-sliming)

- [ ] **Step 1: Re-read `error_handling.md` lines 462-540 + lines 809-940 (AI Agent Checklist)**

- [ ] **Step 2: Ack commit (explicitly call out the sliming risk)**

```bash
git commit --allow-empty -m "chore: TIER-2 READ conductor/code_styleguides/error_handling.md lines 462-940 before Phase 8 — NO silent recovery, NO narrowing+logging"
```

### Tasks 8.1-8.6: Migrate sites

For each of the 6 sites (5 silent-swallow + 1 UNCLEAR):
- Same migration pattern (test, migrate, audit, commit)
- The except body MUST return `Result(data=<zero>, errors=[ErrorInfo(original=e)])`
- NO `logging.error(...)` in except body
- NO `sys.stderr.write(...)` in except body
- NO `pass` in except body

### Task 8.7: Phase 8 invariant + checkpoint

```python
def test_phase_8_invariant_mcp_client_silent_swallow_zero():
 """Phase 8 invariant: 0 INTERNAL_SILENT_SWALLOW sites in mcp_client."""
 data = _load_baseline_audit()
 files = {f["filename"]: f for f in data["files"]}
 mcp = files.get("src\\mcp_client.py") or files.get("src/mcp_client.py")
 silent = [f for f in mcp["findings"] if f.get("category") == "INTERNAL_SILENT_SWALLOW"]
 assert not silent, f"Expected 0 INTERNAL_SILENT_SWALLOW, found {len(silent)}: {[f['line'] for f in silent]}"
 unclear = [f for f in mcp["findings"] if f.get("category") == "UNCLEAR"]
 assert not unclear, f"Expected 0 UNCLEAR, found {len(unclear)}: {[f['line'] for f in unclear]}"
```

---

## Phases 9-10: ai_client.py Batches A-B (17 broad-catches, 2 batches)

Same structure as Phases 3-7 (mcp_client batches). Per-site: test, migrate, audit, commit. Per-phase: invariant test + checkpoint.

### Task 9.0: Phase 9 styleguide re-read + ack
### Tasks 9.1-9.8: Migrate Batch A (≤8 sites)
### Task 9.9: Phase 9 invariant + checkpoint
### Task 10.0: Phase 10 styleguide re-read + ack
### Tasks 10.1-10.8: Migrate Batch B (≤8 sites; some may be silent-swallow or rethrow — see Phase 1 inventory)
### Task 10.9: Phase 10 invariant + checkpoint

---

## Phase 11: ai_client.py Silent-Swallow (9 sites)

**Focus:** The 9 INTERNAL_SILENT_SWALLOW sites in ai_client. Per the user's principle (logging NOT a drain), NO narrowing+logging; full `Result[T]` propagation.

### Task 11.0: Phase 11 styleguide re-read (CRITICAL anti-sliming)
### Tasks 11.1-11.9: Migrate 9 sites
### Task 11.10: Phase 11 invariant + checkpoint

```python
def test_phase_11_invariant_ai_client_silent_swallow_zero():
 """Phase 11 invariant: 0 INTERNAL_SILENT_SWALLOW sites in ai_client."""
 data = _load_baseline_audit()
 files = {f["filename"]: f for f in data["files"]}
 ai = files.get("src\\ai_client.py") or files.get("src/ai_client.py")
 silent = [f for f in ai["findings"] if f.get("category") == "INTERNAL_SILENT_SWALLOW"]
 assert not silent, f"Expected 0 INTERNAL_SILENT_SWALLOW, found {len(silent)}: {[f['line'] for f in silent]}"
```

---

## Phase 12: ai_client.py Rethrow Classification (7 sites)

**Focus:** The 7 INTERNAL_RETHROW sites. Classify per Pattern 1/2/3 from `error_handling.md:625-690`. If a site does not fit any pattern, MIGRATE to `Result[T]`. Do NOT classify as "suspicious" (= sliming).

### Task 12.0: Phase 12 styleguide re-read (Re-Raise Patterns lines 625-690) + ack
### Tasks 12.1-12.7: Classify each rethrow site (or migrate)

For each site:
- Read the site code
- Determine which of the 3 patterns it fits (or "does not fit → migrate")
- If compliant: add a comment explaining which pattern
- If not compliant: use the standard migration pattern
- Per-site: test (if migrated), commit

### Task 12.8: Phase 12 invariant + checkpoint

```python
def test_phase_12_invariant_ai_client_rethrow_zero():
 """Phase 12 invariant: 0 INTERNAL_RETHROW sites in ai_client."""
 data = _load_baseline_audit()
 files = {f["filename"]: f for f in data["files"]}
 ai = files.get("src\\ai_client.py") or files.get("src/ai_client.py")
 rethrow = [f for f in ai["findings"] if f.get("category") == "INTERNAL_RETHROW"]
 assert not rethrow, f"Expected 0 INTERNAL_RETHROW, found {len(rethrow)}: {[f['line'] for f in rethrow]}"
```

---

## Phase 13: rag_engine.py Migration (1 silent-swallow + 5 broad-catch + 3 rethrow = 9 sites)

**Focus:** The 9 sites in rag_engine (the smallest baseline file). Single phase since 9 sites fit comfortably.

### Task 13.0: Phase 13 styleguide re-read + ack
### Tasks 13.1-13.9: Migrate all 9 sites

For each site:
- The 5 broad-catch: standard `_result` helper pattern
- The 1 silent-swallow: full `Result[T]` propagation (NO narrowing+logging)
- The 3 rethrow: classify per Pattern 1/2/3 or migrate

### Task 13.10: Phase 13 invariant + checkpoint

```python
def test_phase_13_invariant_rag_engine_zero_violations():
 """Phase 13 invariant: 0 migration-target violations in rag_engine."""
 data = _load_baseline_audit()
 files = {f["filename"]: f for f in data["files"]}
 rag = files.get("src\\rag_engine.py") or files.get("src/rag_engine.py")
 migration = [f for f in rag["findings"] if f.get("category") in (
 "INTERNAL_BROAD_CATCH", "INTERNAL_SILENT_SWALLOW", "INTERNAL_RETHROW", "UNCLEAR"
 )]
 assert not migration, f"Expected 0 migration-target sites, found {len(migration)}: {[(f['line'], f['category']) for f in migration]}"
```

---

## Phase 14: Audit Gate + End-of-Track Report (5 tasks)

**Focus:** Verify all gates, run the full batched suite, write the report, mark the track complete, update umbrella.

### Task 14.1: Run the strict audit gate

- [ ] **Step 1: Run the strict audit**

```bash
uv run python scripts/audit_exception_handling.py --include-baseline --strict
```

Expected: exit 0; 0 violations across the 3 baseline files

### Task 14.2: Run the unit tests

- [ ] **Step 1: Run all baseline tests**

```bash
uv run python -m pytest tests/test_baseline_result.py -v
```

Expected: ≥102 tests PASSED (88 site + 14 invariant)

### Task 14.3: Run the 11-tier batched suite

- [ ] **Step 1: Run the fixed batched script**

```bash
uv run python scripts/run_tests_batched.py
```

Expected: 11/11 tiers PASS

- [ ] **Step 2: If any tier fails, save the log to `tests/artifacts/PHASE14_TEST_RUN_<timestamp>.log` and report**

### Task 14.4: Write the end-of-track report

**Files:**
- Create: `docs/reports/TRACK_COMPLETION_result_migration_baseline_cleanup_20260620.md`

- [ ] **Step 1: Write the report (template below)**

```markdown
# Track Completion: Result Migration — Sub-Track 5 (Baseline Cleanup)

**Track ID:** `result_migration_baseline_cleanup_20260620`
**Date:** <YYYY-MM-DD>
**Status:** SHIPPED

## 1. Header / Scope Summary

<1-2 sentence summary>

## 2. Phase-by-Phase Summary

<14 sections, one per phase, with audit count delta>

## 3. Audit Results (Pre vs Post)

| Category | Pre-Phase-0 | Post-Phase-14 |
|---|---|---|
| mcp_client INTERNAL_BROAD_CATCH | 40 | 0 |
| mcp_client INTERNAL_SILENT_SWALLOW | 5 | 0 |
| mcp_client UNCLEAR | 1 | 0 |
| ai_client INTERNAL_BROAD_CATCH | 17 | 0 |
| ai_client INTERNAL_SILENT_SWALLOW | 9 | 0 |
| ai_client INTERNAL_RETHROW | 7 | 0 |
| rag_engine INTERNAL_BROAD_CATCH | 5 | 0 |
| rag_engine INTERNAL_SILENT_SWALLOW | 1 | 0 |
| rag_engine INTERNAL_RETHROW | 3 | 0 |
| BOUNDARY_SDK (preserved) | 4 | 4 |
| INTERNAL_PROGRAMMER_RAISE (preserved) | 9 | 9 |
| INTERNAL_COMPLIANT (preserved) | 28 | <new count> |

## 4. Last 3 Failures Encountered

<1-2 sentences per failure>

## 5. Files Modified

| Path | Sites | Description |
|---|---|---|

## 6. Git State

<commit count; first/last commit hashes; branch>

## 7. Recommendation

Campaign 100% complete. All 5 sub-tracks shipped. The data-oriented
`Result[T]` convention is now fully applied to all 65 src/ files.

## 8. Post-Completion Fixes (if any)
```

- [ ] **Step 2: Commit the report**

```bash
git add docs/reports/TRACK_COMPLETION_result_migration_baseline_cleanup_20260620.md
git commit -m "docs(reports): TRACK_COMPLETION_result_migration_baseline_cleanup_20260620 (14 phases complete)"
```

### Task 14.5: Final checkpoint + tracks.md update + umbrella count

- [ ] **Step 1: Phase 14 checkpoint commit**

```bash
git commit --allow-empty -m "conductor(checkpoint): Phase 14 complete — sub-track 5 SHIPPED; campaign 100% complete"
```

- [ ] **Step 2: Update `conductor/tracks.md` row to "shipped 2026-06-XX"**

- [ ] **Step 3: Update umbrella spec count** (campaign 100% complete; all 5 sub-tracks shipped)

```bash
# Edit conductor/tracks/result_migration_20260616/spec.md
# Update the sub-track table: sub-track 5 = 88 migration sites; campaign 100% complete
```

- [ ] **Step 4: Update campaign status report** (`docs/reports/RESULT_MIGRATION_CAMPAIGN_STATUS_20260619.md`) to mark sub-track 5 shipped

- [ ] **Step 5: Final commit**

```bash
git add conductor/tracks.md conductor/tracks/result_migration_20260616/spec.md docs/reports/RESULT_MIGRATION_CAMPAIGN_STATUS_20260619.md conductor/tracks/result_migration_baseline_cleanup_20260620/state.toml conductor/tracks/result_migration_baseline_cleanup_20260620/metadata.json
git commit -m "conductor(plan): sub-track 5 SHIPPED — campaign 100% complete; tracks.md + umbrella + status updated"
```

---

## Summary

**14 phases, ~120 atomic commits, 88 migration sites + 6 stay-as-is + 9 INTERNAL_PROGRAMMER_RAISE + 4 BOUNDARY_SDK + 28 INTERNAL_COMPLIANT, 102+ tests, 1 report.**

| Dimension | Count |
|---|---|
| Source files modified | 3 (mcp_client, ai_client, rag_engine) |
| Migration sites | 88 (62 BC + 15 SS + 10 RETHROW + 1 UNCLEAR) |
| Stay-as-is sites | 41 (4 BOUNDARY_SDK + 9 INTERNAL_PROGRAMMER_RAISE + 28 INTERNAL_COMPLIANT) |
| Tests | ≥102 (88 site + 14 invariant) |
| Phases | 14 |
| Atomic commits | ≥110 |

---

## Self-Review

**1. Spec coverage:** All 15 VCs in spec.md §8 are covered by tasks in this plan. VC-1 (audit --strict) is Task 14.1. VC-2 (0 INTERNAL_BROAD_CATCH) is Phases 3-7 + 9-10 + 13. VC-3 (0 INTERNAL_SILENT_SWALLOW) is Phases 8 + 11 + 13. VC-4 (0 INTERNAL_RETHROW) is Phases 12 + 13. VC-5 (0 UNCLEAR) is Phase 8. VC-6 (4 BOUNDARY_SDK preserved) — no action needed; verify in Phase 14 invariant. VC-7 (9 INTERNAL_PROGRAMMER_RAISE preserved) — no action needed; verify in Phase 14. VC-8 (≥102 tests) is per-phase test additions. VC-9 (11/11 tiers) is Task 14.3. VC-10 (per-phase audit gates) is per-phase invariant tests. VC-11 (14 styleguide-ack commits) is per-phase Task 0. VC-12 (≥110 commits) is per-site commits. VC-13 (report) is Task 14.4. VC-14 (tracks.md) is Task 14.5. VC-15 (umbrella count) is Task 14.5.

**2. Placeholder scan:** No "TBD", "TODO", "implement later", "fill in details" in this plan. All migration patterns show concrete code. All tasks show concrete commands. The `<BATCH_A_LINES>` placeholder in Task 3.9 is a list that gets populated by the inventory (not a code-level placeholder).

**3. Type consistency:** `Result[bool]` / `Result[None]` / `Result[T]` used consistently across all migration tasks. `ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e), source=..., original=e)` consistent with the convention. `tests/test_baseline_result.py` test names consistent with the per-phase pattern.

**4. Anti-sliming protocol:** Enforced via (a) styleguide re-read at start of every phase, (b) per-site audit pre/post check, (c) per-phase invariant test, (d) per-file atomic commits, (e) explicit instruction in Phase 8 (mcp_client silent-swallow) and Phase 11 (ai_client silent-swallow) that narrowing+logging is forbidden, (f) explicit instruction in Phase 12 (ai_client rethrow) that classify-as-suspicious is forbidden.

**5. Migration pattern consistency:** All migration tasks use the same `_result` helper pattern shown in the "Migration Pattern" section. This matches the existing convention in mcp_client + ai_client + rag_engine (per `data_oriented_error_handling_20260606`).

---