105625d650
parallel_search_sources accepted an overall_timeout but never honoured it. The ThreadPoolExecutor ran inside a `with ... as pool` block, whose __exit__ calls shutdown(wait=True); even after as_completed() raised TimeoutError on schedule, leaving the block blocked the caller until every worker finished. A single slow source (e.g. ClawHub) therefore stalled the entire browse for minutes. Manage the executor manually and shut it down with wait=False, cancel_futures=True in a finally, so the timeout actually returns and not-yet-started work is dropped. ClawHubSource._load_catalog_index walked up to 750 sequential pages with no wall-clock bound (each request under its own timeout=30, so nothing errored), and wrote the result to the index cache unconditionally — so an interrupted or slow walk poisoned the cache with a partial catalog. Add a CATALOG_WALK_BUDGET_SECONDS deadline that breaks the walk early, and only write the cache when the walk reaches a natural stop (cursor exhausted or page cap), never on a budget-truncated walk. Adds regression tests covering both bugs (timeout honoured + slow source flagged; budget abort does not poison cache) plus their happy-path invariants.