TimeSlice is the worst: new, add_events, iteration, get_composition, get_results are all pub on a type you can only build standalone and never feed back. TimeSlice::add_events takes &CompetitorStore and Vec<EventKind> — a public method that runs engine work into a void. EventKind is also a near-duplicate of Outcome (same Ranked/Scored variant names, different payloads), which is a second way to confuse a reader.
Competitor publicly exposes raw temporal EP state (rating, message, last_time) with no reachable consumer, and both its methods are pub(crate) — a public type with public fields and no behaviour.
The whole cluster's sole external consumer in the repo is benches/batch.rs. No test, example, README block or doc comment touches any of them. A benchmark harness is dictating the crate's public surface; moving that bench in-crate frees all six.
Index is the interesting one
intern and lookup return it, and no reachable public API accepts one. KeyTable::key(idx), CompetitorStore::get(idx) and SkillStore::* all take Index — and none is obtainable from a History. key_table.rs:20 states the intent:
Power users can promote &K to Index via get_or_create and skip the lookup on subsequent hot-path calls.
That is not achievable through the public API. intern and lookup have no doc comments of their own. So Index is a handle with nowhere to go.
Either make it pay — current_skill_at(Index), learning_curve_at(Index), predict_* accepting &[&[Index]] — or make Indexpub(crate) and drop intern/lookup. As it stands it is surface with no purpose, and it also shadows std::ops::Index, which CompetitorStore literally implements, so use trueskill_tt::* alongside use std::ops::* collides. If it stays, CompetitorId is the better name and would leave the crate with exactly two words for the entity: K (user key) and CompetitorId (interned handle).
Also dead
N01 — zero references in the entire repository, including inside the crate.
N00, N_INF — internal EP identities, cross-module inside src/ only. See #71.
TimeSlice::get_composition / get_results — pub, and their only callers anywhere are #[cfg(test)] code in src/history.rs. They are also the crate's only two C-GETTER violations, which disappear with the type.
Gaussian::damp_natural — pub, called only from src/factor/.
Confirmed not leaking: SkillStore (pub(crate) use), Matrix (never exported), ReadmeDoctests (#[cfg(doctest)], unnameable by design).
Fix
Drop all six from lib.rs's export list; pub mod storage → pub(crate); downgrade the pub fns in time_slice.rs and key_table.rs; move benches/batch.rs's Batch::iteration measurement in-crate.
src/storage/mod.rs also violates the project's own "no mod.rs" convention while we are here.
Breaks: anyone naming these — but since none is obtainable from a History, there is nothing they could have been doing with them.
Found by an API audit, 2026-09-09; construction reproduced independently.
| Type | Obtainable from `History`? | Named by any reachable public signature? |
|---|---|---|
| `TimeSlice<T>` | no — `History::time_slices` is `pub(crate)` | no |
| `EventKind` | no | only by `TimeSlice::add_events` |
| `KeyTable<K>` | no — `History::keys` is private | no |
| `storage::CompetitorStore` | no — `History::agents` is `pub(crate)` | no |
| `Competitor<T, D>` | no — only via `CompetitorStore` | no |
| `Index` | **yes** — `intern`/`lookup` return it | **nothing public consumes one** |
Verified by constructing each from a consumer crate. This compiles and runs, and can never be connected to anything:
```rust
let ts: TimeSlice<i64> = TimeSlice::new(1, 0.0, ConvergenceOptions::default());
println!("{:?}", ts.get_composition()); // []
let cs: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
let mut kt: KeyTable<&'static str> = KeyTable::new();
```
`TimeSlice` is the worst: `new`, `add_events`, `iteration`, `get_composition`, `get_results` are all `pub` on a type you can only build standalone and never feed back. `TimeSlice::add_events` takes `&CompetitorStore` and `Vec<EventKind>` — a public method that runs engine work into a void. `EventKind` is also a near-duplicate of `Outcome` (same `Ranked`/`Scored` variant names, different payloads), which is a second way to confuse a reader.
`Competitor` publicly exposes raw temporal EP state (`rating`, `message`, `last_time`) with no reachable consumer, and both its methods are `pub(crate)` — a public type with public fields and no behaviour.
**The whole cluster's sole external consumer in the repo is `benches/batch.rs`.** No test, example, README block or doc comment touches any of them. A benchmark harness is dictating the crate's public surface; moving that bench in-crate frees all six.
## `Index` is the interesting one
`intern` and `lookup` return it, and **no reachable public API accepts one**. `KeyTable::key(idx)`, `CompetitorStore::get(idx)` and `SkillStore::*` all take `Index` — and none is obtainable from a `History`. `key_table.rs:20` states the intent:
> Power users can promote `&K` to `Index` via `get_or_create` and skip the lookup on subsequent hot-path calls.
That is not achievable through the public API. `intern` and `lookup` have no doc comments of their own. So `Index` is a handle with nowhere to go.
Either make it pay — `current_skill_at(Index)`, `learning_curve_at(Index)`, `predict_*` accepting `&[&[Index]]` — or make `Index` `pub(crate)` and drop `intern`/`lookup`. As it stands it is surface with no purpose, and it also shadows `std::ops::Index`, which `CompetitorStore` literally implements, so `use trueskill_tt::*` alongside `use std::ops::*` collides. If it stays, `CompetitorId` is the better name and would leave the crate with exactly two words for the entity: `K` (user key) and `CompetitorId` (interned handle).
## Also dead
- `N01` — **zero references in the entire repository**, including inside the crate.
- `N00`, `N_INF` — internal EP identities, cross-module inside `src/` only. See #71.
- `TimeSlice::get_composition` / `get_results` — `pub`, and their only callers anywhere are `#[cfg(test)]` code in `src/history.rs`. They are also the crate's only two C-GETTER violations, which disappear with the type.
- `Gaussian::damp_natural` — `pub`, called only from `src/factor/`.
Confirmed **not** leaking: `SkillStore` (`pub(crate) use`), `Matrix` (never exported), `ReadmeDoctests` (`#[cfg(doctest)]`, unnameable by design).
## Fix
Drop all six from `lib.rs`'s export list; `pub mod storage` → `pub(crate)`; downgrade the `pub fn`s in `time_slice.rs` and `key_table.rs`; move `benches/batch.rs`'s `Batch::iteration` measurement in-crate.
`src/storage/mod.rs` also violates the project's own "no `mod.rs`" convention while we are here.
**Breaks:** anyone naming these — but since none is obtainable from a `History`, there is nothing they could have been doing with them.
Found by an API audit, 2026-09-09; construction reproduced independently.
Most of this is now done; one item is left and it is a design call, not a cleanup.
Already gone (the api/cleanup work, which moved benches/batch.rs onto the public API and so freed the whole cluster): TimeSlice, EventKind, KeyTable, storage::CompetitorStore, Competitor are no longer exported — time_slice, key_table, competitor are private modules and storage is pub(crate). N01 is deleted. get_composition / get_results are #[cfg(test)].
Gaussian::damp_natural → pub(crate). This one was a genuine leak, not just a stale export: gaussian is a pub mod, so an EP damping internal called only from src/factor/ was reachable from outside the crate.
The stray pub fns in time_slice.rs, key_table.rs and matrix.rs → pub(crate), so their visibility states what it means rather than relying on the module happening to be private.
Left open: Index. Still pub, still returned by intern/lookup, still accepted by nothing public. The fork the issue lays out is real and I am not picking it unilaterally:
Make it pay — current_skill_at(Index), learning_curve_at(Index), predict_* over &[&[Index]]. Delivers the hot-path story key_table.rs:20 already claims.
Retire it — Index becomes pub(crate), intern/lookup go. Free, and removes the collision with std::ops::Index that CompetitorStore literally implements.
If (1), CompetitorId is the better name and leaves exactly two words for the entity: K and CompetitorId.
Most of this is now done; one item is left and it is a design call, not a cleanup.
**Already gone** (the `api/cleanup` work, which moved `benches/batch.rs` onto the public API and so freed the whole cluster): `TimeSlice`, `EventKind`, `KeyTable`, `storage::CompetitorStore`, `Competitor` are no longer exported — `time_slice`, `key_table`, `competitor` are private modules and `storage` is `pub(crate)`. `N01` is deleted. `get_composition` / `get_results` are `#[cfg(test)]`.
**Done in e4d6dc4** (merged as c3d1afe):
- `Gaussian::damp_natural` → `pub(crate)`. This one was a genuine leak, not just a stale export: `gaussian` is a `pub mod`, so an EP damping internal called only from `src/factor/` was reachable from outside the crate.
- The stray `pub fn`s in `time_slice.rs`, `key_table.rs` and `matrix.rs` → `pub(crate)`, so their visibility states what it means rather than relying on the module happening to be private.
- `storage/mod.rs` → `storage.rs`, `factor/mod.rs` → `factor.rs`.
**Left open: `Index`.** Still `pub`, still returned by `intern`/`lookup`, still accepted by nothing public. The fork the issue lays out is real and I am not picking it unilaterally:
1. **Make it pay** — `current_skill_at(Index)`, `learning_curve_at(Index)`, `predict_*` over `&[&[Index]]`. Delivers the hot-path story `key_table.rs:20` already claims.
2. **Retire it** — `Index` becomes `pub(crate)`, `intern`/`lookup` go. Free, and removes the collision with `std::ops::Index` that `CompetitorStore` literally implements.
If (1), `CompetitorId` is the better name and leaves exactly two words for the entity: `K` and `CompetitorId`.
Option 2 — retired. faa25fb (merged as 6e2ce69). This closes the issue.
Index, History::intern and History::lookup are all pub(crate) or gone. intern stays internal because ingestion needs it; lookup is deleted outright, since current_skill, rating and learning_curve already answer "does this history know this key" and (since #72) all three take a borrowed key.
The three tests that used them were asserting things a caller cannot observe, which is itself the argument for the removal:
comparing two opaque indices for inequality → comparing the two posteriors, and asserting the winner is ahead
intern_is_idempotent → a_repeated_key_is_one_competitor: a key in two events yields one competitor with a two-point learning curve, the observable form of the same claim
Also cleaned up in passing: UnknownKey's doc linked crate::Index (now a private-item link warning), and three # Preconditions blocks plus the README told readers to "pre-filter with lookup or current_skill". They say current_skill now.
The whole issue is done — the five unreachable types went with the api/cleanup work, Gaussian::damp_natural and the stray pub fns in private modules were tightened in e4d6dc4, and mod.rs is gone from storage/ and factor/.
Option 2 — retired. faa25fb (merged as 6e2ce69). This closes the issue.
`Index`, `History::intern` and `History::lookup` are all `pub(crate)` or gone. `intern` stays internal because ingestion needs it; `lookup` is deleted outright, since `current_skill`, `rating` and `learning_curve` already answer "does this history know this key" and (since #72) all three take a borrowed key.
The three tests that used them were asserting things a caller cannot observe, which is itself the argument for the removal:
- comparing two opaque indices for inequality → comparing the two posteriors, and asserting the winner is ahead
- `intern_is_idempotent` → `a_repeated_key_is_one_competitor`: a key in two events yields one competitor with a two-point learning curve, the observable form of the same claim
- `lookup_returns_none_for_missing` → `an_unknown_key_is_unknown`
Also cleaned up in passing: `UnknownKey`'s doc linked `crate::Index` (now a private-item link warning), and three `# Preconditions` blocks plus the README told readers to "pre-filter with `lookup` or `current_skill`". They say `current_skill` now.
The whole issue is done — the five unreachable types went with the `api/cleanup` work, `Gaussian::damp_natural` and the stray `pub fn`s in private modules were tightened in e4d6dc4, and `mod.rs` is gone from `storage/` and `factor/`.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
History?TimeSlice<T>History::time_slicesispub(crate)EventKindTimeSlice::add_eventsKeyTable<K>History::keysis privatestorage::CompetitorStoreHistory::agentsispub(crate)Competitor<T, D>CompetitorStoreIndexintern/lookupreturn itVerified by constructing each from a consumer crate. This compiles and runs, and can never be connected to anything:
TimeSliceis the worst:new,add_events,iteration,get_composition,get_resultsare allpubon a type you can only build standalone and never feed back.TimeSlice::add_eventstakes&CompetitorStoreandVec<EventKind>— a public method that runs engine work into a void.EventKindis also a near-duplicate ofOutcome(sameRanked/Scoredvariant names, different payloads), which is a second way to confuse a reader.Competitorpublicly exposes raw temporal EP state (rating,message,last_time) with no reachable consumer, and both its methods arepub(crate)— a public type with public fields and no behaviour.The whole cluster's sole external consumer in the repo is
benches/batch.rs. No test, example, README block or doc comment touches any of them. A benchmark harness is dictating the crate's public surface; moving that bench in-crate frees all six.Indexis the interesting oneinternandlookupreturn it, and no reachable public API accepts one.KeyTable::key(idx),CompetitorStore::get(idx)andSkillStore::*all takeIndex— and none is obtainable from aHistory.key_table.rs:20states the intent:That is not achievable through the public API.
internandlookuphave no doc comments of their own. SoIndexis a handle with nowhere to go.Either make it pay —
current_skill_at(Index),learning_curve_at(Index),predict_*accepting&[&[Index]]— or makeIndexpub(crate)and dropintern/lookup. As it stands it is surface with no purpose, and it also shadowsstd::ops::Index, whichCompetitorStoreliterally implements, souse trueskill_tt::*alongsideuse std::ops::*collides. If it stays,CompetitorIdis the better name and would leave the crate with exactly two words for the entity:K(user key) andCompetitorId(interned handle).Also dead
N01— zero references in the entire repository, including inside the crate.N00,N_INF— internal EP identities, cross-module insidesrc/only. See #71.TimeSlice::get_composition/get_results—pub, and their only callers anywhere are#[cfg(test)]code insrc/history.rs. They are also the crate's only two C-GETTER violations, which disappear with the type.Gaussian::damp_natural—pub, called only fromsrc/factor/.Confirmed not leaking:
SkillStore(pub(crate) use),Matrix(never exported),ReadmeDoctests(#[cfg(doctest)], unnameable by design).Fix
Drop all six from
lib.rs's export list;pub mod storage→pub(crate); downgrade thepub fns intime_slice.rsandkey_table.rs; movebenches/batch.rs'sBatch::iterationmeasurement in-crate.src/storage/mod.rsalso violates the project's own "nomod.rs" convention while we are here.Breaks: anyone naming these — but since none is obtainable from a
History, there is nothing they could have been doing with them.Found by an API audit, 2026-09-09; construction reproduced independently.
Most of this is now done; one item is left and it is a design call, not a cleanup.
Already gone (the
api/cleanupwork, which movedbenches/batch.rsonto the public API and so freed the whole cluster):TimeSlice,EventKind,KeyTable,storage::CompetitorStore,Competitorare no longer exported —time_slice,key_table,competitorare private modules andstorageispub(crate).N01is deleted.get_composition/get_resultsare#[cfg(test)].Done in
e4d6dc4(merged asc3d1afe):Gaussian::damp_natural→pub(crate). This one was a genuine leak, not just a stale export:gaussianis apub mod, so an EP damping internal called only fromsrc/factor/was reachable from outside the crate.pub fns intime_slice.rs,key_table.rsandmatrix.rs→pub(crate), so their visibility states what it means rather than relying on the module happening to be private.storage/mod.rs→storage.rs,factor/mod.rs→factor.rs.Left open:
Index. Stillpub, still returned byintern/lookup, still accepted by nothing public. The fork the issue lays out is real and I am not picking it unilaterally:current_skill_at(Index),learning_curve_at(Index),predict_*over&[&[Index]]. Delivers the hot-path storykey_table.rs:20already claims.Indexbecomespub(crate),intern/lookupgo. Free, and removes the collision withstd::ops::IndexthatCompetitorStoreliterally implements.If (1),
CompetitorIdis the better name and leaves exactly two words for the entity:KandCompetitorId.Option 2 — retired.
faa25fb(merged as6e2ce69). This closes the issue.Index,History::internandHistory::lookupare allpub(crate)or gone.internstays internal because ingestion needs it;lookupis deleted outright, sincecurrent_skill,ratingandlearning_curvealready answer "does this history know this key" and (since #72) all three take a borrowed key.The three tests that used them were asserting things a caller cannot observe, which is itself the argument for the removal:
intern_is_idempotent→a_repeated_key_is_one_competitor: a key in two events yields one competitor with a two-point learning curve, the observable form of the same claimlookup_returns_none_for_missing→an_unknown_key_is_unknownAlso cleaned up in passing:
UnknownKey's doc linkedcrate::Index(now a private-item link warning), and three# Preconditionsblocks plus the README told readers to "pre-filter withlookuporcurrent_skill". They saycurrent_skillnow.The whole issue is done — the five unreachable types went with the
api/cleanupwork,Gaussian::damp_naturaland the straypub fns in private modules were tightened ine4d6dc4, andmod.rsis gone fromstorage/andfactor/.