[a1f52d13] Rebuild evidence-assembly timeout fix on current main (#796)

* [a1f52d13] feat(gateway): re-apply evidence-assembly timeout fix on current main

Rebuilds the bounded-timeout fix for claim_review/evidence()/roboco_git_diff
that PR #765 carried as contaminated pre-Board-Programs file copies. The
rebase restored the current-main baseline; this commit re-applies ONLY the
validated timeout-fix hunks on top of that baseline:

- config.py: evidence_assembly_timeout_seconds (90.0s) and
  conventions_validator_timeout_seconds (45s) Settings fields
- envelope.py: Envelope.gateway_timeout classmethod for structured
  timeout errors naming the stalled component
- git.py: GitService.diff_and_files (single resolution, concurrent
  diff+--name-only subprocesses), settings-backed conventions-validator
  timeout, and changed_files passthrough to conventions_check_for_task
- qa.py: claim_review bounded-timeout wrapper around
  _build_qa_claim_evidence, split into _qa_git_and_conventions and
  _qa_db_reads concurrent segments (pass_review signature and gate
  untouched)
- content_actions.py: evidence() bounded wrapper with _evidence_git_and_fetch
  and _evidence_db_reads concurrent split (all other methods untouched)
- git routes: GET /diff bounded asyncio.wait_for guard with 504 on trip

The pre-existing methods (sync_env_branch, open_sync_pr, pass_review with
criteria_verified gate, all propose_*/request_render methods) are preserved
and untouched. The test files already exist on current main.

* [a1f52d13] fix(git-routes): wrap workspace resolution in bounded timeout for GET /diff

The bounded asyncio.wait_for must cover the workspace resolution step too,
not just the diff+stat subprocesses — the integration test makes
get_workspace slow (not _run_git), so the timeout guard needs to enclose
the entire resolve-and-diff path to return a structured 504 on trip.

* [a1f52d13] test: update mock setups from git.diff/list_changed_files to diff_and_files

Tests that exercise claim_review and evidence() paths need their git
mocks to return a tuple from diff_and_files instead of separate diff
and list_changed_files return values, matching the new combined accessor
introduced by the evidence-assembly timeout fix.

* [a1f52d13] test: configure diff_and_files mock in lifecycle parity test _make_deps

The spec-parity test test_claim_review_matches_spec[qa-awaiting_qa] failed
with ValueError at qa.py:330 because _make_deps set git=AsyncMock() without
configuring diff_and_files.return_value, so the await resolved to a MagicMock
instead of a (diff_summary, files_changed) 2-tuple. Adds the same
return_value = ("", []) configuration that commit 014a0d03 applied to the
other 4 test files.

* [a1f52d13] docs(evidence-assembly-timeout): correct GET /diff guard scope and _qa_git_and_conventions timing field

---------

Co-authored-by: Backend Developer 2 <be-dev-2@roboco.tech>
Co-authored-by: Backend Documenter <be-doc@roboco.tech>
This commit is contained in:
roboco-app[bot]
2026-08-02 03:22:25 +00:00
committed by GitHub
co-authored by Backend Developer 2 Backend Documenter
parent 89254f796c
commit ce89a04d26
12 changed files with 6314 additions and 974 deletions
@@ -37,7 +37,7 @@ def _make_deps(**overrides: AsyncMock) -> ContentActionsDeps:
else:
git = AsyncMock()
git.commit.return_value = {"sha": "abc12345"}
git.diff.return_value = ""
git.diff_and_files.return_value = ("", [])
a2a = overrides.get("a2a", AsyncMock())
journal = overrides.get("journal", AsyncMock())
@@ -270,7 +270,7 @@ async def test_evidence_blocks_when_not_assignee() -> None:
task_svc = AsyncMock()
task_svc.get.return_value = task_obj
git_svc = AsyncMock()
git_svc.diff.return_value = ""
git_svc.diff_and_files.return_value = ("", [])
workspace_svc = AsyncMock()
deps = _make_deps(task=task_svc, git=git_svc, workspace=workspace_svc)
ca = ContentActions(deps)
@@ -279,7 +279,7 @@ async def test_evidence_blocks_when_not_assignee() -> None:
body = env.as_dict()
assert body["error"] == "not_authorized"
workspace_svc.fetch_branch_for_inspection.assert_not_awaited()
git_svc.diff.assert_not_awaited()
git_svc.diff_and_files.assert_not_awaited()
# ---------------------------------------------------------------------------
@@ -410,7 +410,7 @@ async def test_evidence_unassigned_task_allows_inspection() -> None:
task_svc = AsyncMock()
task_svc.get.return_value = task_obj
git_svc = AsyncMock()
git_svc.diff.return_value = "diff content"
git_svc.diff_and_files.return_value = ("diff content", [])
workspace_svc = AsyncMock()
deps = _make_deps(task=task_svc, git=git_svc, workspace=workspace_svc)
ca = ContentActions(deps)
@@ -446,8 +446,7 @@ async def test_evidence_allows_dependency_inspection() -> None:
task_svc.get.return_value = target
task_svc.list_assigned_for_agent.return_value = [callers_task]
git_svc = AsyncMock()
git_svc.diff.return_value = ""
git_svc.list_changed_files.return_value = []
git_svc.diff_and_files.return_value = ("", [])
workspace_svc = AsyncMock()
deps = _make_deps(task=task_svc, git=git_svc, workspace=workspace_svc)
ca = ContentActions(deps)