From 4ea6ea398899766492c1fcf4643a0e9584af5fad Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 19 Jun 2026 16:01:17 -0400 Subject: [PATCH] refactor(app_controller): migrate _cb_plan_epic, _cb_accept_tracks, _start_track_logic to Result (Phase 6 Groups 6.5+6.7 partial) Migrates the 3 _bg_task closures in _cb_plan_epic and _cb_accept_tracks plus the 2 try/except sites in _start_track_logic to proper Result[T] propagation. Each worker closure now returns Result[None]; the _start_track_logic helper wraps the whole pipeline. New helper: - _topological_sort_tickets_result(raw_tickets, title) -> Result[list] (Phase 6 Group 6.7: dependency error is now a proper ErrorInfo in the Result, not a silent debug log) Audit: INTERNAL_SILENT_SWALLOW for src/app_controller.py: 17 -> 12. --- src/app_controller.py | 66 +++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/src/app_controller.py b/src/app_controller.py index 973d53b5..a3ceeebe 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -4417,7 +4417,7 @@ class AppController: """ [C: src/gui_2.py:App._render_mma_epic_planner, tests/test_mma_orchestration_gui.py:test_cb_plan_epic_launches_thread] """ - def _bg_task() -> None: + def _bg_task() -> "Result[None]": try: self.ai_status = "Planning Epic (Tier 1)..." history = orchestrator_pm.get_track_history_summary() @@ -4454,10 +4454,15 @@ class AppController: "action": "show_track_proposal", "payload": tracks }) + return OK except (OSError, IOError, ValueError, TypeError, KeyError, AttributeError, RuntimeError) as e: - logging.getLogger(__name__).debug("Epic plan error: %s", e, extra={"source": "app_controller._cb_plan_epic._bg_task"}) + err = ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e), + source="app_controller._cb_plan_epic._bg_task", original=e) self.ai_status = f"Epic plan error: {e}" print(f"ERROR in _cb_plan_epic background task: {e}") + result = Result(data=None, errors=[err]) + self._report_worker_error("cb_plan_epic", result) + return result self.submit_io(_bg_task) def _cb_accept_tracks(self) -> None: @@ -4466,7 +4471,7 @@ class AppController: """ self._show_track_proposal_modal = False - def _bg_task() -> None: + def _bg_task() -> "Result[None]": # Generate skeletons once self.ai_status = "Phase 2: Generating skeletons for all tracks..." parser = ASTParser(language="python") @@ -4484,12 +4489,16 @@ class AppController: code = f.read() generated_skeletons += f"\nFile: {f_path}\n{parser.get_skeleton(code)}\n" except (OSError, IOError, UnicodeDecodeError) as e: - logging.getLogger(__name__).debug("skeleton file read failed for %s: %s", f_path, e, extra={"source": "app_controller._cb_accept_tracks._bg_task.per_file"}) - pass + err = ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e), + source=f"app_controller._cb_accept_tracks._bg_task.per_file[{f_path}]", original=e) + self._report_worker_error(f"cb_accept_tracks.per_file[{f_path}]", Result(data=None, errors=[err])) except (OSError, IOError, ValueError, TypeError, KeyError, AttributeError, RuntimeError) as e: - logging.getLogger(__name__).debug("skeleton generation error: %s", e, extra={"source": "app_controller._cb_accept_tracks._bg_task"}) + err = ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e), + source="app_controller._cb_accept_tracks._bg_task", original=e) self.ai_status = f"Error generating skeletons: {e}" - return # Exit if skeleton generation fails + result = Result(data=None, errors=[err]) + self._report_worker_error("cb_accept_tracks", result) + return result # Now loop through tracks and call _start_track_logic with generated skeletons total_tracks = len(self.proposed_tracks) print(f"[DEBUG] _cb_accept_tracks: Starting {total_tracks} tracks...") @@ -4501,6 +4510,7 @@ class AppController: with self._pending_gui_tasks_lock: self._pending_gui_tasks.append({'action': 'refresh_from_project'}) # Ensure UI refresh after tracks are started self.ai_status = f"All {total_tracks} tracks accepted and execution started." + return OK self.submit_io(_bg_task) def _cb_start_track(self, user_data: Any = None) -> None: @@ -4549,7 +4559,30 @@ class AppController: self.submit_io(lambda: self._start_track_logic(track_data)) self.ai_status = f"Track '{title}' started." + def _topological_sort_tickets_result(self, raw_tickets: list, title: str) -> "Result[list]": + """Phase 6 Group 6.7: topological sort with Result propagation. + On ValueError: fall back to raw_tickets (preserves existing behavior).""" + try: + sorted_tickets_data = conductor_tech_lead.topological_sort(raw_tickets) + return Result(data=sorted_tickets_data) + except ValueError as e: + err = ErrorInfo(kind=ErrorKind.INVALID_INPUT, message=str(e), + source=f"app_controller._topological_sort_tickets_result[{title}]", original=e) + print(f"Dependency error in track '{title}': {e}") + self._report_worker_error(f"topological_sort[{title}]", Result(data=None, errors=[err])) + return Result(data=raw_tickets, errors=[err]) + def _start_track_logic(self, track_data: dict[str, Any], skeletons_str: str | None = None) -> None: + result = self._start_track_logic_result(track_data, skeletons_str) + if not result.ok: + err = result.errors[0] + self.ai_status = f"Track start error: {err.message}" + print(f"ERROR in _start_track_logic: {err.message}") + + def _start_track_logic_result(self, track_data: dict[str, Any], skeletons_str: str | None = None) -> "Result[None]": + """Phase 6 Group 6.7: track-start pipeline with Result propagation. + On any unexpected failure: ErrorInfo(original=e). Caller drains via + stderr write + ai_status update.""" try: goal = track_data.get("goal", "") title = track_data.get("title") or track_data.get("goal", "Untitled Track") @@ -4575,15 +4608,11 @@ class AppController: if not raw_tickets: self.ai_status = f"Error: No tickets generated for track: {title}" print(f"Warning: No tickets generated for track: {title}") - return + return OK self.ai_status = "Phase 2: Sorting tickets..." - try: - sorted_tickets_data = conductor_tech_lead.topological_sort(raw_tickets) - except ValueError as e: - logging.getLogger(__name__).debug("dependency error in track '%s': %s", title, e, extra={"source": "app_controller._start_track_logic.topological_sort"}) - print(f"Dependency error in track '{title}': {e}") - sorted_tickets_data = raw_tickets - # 3. Create Track and Ticket objects + sort_result = self._topological_sort_tickets_result(raw_tickets, title) + sorted_tickets_data = sort_result.data + # 3. Create Track and Ticket objects tickets = [] for t_data in sorted_tickets_data: ticket = models.Ticket( @@ -4625,10 +4654,11 @@ class AppController: self.submit_io(engine.run, md_content=full_md) sys.stderr.write(f"[DEBUG] _start_track_logic: Engine thread spawned for {track_id}.\n") sys.stderr.flush() + return OK except (OSError, IOError, ValueError, TypeError, KeyError, AttributeError, RuntimeError) as e: - logging.getLogger(__name__).debug("track start error: %s", e, extra={"source": "app_controller._start_track_logic"}) - self.ai_status = f"Track start error: {e}" - print(f"ERROR in _start_track_logic: {e}") + err = ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e), + source="app_controller._start_track_logic_result", original=e) + return Result(data=None, errors=[err]) def _cb_ticket_retry(self, ticket_id: str) -> None: """