Updates both the per-site report and the completion report for result_migration_small_files_20260617 with a Phase 14 addendum that: - Documents the 2 fixes (Issue 1: GUI subprocess crash; Issue 2: xdist race in workspace fixture) - References the follow-up track live_gui_test_fixes_20260618 - States the final test pass count: 11/11 tiers PASS clean - Lists the remaining Gemini 503 skip markers as out of scope - Confirms sub-track 2 is fully ready for merge with no documented issues from this track Sub-track 3 (result_migration_app_controller) is now unblocked.
9.4 KiB
Result Migration Sub-Track 2 — Per-Site Decisions for the 4 SMALL UNCLEAR Sites
This document records the per-site classification decisions for the 4 UNCLEAR sites identified in the result_migration_review_pass_20260617 audit. Each site is reviewed and either classified as Compliant (no migration) or Migration-target (queued for Phase 3+ migration).
The pre-Phase-1 audit reported 4 UNCLEAR sites in the SMALL bucket. After Phase 1's audit-script bug fixes, the audit counts are slightly different (see audit_post_phase1.json). The decisions below use the post-Phase-1 site lines.
Site 1: src/outline_tool.py:49 — Migration-target
Snippet (lines 45-52):
def outline(self, code: str) -> str:
code = code.lstrip(chr(0xFEFF))
try:
tree = ast.parse(code)
except SyntaxError as e:
return f"ERROR parsing code: {e}"
Classification rationale:
- Function signature:
def outline(self, code: str) -> str ast.parse()is stdlib I/O that can raiseSyntaxError- The except handler returns an error string, NOT a Result or ErrorInfo
- Caller cannot distinguish a valid outline from an error message
Decision: Migration-target. The function should return Result[str] where the success path returns Result(data=outline_str) and the parse-error path returns Result(data=NIL_T, errors=[ErrorInfo(category="syntax_error", message=str(e), source="outline_tool")]). The caller is updated to check result.ok and result.errors.
Migration site: Phase 7: src/outline_tool.py (task t7_6, included in the 3 sites for that file).
Site 2: src/summarize.py:36 — Migration-target
Snippet (lines 33-40):
def _summarise_python(path: Path, content: str) -> str:
lines = content.splitlines()
line_count = len(lines)
parts = [f"**Python** — {line_count} lines"]
try:
tree = ast.parse(content.lstrip(chr(0xFEFF)), filename=str(path))
except SyntaxError as e:
parts.append(f"_Parse error: {e}_")
return "\n".join(parts)
Classification rationale:
- Function signature:
def _summarise_python(path: Path, content: str) -> str ast.parse()is stdlib I/O that can raiseSyntaxError- The except handler appends to
partsand returns the joined string - Caller cannot distinguish a valid summary from a parse-error message
Decision: Migration-target. Same pattern as outline_tool.py:49. Function should return Result[str] with proper ErrorInfo conversion.
Migration site: Phase 7: src/summarize.py (task t7_8, included in the 2 sites for that file).
Site 3: src/conductor_tech_lead.py:120 — Compliant (no migration)
Snippet (lines 116-122):
try:
sorted_ids = dag.topological_sort()
except ValueError as e:
raise ValueError(f"DAG Validation Error: {e}")
Classification rationale:
- Function is part of a public API (
generate_ticketsor similar; the function returnslist[dict]) dag.topological_sort()is internal code that raisesValueErrorfor cycle detection (programmer-error / validation failure)- The except handler catches
ValueErrorand re-raises with a more descriptive message ("DAG Validation Error: ...") - This is the wrap-and-rethrow pattern: catch + augment message + re-raise same exception type
- Migrating to
Result[List[Ticket]]would change the public API contract; out of scope for sub-track 2
Decision: Compliant. Keep the rethrow pattern. The function's validation failure is a programmer-error signal (the DAG has a cycle, which is a bug in the input data, not a runtime condition). Document the decision in the per-site table; no migration.
Migration site: None (stays as-is).
Site 4: src/openai_compatible.py:87 — Compliant (already migrated; audit heuristic gap)
Snippet (lines 78-90):
try:
if request.stream:
response = _send_streaming(client, kwargs, request.stream_callback)
else:
response = _send_blocking(client, kwargs)
return Result(data=response)
except OpenAIError as exc:
empty_resp = NormalizedResponse(text="", tool_calls=[], usage_input_tokens=0, ...)
return Result(data=empty_resp, errors=[_classify_openai_compatible_error(exc, source="openai_compatible")])
Classification rationale:
- Function signature:
def send_openai_compatible(client: Any, request: OpenAICompatibleRequest, *, capabilities: Any) -> Result[NormalizedResponse] OpenAIErroris a third-party SDK exception- Both paths return
Result[NormalizedResponse]; the except path converts toResult(data=empty_resp, errors=[ErrorInfo]) - This is a properly-migrated SDK-boundary site following the data-oriented convention
- The audit's heuristic classifies it as UNCLEAR because:
- The function is named
send_openai_compatible, NOT*_result(so theis_in_result_funcheuristic at #3 doesn't fire) - The third-party SDK is called via
client.chat.completions.create(...), not a literalopenai.*reference (sois_third_partyheuristic at #4 doesn't fire) - The except body is a multi-line Result construction (not a simple
return Result(...))
- The function is named
Decision: Compliant. The site is already a textbook example of the data-oriented convention: catch SDK exception, convert to ErrorInfo, return Result with errors. The audit's heuristic gap is a follow-up improvement.
Audit heuristic gap (optional follow-up): Add a heuristic that recognizes "try/except SDK_error + body returns Result with errors list" pattern. This would catch future sites that follow the same pattern without requiring a literal openai.* module reference. See "Audit Heuristic Improvement" section below.
Migration site: None (already migrated).
Per-Site Summary
| Site | File:Line | Decision | Migration Plan |
|---|---|---|---|
| 1 | src/outline_tool.py:49 |
Migration-target | Phase 7 (t7_6): migrate to Result[str] |
| 2 | src/summarize.py:36 |
Migration-target | Phase 7 (t7_8): migrate to Result[str] |
| 3 | src/conductor_tech_lead.py:120 |
Compliant (no migration) | Stays as-is (wrap-and-rethrow) |
| 4 | src/openai_compatible.py:87 |
Compliant (already migrated) | Stays as-is (Result-based) |
Migration-target count: 2 sites (added to Phase 7 batches t7_6 and t7_8). Compliant-no-migration count: 2 sites (no code change).
Audit Heuristic Improvement (Optional Follow-up)
The 4 UNCLEAR classifications suggest 2 heuristic gaps:
-
outline_tool.py:49/summarize.py:36(SyntaxError + return formatted str): The audit doesn't have a heuristic for "narrow except (SyntaxError) + return formatted error string." This is a common pattern but the convention says functions should return Result. A heuristic could flag these as migration-targets (INTERNAL_BROAD_CATCH-style violation) so they're caught in future audits. -
openai_compatible.py:87(Result-based SDK boundary): The audit doesn't have a heuristic for "try/except SDK_error + body returns Result with errors list." This is the canonical migrated pattern. A heuristic could classify these as BOUNDARY_SDK or INTERNAL_COMPLIANT.
These heuristic improvements are deferred to a follow-up track. The sub-track 2 migrations (Phase 7) handle the 2 migration-target sites directly.
Phase 14 Addendum (Live GUI Test Fixes)
This track shipped with 2 documented test infrastructure issues that
blocked the full closure of sub-track 2. Both issues have been fixed
in the follow-up track live_gui_test_fixes_20260618.
Issue 1: test_execution_sim_live GUI subprocess crash (tier-3-live_gui)
GUI subprocess crashed mid-test with 0xC00000FD = STATUS_STACK_OVERFLOW.
Root cause: imgui.set_window_focus("Response") was called directly
during the response panel render, exhausting the GUI main thread's
1.94 MB stack.
Fix: defer the focus call to the next frame's idle phase via a new
_pending_focus_response flag. Mirrors the existing
_autofocus_response_tab pattern at gui_2.py:5353-5356.
Tracks the same root cause as test_z_negative_flows.py (documented
in docs/reports/NEGATIVE_FLOWS_INVESTIGATION_20260617_REFINED.md).
Issue 2: test_live_gui_workspace_exists xdist race (tier-1-unit-gui)
In pytest-xdist batched runs, the owner worker's live_gui fixture
teardown removes the shared workspace path via shutil.rmtree when
the owner's session ends. This can race with client workers' tests
that assert live_gui_workspace.exists(), leaving the workspace
missing.
Root cause: the live_gui_workspace fixture returned handle.workspace
without ensuring the path exists.
Fix: call workspace.mkdir(parents=True, exist_ok=True) before
returning. Idempotent and resilient to concurrent teardown.
Pre-existing on parent commit 4ab7c732 (verified in
tests/artifacts/PHASE14_PARENT_VERIFICATION.log).
Final result: 11/11 tiers PASS clean
The 11/11 verification is in tests/artifacts/PHASE14_TEST_RUN_RESULTS.log.
| Tier | Status |
|---|---|
| tier-1-unit-comms | PASS |
| tier-1-unit-core | PASS |
| tier-1-unit-gui | PASS |
| tier-1-unit-headless | PASS |
| tier-1-unit-mma | PASS |
| tier-2-mock_app-comms | PASS |
| tier-2-mock_app-core | PASS |
| tier-2-mock_app-gui | PASS |
| tier-2-mock_app-headless | PASS |
| tier-2-mock_app-mma | PASS |
| tier-3-live_gui | PASS |
The 4 Gemini 503 pre-existing skip markers remain (out of scope for
the fix track; deferred to a follow-up track to mock the Gemini API
in summarize.summarise_file).
Sub-track 2 (result_migration_small_files_20260617) is now FULLY
ready for merge with no documented issues from this track. Sub-track
3 (result_migration_app_controller) is unblocked.