Summary
RrfRank.MarshalJSON auto-negates its fusion score, so the expression tree always operates on -rrf_sum, which is always ≤ 0 on any realistic corpus. This makes several arithmetic compositions mathematically meaningless or degenerate:
| Composition |
Reason |
rrf.Log() |
Log of non-positive is undefined → server silently drops scores |
rrf.Max(Val(0)) |
max(x, 0) == 0 for x ≤ 0 → all scores collapse to 0 |
rrf.Min(Val(0)) |
min(x, 0) == x for x ≤ 0 → no-op (harmless but pointless) |
The degenerate runtime behavior is tracked separately as a [BUG]. This enhancement is the complementary client-side preventive measure: reject these compositions at construction time so the caller sees a clear error before a query is ever sent.
Proposed Design
Add a lightweight Validate() method (or fail-fast in the arithmetic methods themselves) on the wrapper ranks that detect an inner *RrfRank and reject the degenerate compositions.
Example sketch:
func (r *RrfRank) Log() Rank {
return &deferredErrorRank{
err: errors.New("RrfRank.Log(): log of the auto-negated fused score is undefined — " +
"RRF fusion output is always non-positive after internal negation"),
}
}
The error surfaces at MarshalJSON time (same pattern as UnknownRank at rank.go:85), keeping the fluent API ergonomic while preventing silent footguns.
Alternatives
- Hard error return from the arithmetic methods — breaks the fluent API (
Rank return type would need to become (Rank, error)).
- Compile-time rejection via separate types — e.g., split
RrfRank into a narrower type that does not implement Log/Max(0). Overkill.
- Runtime warning via a package-level logger — too easy to miss; not idiomatic for a library.
The deferred-error pattern (option shown above) is the best fit for this codebase.
Scope Note
This enhancement only addresses the client-side preventive guard. The underlying server-side silent degeneration is tracked in the accompanying [BUG] issue.
Discovery
Flagged during PR review of #496 by the silent-failure-hunter agent and pinned as a regression baseline in Phase 21.1 cloud tests (TestCloudClientSearchRRFArithmetic).
Summary
RrfRank.MarshalJSONauto-negates its fusion score, so the expression tree always operates on-rrf_sum, which is always≤ 0on any realistic corpus. This makes several arithmetic compositions mathematically meaningless or degenerate:rrf.Log()rrf.Max(Val(0))max(x, 0) == 0forx ≤ 0→ all scores collapse to 0rrf.Min(Val(0))min(x, 0) == xforx ≤ 0→ no-op (harmless but pointless)The degenerate runtime behavior is tracked separately as a
[BUG]. This enhancement is the complementary client-side preventive measure: reject these compositions at construction time so the caller sees a clear error before a query is ever sent.Proposed Design
Add a lightweight
Validate()method (or fail-fast in the arithmetic methods themselves) on the wrapper ranks that detect an inner*RrfRankand reject the degenerate compositions.Example sketch:
The error surfaces at
MarshalJSONtime (same pattern asUnknownRankatrank.go:85), keeping the fluent API ergonomic while preventing silent footguns.Alternatives
Rankreturn type would need to become(Rank, error)).RrfRankinto a narrower type that does not implementLog/Max(0). Overkill.The deferred-error pattern (option shown above) is the best fit for this codebase.
Scope Note
This enhancement only addresses the client-side preventive guard. The underlying server-side silent degeneration is tracked in the accompanying
[BUG]issue.Discovery
Flagged during PR review of #496 by the silent-failure-hunter agent and pinned as a regression baseline in Phase 21.1 cloud tests (
TestCloudClientSearchRRFArithmetic).