Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ concurrency:
cancel-in-progress: true

jobs:
rust-core:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install pinned Rust toolchain
run: >-
rustup toolchain install 1.97.1 --profile minimal
--component clippy --component rustfmt --component llvm-tools-preview
- name: Install pinned Rust coverage tool
run: cargo +1.97.1 install cargo-llvm-cov --locked --version 0.8.6
- run: cargo +1.97.1 fmt --all -- --check
- run: cargo +1.97.1 clippy --workspace --all-targets -- -D warnings
- run: >-
cargo +1.97.1 llvm-cov --package rankweave-core
--fail-under-lines 100 --fail-under-functions 100
--fail-under-regions 100

test:
runs-on: ubuntu-latest
timeout-minutes: 10
Expand All @@ -27,6 +47,8 @@ jobs:
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
- name: Install pinned Rust toolchain
run: rustup toolchain install 1.97.1 --profile minimal
Comment on lines +50 to +51

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Pinned Rust toolchain not used when building the extension

The test and package jobs install toolchain 1.97.1 but never set it as default or call cargo +1.97.1. uv sync drives maturin, which uses the runner's default cargo, so the compiled extension is built with an unpinned toolchain. Only the rust-core job pins the version. Same pattern in the hourly workflow at line 144.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: "0.11.29"
Expand Down Expand Up @@ -59,6 +81,8 @@ jobs:
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.13"
- name: Install pinned Rust toolchain
run: rustup toolchain install 1.97.1 --profile minimal
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: "0.11.29"
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/hourly-commercialization-loop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,10 @@ jobs:
set -euo pipefail
venv="/tmp/rankweave-automation-venv-${GITHUB_RUN_ID}"
sudo rm -rf "$venv"
rustup toolchain install 1.97.1 --profile minimal
python -m venv "$venv"
"$venv/bin/python" -m pip install --upgrade pip
"$venv/bin/python" -m pip install -e ".[dev]" hatchling
"$venv/bin/python" -m pip install -e ".[dev]"
sudo chown -R root:root "$venv"
sudo chmod -R a-w "$venv"
sandbox_uid="$(id -u nobody)"
Expand Down Expand Up @@ -653,7 +654,7 @@ jobs:
)

metadata = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))
if metadata["build-system"]["build-backend"] != "hatchling.build":
if metadata["build-system"]["build-backend"] != "maturin":
raise SystemExit("build backend may not change")
if metadata["project"].get("dependencies") != []:
raise SystemExit("RankWeave runtime dependencies must remain empty")
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ dist/
venv/
.coverage
htmlcov/
.codegraph/
target/
src/rankweave/*.so
10 changes: 6 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Operating guide for automated agents working in this repo.

## What this is

`rankweave` is a **pure-Python, stdlib-only** library and command-line tool for
`rankweave` is a Python library and command-line tool backed by one Rust
calculation core for
language-agnostic hybrid-retrieval fusion, effectiveness evaluation, paired and
family-wise statistical comparison, offline policy tuning, strict TREC
interchange, and direct TREC benchmark comparison. It was extracted from
Expand All @@ -14,9 +15,10 @@ Context Search under the lab's ONE SOURCE MULTI USE convention

## Hard rules

- **No dependencies.** The runtime imports only the Python standard library.
Do not add a runtime dependency; if you think you need one, the feature
probably belongs in the consumer, not here.
- **No third-party Python runtime dependencies.** Python adapters import only
the standard library and the packaged `rankweave._rankweave_core` extension.
Calculation belongs in `rankweave-core`; do not add a Python fallback or a
second arithmetic implementation.
- **Store-agnostic.** RankWeave never talks to a database, embedding provider,
search index, or benchmark download service. It fuses scores, evaluates and
compares rankings, selects offline policies, parses interchange artifacts,
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ All notable changes to rankweave are documented here. The format follows [Keep a
## [Unreleased]

### Added
- Introduced the first `rankweave-core` Rust vertical slice and thin PyO3
adapter for theoretical min-max normalization and unweighted Reciprocal Rank
Fusion, with exact public-Python parity and complete core coverage.
- ADR 0005 defines the versioned public-API compatibility policy: names in
`rankweave.__all__` frozen as of `0.18.0` are not removed or renamed within
a minor version, enforced by `tests/test_public_api_compatibility.py`.
Expand Down
137 changes: 137 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[workspace]
members = ["crates/rankweave-core", "crates/rankweave-python"]
resolver = "2"

[workspace.package]
edition = "2024"
license = "Apache-2.0"
rust-version = "1.97.1"
version = "0.18.0"
9 changes: 9 additions & 0 deletions crates/rankweave-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "rankweave-core"
version.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true

[lib]
path = "src/lib.rs"
34 changes: 34 additions & 0 deletions crates/rankweave-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! Deterministic calculation primitives for RankWeave.

/// Scale a finite score with finite theoretical bounds and clamp it to `[0, 1]`.
#[must_use]
pub fn theoretical_min_max_normalize(score: f64, lower: f64, upper: f64) -> f64 {
((score - lower) / (upper - lower)).clamp(0.0, 1.0)
}

/// Sum Reciprocal Rank Fusion contributions in caller-provided channel order.
#[must_use]
pub fn reciprocal_rank_fusion_score(ranks: &[u64], rank_constant_eta: u64) -> f64 {
ranks.iter().fold(0.0, |score, rank| {
score + 1.0 / (rank_constant_eta + rank) as f64
})
}
Comment on lines +5 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Arithmetic parity holds for realistic inputs

The migrated normalization and RRF preserve exact float results for realistic inputs: Rust clamp(0.0, 1.0) matches Python min/max, and the fold sums in the same channel order with identical IEEE-754 ops. NaN, infinite, and non-integer inputs are rejected in Python before delegation, and the empty-mapping RRF case returns 0.0 in both.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


#[cfg(test)]
mod tests {
use super::{reciprocal_rank_fusion_score, theoretical_min_max_normalize};

#[test]
fn normalization_uses_theoretical_bounds_and_clamps() {
assert_eq!(theoretical_min_max_normalize(0.5, 0.0, 2.0), 0.25);
assert_eq!(theoretical_min_max_normalize(3.0, 0.0, 2.0), 1.0);
}

#[test]
fn rrf_preserves_input_order_for_the_reduction() {
assert_eq!(
reciprocal_rank_fusion_score(&[1, 3], 60),
1.0 / 61.0 + 1.0 / 63.0
);
}
}
15 changes: 15 additions & 0 deletions crates/rankweave-python/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "rankweave-python"
version.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true

[lib]
name = "_rankweave_core"
crate-type = ["cdylib"]
path = "src/lib.rs"

[dependencies]
pyo3 = { version = "0.29.2", features = ["extension-module"] }
rankweave-core = { path = "../rankweave-core" }
20 changes: 20 additions & 0 deletions crates/rankweave-python/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! Python bindings for the RankWeave calculation core.

use pyo3::prelude::*;

#[pyfunction]
fn theoretical_min_max_normalize(score: f64, lower: f64, upper: f64) -> f64 {
rankweave_core::theoretical_min_max_normalize(score, lower, upper)
}

#[pyfunction]
fn reciprocal_rank_fusion_score(ranks: Vec<u64>, rank_constant_eta: u64) -> f64 {
rankweave_core::reciprocal_rank_fusion_score(&ranks, rank_constant_eta)
}

#[pymodule]
fn _rankweave_core(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_function(wrap_pyfunction!(theoretical_min_max_normalize, module)?)?;
module.add_function(wrap_pyfunction!(reciprocal_rank_fusion_score, module)?)?;
Ok(())
}
9 changes: 5 additions & 4 deletions docs/product-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,11 @@ flowchart LR
reimplement those models.
- contextual-orchestrator owns LLM and model-routing decisions.

The current released implementation is dependency-free Python. Issue #45
tracks migration of production fusion/evaluation arithmetic to a Rust core
behind the existing public contract under ADR 0006. Until that work is released and pinned,
consumers must not claim a Rust production engine.
The current release remains dependency-free Python. This development head
moves theoretical min-max normalization and unweighted RRF into the Rust core;
issue #45 tracks the remaining fusion and evaluation migration under ADR 0006.
Until a Rust-backed release is published and pinned, consumers must not claim
the engine is available to them.

## 7. Explicit non-goals

Expand Down
13 changes: 8 additions & 5 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ list` reality.

## 1. Product identity and responsibility boundary

RankWeave is a **leaf product**: a dependency-free, pure-Python,
standard-library-only library and CLI for hybrid-retrieval score fusion,
RankWeave is a **leaf product**: a Python library and CLI with no third-party
Python runtime dependencies and one packaged Rust calculation core for hybrid-retrieval score fusion,
ranking evaluation, paired/family statistical comparison, offline policy
tuning (including caller-owned blocked-fold cross-validation and
availability-time backtesting), and strict TREC interchange
Expand Down Expand Up @@ -181,10 +181,13 @@ Severity: 🔴 blocks a user today · 🟡 user-visible friction · 🟢 hardeni
file a follow-up issue tracking the re-pin once the central scheduler's
current commit is confirmed stable, rather than merging it unreviewed
inside an unrelated PR.
5. 🔴 **Production fusion arithmetic is still Python-only** (issue #45).
5. 🟡 **The first production fusion primitives are Rust-backed; migration is
incomplete** (issue #45).
LineageWeave ADR 0225 names RankWeave as the sole fusion owner, while the
current package implements the arithmetic in Python. The required migration
keeps the public Python surface as an adapter over one Rust core, preserves
development head now routes theoretical normalization and unweighted RRF
through `rankweave-core`; convex, weighted, evaluation, comparison, and
tuning arithmetic still execute in Python. The remaining migration keeps
the public Python surface as an adapter over one Rust core, preserves
exact documented semantics, and publishes provenance and limitations with
every calculation envelope. CPU multithreading and an optional GPU path
require exact-workload parity and benchmark evidence; no throughput claim
Expand Down
2 changes: 2 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tools]
rust = "1.97.1"
Loading
Loading