Skip to content

refactor(search)!: use abnegate-search for chat web search - #113

Merged
abnegate merged 5 commits into
mainfrom
migrate/search
Oct 7, 2026
Merged

abnegate merged 5 commits into
mainfrom
migrate/search

Conversation

@abnegate

@abnegate abnegate commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Breaks your .env

  • SEARCH_TIMEOUT_SECS → SEARCH_TIMEOUT_SECONDS. The old name is no longer read and there is no fallback. Rename it in .env if you set it. Docker deployments were not affected before this PR, because compose never passed the old name to the manager. Compose now passes SEARCH_TIMEOUT_SECONDS, so the setting takes effect there for the first time.

Summary

Moves zone's chat web search onto abnegate-search ("0.1.3", a caret whose floor is the tested version), the crate extracted from runner/zone_search and published from abnegate/crates, and deletes the local copy (about 1,400 lines).

zone's copy now notes
zone_search::{SearxngClient, SearchContext, SearchHit, TimeRange, format_search_context, sanitize_query, needs_web_search} abnegate_search::* Same names at the crate root, without the client:: module path. SearchHit and WebSearchConfig are #[non_exhaustive], so zone builds them with SearchHit::new(..), WebSearchConfig::new(url) and with_enabled(..).
zone_search::WebSearchConfig::from_env (Gluetun URL, on by default, VPN gate) zone_server::services::search::{from_environment, defaults, DEFAULT_QUERY_URL} The crate has no opinion on where SearXNG runs and starts switched off. The new wrapper holds zone's policy: the http://gluetun:8080/... query URL, search on unless SEARCH_ENABLE_WEB_SEARCH turns it off, and search off while public web waits for the VPN. The VPN check now calls zone_core::vpn::allows_public() instead of the copy the old crate kept.
zone_search::observe_searches(fn(&'static str, ..)) abnegate_search::observe_searches(fn(Outcome, ..)) metrics::record_searxng takes the typed Outcome and records outcome.as_str() as the status label.

runner/Cargo.toml drops zone_search from members/default-members and adds abnegate-search = "0.1.3" beside the other abnegate-* carets. zone_server/Cargo.toml swaps the path dependency for { workspace = true }. Cargo.lock was regenerated by cargo.

The env rename also updates .env.example, docker-compose.yml, docker-compose.dev.yml (now passing SEARCH_TIMEOUT_SECONDS=${SEARCH_TIMEOUT_SECONDS:-15} beside SEARCH_RESULT_COUNT) and docs/CONFIGURATION.md. helm/** sets no SEARCH_* variables, so nothing changed there. The README.md tree and the zone_search paths in docs/SYSTEM-PROMPT-RESEARCH.md now point at the crate.

The wrapper's tests change SEARCH_* variables, and config::tests changes SEARCH_TIMEOUT_SECONDS, in the same test binary. Each used to guard its changes with a lock of its own (zone_core::vpn::Hold had one, config::tests another, the local embedding provider's test a third), so under threaded cargo test they could race, and the // SAFETY comments claimed an exclusion none of them had. The first commit adds zone_core::variables::Variables: one process-wide lock, each variable saved before its first change and restored before the lock is released. vpn::Hold now holds a Variables (same constructors, plus variables() to change more variables under the same lock), and Variables::install sets a value a whole test binary shares (the chat agent suite's ZONE_CHAT_AGENT_CWD, the handover suite's PATH) under the same lock. config::tests, vpn::tests, the run_command environment test, services::search::tests, the local embedding cache test and those two suites change the environment only through it; git grep -n 'set_var\|remove_var' runner now matches only zone_core/src/variables.rs. The helpers config::tests and the wrapper's tests each kept are gone.

Variables offers safe set/remove over the unsafe calls, and production reads the environment on worker threads without its lock, so it must not reach a server or CLI build. variables and Hold compile only under cfg(any(test, feature = "test-support")). zone_core's new test-support feature is enabled only by zone_context's and zone_server's dev-dependencies, and resolver 2 keeps it out of normal builds. Nothing outside tests used Hold. The module documentation states what the lock does not cover: a read that bypasses std::env, such as libc's getenv inside getaddrinfo.

Commits:

  • test(core): serialise every environment change behind one lock
  • refactor(search)!: use abnegate-search for chat web search
  • fix(compose): pass SEARCH_TIMEOUT_SECONDS to the manager. Compose never passed the timeout to the manager, under either name.
  • docs(search): document SEARCH_TIMEOUT_SECONDS
  • refactor(chat): box the route a chat turn carries (see below)

Behaviour differences

Each item is covered by a zone test or by the crate's own suite.

Configuration (tests in services::search::tests and config::tests)

  • Defaults are unchanged for zone. With no SEARCH_* set, search is on (still subject to the VPN gate) and queries http://gluetun:8080/search?q=<query>&format=json. The crate's own default is off and http://127.0.0.1:8080/..., and the wrapper overrides both. Test configs use services::search::defaults() (off, Gluetun URL), as WebSearchConfig::default() did before. defaults() builds the config with WebSearchConfig::new and does not pass it through the crate's bounded() clamp; that is harmless, because its result count (5) and timeout (15s) are already in range, and SearxngClient::new clamps whatever it is given.
  • SEARCH_TIMEOUT_SECS → SEARCH_TIMEOUT_SECONDS (see top). environment_matrix_validates_secrets_and_loads_server_settings pins both halves: the old name leaves the timeout at 15s, and the new name sets it.
  • The timeout is a Duration (config.web_search.timeout), not timeout_secs: u64. The client still holds it to 1–60s and the result count to 1–20, whatever built the config.
  • WebSearchConfig's Debug prints [REDACTED] for the query URL, which can carry a credential.

Metrics (metrics::tests::searxng_outcomes_keep_their_status_labels)

  • zone_searxng_requests_total and zone_searxng_duration_seconds keep the labels ok, empty_query, http_error and status_error. Three statuses are new:
    • disabled: a search attempted while switched off. It used to go unrecorded.
    • decode_error: a body that is not SearXNG JSON. It used to count as http_error.
    • too_large: a body over 1 MiB, which the old client read without a limit.
  • The chat-quality dashboard groups by status, so the new labels show up as new series and nothing breaks.

Client (crate suite)

  • Redirects are refused, not followed, so a configured instance cannot pass the query to another host. A redirect now counts as status_error.
  • Bodies are capped at 1 MiB.
  • Transport errors no longer include the request URL, which carries the query.
  • The User-Agent changes from zone-server/web-search to abnegate-search/<version>.
  • A search while switched off returns Error::Disabled ("Search is switched off") instead of Offline ("Public web access is offline until the VPN is enabled."). Zone checks enabled before it builds a client and maps any error to its own message, so the model and the user see no change. The VPN refusals in agent/web.rs still come from zone_core::vpn.

Prompt text (crate suite; chat_ws_tests updated)

  • The search capability now opens "The server can search the public web via SearXNG", not "Zone can search…". It refers to "a search tool" and "a page-fetching tool" generically, where it used to name web_search and fetch_url. Both tools still register under the same names with the same descriptions. chat_ws_tests now asserts the new opening and the generic "When a search tool is among the callable tools" sentence, and checks that the agent model is offered web_search (on the wire or in the deferred tool list), since the capability text no longer names it.

  • format_search_context sanitises each result's title, URL and snippet:

    • control characters and line separators become spaces;
    • < and > become ‹ and ›;
    • titles are cut at 200 characters, URLs at 500 and snippets at 1,000, with an ellipsis.

    A result can no longer close <web_search_results> or forge a line that looks like another result. The surrounding wording is unchanged.

  • Every title, URL and snippet also goes through abnegate_secret::sanitize (crate src/context.rs, untrusted). It strips terminal escape sequences and invisible or bidi characters, and replaces credential-shaped substrings (token and signature query values, JWTs, URL userinfo, long high-entropy runs; hex digests are exempt) with [REDACTED]. A presigned result URL therefore reaches the model as …?token=[REDACTED], and a fetch_url on that text fails, where the old client passed the credential through. Citations are unaffected: identify registers each hit by its raw hit.url, so the identifier still names the address SearXNG returned. Pinned by agent::web::tests::a_credential_in_a_result_url_reaches_the_model_redacted.

  • The needs_web_search heuristic, query sanitising, URL templating and the time_range parameter (TimeRange::PARAM → TimeRange::PARAMETER, same wire name) are unchanged.

Verification

Run in runner/ after touching every .rs file, with output showing the crates built from this worktree. Unless noted, with --no-default-features --features zone_context/test-utils:

  • cargo fmt --all --check: PASS
  • cargo clippy -p zone_core -p zone_context -p zone_server --all-targets -- -D warnings: PASS
  • cargo clippy -p zone_core -p zone_context -p zone_server --all-targets --all-features -- -D warnings (CI's form, scoped to the crates this PR touches): PASS
  • Normal build, no test targets: cargo check -p zone_core -p zone_server: PASS. Run with -v, it shows zone_core built with only --cfg 'feature="sqlx"', so variables and Hold are absent. cargo tree -e normal,build,features -i zone_core shows no test-support, while -e all,features shows it enabled through the dev-dependency.
  • cargo check --workspace --exclude zone_desktop --all-targets: PASS
  • Threaded cargo test (not nextest), so the environment tests share one process: cargo test -p zone_core --lib -- vpn:: variables:: shelling_tools_give_the_child_only_the_context_environment: PASS, 12/12. cargo test -p zone_context --lib --features local-embeddings -- adapters::web:: cache_dir_env_override: PASS, 25/25 (and cache_dir_env_override alone, 1/1). cargo test -p zone_server --lib -- config:: services::search:: agent::web::: PASS, 71/71. The built zone_server test binary was then run 25 more times with config:: services::search:: agent::web:: vpn --test-threads=16: 25/25 PASS.
  • New tests: zone_core::variables::tests::every_change_is_undone_to_the_value_before_the_first, zone_core::vpn::tests::a_hold_takes_the_lock_every_variables_guard_takes (fails if Hold takes any other lock), agent::web::tests::a_credential_in_a_result_url_reaches_the_model_redacted.
  • zone-verify.sh … scoped -- -p zone_core -p zone_context -p zone_server --lib --test chat_search_tests --test chat_ws_tests --test knowledge_url_tests --test message_embedding_tests --test sync_tests --test chat_agent_tests --test chat_handover_tests against a throwaway Postgres: PASS, 4399/4399 (40 skipped)
  • Unchanged from the previous round, because the compose files have not changed since: python3 scripts/test-compose-up.py (fake docker/compose on PATH, starts no containers): PASS, 15 tests. docker compose --env-file .env.example -f docker-compose.yml [-f docker-compose.dev.yml] config (parses config only): PASS. The manager gets SEARCH_TIMEOUT_SECONDS: "15".
  • git grep -n 'zone_search\|SEARCH_TIMEOUT_SECS\b' matches only the regression test in config.rs that proves the old name is ignored.

This branch also carries refactor(chat): box the route a chat turn carries, cherry-picked unchanged from migrate/sanitize (71de4d0); its patch is byte-identical. main currently fails clippy -D warnings on ws::chat::Routing (large_enum_variant, clippy 1.98), and carrying the same commit lets this PR and the sanitize PR merge in either order. For the same reason, clippy -D warnings fails on each commit before it, as it does on main.

Not verified:

  • the full workspace test suite (the ready verify runs later);
  • clippy --all-features over the whole workspace (only the three touched crates);
  • a live SearXNG behind Gluetun;
  • a fetch_url of a redacted presigned URL against a real bucket.

Follow-ups

  • abnegate/crates should export the search environment variable names as constants (or offer a WebSearchConfig::load_with(reader)), so zone's wrapper (services/search.rs, ENABLED_VARIABLE and QUERY_URL_VARIABLE) need not repeat SEARCH_ENABLE_WEB_SEARCH and SEARCH_SEARXNG_QUERY_URL.

🤖 Generated with Claude Code

abnegate and others added 5 commits October 5, 2026 18:25
std::env::set_var and remove_var are unsound while another thread reads
the environment, and cargo test runs tests on threads. zone_core::vpn::Hold,
config's tests and the local embedding provider's test each guarded their
changes with a lock of their own, so a test could rewrite variables while
another in the same binary read them, and the SAFETY comments claimed an
exclusion none of them had.

zone_core::variables::Variables is now the one guard: it takes a single
process-wide lock, records each variable before its first change and
restores it before the lock is released. Hold keeps its constructors but
holds a Variables, and hands it out so a test can change more variables
under the same lock. Variables::install sets a value a whole test binary
shares under the same lock. Config's, vpn's, the run_command environment
test, the local embedding cache test and the chat agent and handover
suites change variables only through it.

Safe set and remove over unsafe calls must not reach production, where
worker threads read the environment without the lock, so variables and
Hold compile only for zone_core's own tests and its new test-support
feature, which zone_context and zone_server enable as dev-dependencies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zone_search was extracted to abnegate/crates as abnegate-search, so the
local copy goes and zone_server uses the published crate. The crate stays
neutral about where SearXNG runs and starts switched off; zone keeps its
own policy in services::search: the Gluetun query URL, search on unless
SEARCH_ENABLE_WEB_SEARCH turns it off, and off while public web waits for
the VPN (zone_core::vpn::allows_public, the same gate the old crate
re-implemented). The seven VPN-gating tests move with it, changing the
environment only under the Hold they already take.

The crate reads the timeout from SEARCH_TIMEOUT_SECONDS. There is no
fallback to SEARCH_TIMEOUT_SECS, and the config test pins that the old
name no longer sets the timeout.

The search observer now reports a typed Outcome, recorded under its
stable label, which adds the disabled, decode_error and too_large
statuses to zone_searxng_requests_total.

Every result title, URL and snippet now passes through the crate's
sanitizer, which redacts credential-shaped text such as a presigned URL's
token before the model reads it; a web tool test pins that. The capability
text names the search tool only generically, so the chat test now asserts
that sentence and that an agent model is offered web_search.

BREAKING CHANGE: SEARCH_TIMEOUT_SECS is no longer read; set
SEARCH_TIMEOUT_SECONDS instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The manager container sees only the variables compose lists, so the
search timeout could never be set in Docker: neither the old
SEARCH_TIMEOUT_SECS nor the renamed SEARCH_TIMEOUT_SECONDS reached it.
Listing it beside SEARCH_RESULT_COUNT, with the same 15-second default
the server uses, makes the setting take effect there for the first time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The renamed timeout had no entry in .env.example or CONFIGURATION.md, so
an operator had no way to find it. Both now list it next to
SEARCH_RESULT_COUNT, along with the 1-60 and 1-20 ranges the client
holds the two settings to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clippy 1.98 flags Routing as a large enum: the Chat variant's Route
makes every Routing at least 856 bytes, 216 more than the next largest
variant, and clippy -D warnings fails zone_server on main. Boxing the
route keeps the enum near the media variants' size; it is built once
per turn, so the allocation costs nothing that matters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
CONTRIBUTING.md — configured

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: abnegate/zone/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 19535ddb-414e-419a-b1c6-91c98294015b
📥 Commits

Reviewing files that changed from the base of the PR and between a376d11 and 48c2705.

⛔ Files ignored due to path filters (1)
  • runner/Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock, !**/*.lock
📒 Files selected for processing (38)
  • .env.example
  • README.md
  • docker-compose.dev.yml
  • docker-compose.yml
  • docs/CONFIGURATION.md
  • docs/SYSTEM-PROMPT-RESEARCH.md
  • runner/Cargo.toml
  • runner/zone_context/Cargo.toml
  • runner/zone_context/src/embeddings/providers/local.rs
  • runner/zone_core/Cargo.toml
  • runner/zone_core/src/lib.rs
  • runner/zone_core/src/tools/command.rs
  • runner/zone_core/src/variables.rs
  • runner/zone_core/src/vpn.rs
  • runner/zone_search/Cargo.toml
  • runner/zone_search/src/client.rs
  • runner/zone_search/src/config.rs
  • runner/zone_search/src/lib.rs
  • runner/zone_search/src/observe.rs
  • runner/zone_search/src/time_range.rs
  • runner/zone_server/Cargo.toml
  • runner/zone_server/src/agent/web.rs
  • runner/zone_server/src/config.rs
  • runner/zone_server/src/main.rs
  • runner/zone_server/src/metrics.rs
  • runner/zone_server/src/services/chat/session.rs
  • runner/zone_server/src/services/chat/session/tail.rs
  • runner/zone_server/src/services/mod.rs
  • runner/zone_server/src/services/search.rs
  • runner/zone_server/src/state.rs
  • runner/zone_server/src/ws/chat.rs
  • runner/zone_server/tests/chat_agent_tests.rs
  • runner/zone_server/tests/chat_handover_tests.rs
  • runner/zone_server/tests/chat_search_tests.rs
  • runner/zone_server/tests/chat_ws_tests.rs
  • runner/zone_server/tests/common/mod.rs
  • runner/zone_server/tests/message_embedding_tests.rs
  • runner/zone_server/tests/sync_tests.rs
💤 Files with no reviewable changes (7)
  • README.md
  • runner/zone_search/src/lib.rs
  • runner/zone_search/src/observe.rs
  • runner/zone_search/Cargo.toml
  • runner/zone_search/src/client.rs
  • runner/zone_search/src/time_range.rs
  • runner/zone_search/src/config.rs

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Summary

Summary by CodeRabbit

  • New Features
    • Web search now supports a configurable request timeout, defaulting to 15 seconds and accepting values from 1 to 60 seconds.
    • Search availability respects public-access settings, and search-result URLs with embedded credentials are redacted in rendered output.
  • Documentation
    • Clarified that the number of search results provided to chat is limited to 1–20.
    • Documented the web-search timeout setting and its supported range.

Walkthrough

Zone replaces its local web-search crate with abnegate-search and adds a Zone-specific search configuration service. The change documents and configures search timeouts, updates server search and metrics integration, and introduces a shared guard for test environment-variable changes.

Changes

Web search integration

Layer / File(s) Summary
Adopt the shared search crate
runner/Cargo.toml, runner/zone_search/*, runner/zone_server/Cargo.toml, runner/zone_server/src/services/chat/*, runner/zone_server/src/ws/chat.rs, runner/zone_server/tests/chat_search_tests.rs, README.md, docs/SYSTEM-PROMPT-RESEARCH.md
The runner removes zone_search and adds abnegate-search. Server imports and research references now point to the shared crate.
Configure Zone search
runner/zone_server/src/services/search.rs, runner/zone_server/src/services/mod.rs, runner/zone_server/src/config.rs, runner/zone_server/src/agent/web.rs, runner/zone_server/src/state.rs, runner/zone_server/tests/*, .env.example, docker-compose*.yml, docs/CONFIGURATION.md
A new search service applies Zone’s query URL default and VPN access rules. Search timeout configuration is exposed through the manager environment and documented with its 15-second default and 1–60-second limit. The agent uses Duration timeouts with a five-second margin.
Connect chat search and metrics
runner/zone_server/src/main.rs, runner/zone_server/src/metrics.rs, runner/zone_server/src/ws/chat.rs, runner/zone_server/src/agent/web.rs, runner/zone_server/tests/chat_ws_tests.rs
Search observation registration uses the shared crate. Metrics accept typed outcomes, and chat tests check search-tool availability. A web-agent test checks credential redaction in rendered search-result URLs.

Test environment guards

Layer / File(s) Summary
Add the shared environment guard
runner/zone_core/Cargo.toml, runner/zone_core/src/lib.rs, runner/zone_core/src/variables.rs, runner/zone_core/src/vpn.rs, runner/zone_core/src/tools/command.rs, runner/zone_context/Cargo.toml, runner/zone_server/Cargo.toml
Variables serializes guarded environment changes and restores saved values when dropped. VPN test support uses the guard, and dependent crates enable test-support.
Use guarded changes in tests
runner/zone_context/src/embeddings/providers/local.rs, runner/zone_server/src/config.rs, runner/zone_server/tests/chat_agent_tests.rs, runner/zone_server/tests/chat_handover_tests.rs
Tests replace direct environment mutations and local environment helpers with Variables locking, isolation, and installation.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ConfigFromEnv
  participant SearchService
  participant AbnegateSearch
  participant VpnAccess
  participant WebAgent
  ConfigFromEnv->>SearchService: load web-search settings
  SearchService->>AbnegateSearch: read shared environment configuration
  SearchService->>VpnAccess: check public access
  SearchService->>ConfigFromEnv: return Zone search configuration
  ConfigFromEnv->>WebAgent: provide search configuration
Loading

Merge Risk: ⚪ Minimal · up to 48c27

The search migration and test-guard changes have no established merge-blocking issue. Normal checks remain appropriate before merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 48c27

The inspected application controls preserve VPN and offline restrictions. However, the shared client's parsing and request-limit guarantees could not be fully verified. The timeout-setting rename also requires configuration changes during upgrade and rollback.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The relevant exposure is server-side search egress and delivery of retrieved content to chat or agent contexts. Users and models influence query text; the inspected enablement, endpoint, and VPN settings are process configuration. A defect in shared-client controls could therefore affect multiple searches within a deployment, while citation association remains chat-scoped in the inspected callers.

Trust Boundaries and Controls

  • observed — The local wrapper independently applies VPN permission after shared configuration parsing. Agent web tools are not registered for offline scopes, disabled search, or a blank endpoint, and execution rechecks offline/VPN refusal. These controls predate the migration or preserve its base policy; their presence is not evidence that every dependency-level control is equivalent.

Resilience and Maintainability Implications

  • observed — The mutable environment guard and vpn::Hold are compiled only for tests or the test-support feature. The inspected server and context manifests enable that feature through development dependencies, separating test mutation authority from ordinary production dependencies.

Hardening Proposals

  • proposed — Close the migration assurance gap by checking the exact resolved shared-client implementation for enable parsing, disabled-state network behavior, timeout/result bounds, query sanitization, and retrieved-content formatting. Preserve these expectations as integration compatibility checks rather than assuming the extraction guarantees parity.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.51% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 98 functions across 22 files. (9 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: replacing the local chat web-search implementation with abnegate-search.
Description check ✅ Passed The description explains the search migration, configuration changes, behavior differences, and verification results. It is directly related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 75.51% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 98 functions across 22 files. (9 skipped: 9 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@abnegate
abnegate merged commit f4c8ae0 into main Oct 7, 2026
18 checks passed
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