fix(gateway): gate review diffs against the task's real parent branch (#444) (#454)

The in-path PR-review gate's evidence diff (claim_gate_review) and the
pr_pass conventions guard derived their diff base via parent_branch_for
string surgery, which reuses the child branch's own team segment — wrong
for every cross-team hop (a frontend child of a main_pm root derives a
ref that never existed) and silently falls back to the repo default
branch, so the reviewer judged the entire inherited base-branch content
as the task's own work and failed acceptance criteria the task never
touched. Bounced a live goals-tab fix three times, unfixable by branch
surgery.

The gate now resolves the base via resolve_parent_branch (the parent
task's recorded branch_name, cross-team correct) and threads it as a new
preferred_parent override through git.diff / list_changed_files /
conventions_check_for_task — consulted only when no explicit base is
given, so the pinned literal-base contract (base="HEAD~1") and every
other diff caller (QA, doc, content) are byte-identical. Parent lookup
fails open (derived-base fallback) like the other resolve_parent_branch
call sites, and is skipped entirely while the conventions flag is off.

Also excludes .uv-cache/ and .claude/ (agent worktrees, private uv
cache) from the markdown prose scanner — both are repo-local tool dirs
whose vendored/generated files tripped make reflow-check.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-10 19:55:38 +02:00
committed by GitHub
co-authored by Renn F
parent 76a396b152
commit 7ff70ab5e2
8 changed files with 438 additions and 14 deletions
@@ -95,6 +95,28 @@ async def test_no_changed_files_still_fails_open() -> None:
assert result["findings"] == []
@pytest.mark.asyncio
async def test_preferred_parent_forwards_to_list_changed_files() -> None:
"""The in-path PR-review gate's cross-team parent (see ``diff``) must
reach ``list_changed_files`` so the validator never analyzes files
inherited from the wrong-team derived base."""
svc = _service()
_bind(svc, "_workspace_for_branch", AsyncMock(return_value=Path("/tmp/ws")))
changed = AsyncMock(return_value=[])
_bind(svc, "list_changed_files", changed)
actor_id = uuid4()
await svc.conventions_check_for_task(
actor_id,
_task("feature/frontend/root--cell"),
preferred_parent="feature/main_pm/root",
)
changed.assert_awaited_once_with(
branch_name="feature/frontend/root--cell",
actor_agent_id=actor_id,
preferred_parent="feature/main_pm/root",
)
@pytest.mark.asyncio
async def test_validator_timeout_fails_closed_and_reaps(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch