Repository navigation
refactor(content-reach): route the browser backend through the canonical interface (#13236) - #13313
Merged
Merged
Conversation
…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.
Contributor
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #13236
Thinking Path
content_reachno longer importsresearch_browser_manager. It states what itneeds and lets dispatch resolve it:
EXTRACT_STRUCTUREDis both the honest requirement and what pins the routing:ContentResultcarriesstructuredalongsidetext, and the research stack isthe only one that produces it.
What Changed
content_reach/backends/browser.py—_navigateresolves a browser bycapability instead of importing
research_browser_manager;probe()asksthe interface the same question the fetch will ask;
_raise_for_failed_resultreads the canonical
BrowserResult;_get_managerremoved; module docstringcorrected.
browser_backends/in_process.py— the two wrapper bugs described below.tests/content_reach/conftest.py— new sharedstub_browser_managerfixture.Migrating a second real caller found two bugs in the wrapper I shipped
Both are in
InProcessBrowserBackendfrom #13226, and both are fixed here atthe source rather than worked around:
1. An SSRF-guard rejection was silently losing its flag.
research_urlshort-circuits on a #13018 guard block and returns the navigate result
directly — so
blocked_by_guardsits at the top level, while_to_resultonly read it from the nested
"navigation"key.content_reachdistinguishes"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 thatresearch_browser_managerimports — but that module imports fine withoutPlaywright installed and exposes
PLAYWRIGHT_AVAILABLEto say so, which isexactly what
content_reach.probe()read before. It now reads the flag.The test conversion
Five files patched
content_reach.backends.browser._get_managerto inject astub, 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_managerfixture(
tests/content_reach/conftest.py) that registers the real backends and pointsthe 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_manageris 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_reachimports nostack directly.
Incidental fix:
metadata["title"]read a top-level"title"thatresearch_urlnever returned, so it was always"". The canonical resultcarries the real page title.
Verification
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)