Private
Public Access
0
0

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.
This commit is contained in:
2026-06-19 16:01:17 -04:00
parent ec3950996d
commit 4ea6ea3988
+48 -18
View File
@@ -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:
"""