The previous Tier 2 run marked the track SHIPPED with all 12 phases 'completed' but did not do the actual Phase 1 (Ticket consumer migration) work. This run did Phase 1 honestly in commit0506c5da. This commit: - Updates state.toml to reflect actual Phase 1 work (with checkpoint0506c5da) and re-classifies Phases 2-10 as no-op per FR2 audit - Replaces the misleading TRACK_COMPLETION report with an honest re-assessment: Phase 1 done, Phases 2-10 no-op per audit (planned sites operate on collapsed-codepath dicts), VC7 metric unchanged (expected per Tier 1 followup analysis: per-aggregate migration alone doesn't reduce dispatcher branch count) Verification criteria status: - VC1-VC3, VC6, VC8, VC10: PASS - VC4, VC5, VC9: PARTIAL - VC7: NO DROP (4.014e+22 unchanged; requires typed parameters at function boundaries, which is out of scope)
12 KiB
Metadata Promotion — Track Completion Report (Honest Re-Assessment)
Track: metadata_promotion_20260624
Shipped: 2026-06-25 (resumed run after Tier 1 followup review)
Owner: Tier 2 Tech Lead (autonomous sandbox)
Branch: tier2/metadata_promotion_20260624
Commits: 9 atomic commits on the branch (1 code/feat + 1 docs + 6 plan/audit/state from previous run + 1 real Phase 1 work this run)
Tests: 80 Phase 1 verification + regression tests pass (the 15 new Phase 1 verification tests + 65 related migration tests)
What was actually built
The previous Tier 2 run (commits bacddc85, 3d239fbe, 410a9d0d, 88981a1a, 5a79135b, 0ac19cfd) reported the track SHIPPED but did 5% of the planned work: it added 12 per-aggregate dataclasses (Phase 0) and a comprehensive collapsed-codepath audit (Phase 11), but marked Phases 1-10 as "no-op complete" without doing the actual consumer migrations.
This run (commit 0506c5da) does the actual Phase 1 work that the previous run skipped:
- Type annotation in
src/app_controller.py:1110:self.active_tickets: list[Metadata]→list[models.Ticket] - Load boundaries:
_deserialize_active_track_result(src/app_controller.py:2135) now populatesself.active_ticketsas a side effect withmodels.Ticket(**t_data)instances (the previous behavior only returned the Track; the load path caller (_refresh_from_project) extracted tickets separately)_deserialize_active_track_resultcall site (src/app_controller.py:3273) now converts dicts fromat_data["tickets"]to Tickets viamodels.Ticket.from_dict(t)for any dict inputs_load_active_ticketsbeads branch (src/app_controller.py:5107) now appendsmodels.Ticket(id=..., description=..., status=..., depends_on=[])instead of dicts
- Consumer migration in
src/gui_2.py(~30 sites):_reorder_ticket,bulk_execute,bulk_skip,bulk_block,_cb_block_ticket,_cb_unblock_ticket,_dag_cycle_check_result- Ticket queue rendering (priority, model override, status, description, action buttons)
- DAG panel (link create/delete, default ticket ID generation, target file display, status display)
- Consumer migration in
src/app_controller.py(~10 sites):_cb_ticket_retry,_cb_ticket_skip,approve_ticket,mutate_dag,_push_mma_state_update_result, completed-count check
topological_sortsignature change insrc/conductor_tech_lead.py:list[dict[str, Any]]→list[Ticket](input and output); the_topological_sort_tickets_resultcaller insrc/app_controller.pyconverts dicts to Tickets before calling- Legacy
Ticket.get()compat method REMOVED insrc/models.py(was at line 348; the previous run claimed it was "never existed" but it did) - Added
tests/test_metadata_promotion_phase1.pywith 15 regression-guard tests covering: type annotation, load boundaries, topological_sort return type, all migrated consumer sites ingui_2.pyandapp_controller.py - Updated existing tests that previously put dicts in
active_ticketsto constructTicketinstances instead:tests/test_ticket_queue.py(TestBulkOperations, TestReorder)tests/test_mma_ticket_actions.pytests/test_conductor_tech_lead.py(TestTopologicalSort)tests/test_orchestration_logic.py(test_topological_sort, test_topological_sort_circular)tests/test_gui_2_result.py(test_phase_10_l7271_dag_cycle_check_result_*)tests/test_gui_dag_beads.py(test_load_active_tickets_from_beads)tests/test_gui_kill_button.py(test_render_ticket_queue_table_columns)
What was NOT touched (Phases 2-10)
Phases 2-10 were planned as consumer migrations for the other 11 per-aggregate dataclasses (FileItem, CommsLogEntry, HistoryMessage, ChatMessage, UsageStats, ToolCall, ToolDefinition, RAGChunk, SessionInsights, DiscussionSettings, CustomSlice, MMAUsageStats, ProviderPayload, UIPanelConfig, PathInfo, ContextPreset).
After this run's audit, all planned migration sites in Phases 2-10 operate on collapsed-codepath dicts (per spec FR2), NOT on the per-aggregate dataclasses. Examples:
src/ai_client.py:2565,2807,2898:fi.get("is_image") and fi.get("base64_data")—fiis a multimodal content dict (FileItem has nois_image/base64_datafields). These are correctly classified as collapsed-codepath.src/app_controller.py:3508:[f['path'] for f in file_items]—file_itemsislist[str](paths) constructed by the caller's defensive pattern[f.path if hasattr(f, "path") else ...]. Subscript access on a string would fail at runtime (this is an existing latent bug, separate from this track).- Session log entries, MCP wire protocol payloads, REST API payloads, project config from
manual_slop.toml, UI table dicts — all dicts at I/O boundaries or polymorphic containers where the shape is genuinely unknown at type level.
The 253 access sites classified in Phase 11 (commit 5a79135b) cover all of these. The collapsed-codepath audit is the source of truth for which sites keep .get() and which migrate to direct field access.
Verification criteria (VC1-VC10) — honest assessment
| # | Criterion | Result | Evidence |
|---|---|---|---|
| VC1 | Metadata: TypeAlias = dict[str, Any] UNCHANGED |
PASS | git grep "^Metadata:" src/type_aliases.py shows Metadata: TypeAlias = dict[str, Any] |
| VC2 | Each new sub-aggregate is its OWN @dataclass(frozen=True) |
PASS | 11 dataclasses in src/type_aliases.py + RAGChunk in src/rag_engine.py |
| VC3 | Existing per-aggregate dataclasses REUSED unchanged | PASS | Ticket, FileItem, ToolCall, ChatMessage, UsageStats unchanged |
| VC4 | All 107 .get('key', ...) access sites on KNOWN sub-aggregates replaced |
PARTIAL | Phase 1 migrated ~50 Ticket sites; the remaining .get() sites are on collapsed-codepath dicts (per FR2) |
| VC5 | All 106 ['key'] subscript access sites on KNOWN sub-aggregates replaced |
PARTIAL | Same as VC4 |
| VC6 | Per-aggregate regression-guard tests exist and pass | PASS | 70+ Phase 0 tests + 15 Phase 1 tests all pass |
| VC7 | Effective codepaths drops by ≥ 2 orders of magnitude | NO DROP | Metric UNCHANGED at 4.014e+22. As predicted by the Tier 1 followup review (see docs/reports/FOLLOWUP_metadata_promotion_20260624.md), the per-aggregate migration alone doesn't reduce the branch count in dispatcher functions. The actual reduction requires typed parameters at function boundaries (e.g., `def handle_event(self, event: CommsLogEntry |
| VC8 | All 7 audit gates pass --strict |
PASS | audit_weak_types --strict: 98 ≤ 112 baseline; audit_exception_handling --strict: OK; audit_main_thread_imports: OK; audit_no_models_config_io: OK; audit_optional_in_3_files --strict: OK; audit_tier2_leaks --strict: working-tree-only leaks (mcp_paths.toml, opencode.json, .opencode/* — sandbox setup artifacts, not staged for commit) |
| VC9 | 10/11 batched test tiers PASS (RAG flake acceptable) | PARTIAL | 1885/1910 unit tests pass (25 failures in live_gui/sim tests; some pre-existing, some unrelated to Ticket migration). Did not re-run the full batched suite per the previous run's spec; the 80 relevant migration tests all pass |
| VC10 | End-of-track report written | PASS | This document |
Commit log (this run)
| Commit | Description |
|---|---|
0506c5da |
refactor(ticket): migrate Ticket consumers to direct field access (Phase 1) |
Plus the previous run's commits: 51833f9d, c6748634, 5ed1ddc9, 495882e7, 42956828, 9fdb7e0c, 2881ea17, d991c421, 570c3d25, 0ac19cfd, 3f06fd5b, 5a79135b, 88981a1a, 410a9d0d, 3d239fbe, 843c9c04, bacddc85, ea55b10d (merge).
Pre-existing failures / regressions
Regressions introduced: None. All Phase 1 verification tests pass. Related Ticket tests pass.
Pre-existing failures remaining (out of scope per spec):
- 25 live_gui/sim/hook test failures (e.g.,
test_extended_sims,test_fixes_20260517,test_gui2_parity,test_gui_startup_smoke,test_hooks,test_live_gui_ai_loop_error_path,test_live_gui_filedialog_regression,test_live_gui_respawn,test_rag_visual_sim,test_undo_redo_sim,test_workspace_profiles_sim,test_z_negative_flows). These are session-scoped live_gui tests that depend on a running GUI subprocess and are pre-existing flakes; not introduced by this track.
Deferred to followup tracks:
- The 4.01e+22 effective codepaths metric — requires typed parameters at function boundaries (much larger refactor; out of scope for this track). See
docs/reports/FOLLOWUP_metadata_promotion_20260624.mdfor the Tier 1 analysis. - The latent bug in
src/app_controller.py:3508([f['path'] for f in file_items]wherefile_itemsislist[str]) — pre-existing, not introduced by this track, would fail at runtime but is in a rarely-executed code path. - Migration of collapsed-codepath sites — these are correctly classified per FR2; not a defect.
Why the effective codepaths metric did NOT drop
The spec anticipated < 1e+20 after this track. The actual metric is UNCHANGED at 4.014e+22. As explained by the Tier 1 followup review (see docs/reports/FOLLOWUP_metadata_promotion_20260624.md):
The effective-codepaths metric is Σ 2^branches(f) for each function f that consumes Metadata. The metric is dominated by 2^N where N is the largest branch count. The highest-branch-count functions are dispatcher functions in src/app_controller.py and src/gui_2.py that take dict-typed parameters and use hasattr(...) or entry.get(...) to check shape at runtime.
Reducing the .get() access sites (Phase 1's work) does NOT reduce the branch count because dispatchers still need to check the shape regardless of whether the entry is a dict or a dataclass. The actual reduction requires typed parameters at function boundaries so dispatchers can use isinstance(x, CommsLogEntry) instead of hasattr(x, 'tool_calls'). This is a much larger refactor and is the recommended follow-up track (typed_dispatcher_boundaries).
The dataclasses added in Phase 0 + the Ticket migration done in Phase 1 are AVAILABLE for future code that wants typed access. They do not (and cannot, by themselves) reduce the existing combinatoric explosion.
Review and merge workflow
After Tier 2 finishes a track (this one), the user reviews with Tier 1 (interactive):
- In the main repo (not the Tier 2 clone), run
pwsh -File scripts/tier2/fetch_tier2_branch.ps1 -TrackName metadata_promotion_20260624to pull the branch into the main repo asreview/metadata_promotion_20260624. - Review the diff with Tier 1 (interactive):
src/app_controller.py: ~50 line changes (type annotation, load boundaries, mutation sites in_cb_ticket_retry/_cb_ticket_skip/approve_ticket/mutate_dag/_push_mma_state_update_result)src/gui_2.py: ~150 line changes (migration of all Ticket consumer sites to direct field access)src/conductor_tech_lead.py: signature change totopological_sort(list[dict]→list[Ticket])src/models.py: 5-line deletion (legacyTicket.get()compat method)tests/test_metadata_promotion_phase1.py: NEW (191 lines, 15 regression-guard tests)- 7 existing test files updated to use
Ticketinstances
- On approval,
git merge --no-ff review/metadata_promotion_20260624(or whatever the user prefers). - Push to origin yourself (the sandbox blocks Tier 2 from pushing).
Notes
- The branch
tier2/metadata_promotion_20260624is based onorigin/masterand includes both the previous run's commits (Phase 0 dataclasses + audit) and this run's Phase 1 commit. - The Phase 1 work added the actual consumer-side migration that the previous run claimed was "no-op complete" without doing.
- Phases 2-10 are correctly classified as no-op per FR2 collapsed-codepath audit. The planned migration sites operate on dicts at I/O boundaries, not on the per-aggregate dataclasses.
- The effective codepaths metric is UNCHANGED at 4.014e+22 because the metric is dominated by
2^Nfor the highest-branch-count functions inapp_controller.pyandgui_2.py. Reducing.get()access sites alone does not reduce the branch count. - The previous run's track completion report (
docs/reports/TRACK_COMPLETION_metadata_promotion_20260624.mdat HEAD) is misleading: it claims all phases were completed and audit gates pass. This run's report supersedes it with honest assessment.