mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
102 lines
6.2 KiB
Markdown
102 lines
6.2 KiB
Markdown
# Route-Helper-Extraction Refactor Silently Dropped Product Code (PR #769/#772)
|
|||
|
|
|
||
|
|
## Description
|
||
|
|
|
||
|
|
An earlier route-helper-extraction refactor (Batch A task `4baffaa3`, Batch B task
|
||
|
|
`f8480831`) moved helper functions out of route files into their proper service-layer
|
||
|
|
modules per the Architectural Conventions Standard. During that extraction, the branch
|
||
|
|
silently **dropped**, rather than relocated, a substantial slice of pre-existing product
|
||
|
|
behavior. The follow-up PR (#769, later reopened as #772) bounced `needs_revision` twice
|
||
|
|
on `pr_gate` findings before the cause was correctly diagnosed: `sync_branch` on the
|
||
|
|
subtask returned `head==base tip` (no drift) both times, ruling out branch staleness and
|
||
|
|
confirming the content was genuinely missing from the branch, not just out of sync with
|
||
|
|
`main`.
|
||
|
|
|
||
|
|
## Root Cause
|
||
|
|
|
||
|
|
The extraction commit(s) removed the following without a corresponding relocation, each
|
||
|
|
verified against real call sites that would `NameError`/`AttributeError` at runtime:
|
||
|
|
|
||
|
|
- **`roboco/services/task.py`**: ~22 public `TaskService` methods — all 12
|
||
|
|
`list_open_*_cycles` board-program queries, `list_sentinel_reports`,
|
||
|
|
`list_periscope_briefs`, `sequence_hold_reason`, `task_spend_usd`,
|
||
|
|
`project_month_spend_usd`, `terminal_children_count`, `self_heal_ac_ids`,
|
||
|
|
`resolve_scales_task_ref`, `list_open_env_sync_tasks`, `_close_task_pr_best_effort`,
|
||
|
|
`_inherit_upstream_base`, `list_completed_coroner_postmortems`, and the module-level
|
||
|
|
`_fire_coroner_bounce_hook` plus its sibling `_fire_coroner_cancel_hook_if_work_started`.
|
||
|
|
Callers spanned `coroner.py`, `sentinel.py`, `periscope.py`, `project.py`,
|
||
|
|
`coroner_engine.py`, `content_actions.py`, `choreographer/_impl.py`, `orchestrator.py`,
|
||
|
|
and every `*_engine.py` site calling a `list_open_*_cycles` method.
|
||
|
|
- **`roboco/services/video_engine.py`**: `reauthor_from_rejection`,
|
||
|
|
`_open_video_task_locked`, `_resolve_reauthor_project`, plus supporting occasion-lock /
|
||
|
|
AC scene-criterion / product-name-resolution code `test_video_engine.py` depends on.
|
||
|
|
- **`roboco/services/video_post_service.py`**: `_platform_configured`,
|
||
|
|
`_reauthor_after_reject`, plus a CANCELLED-draft `approve()` guard and the
|
||
|
|
unconfigured-platform-skip behavior `test_video_post_service.py` requires.
|
||
|
|
- **`roboco/services/a2a.py`**: `_maybe_wake_ceo_recipient`,
|
||
|
|
`_ack_pending_wake_notifications` (the CEO-DM-wake path wired into `send_chat_message` /
|
||
|
|
`interject_as_ceo` / `get_unread_messages`).
|
||
|
|
- **25 tests** deleted with zero replacements in `test_video_routes.py`,
|
||
|
|
`test_tasks_routes.py`, `test_orchestrator_manual_spawn.py`, including
|
||
|
|
security-relevant coverage: symlink-traversal confinement, non-CEO-forbidden checks,
|
||
|
|
agent-id-traversal rejection, `budget_usd` validation.
|
||
|
|
|
||
|
|
## Solution Implemented
|
||
|
|
|
||
|
|
Every named method, hook, and test was reconstructed against its real callers and
|
||
|
|
pre-existing test expectations (not guessed) and restored in the same modules the
|
||
|
|
extraction had emptied them from:
|
||
|
|
|
||
|
|
- All ~22 `task.py` methods restored, including wiring `self_heal_ac_ids` into
|
||
|
|
`_parent_ac_ref_sets` so the AC-coverage digest self-heals a legacy/drifted parent
|
||
|
|
instead of staying permanently inert, and wiring the Coroner bounce hook
|
||
|
|
(`CORONER_BOUNCE_THRESHOLD = 3`) into `_emit_status_transition_audit`'s
|
||
|
|
revision-count-bump branch via a new `_schedule_coroner_bounce_hook` helper (kept
|
||
|
|
separate so the scheduling try/except doesn't inflate that chokepoint's own
|
||
|
|
complexity), plus the sibling `_fire_coroner_cancel_hook_if_work_started` into
|
||
|
|
`cancel()`.
|
||
|
|
- `video_engine.py` / `video_post_service.py` / `a2a.py` methods restored with their
|
||
|
|
supporting code, verified directly against each file's existing test suite.
|
||
|
|
- The 25 deleted tests restored, including the named security-relevant cases
|
||
|
|
(`test_validated_agent_id_still_rejects_traversal`, symlink confinement,
|
||
|
|
non-CEO-forbidden, `budget_usd` validation); pause/resume coverage in
|
||
|
|
`test_tasks_routes.py` was confirmed already intact and not actually missing.
|
||
|
|
- `_finalize_claim`'s upstream-base-inherit condition was extracted into a pure
|
||
|
|
`_should_inherit_upstream_base` helper: restoring the Coroner hooks pushed
|
||
|
|
`_emit_status_transition_audit` / `cancel()` / `_finalize_claim` over the xenon
|
||
|
|
complexity budget, so all three were brought back under threshold with identical
|
||
|
|
behavior.
|
||
|
|
|
||
|
|
**Explicitly out of scope, left untouched:** `board_programs.py`, `coroner.py`,
|
||
|
|
`dogfood.py`, `github_app.py`, `mirror.py`, `periscope.py`, `pest_control.py`,
|
||
|
|
`scales.py`, `sentinel.py`, `spackle.py`, `telegram.py` route files — none are named in
|
||
|
|
the parent task's (`d41bbbf5`) file-enumeration scope. Their own unrelated
|
||
|
|
helper-placement findings were confirmed as a separate, later architectural-drift
|
||
|
|
surface and were not pulled into this diff.
|
||
|
|
|
||
|
|
## Impact
|
||
|
|
|
||
|
|
- **Scope:** Restoration only — no new product behavior beyond re-wiring the Coroner
|
||
|
|
bounce/cancel hooks the extraction had silently dropped (that wiring was already
|
||
|
|
documented at the architecture level in `CLAUDE.md`'s Board Program registry as "wired
|
||
|
|
at TaskService's bounce/cancel chokepoints"; this PR is what makes that description
|
||
|
|
true again on disk).
|
||
|
|
- **Risk:** The 27+ real call sites named above would have raised
|
||
|
|
`NameError`/`AttributeError` at runtime had this shipped un-restored, and 3
|
||
|
|
security-relevant test cases would have stayed uncovered.
|
||
|
|
- **Verification:** `mypy roboco/ tests/` clean (0 errors across 1438 files); `ruff
|
||
|
|
format`/`check` clean; xenon complexity clean; the 3 originally-flagged test files
|
||
|
|
plus `test_coroner_hooks.py` pass against a live sandbox Postgres (281+ tests); the
|
||
|
|
full 656-test targeted regression suite passes with no new failures.
|
||
|
|
|
||
|
|
## Prevention Measures
|
||
|
|
|
||
|
|
A **placement-only** refactor (moving a definition to satisfy the Architectural
|
||
|
|
Conventions Standard) must be diffed against its source branch for **deletions**, not
|
||
|
|
just additions, before it is called done — `git diff --stat` on the refactor branch vs.
|
||
|
|
its base, or a simple count of `def ` occurrences per touched file pre/post, would have
|
||
|
|
caught this class of silent drop immediately. When a `pr_gate` finding claims code is
|
||
|
|
"missing" post-refactor, verify with `sync_branch` (or an equivalent stale-vs-real
|
||
|
|
check) before assuming the finding is a false-positive staleness artifact — in this
|
||
|
|
case the branch was never stale; the code was actually gone.
|