Repository navigation
fix: wire three declared-but-not-enforced defects (research budget, link-pipeline cap, stale debug assertion) (#13013, #13021, #13037) - #13039
Merged
Conversation
…truncation point (#13013) extract_claims slices fetched content to budget.max_content_chars before sending it to the LLM but the orchestrator never recorded which sources that happened to. Record the source URL (and log) whenever the fetched markdown exceeds the same content-char bound so truncation is now a structured, testable field instead of only log-based observability.
…uring streaming (#13021) media/link/pipeline.py declared _MAX_CONTENT_LENGTH but _fetch_and_parse's BeautifulSoup fallback did an unbounded response.text() read. Enforce the cap during the chunked read (mirroring web_fetch/fetcher.py._fetch_bs4) so an oversized/slow-drip response is cut off mid-stream instead of read fully into memory then discarded. The cap is now an env-sourced ssot_config field (AUTOBOT_LINK_PIPELINE_MAX_CONTENT_BYTES) rather than a bare literal, and a rejection logs distinguishably ("REJECTED ... distinct from a fetch/ connection failure") from genuine fetch/connection errors. LinkPipeline/process_url currently has no production caller (api/ knowledge_scrape.py and peers call web_fetch.WebFetcher directly) so this closes a latent resource-exhaustion gap rather than a live one -- fixed per "never delete code, wire it in" ahead of any endpoint being wired to it.
…or.tool_registry premise (#13037) Orchestrator has never had a tool_registry attribute (git log --follow shows this script untouched except renames/relicensing since the project's earliest history) -- it owns agent_registry for agent routing. Tool execution is a separate pipeline owned by the canonical tools.tool_registry.get_tool_registry() singleton, dispatched from task_handlers/executor.py / api/agent.py. Rewrote the script's assertions to match that real wiring (agent_registry on Orchestrator, get_tool_registry() for tool dispatch/execution) instead of deleting them, preserving the original intent of proving the tool-dispatch pipeline actually runs a tool end-to-end.
Contributor
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
This was referenced Jul 30, 2026
Closed
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.
Thinking Path
Three issues filed from prior sessions' reading, each in the same defect class -- something declared/asserted that was never actually wired to reality. Verified each premise against current code before touching anything (
file:line), per this session's repeated finding that filed premises are sometimes wrong.ResearchBudget.truncated_sources(services/research/models.py:88) is a dataclass field, never appended to anywhere -- confirmed via grep acrossservices/research/*.py. The one genuine, existing truncation point in the pipeline isextractor.py:102'scontent[:max_content_chars]slice, driven bybudget.max_content_chars-- butextract_claims()only receives the int, not the budget object, so it can't record into it itself. Wired the check at the call site (orchestrator.py._land_page, which holds both the budget and the pre-slice markdown) instead of duplicating the slicing logic.media/link/pipeline.py:79declares_MAX_CONTENT_LENGTH = 1_000_000-- confirmed viagrep -n "_MAX_CONTENT_LENGTH"returning only the declaration._fetch_and_parse's BS4 fallback didawait response.text(...)unbounded. Reachability: confirmed via security(web_fetch/media): fetcher.py and link/pipeline.py have the same validate-then-connect SSRF TOCTOU as content_reach (#13017) #13019's own text thatLinkPipeline/MediaPipelineManagerhas zero production callers (api/knowledge_scrape.pyand peers callweb_fetch.WebFetcherdirectly);process_url()is only referenced from its own test file. This is a latent/unwired gap, not a live exploitable one -- fixed per "never delete code, wire it in" ahead of any endpoint being wired to this pipeline, not framed as an active incident.tools/tool_registry_debug_e2e_test.py:25assertsorchestrator.tool_registry. Confirmed viaorchestrator.py(root) thatOrchestratoronly ever hadself.agent_registry(line 159) -- notool_registry,available_tools,_execute_planned_action, orlocal_workereither, all also referenced by this same stale script.git log --followshows the file untouched except repo-wide renames/relicensing since the project's earliest history, predating the refactor(backend): one conductor — single Orchestrator class, rename 6 misnamed '*Orchestrator' classes #5040 "single conductor" orchestrator consolidation. The script's intent (prove the tool-dispatch pipeline is wired and runs a tool) is still valid; its premise about where that lives was wrong -- the real surface is the canonicaltools.tool_registry.get_tool_registry()singleton, dispatched fromtask_handlers/executor.py/api/agent.py, not anything owned byOrchestrator.All three were fully deliverable -- none needed a scope-changing decision, so nothing was dropped.
What Changed
#13013 (
services/research/orchestrator.py,orchestrator_test.py):ResearchOrchestrator._record_truncation_if_needed(), called from_land_pageright after a page is fetched: iflen(markdown) > budget.max_content_chars(the same boundextract_claimsslices to), appends the URL tobudget.truncated_sourcesand logs it.#13021 (
media/link/pipeline.py,pipeline_test.py,autobot_shared/ssot_config.py):link_pipeline_max_content_bytesfield onssot_config(AUTOBOT_LINK_PIPELINE_MAX_CONTENT_BYTES, default1_000_000) -- replaces the bare literal._read_bounded_content()streams the fallback fetch response in 64 KiB chunks (mirroringweb_fetch/fetcher.py._fetch_bs4's existing pattern) and returnsNonethe instant the cap is crossed -- remaining chunks are never requested, so an oversized/slow-drip body is cut off mid-stream, not read fully then discarded."... REJECTED ... body exceeded max_content_length ... (distinct from a fetch/connection failure)"at WARNING, separate from the existing SSRF/connection/HTTP-error log lines._parse_html; chunk consumption stops well short of the full stream once the cap is crossed (proves the cut, not just a flag); in-budget body still parses normally (regression guard).pipeline_test.py(shared byTestLinkPipelineHttpandTestLinkPipelineJina's BS4-fallback tests, including PR fix(web_fetch): pin SSRF guard connect target in fetcher.py + link/pipeline.py (#13019) #13022's SSRF-pinning-adjacent fixtures) to also supplyresponse.content.iter_chunked(), since the read path moved offresponse.text(). No assertions in those pre-existing tests changed.#13037 (
tools/tool_registry_debug_e2e_test.py):Orchestratorhasagent_registry(populated) and nevertool_registry; the canonicalget_tool_registry()singleton has tools wired (respond_conversationallypresent inget_available_tools()); andexecute_tool("respond_conversationally", ...)actually runs end-to-end (mirrors the original script's_execute_planned_actionintent, now via the real dispatch method).Verification
Per-issue tests:
Full related-scope sweep (services/research, media/link, tools/tool_registry*, api/knowledge_scrape_test.py, ssot_config_test.py):
cp-swapped baseline (origin/Dev_new_guicontent restored in-place, tests re-run, then my changes restored viacpfrom a scratch copy -- nogit stash):tests/test_startup_imports.py-- 350 passedtests/test_startup_imports.py-- 350 passed (identical ratio, no regression)media/linkSSRF-pinning tests (TestLinkPipelineFetchSSRFPinning,TestLinkPipelineHttp) and fix(knowledge): exclude quarantined research facts at every reachable KB search site (#13009) #13029/fix(knowledge): repair 4 broken KB-search call sites found by #13009's audit #13038's KB-search paths (api/chat_knowledge_test.py,knowledge/quarantine_test.py,services/research/quarantine_boundary_test.py) all still green, same assertions.Model Used
Claude Sonnet 5