# Track Specification: Result Migration — Sub-Track 4 (gui_2.py)

**Track ID:** `result_migration_gui_2_20260619`
**Status:** Active (spec approved 2026-06-19)
**Priority:** A (completes the data-oriented error handling convention for the largest source file)
**Owner:** Tier 2 Tech Lead
**Type:** refactor (13 phases; anti-sliming protocol enforced per phase)
**Scope:** 54 sites across 1 source file (`src/gui_2.py`, 260KB / 7282 lines) + 1 new test file + 3 new render functions
**Parent tracks:** `result_migration_20260616` (umbrella), `result_migration_app_controller_20260618` (sub-track 3, SHIPPED 2026-06-19 with Phase 7), `result_migration_small_files_20260617` (sub-track 2, SHIPPED 2026-06-18), `result_migration_review_pass_20260617` (sub-track 1, SHIPPED 2026-06-17), `data_oriented_error_handling_20260606` (convention ancestor, SHIPPED 2026-06-12)

> **Note on effort estimates:** per Tier 1 rules (see `conductor/workflow.md` §"Tier 1 Track Initialization Rules"), this spec does NOT include day estimates. Effort is measured by scope (N files, M sites, N phases). The user / Tier 2 agent decides the actual pacing.

---

## 0. TL;DR

This is sub-track 4 of the 5-sub-track `result_migration_20260616` umbrella. It migrates `src/gui_2.py` (the largest source file in the codebase; the immediate-mode ImGui rendering layer) to the data-oriented `Result[T]` convention. The umbrella originally estimated 55 sites at T-shirt XL; the current audit shows 54 sites (38 V + 2 S + 2 UNCLEAR + 12 C) — the UNCLEAR count dropped 14→2 after sub-track 1's review pass and sub-track 3 Phase 7's heuristic tightening reclassified them.

**Why 13 phases (not the umbrella's "1-2 phases"):** per the user's directive (2026-06-19), this track uses an **anti-sliming protocol** with extra phases to give Tier 2 well-defined, narrow scope per phase. The previous sub-tracks slimed when scope felt tight (sub-track 2 Phase 10 slimed 21 of 26 sites via 5 laundering heuristics; sub-track 3 Phase 3 slimed 8 sites via logging.debug bodies). The 13-phase structure caps each phase at ~10 sites with explicit per-phase audit gates.

**What this track consumes from sub-track 3:** 8 controller state attributes added by Phase 6 (`_last_request_errors`, `_worker_errors` + lock, `_startup_timeline_errors`, `_signal_handler_error`, `_inject_preview_error`, `_mcp_config_parse_error`, `_save_project_error`, `_model_fetch_errors`). These are the **data plane**; sub-track 4 adds the **drain plane** (3 new render functions) and migrates the 42 migration-target sites to feed their errors into the data plane.

**What this track enables:** sub-track 5 (`result_migration_baseline_cleanup`) which closes the 77 violations in the 3 refactored baseline files (mcp_client.py, ai_client.py, rag_engine.py). Once gui_2.py is migrated, the data-oriented convention is **fully applied** to all 65 src/ files except the baseline.

---

## 1. Overview

### 1.1 The State Before This Track (as of 2026-06-19)

Per `uv run python scripts/audit_exception_handling.py --src src/gui_2.py`:

```
src/gui_2.py (V=38, S=2, ?=2, C=12, total=54)
 INTERNAL_BROAD_CATCH           25
 INTERNAL_SILENT_SWALLOW        13
 UNCLEAR                        2
 INTERNAL_RETHROW               2
 INTERNAL_COMPLIANT             12
```

**Migration target: 38 V + 2 S + 2 UNCLEAR = 42 sites.** The 12 INTERNAL_COMPLIANT sites stay as-is. The 38 broad-catches are the bulk; the 13 silent-swallows are the sliming-prone ones.

### 1.2 The Goal

Migrate all 42 migration-target sites to the data-oriented convention, using the 8 controller state attributes as the data plane and adding 3 new render functions as the drain plane. After this track ships:

- 0 `INTERNAL_SILENT_SWALLOW` in `src/gui_2.py` (every logging-only except body is replaced with Result propagation).
- 0 `INTERNAL_BROAD_CATCH` in `src/gui_2.py` (every `except Exception` is converted to a `_result` helper + caller checks `.ok`).
- 0 `UNCLEAR` in `src/gui_2.py` (the 2 remaining sites are classified compliant or migrated).
- 0 `INTERNAL_RETHROW` (the 2 re-raise sites are classified as Pattern 1/2/3 from `error_handling.md` or migrated).
- `audit_exception_handling.py --src src/gui_2.py --strict` exits 0.
- 11-tier batched test suite passes with no new regressions.

### 1.3 The 13-Phase Structure (Anti-Sliming Protocol)

The umbrella estimated "1-2 phases" for sub-track 4. The user's directive (2026-06-19) is to use **extra phases** so Tier 2 has narrow, well-defined scope per phase. **No phase has more than 10 migration sites.** Every phase has a per-phase audit gate. Every phase starts with a styleguide re-read.

| Phase | Sites | Tests | Audit gate |
|---|---|---|---|
| 0. Setup + styleguide re-read | 0 | 0 | n/a |
| 1. Site inventory + classification | 0 | 0 | inventory doc complete |
| 2. Drain plane wiring (3 new render functions) | 0 | 3 | render functions render without crash |
| 3. INTERNAL_BROAD_CATCH batch A (render-loop) | ≤10 | ≤10 | INTERNAL_BROAD_CATCH count drops by batch A count |
| 4. INTERNAL_BROAD_CATCH batch B (modal/dialog) | ≤10 | ≤10 | count drops by batch B |
| 5. INTERNAL_BROAD_CATCH batch C (event handlers) | ≤10 | ≤10 | count drops by batch C |
| 6. Signal handler sites | ≤5 | ≤5 | drain verified (Pattern 3 from styleguide) |
| 7. Worker / background sites | ≤5 | ≤5 | thread-safety verified |
| 8. Property setter / state sites | ≤5 | ≤5 | side-effect chain verified |
| 9. Helper / utility sites | ≤5 | ≤5 | stateless verified |
| 10. INTERNAL_SILENT_SWALLOW migrations | ≤13 | ≤13 | count drops to 0 |
| 11. INTERNAL_RETHROW classification | ≤2 | ≤2 | all classified per Pattern 1/2/3 |
| 12. UNCLEAR classification | ≤2 | ≤2 | count drops to 0 |
| 13. Audit gate + end-of-track report | 0 | 1 invariant test | `--strict` exits 0; 11/11 tiers PASS |

**Total: ~42 migration sites + 6 infra sites + 55+ tests + 1 report, in 13 phases.**

---

## 2. Current State Audit (as of commit `f2fef7d2`)

### 2.1 Already Implemented (DO NOT re-implement)

These are the conventions and infrastructure already in place. Sub-track 4 MUST use them; sub-track 4 MUST NOT recreate them.

| Item | Location | What it does |
|---|---|---|
| `Result[T]` dataclass | `src/result_types.py:91-105` | The data-oriented container |
| `ErrorInfo` dataclass + `ErrorKind` enum | `src/result_types.py:117-130` | The canonical error type |
| `audit_exception_handling.py --strict` gate | `scripts/audit_exception_handling.py:1-1100` | The CI gate |
| `_is_fastapi_handler` heuristic (Phase 7 tightening) | `scripts/audit_exception_handling.py:318-460` | BOUNDARY_FASTAPI only when except body raises HTTPException or returns Result |
| `_except_body_drains_via_http_exception_or_result` | `scripts/audit_exception_handling.py:333` | Drain point detection |
| `_except_body_has_logging` | `scripts/audit_exception_handling.py:365` | Logging body detection |
| 5 regression-guard tests | `tests/test_audit_heuristics.py` | Lock the heuristic |
| `_last_request_errors` attribute | `src/app_controller.py:862` | Per-request error accumulator |
| `_worker_errors` + `_worker_errors_lock` | `src/app_controller.py` (Phase 6 Group 6.5) | Worker error accumulator |
| `_startup_timeline_errors` | `src/app_controller.py` (Phase 6 Group 6.2) | Startup error accumulator |
| `_signal_handler_error` | `src/app_controller.py` (Phase 6 Group 6.1) | Signal handler error |
| `_inject_preview_error` | `src/app_controller.py` (Phase 6 Group 6.3) | Inject preview error |
| `_mcp_config_parse_error` | `src/app_controller.py` (Phase 6 Group 6.3) | MCP config parse error |
| `_save_project_error` | `src/app_controller.py` (Phase 6 Group 6.3) | Project save error |
| `_model_fetch_errors` | `src/app_controller.py` (Phase 6 Group 6.4) | Per-provider model fetch errors |
| `_report_worker_error` helper | `src/app_controller.py` (Phase 6 Group 6.5) | Worker error drain |
| `_rag_search_result` helper | `src/app_controller.py:3475` | RAG search returns Result |
| `_symbol_resolution_result` helper | `src/app_controller.py` (Phase 6 Group 6.6) | Symbol resolution returns Result |
| `_execute_gui_task_result` helper | `src/app_controller.py` (Phase 6 Group 6.6) | GUI task returns Result |
| `error_handling.md` Drain Points section | `conductor/code_styleguides/error_handling.md:356-516` | The 5 drain patterns + heuristic D |
| `error_handling.md` Broad-Except table | `conductor/code_styleguides/error_handling.md:520-540` | `narrow + log = INTERNAL_SILENT_SWALLOW` (the rule) |

### 2.2 Gaps to Fill (This Track's Scope)

The umbrella originally estimated 55 sites; the current audit shows 54. The migration target is **42 sites** (38 V + 2 S + 2 UNCLEAR). Plus 6 infra sites for the drain plane.

**Per-file breakdown (gui_2.py only):**
- 25 INTERNAL_BROAD_CATCH (the bulk; render-loop + modal + event-handler batches)
- 13 INTERNAL_SILENT_SWALLOW (logging-only except bodies — the sliming-prone ones per the user's principle)
- 2 UNCLEAR (need manual classification in Phase 12)
- 2 INTERNAL_RETHROW (need Pattern 1/2/3 classification in Phase 11)

**Infrastructure gaps:**
- 3 new render functions for the drain plane (error modal consumer, worker error indicator, last-request errors modal)
- 1 new test file (`tests/test_gui_2_result.py`) with ≥55 tests
- 1 new invariant test per phase (13 total) to lock per-phase progress

---

## 3. Goals

### 3.1 Primary Goal

Migrate all 42 migration-target sites in `src/gui_2.py` to the data-oriented `Result[T]` convention, with each site's error either accumulating in one of the 8 controller state attributes (the data plane) OR triggering a drain modal immediately.

### 3.2 Secondary Goals

1. **Establish the drain plane** in gui_2.py: 3 new render functions (`render_error_tint_modal` consumer, `_render_worker_error_indicator`, `_render_last_request_errors_modal`) that read from the controller's data plane.
2. **Verify per-phase audit gates**: each phase's audit command shows the expected count drop.
3. **No new regressions**: 11/11 batched test tiers PASS at track end.
4. **Per-site unit tests**: 1 test per migrated site (≥42) + 1 invariant test per phase (13).
5. **No sliming**: per-phase protocol with styleguide re-read + audit gate.

### 3.3 Non-Goals

- Adding new error sites (this track migrates EXISTING `try/except`, not adds new ones).
- Changing the audit heuristic (sub-track 3 Phase 7 already tightened it; this track uses the existing heuristic).
- Migrating `tests/` files (the `public_api_migration_and_ui_polish_20260615` track already migrated 22 test files; the remaining tests are out of scope).
- Migrating `src/gui_2.py:1349` (the +1 site from sub-track 1's review pass) — that's already correctly classified by the Phase 7 heuristic; verify in Phase 12.
- Sub-track 5 (baseline cleanup) — separate track after this one ships.

---

## 4. Functional Requirements

### 4.1 Drain Plane Infrastructure (Phase 2)

**FR-DP-1** `src/gui_2.py` adds a new render function `render_controller_error_modal(app: App)` that:
- Reads `app._last_request_errors`, `app._worker_errors`, `app._startup_timeline_errors`, `app._signal_handler_error`, `app._inject_preview_error`, `app._mcp_config_parse_error`, `app._save_project_error`, `app._model_fetch_errors`.
- For each non-empty attribute, opens an `imgui.open_popup(f"Error: {attr_name}")` with the errors displayed.
- Returns nothing (drain point per `error_handling.md:396-407` Pattern 2).

**FR-DP-2** `src/gui_2.py` adds `_render_worker_error_indicator(app: App)` that:
- Renders a small status-bar widget (e.g., `[!] 3 worker errors`).
- Click opens `render_controller_error_modal`.
- Visible only when `app._worker_errors` is non-empty.

**FR-DP-3** `src/gui_2.py` adds `_render_last_request_errors_modal(app: App)` that:
- Reads `app._last_request_errors` and shows per-request errors.
- Called from `_handle_generate_send` after each AI request completes.
- Modal opens only if errors accumulated during the request.

### 4.2 INTERNAL_BROAD_CATCH Migrations (Phases 3, 4, 5)

**FR-BC-1** For each of the 25 INTERNAL_BROAD_CATCH sites, the migration follows this pattern:
1. Extract a `_render_<feature>_result(app, ...)` helper that returns `Result[T]` (T = the data the caller needs: `bool`, `dict`, `str`, `None`, etc.).
2. The helper's except body returns `Result(data=<zero-value>, errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e), source="gui_2.<helper>", original=e)])`.
3. The caller checks `.ok` and `.errors`. On error, the caller either accumulates in the appropriate controller attribute OR triggers `render_controller_error_modal` immediately.

**FR-BC-2** Batch A (Phase 3, render-loop sites): the ~10 broad-catch sites inside `render_*` functions called every frame. Failures here cannot crash the render loop; the migration must guarantee `try/finally` cleanup or `Result` propagation that doesn't propagate to the outer render frame.

**FR-BC-3** Batch B (Phase 4, modal/dialog sites): the ~8 broad-catch sites inside modal functions (e.g., `render_approve_script_modal`, `render_patch_modal`). Failures here CAN trigger `imgui.open_popup` to show the error inline (Pattern 2).

**FR-BC-4** Batch C (Phase 5, event handler sites): the ~7 broad-catch sites inside event handlers (e.g., `_handle_approve_ask`, `_handle_save_anyway_click`). Failures here accumulate in `app._last_request_errors` or a similar per-event accumulator.

### 4.3 Signal Handler Sites (Phase 6)

**FR-SH-1** The 2 INTERNAL_RETHROW sites in signal handlers (`_init_actions` + similar) are migrated to Pattern 3 from `error_handling.md:409-419`: `sys.stderr.write(...) + sys.exit(1)` IS the drain. The except body MUST NOT swallow the error; it MUST terminate the app or trigger an intentional drain.

### 4.4 Worker / Background Sites (Phase 7)

**FR-WB-1** The ~5 broad-catch sites in worker closures (callbacks invoked from `_io_pool`) use `app._report_worker_error(op_name, result)` helper (added in sub-track 3 Phase 6 Group 6.5) to drain errors to `app._worker_errors`. Thread-safety: `app._worker_errors_lock` is acquired on every append.

### 4.5 Property Setter / State Sites (Phase 8)

**FR-PS-1** The ~3 broad-catch sites in property setters / state mutations: each setter extracts a `_set_<attr>_result(app, value) -> Result[None]` helper; the legacy setter calls `_report_worker_error` on failure (per sub-track 3 Phase 6 Group 6.3 pattern for `_save_active_project`).

### 4.6 Helper / Utility Sites (Phase 9)

**FR-HU-1** The ~3 broad-catch sites in module-level helpers (e.g., `_check_auto_refresh_context_preview`): each helper returns `Result[T]`; callers check `.ok` and accumulate in the appropriate controller attribute.

### 4.7 INTERNAL_SILENT_SWALLOW Migrations (Phase 10)

**FR-SS-1** The 13 INTERNAL_SILENT_SWALLOW sites (logging-only except bodies) are the sliming-prone ones. Per the user's principle (2026-06-17) and `error_handling.md:530`, **logging is NOT a drain**. Each site MUST be migrated to `Result[T]` propagation. No narrowing + logging; no pass after logging; no "intentional silent recovery."

### 4.8 INTERNAL_RETHROW Classification (Phase 11)

**FR-RT-1** The 2 INTERNAL_RETHROW sites are classified per the 3 legitimate patterns from `error_handling.md:625-690`:
- Pattern 1: Catch + convert + raise as different type (compliant if convert is meaningful).
- Pattern 2: Catch + log + re-raise (compliant if log provides value beyond re-raise).
- Pattern 3: Catch + cleanup + re-raise via `try/finally` (compliant; canonical cleanup pattern).

If a site does not fit any pattern, it is migrated to Result[T] (NOT classified as "suspicious" — sliming).

### 4.9 UNCLEAR Classification (Phase 12)

**FR-UC-1** The 2 UNCLEAR sites are read individually; each is classified compliant (with a comment explaining why) or migrated. The audit script's heuristic should already classify them; verify the classification is correct per the Phase 7 heuristic (`_is_fastapi_handler` + drain detection).

### 4.10 Tests (per phase)

**FR-T-1** Every migration site has a unit test in `tests/test_gui_2_result.py` that verifies:
- The helper returns `Result[T]` with `data=<expected>` on success.
- The helper returns `Result[T]` with `errors=[ErrorInfo(...)]` on failure (mock the inner call to raise).
- The caller checks `.ok` and either accumulates or triggers a drain.

**FR-T-2** Every phase has 1 invariant test in `tests/test_gui_2_result.py` named `test_phase_N_<phase_name>_invariant` that verifies the per-phase audit gate (e.g., `test_phase_3_invariant_broad_catch_batch_a_dropped`).

---

## 5. Non-Functional Requirements

**NFR-1** `audit_exception_handling.py --src src/gui_2.py --strict` exits 0 at end of Phase 13.
**NFR-2** 11-tier batched test suite passes with no new regressions at end of Phase 13.
**NFR-3** All new code uses 1-space indentation per `conductor/product-guidelines.md` "AI-Optimized Compact Style."
**NFR-4** Per-file atomic commits (1 site = 1 commit) per `conductor/workflow.md`.
**NFR-5** Every migration phase's commit message includes "TIER-2 READ conductor/code_styleguides/error_handling.md end-to-end before Phase N" per the AI Agent Checklist.
**NFR-6** No diagnostic noise in production code (no `[X_DIAG] sys.stderr.write(...)` lines left uncommitted).
**NFR-7** No `@pytest.mark.skip` markers added (per `conductor/workflow.md` Skip-Marker Policy).
**NFR-8** No new `Optional[T]` return types (the convention's `Result[T]` ban in refactored files).
**NFR-9** No new `try/except` sites added that have logging-only except bodies (the sliming pattern).
**NFR-10** Hot reload is NOT used for verification (per `live_gui_test_fixes_20260618` findings; hot reload is fragile). Use live_gui tests instead.

---

## 6. Architecture Reference

- `conductor/code_styleguides/error_handling.md` — the canonical convention. **READ END-TO-END** at start of each phase.
- `conductor/code_styleguides/error_handling.md:356-516` — Drain Points section (5 patterns + Heuristic D).
- `conductor/code_styleguides/error_handling.md:462-476` — "What is NOT a drain point" (logging is NOT a drain).
- `conductor/code_styleguides/error_handling.md:520-540` — Broad-Except Distinction table.
- `conductor/code_styleguides/error_handling.md:809-940` — AI Agent Checklist (5 MUST-DO + 7 MUST-NOT-DO).
- `conductor/tracks/result_migration_app_controller_20260618/spec.md` §12-§21 — sub-track 3's Phase 6 addendum (the pattern this track mirrors).
- `conductor/tracks/result_migration_small_files_20260617/spec.md` — sub-track 2's sliming precedent (Phase 10→11 redo).
- `conductor/tracks/result_migration_review_pass_20260617/spec.md` — sub-track 1's UNCLEAR classification pattern.
- `docs/guide_gui_2.md` — gui_2.py architecture guide (the App class lifecycle, render function delegation pattern).
- `docs/guide_app_controller.md` — AppController + state attributes (the data plane this track consumes).
- `scripts/audit_exception_handling.py:318-460` — the Phase 7 audit heuristic (5 regression-guard tests in `tests/test_audit_heuristics.py` lock the behavior).

---

## 7. Per-Phase Migration Strategy

Each phase follows the **anti-sliming protocol**:

1. **Pre-phase styleguide re-read** (commits 1 of the phase): Tier 2 reads `error_handling.md` end-to-end. Commit message: "TIER-2 READ conductor/code_styleguides/error_handling.md end-to-end before Phase N."
2. **Site inventory check** (only Phase 1): Tier 2 walks the audit's JSON output for the phase's sites, classifies each (current code, target migration, drain point), writes the classification to `tests/artifacts/PHASE<N>_SITE_INVENTORY.md`.
3. **Red** (1 commit per site): Write the unit test in `tests/test_gui_2_result.py`. Run test — must FAIL.
4. **Audit pre-check** (no commit): `uv run python scripts/audit_exception_handling.py --src src/gui_2.py 2>&1 | grep "<pattern>"` to confirm the site's category BEFORE migration.
5. **Green** (1 commit per site): Migrate the site. Use a `_result` helper + the appropriate controller attribute OR a drain modal. Run test — must PASS.
6. **Audit post-check** (no commit): Same command. Confirm the site moved out of the violation category.
7. **Phase invariant test** (1 commit at end of phase): `test_phase_N_<phase>_invariant` verifies the per-phase count drop.
8. **Per-file atomic commit** per `workflow.md`.

If a site "resists migration" in any phase, Tier 2 MUST report (per `workflow.md` "Per-Task Decision Protocol") — not invent a heuristic. The user (Tier 1) decides whether to fix forward or defer.

### 7.1 Phase 0: Setup + Styleguide Re-Read
**Tasks:**
- Create track directory (already exists: `conductor/tracks/result_migration_gui_2_20260619/`)
- Update `conductor/tracks.md` with new row
- Tier 2 reads `conductor/code_styleguides/error_handling.md` end-to-end
- Acknowledge in commit message

**Verify:** No code; verification is the commit message.

### 7.2 Phase 1: Site Inventory + Classification
**Tasks:**
- Run `uv run python scripts/audit_exception_handling.py --src src/gui_2.py --json > tests/artifacts/PHASE1_AUDIT.json`
- Walk every finding; for the 42 migration-target sites, record: line, category, current code, target migration pattern, drain point
- Write `tests/artifacts/PHASE1_SITE_INVENTORY.md` (markdown table)

**Verify:** The inventory doc has 42 rows + is committed.

### 7.3 Phase 2: Drain Plane Wiring
**Tasks:**
- Add `render_controller_error_modal(app: App)` (read all 8 controller attributes; drain to imgui popup)
- Add `_render_worker_error_indicator(app: App)` (status-bar widget)
- Add `_render_last_request_errors_modal(app: App)` (per-request error modal)
- Wire each render function into the appropriate existing call sites
- 3 unit tests verifying each render function renders without crash when attributes are populated / empty

**Verify:** The 3 render functions exist; the 3 tests pass; `audit --strict` still passes (no new violations introduced).

### 7.4 Phase 3: INTERNAL_BROAD_CATCH Batch A (Render-Loop)
**Scope:** The ~10 broad-catch sites in render-loop functions (sites called every frame from `render_main_interface`).

**Migration pattern:**
- Each `_render_<feature>` function extracts a `_render_<feature>_result(app, ...) -> Result[bool]` helper.
- The helper's except body returns `Result(data=False, errors=[ErrorInfo(...)])`.
- The caller checks `.ok`; if False, the helper's caller logs to `app._last_request_errors` (or a render-loop-specific accumulator).

**Verify:** `--strict` exits 0 for the batch A sites; 10 unit tests pass; render-loop output is identical for success paths.

### 7.5 Phase 4: INTERNAL_BROAD_CATCH Batch B (Modal/Dialog)
**Scope:** The ~8 broad-catch sites in modal functions (`render_approve_script_modal`, `render_patch_modal`, etc.).

**Migration pattern:**
- Each modal extracts a `<modal>_<action>_result(app, ...) -> Result[bool]` helper.
- On error, the caller triggers `render_controller_error_modal` immediately (Pattern 2 drain).

**Verify:** 8 unit tests pass; modal error messages render correctly when triggered.

### 7.6 Phase 5: INTERNAL_BROAD_CATCH Batch C (Event Handlers)
**Scope:** The ~7 broad-catch sites in event handlers (`_handle_approve_ask`, etc.).

**Migration pattern:**
- Each handler extracts a `_handle_<event>_result(app, ...) -> Result[bool]` helper.
- On error, the caller accumulates in `app._last_request_errors` (the data plane).

**Verify:** 7 unit tests pass; the per-event accumulator is populated correctly.

### 7.7 Phase 6: Signal Handler Sites
**Scope:** The 2 INTERNAL_RETHROW sites in `_init_actions` + similar.

**Migration pattern:** Pattern 3 from styleguide: `sys.stderr.write(...) + sys.exit(1)` is the drain. The migration extracts a `_install_<signal>_result() -> Result[None]` helper; on failure, the helper writes to stderr + calls `sys.exit(1)`.

**Verify:** 2 unit tests pass; app termination is triggered correctly (use a test fixture that captures `sys.exit`).

### 7.8 Phase 7: Worker / Background Sites
**Scope:** The ~5 broad-catch sites in worker closures.

**Migration pattern:** Use `app._report_worker_error(op_name, result)` helper (added in sub-track 3 Phase 6 Group 6.5). Thread-safety: `app._worker_errors_lock` is acquired on every append.

**Verify:** 5 unit tests pass; thread-safety is verified with a multi-thread test that appends concurrently.

### 7.9 Phase 8: Property Setter / State Sites
**Scope:** The ~3 broad-catch sites in property setters / state mutations.

**Migration pattern:** Per sub-track 3 Phase 6 Group 6.3 pattern: extract `_set_<attr>_result(app, value) -> Result[None]`; legacy setter calls `_report_worker_error` on failure.

**Verify:** 3 unit tests pass.

### 7.10 Phase 9: Helper / Utility Sites
**Scope:** The ~3 broad-catch sites in module-level helpers.

**Migration pattern:** Each helper returns `Result[T]`; callers check `.ok` and accumulate in the appropriate controller attribute.

**Verify:** 3 unit tests pass.

### 7.11 Phase 10: INTERNAL_SILENT_SWALLOW Migrations
**Scope:** The 13 INTERNAL_SILENT_SWALLOW sites (logging-only except bodies).

**Migration pattern:** Per the user's principle (logging is NOT a drain). Each site extracts a `_<feature>_result(app, ...) -> Result[T]` helper; the except body returns `Result(data=<zero>, errors=[ErrorInfo(original=e)])`. No narrowing + logging; no pass after logging.

**Verify:** 13 unit tests pass; `--strict` audit shows 0 INTERNAL_SILENT_SWALLOW.

### 7.12 Phase 11: INTERNAL_RETHROW Classification
**Scope:** The 2 INTERNAL_RETHROW sites.

**Migration pattern:** Classify per Pattern 1/2/3 from `error_handling.md:625-690`. If a site does not fit any pattern, migrate to `Result[T]` (NOT classified as "suspicious").

**Verify:** 2 unit tests pass; the 2 sites are either classified compliant or migrated to Result.

### 7.13 Phase 12: UNCLEAR Classification
**Scope:** The 2 UNCLEAR sites.

**Migration pattern:** Read each site individually; classify compliant (with comment) or migrate. Verify the Phase 7 heuristic classifies correctly.

**Verify:** 2 unit tests pass; `--strict` audit shows 0 UNCLEAR.

### 7.14 Phase 13: Audit Gate + End-of-Track Report
**Tasks:**
- Run `uv run python scripts/audit_exception_handling.py --src src/gui_2.py --strict` — verify exit 0
- Run `uv run python scripts/run_tests_batched.py` — verify 11/11 tiers PASS
- Run `uv run python -m pytest tests/test_gui_2_result.py -v` — verify all tests pass
- Write `docs/reports/TRACK_COMPLETION_result_migration_gui_2_20260619.md`
- Update `conductor/tracks.md` row to "shipped"
- Update umbrella spec count
- Phase 13 checkpoint commit with git note

**Verify:** `--strict` exits 0; 11/11 tiers PASS; report is committed; tracks.md updated.

---

## 8. Verification Criteria

The track is "complete" when ALL of the following hold:

- **VC-1** `audit_exception_handling.py --src src/gui_2.py --strict` exits 0.
- **VC-2** 0 INTERNAL_BROAD_CATCH sites in `src/gui_2.py` (25 → 0).
- **VC-3** 0 INTERNAL_SILENT_SWALLOW sites in `src/gui_2.py` (13 → 0).
- **VC-4** 0 UNCLEAR sites in `src/gui_2.py` (2 → 0).
- **VC-5** 0 INTERNAL_RETHROW sites in `src/gui_2.py` (2 → 0 or classified compliant).
- **VC-6** 3 new render functions exist: `render_controller_error_modal`, `_render_worker_error_indicator`, `_render_last_request_errors_modal`.
- **VC-7** `tests/test_gui_2_result.py` exists with ≥55 tests (42 site tests + 13 invariant tests), all pass.
- **VC-8** 11-tier batched test suite passes with no new regressions.
- **VC-9** Per-phase audit gates verified (each phase's commit shows the expected count drop in the audit output).
- **VC-10** Tier 2 acknowledged styleguide re-read at start of each phase (commit message contains "TIER-2 READ conductor/code_styleguides/error_handling.md end-to-end").
- **VC-11** Git history shows ≥60 atomic commits (42 site migrations + 13 phase setup commits + 3 infra commits + 2 docs commits).
- **VC-12** End-of-track report at `docs/reports/TRACK_COMPLETION_result_migration_gui_2_20260619.md` covers all 13 phases.
- **VC-13** `conductor/tracks.md` row updated to "shipped 2026-06-XX."
- **VC-14** Umbrella spec count updated to reflect actual scope (42 migration sites + 6 infra sites = 48 sites in this sub-track; umbrella total now ~272 sites across all 5 sub-tracks).

---

## 9. Out of Scope

- **Sub-track 5** (`result_migration_baseline_cleanup`) — separate track; this track's shipping is the dependency.
- **Migrating `tests/` files** — out of scope per `conductor/tracks/data_oriented_error_handling_20260606/spec.md`.
- **Adding new `try/except` sites** — this track migrates EXISTING sites only.
- **Changing the audit heuristic** — sub-track 3 Phase 7 already tightened it; this track uses the existing heuristic.
- **Hot reload verification** — fragile per `live_gui_test_fixes_20260618`; use live_gui tests instead.
- **Removing the legacy wrappers** — when extracting `_result` helpers, the legacy wrappers are preserved (per sub-track 3 Phase 6 Group 6.3 pattern for `_save_active_project`); a follow-up track can migrate callers to the `_result` variants.
- **Wire-up of the 8 controller state attributes** — sub-track 3 Phase 6 already added the attributes; this track only consumes them.

---

## 10. Risks

| ID | Risk | Likelihood | Mitigation |
|---|---|---|---|
| R4-1 | Tier 2 invents a laundering heuristic for the 2 UNCLEAR sites at gui_2.py:1349 | medium | Phase 12 forces explicit classification with comment; the Phase 7 heuristic already classifies it; 5 regression-guard tests in `tests/test_audit_heuristics.py` lock the behavior |
| R4-2 | Tier 2 doesn't migrate INTERNAL_SILENT_SWALLOW sites that "look like" logging-only but aren't drained | medium | Phase 1 inventory forces explicit classification per site BEFORE coding; Phase 10's audit gate enforces 0 silent-swallow |
| R4-3 | gui_2.py's render loop changes break the immediate-mode frame | medium | Render-loop sites are isolated in Phase 3; visual verification via live_gui tests; per-site unit tests verify success-path output is identical |
| R4-4 | Scope grows as Tier 2 finds more sites mid-migration | low | Phase 1 inventory freezes the 42-site list; if new sites are discovered, they're tracked but NOT migrated in this track (added to a follow-up) |
| R4-5 | The user's principle ("logging is NOT a drain") is misapplied | low | Styleguide re-read at start of each phase; commit-message acknowledgment; 13 invariant tests verify per-phase progress |
| R4-6 | Thread-safety violation in worker sites (Phase 7) | low | `app._worker_errors_lock` is already in place (sub-track 3 Phase 6); multi-thread unit test verifies |
| R4-7 | The 11-tier batched suite times out before all tiers run (per `result_migration_small_files_20260617` Phase 12→13 incident) | medium | Phase 13 uses `uv run python scripts/run_tests_batched.py` (the fixed script from sub-track 2 Phase 13.1); if it times out, Tier 2 reports and the user decides |
| R4-8 | Per-phase audit gate shows wrong count (heuristic misclassification) | low | The audit heuristic was verified by 5 regression-guard tests in sub-track 3 Phase 7; if a count is wrong, Tier 2 reports |

---

## 11. See Also

- `conductor/code_styleguides/error_handling.md` — the canonical convention (READ at start of each phase)
- `conductor/code_styleguides/data_oriented_design.md` — the canonical DOD reference
- `docs/AGENTS.md` §"The 4 memory dimensions" — the cross-cutting lens
- `docs/guide_gui_2.md` — gui_2.py architecture guide
- `docs/guide_app_controller.md` — AppController state attributes (the data plane)
- `conductor/tracks/result_migration_20260616/spec.md` — the umbrella spec
- `conductor/tracks/result_migration_app_controller_20260618/spec.md` — sub-track 3 (the data plane source)
- `conductor/tracks/result_migration_small_files_20260617/spec.md` — sub-track 2 (the sliming precedent)
- `conductor/tracks/result_migration_review_pass_20260617/spec.md` — sub-track 1 (the UNCLEAR classification precedent)
- `conductor/tracks/live_gui_test_fixes_20260618/spec.md` — the hot-reload fragility findings (do NOT use hot reload)
- `scripts/audit_exception_handling.py` — the audit script (the gate)
- `tests/test_audit_heuristics.py` — the heuristic regression-guard tests