0007 the deliberate divergences - CyrilB1531/lodestar GitHub Wiki
Status: accepted Β· Date: 2026-09-20
Every algorithm here is measured against a Python reference and frozen into
tests/oracles/, so parity is the default and a difference is an event. Nine records asked
the same question one member at a time: this member does not answer what the reference
answers β is that allowed? They answered it nine times, and a reader who wanted the rule
read nine records and assembled it.
The nine cases fall into three shapes, and the shape is what decides them.
The reference is wrong in a way it does not state. jellyfish 1.2.1 diverges from the
published Hamming and Jaro definitions on 62 of 1241 corpus pairs, always on combining
marks, emoji or mixed scripts, and the cause is inside a Rust core nobody here can read
(0005). The same library measures a Match Rating codex by its UTF-8 byte length in
three places β the six-character truncation, the length-gap check and the minimum-rating
lookup β so match_rating_codex("δΈ¦δΈδΈδΈ") returns a six-character codex from a
four-character word, longer than the word it truncated (0080). scikit-learn 1.9.0 raises
ValueError: math domain error on mutual_info_score([], []), from an unguarded log(0)
eight frames down, with no parameter guard and no docstring note (0039).
The reference's behaviour is unseeded, or it degrades in silence. sklearn.cluster.KMeans
was observed breaking an exact distance tie toward the second centre in one configuration and
the first in another, inside compiled Cython over a BLAS product (0093). tokenizers 0.23.1
accepts a vocabulary that declares byte_fallback without all 256 <0xXX> pieces and
degrades per symbol to the unknown token β or, with no unk_token, drops the symbol and lets
its neighbours merge across the hole (0063). difflib.SequenceMatcher applies an
autojunk heuristic past 200 elements that is an optimisation of difflib, not a property
of the Ratcliff-Obershelp metric (0006).
The reference refuses, or answers, where the answer is the caller's guarantee. MAPIE 1.5.0
raises when alpha < 1/(n + 1), and under allow_infinite_bounds=True returns a finite
interval whose half-width is the largest calibration score β narrower than the level asked
for, in exactly the regime where nobody would notice (0070). scipy answers (nan, nan) with
a warning on a sample its own guard finds degenerate, which under nan_policy='omit' would
make one input raise or not according to how it arrived (0117).
0023 is in this family for the opposite reason, and is kept in the table below because of
it: BpeTokenizer.Decode used to throw on a byte sequence that is not well-formed UTF-8,
where tokenizers substitutes U+FFFD. Nothing defended the strict decode β it made one-id-at-a-time
decoding unusable for any text outside Latin-1 β so the code was aligned with the reference
and the divergence was withdrawn. That is the axis running the other way.
A divergence from the reference is accepted only when reproducing it would mean reproducing a defect, an unseeded behaviour or a refusal the reference itself does not defend β otherwise the reference wins and the code is aligned with it.
An accidental byte/character mixup, a tie broken inside a compiled kernel, a silent degradation into a wrong token stream and a logarithm of zero are all of that kind. A rule table, a default, a parameter's presence, an exception type and a lossy decode are not: they are what a caller migrating a script depends on, and they are reproduced even where they look wrong read on their own.
Every accepted divergence carries a row in docs/equivalence.md, written in the same commit
as the member. The row states what the reference does and what this does, so a reader meets
the divergence in the mapping table rather than in a stack trace. Where the corpus cannot hold
the case β because the reference produces no value for it β a named test pins it instead, and
that test names this record.
| member | what the reference does | what this does | why the divergence is accepted | record |
|---|---|---|---|---|
Hamming.Distance, Jaro.Similarity, JaroWinkler.Similarity
|
jellyfish 1.2.1 departs from the published definition on 62 of 1241 pairs, all carrying combining marks, emoji or mixed scripts; it is neither a byte comparison nor an NFC effect |
the published definition, over TextElement.Utf16Unit or TextElement.CodePoint; the oracle is generated from an explicit reference of that definition and records the jellyfish divergence count |
an unspecified quirk of a Rust core, reproducible only by copying it; parity holds on every input where jellyfish computes a standard result, pinned by the classic name pairs | 0005 |
RatcliffObershelp.Similarity |
difflib.SequenceMatcher treats an element occupying more than 1% of positions as junk past 200 elements, changing ratio()
|
2Β·M/T over the recursive pairing, discarding nothing; the oracle is frozen with autojunk=False
|
autojunk is an optimisation of difflib, not a property of the metric; identical to the default below 200 elements and to autojunk=False at any length |
0006 |
MutualInformation.Score |
mutual_info_score([], []) raises ValueError: math domain error from log(pi.sum()), with no guard and no note |
returns 0.0, as every sibling agreement metric answers on an empty input |
an unguarded logarithm surfacing, not a designed refusal β it was found by a generator crashing; mutual_info_score is 0.0 on every one-class input, and empty is that shape's boundary |
0039 |
TokenizerJsonLoader.LoadBpe, BpeVocabulary
|
tokenizers 0.23.1 accepts byte_fallback: true with a partial <0xXX> alphabet and degrades per symbol to <unk>, or drops the symbol outright when no unk_token is declared; it accepts any decoder shape |
refuses the load, naming the first missing piece, and reads the decoder of such a file strictly β a bare ByteFallback, or exactly Sequence[Replace, ByteFallback, Fuse, Strip]
|
the degradation is silent and its output is a wrong token stream; refusing beats embeddings that are quietly wrong, and a complete alphabet also makes the reference's own unknown-token ordering bug unreachable rather than something to reproduce | 0063 |
SplitConformal.Quantile at alpha < 1/(n + 1)
|
MAPIE 1.5.0 raises ValueError; under allow_infinite_bounds=True the regressor returns a finite interval whose half-width is the largest calibration score, and the classifier has no such flag |
returns double.PositiveInfinity under either ConformalQuantileRule, carried to (-inf, +inf) by Interval and to the full label set by PredictionSet
|
MAPIE's finite answer under-covers silently, in the one regime where the calibration set is already too small for anyone to notice; a package promising finite-sample coverage cannot ship that as its edge case | 0070 |
MatchRatingApproach.Codex, MatchRatingApproach.Compare
|
jellyfish 1.2.1 measures the codex in UTF-8 bytes for the six-character truncation, the length-gap check and the minimum-rating lookup, so a codex can grow under truncation and two 2-character codices can be refused a rating |
measures string.Length throughout β equal to the character count for every codex either side produces |
a byte/character mixup nothing in the 1977 description states; deriving it would mean reading it out of the reference implementation, which 0149 forbids |
0080 |
KMeans.Fit on an exact tie |
sklearn.cluster.KMeans(algorithm="lloyd") assigned the second centre in one measured configuration and the first in another; neither < nor <= reproduces both |
the lowest-indexed centre, numpy.argmin's rule, implemented as a strict < over the squared distance |
the choice lives in compiled Cython driven by BLAS and cannot be read, only guessed; and the tie is arbitrary in Lloyd's algorithm β a sustained one differs by a relabelling every metric here is invariant to | 0093 |
ShapiroWilk.Test, KruskalWallis.Test under NanPolicy.Omit; ChiSquare.Contingency on a NaN cell |
scipy answers (nan, nan) with a warning on a sample omission left degenerate, and chi2_contingency carries a NaN cell through |
raises ArgumentException: omission is a filter and the family's own guards run afterwards, unchanged; a contingency cell is a count and its marginals are divided by |
answering (nan, nan) only on the path reached through omission would make one degenerate input raise or not according to how it arrived, which is a worse contract than either answer |
0117 |
BpeTokenizer.Decode |
tokenizers decodes lossily β U+FFFD for a byte sequence that is not well-formed β and encodes strictly |
the same: U+FFFD on the way out, a lone surrogate still refused on the way in |
not a divergence β the strict decode was one, and it was withdrawn. It threw on 6 of 6 ids for ζ±δΊ¬ π decoded one at a time, and nothing defended that. The asymmetry is the reference's own, not a policy invented here |
0023 |
- Nine rows in
docs/equivalence.mdcarry these cases. Two are incomplete and are corrected in this lot:jaro_winkler_similarity, which states the Winkler parameters and not the combining-mark divergence its sibling rows state, andmatch_rating_codex, which claims exact parity over 420 words without naming the truncation half of0080β the comparison row names it, the codex row does not. - Four cases cannot be frozen, because the reference produces no value to freeze: MAPIE
raises where
Quantileanswers an infinity,jellyfishhas nothing correct past the byte-safe codex range,tokenizersaccepts every file this loader refuses, andscikit-learnraises on the empty labelling. They are pinned bySplitConformalEdgeTests, by[InlineData]facts inMatchRatingApproachOracleTests, by loader tests, and by amutual_information: nullfixture inclustering_agreement.json. - No frozen case turns on the k-means tie: the two fixtures that hinged on one were replaced with centres the arithmetic decides between.
- The oracle corpora are generated from an explicit reference of the published definition
wherever the library is the thing diverging β
_hamming_reference,_jaro_referenceand_jaro_winkler_referenceintools/generate_oracles.py, anddifflibcalled withautojunk=False. - A change in a reference that closes one of these gaps does not reopen the decision by
itself. The divergence was never a bet on the reference staying wrong; it was a refusal to
freeze something unreadable or indefensible. What surfaces such a change is the
Oracles are reproduciblejob, and what answers it is a new record.