feat(ports): SearchProvider port + RustSearchAdapter (PLAN.md Fases 0-1) - #78
Merged
Conversation
…arch Adds two hexagonal-boundary Protocols: SearchProvider (index/query/fetch) and VisionProvider (describe_image/ocr/analyze_screenshot), per PLAN.md Fase 0. web_search's SearXNG/DuckDuckGo logic is wrapped as WebSearchProvider, the default SearchProvider adapter, with no change in observable behavior — the existing 16 tests pass unmodified. Adds SearchError to the domain error hierarchy (replacing the adapter-local WebSearchQueryError) and a reusable, parametrized contract-test suite for each port so any future adapter is validated against the same assertions. No public API change: SearchProvider/VisionProvider are not exported from nullain/__init__.py. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements the SearchProvider port against the nullain-search PyO3 wheel (tantivy BM25 core, github.com/netty-linux/nullain-sdk-search): index/query delegate to the Rust index via asyncio.to_thread, fetch delegates to an injected WebSearchProvider instead of the Rust index's own fetch (which only retrieves content it indexed itself — a different contract than this port's "fetch a URL"). PyO3 exceptions (ValueError/OSError/RuntimeError) translate to SearchError; a missing wheel raises a clear ImportError pointing at the new nullain-sdk[search-rust] extra rather than failing to import the port module. The contract-test suite now runs against both adapters, with the Rust case skipif-gated on the wheel being importable. tool.uv.sources temporarily points nullain-search at the sibling repo's path (not yet published to PyPI — tracked in #77) so `uv sync` resolves; swap for a plain version constraint once the wheel is released. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- PLAN.md: Fase 0 and Fase 1 marked done (2026-08-11) with what was delivered and what's still pending (PyPI publish, tracked in #77; ripgrep benchmark not done this pass). - docs/architecture.md: documents the now-established ports/adapters pattern — where a new port/adapter pair lives (co-located module vs adapters/ subpackage + selector), how optional-dependency adapters register conditionally (lazy import, ImportError with install hint, pyproject extra, skipif tests), and that contract tests gate any new adapter for an existing port. - README.md / README.pt-BR.md: architecture diagram gains the SearchProvider node; new "Optional sub-SDKs" section links nullain-sdk-search and the search-rust extra. - CHANGELOG.md: [Unreleased] entry for SearchProvider/VisionProvider, SearchError, RustSearchAdapter, and the contract-test suites. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
) tool.uv.sources points nullain-search at ../nullain-sdk-search since the wheel isn't on PyPI yet — CI needs that path checked out to resolve the workspace, same as local dev. Every job that runs `uv sync`/`uv build` gets a second checkout of netty-linux/nullain-sdk-search at that relative path. Remove once #77 lands (wheel published, source override replaced with a plain version constraint). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
actions/checkout's `path` input refuses any location outside the main
repo's checkout directory ("Repository path ... is not under ..."), so
the previous `path: ../nullain-sdk-search` failed immediately. Checks out
into a subdirectory instead and moves it up one level with a follow-up
shell step, landing at the same ../nullain-sdk-search path uv sync
expects.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
Summary
SearchProvider/VisionProviderProtocol ports;web_searchrefactored ontoWebSearchProvider(the defaultSearchProvideradapter) with no observable behavior change;SearchErroradded to the domain error hierarchy; reusable parametrized contract-test suites for both ports.RustSearchAdapterbacked by thenullain-searchwheel (tantivy BM25, nullain-sdk-search) —index/queryviaasyncio.to_thread,fetchdelegates to an injectedWebSearchProvider, PyO3 exceptions translated toSearchError. Newnullain-sdk[search-rust]extra. Contract suite runs against both adapters, Rust caseskipif-gated on the wheel.docs/architecture.mddocuments the ports/adapters + conditional-registration + contract-test pattern, READMEs (EN/PT-BR) gain the SearchProvider node and an "Optional sub-SDKs" section, CHANGELOG.md[Unreleased]entry.tool.uv.sourcestemporarily pointsnullain-searchat the sibling repo's local path since the wheel isn't on PyPI yet.No public API change —
SearchProvider/VisionProvider/RustSearchAdapter/SearchErrorare not exported fromnullain/__init__.py.Test plan
make check(ruff + pyright strict + pytest) verified green at each of the 3 commits individually, not just at HEADrust_searchcases skip cleanly) and with it (maturin develop --releaseagainst the real sibling repo — 14/14 pass)tests/unit/test_web_search.py's pre-existing 16 assertions pass unmodified (proof of zero behavior change in the refactor)nullain-sdk/src/nullain/__init__.py— zero lines changed🤖 Generated with Claude Code