Skip to content

refactor(content-reach): route the browser backend through the canonical interface (#13236) - #13313

Merged
mrveiss merged 3 commits into
Dev_new_guifrom
issue-13236-contentreach
Aug 2, 2026
Merged

mrveiss merged 3 commits into
Dev_new_guifrom
issue-13236-contentreach

Conversation

@mrveiss

@mrveiss mrveiss commented Aug 2, 2026 •

Copy link
Copy Markdown
Owner

Closes #13236

Partial — #13236 must stay OPEN. The close keyword is required by the
Validate PR issue link gate. This is step 3 of 5; step 5
(api/playwright) remains.

Thinking Path

content_reach no longer imports research_browser_manager. It states what it
needs and lets dispatch resolve it:

_REQUIRES = frozenset({Capability.NAVIGATE, Capability.EXTRACT_STRUCTURED})

EXTRACT_STRUCTURED is both the honest requirement and what pins the routing:
ContentResult carries structured alongside text, and the research stack is
the only one that produces it.

What Changed

  • content_reach/backends/browser.py — _navigate resolves a browser by
    capability instead of importing research_browser_manager; probe() asks
    the interface the same question the fetch will ask; _raise_for_failed_result
    reads the canonical BrowserResult; _get_manager removed; module docstring
    corrected.
  • browser_backends/in_process.py — the two wrapper bugs described below.
  • tests/content_reach/conftest.py — new shared stub_browser_manager fixture.
  • 4 test files converted onto it; the Content Reach hardening follow-ups: parallel probe_all, env TTL, subclass guard, httpx DRY, browser smoke (#10932) #11078 smoke test repointed.

Migrating a second real caller found two bugs in the wrapper I shipped

Both are in InProcessBrowserBackend from #13226, and both are fixed here at
the source rather than worked around:

1. An SSRF-guard rejection was silently losing its flag. research_url
short-circuits on a #13018 guard block and returns the navigate result
directly — so blocked_by_guard sits at the top level, while _to_result
only read it from the nested "navigation" key. content_reach distinguishes
"blocked by SSRF guard" from a generic failure in its error message, and that
distinction would have quietly vanished. Caught because an existing test
asserts the specific message.

2. probe() was weaker than the check it replaced. It only verified that
research_browser_manager imports — but that module imports fine without
Playwright installed and exposes PLAYWRIGHT_AVAILABLE to say so, which is
exactly what content_reach.probe() read before. It now reads the flag.

The test conversion

Five files patched content_reach.backends.browser._get_manager to inject a
stub, including the two SSRF-guard test files — the reason I flagged this
step as having more blast radius than its size suggests.

They now use a shared stub_browser_manager fixture
(tests/content_reach/conftest.py) that registers the real backends and points
the in-process one at the same stub object the tests already build. The
injection stays a one-liner at each call site, so the tests kept their original
shape and assertions.

_get_manager is removed — nothing referenced it after the migration. Its
#11078 smoke test is repointed rather than deleted: the property it covered
(the lazy import resolves to the singleton, not a fresh manager — a second
instance would mean per-caller browser sessions) still matters, it just lives
one layer down now. A new structural test asserts content_reach imports no
stack directly.

Incidental fix: metadata["title"] read a top-level "title" that
research_url never returned, so it was always "". The canonical result
carries the real page title.

Verification

tests/content_reach                                   147 passed  (baseline 146)
+ browser_backends + autobot_shared/browser + web_fetch  199 passed
bandit                                                  0 issues
isort · black --line-length=120 · flake8                clean

Verified against the 146-passing baseline captured before touching
anything, as I said I would — every original test still passes; the extra one
is the new structural check.

Model Used

Claude Opus 5 (1M context)

…cal interface (#13236)

ADR-009 step 3. content_reach no longer imports research_browser_manager --
it asks for what it needs and lets dispatch resolve it:

  _REQUIRES = {Capability.NAVIGATE, Capability.EXTRACT_STRUCTURED}

EXTRACT_STRUCTURED is both the honest requirement and what pins the routing:
ContentResult carries `structured` alongside `text`, and the research stack
is the only one producing it.

Migrating a second real caller found two bugs in the in-process wrapper I
shipped in #13226, both fixed here at the source:

1. A #13018 SSRF-guard rejection was silently losing its flag. research_url
   short-circuits on a guard block and returns the navigate result DIRECTLY,
   so `blocked_by_guard` sits at the top level -- but _to_result only read it
   from the nested "navigation" key. content_reach distinguishes a guard
   rejection from a generic failure in its error message, and that
   distinction would have quietly disappeared.

2. probe() only checked that research_browser_manager imports. That module
   imports fine without Playwright installed and exposes PLAYWRIGHT_AVAILABLE
   to say so, which is exactly what content_reach's own probe() read before.
   The backend's probe was weaker than the check it replaced; it now reads
   the flag.

Test conversion: five files patched content_reach.backends.browser._get_manager
to inject a stub, including the two SSRF-guard test files. They now go through
a shared `stub_browser_manager` fixture in tests/content_reach/conftest.py,
which registers the real backends and points the in-process one at the same
stub object the tests already build -- so the injection stays a one-liner at
each call site.

_get_manager is removed (nothing referenced it after the migration), and its
#11078 smoke test is repointed at InProcessBrowserBackend._manager rather than
deleted: the property it covered -- that the lazy import resolves to the
SINGLETON, not a fresh manager -- still matters, it just lives one layer down.
A new structural test asserts content_reach imports no stack directly.

Incidental fix: metadata["title"] read a top-level "title" that research_url
never returned, so it was always "". The canonical result carries the real
page title.

147 tests in tests/content_reach (baseline 146), 199 across content_reach,
browser_backends, autobot_shared/browser and web_fetch. bandit 0 issues.
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No hardcoded values detected that have SSOT config equivalents!

@mrveiss
mrveiss merged commit 11f79e3 into Dev_new_gui Aug 2, 2026
51 of 53 checks passed
@mrveiss
mrveiss deleted the issue-13236-contentreach branch August 2, 2026 19:28
@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.14286% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
autobot-backend/browser_backends/in_process.py 66.66% 1 Missing ⚠️
autobot-backend/content_reach/backends/browser.py 94.73% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant