fix(s31): close 1 review MAJOR — bound the blend weight so the hybrid preserves structural order

This commit is contained in:
Kjell Tore Guttormsen 2026-07-25 20:03:37 +02:00
commit 6618f67c3f

View file

@ -78,8 +78,29 @@ if TYPE_CHECKING: # verdicts imports agent_framework — keep it out of the run
EMBED_DIM = 64 EMBED_DIM = 64
# Blend weight for HybridRanker: score = weight * cosine + (1 - weight) * structural. # Blend weight for HybridRanker: score = weight * cosine + (1 - weight) * structural.
# 0.5 gives the two falsifiable signals equal say; the caller can override per construction. # The caller can override per construction.
SEMANTIC_WEIGHT_DEFAULT = 0.5 #
# 0.25, and the reason is MEASURED, not aesthetic. The previous value (0.5) was justified as
# "giving the two falsifiable signals equal say" — a claim about equivalence that the measurement
# refutes. Over the 1770 pairs of a fully enumerated 60-feature family, the ``FakeEmbedder``
# cosine is a near-constant: mean 0.7500, sd 0.0380, range [0.6123, 0.8614] => spread 0.2492. A
# term with that little dynamic range is a TIE-BREAKER, not a co-equal signal, and weighting it as
# though it were an equal partner does not make it one — it just lets 0.25 of a near-constant
# outvote real structural distance.
#
# Worst-case bound: the semantic term can overturn a structural gap G only when
# w * spread > (1 - w) * G, i.e. it is safe while ``w < G / (G + spread)``. With G = one
# ``verdicts._W_MAGNITUDE`` step (0.15) and the measured spread, that ceiling is 0.3758 — so 0.5
# sat ABOVE it and the ordering was genuinely overturnable; 0.25 sits below with margin. Stated as
# a property rather than a fixture fit: w = 0.25 holds for any spread below 0.45, against 0.2492
# observed. Measured, not assumed: 9 adverse triples at w = 0.5, zero at every weight from 0.45
# down to 0.05 (see ``test_hybrid_preserves_structural_order_across_one_magnitude_step``).
#
# Lowering it costs nothing the seam is for: cosine still decides 100% of exact structural ties at
# any w > 0, which is the role SC2 pins. Only the co-equal-signal role disappears, and it was
# never defended. An injected REAL embedder has a different cosine distribution and therefore
# needs its own weight justification — this number is derived from the shipped fake one.
SEMANTIC_WEIGHT_DEFAULT = 0.25
# Mirrors ``verdicts._MAGNITUDE_BUCKETS``. DUPLICATED, not imported, on purpose: importing it # Mirrors ``verdicts._MAGNITUDE_BUCKETS``. DUPLICATED, not imported, on purpose: importing it
# would pull ``verdicts`` (and therefore ``agent_framework``) into this module's runtime graph # would pull ``verdicts`` (and therefore ``agent_framework``) into this module's runtime graph
@ -121,7 +142,13 @@ def _canonical_feature_string(features: ProposalFeatures) -> str:
Honesty limit, stated at the site: over a structural tie the cosine ordering is deterministic Honesty limit, stated at the site: over a structural tie the cosine ordering is deterministic
but semantically ARBITRARY, because the shipped ``FakeEmbedder`` is a sha256 projection with no but semantically ARBITRARY, because the shipped ``FakeEmbedder`` is a sha256 projection with no
semantics. Retrieval *quality* arrives only with an injected embedder the seam is the semantics. Retrieval *quality* arrives only with an injected embedder the seam is the
deliverable, not better ranking.""" deliverable, not better ranking.
And the limit is wider than "ties": an arbitrary term does not politely confine itself to
exact ties. It also decides NEAR-ties, and ``SEMANTIC_WEIGHT_DEFAULT`` is the only thing that
bounds how near at weight w it can overturn any structural gap below ``w/(1-w)`` times the
cosine spread. That is why the weight is derived from a measurement rather than chosen for
symmetry, and why raising it is a correctness change, not a taste one."""
return "|".join( return "|".join(
( (
",".join(sorted(features.affected_codes)), ",".join(sorted(features.affected_codes)),