Docs: no README quickstart, 112 missing_docs warnings, and the only Joint example does not compile downstream #77

Closed
opened 2026-09-09 17:57:17 +00:00 by logaritmisk · 1 comment
Owner

The per-item documentation is unusually good — InferenceError, ITERATIONS, UnknownKeys, Joint and converge carry prose with measurements in it. The gaps are structural: what a reader meets first, and what is missing entirely.

1. The README has no quickstart, and is stale on everything recent

Heading order: title → Other implementations (a link dump) → Drift (an expert topic, ~130 lines) → Scored outcomesPredictionWhich match to play nextTodo.

No install line, no "what is this", and no record_winner → converge → current_skill block until line ~226 of 307, buried in a subsection called "Asking about one competitor". The crate publishes to a private registry, so the git host's README is the front door; the good getting-started material exists only in src/lib.rs.

Names appearing zero times in the README: Joint, register, converge_partial, posterior_of, posterior_of_at, expected_variance_reduction, predict_margin, GridTooCoarse, rating(), learning_curve(s), filtered_*, record_draw, ConvergenceOptions, Game, rayon. The strictness of converge — the crate's most opinionated recent decision, with a long rationale in the source — is not mentioned.

rayon appears 0 times; approx appears once, in a ticked Todo box. Feature flags are documented only in lib.rs.

The two canonical examples also disagree. lib.rs writes History::default() and current_skill("alice"); the README writes History::builder().build() and current_skill(&"alice"). Both compile, and nothing says which is idiomatic.

2. #![deny(missing_docs)] is off, and 43.6% of the public surface is undocumented

cargo rustdoc --all-features -- -W missing_docs112 warnings. By file:

File n
error.rs 27 (the enum itself + all 26 variant fields; the variants are documented)
lib.rs 13 (3 modules; BETA MU SIGMA GAMMA P_DRAW EPSILON N01 N00 N_INF; Index)
event.rs 11
storage/competitor_store.rs 10
time_slice.rs 9
convergence.rs 8
game.rs 8
history.rs 8
key_table.rs 7
gaussian.rs 4 (pi tau mu sigma)
outcome.rs 4
competitor.rs 2
event_builder.rs 1

The worst three are all first-contact: History::current_skill — the method the crate's own first example calls — EventBuilder, the type h.event(t) hands you, and Gaussian::mu().

Cost to turn the lint on: ~62 items are mechanical (the 26 InferenceError field docs already exist as prose inside the Display format strings). ~50 need real writing. Roughly a day and a half. Worth doing after #73 removes six types, which takes the 112 well under 90.

Also: history.rs:1438 links [History::member_skills], which is private → a broken link on the published docs.

3. The only Joint example does not compile downstream

history.rs:1279 opens # use smallvec::smallvec;. smallvec is a dependency of the crate but is not re-exported:

error[E0432]: unresolved import `smallvec`
help: if you wanted to use a crate named `smallvec`, use `cargo add smallvec`

The root cause is broader — four public items expose smallvec types:

  • Event::teams: SmallVec<[Team<K>; 4]> (public field)
  • Team::members: SmallVec<[Member<K>; 4]> (public field)
  • Outcome::Ranked(SmallVec<[u32; 4]>) and Outcome::Scored { scores: SmallVec<..> }
  • ConvergenceReport::per_iteration_time: SmallVec<[Duration; 32]>

You can build an Event without the dependency (vec![..].into() coerces) and iterate the timings via Deref. But naming any of these — a helper returning a teams list, a match arm binding ranks and passing it on — requires adding smallvec and keeping its major version in lockstep. A smallvec 2.0 becomes a breaking change here.

Both examples/scored.rs and examples/atp.rs use smallvec! directly, so the crate's own worked examples teach the leaking path. The README's Event literal dodges it with .into_iter().collect() — a third spelling of the same construction.

Fix: pub use smallvec;, or make the fields private behind accessors. Outcome::as_ranks/as_scores are already written and pub(crate) — promoting those two and making the payloads opaque closes it and costs nothing.

4. Game's three constructors have # Errors as their entire doc

game.rs:456, :520, :598 — no summary line, so rustdoc's index renders the error list as the summary. And two doc comments put # Errors before # Preconditions with the error list stranded after, so rustdoc renders an empty Errors section: History::expected_information_gain and History::predict_ranking. Free to fix.

5. Missing "which entry point?" guidance

Game, record_winner, EventBuilder and add_events are presented as peers. Game is listed in "Core types" beside History with no hint that it does not participate in a history at all. A reader has no basis to choose.

Breaks: nothing in 1, 2, 4, 5. Item 3 breaks struct-literal construction of Event/Team if the fields go private.

Found by an API audit, 2026-09-09; the smallvec failure reproduced from a consumer crate.

The per-item documentation is unusually good — `InferenceError`, `ITERATIONS`, `UnknownKeys`, `Joint` and `converge` carry prose with measurements in it. The gaps are structural: what a reader meets first, and what is missing entirely. ## 1. The README has no quickstart, and is stale on everything recent Heading order: title → *Other implementations* (a link dump) → *Drift* (an expert topic, ~130 lines) → *Scored outcomes* → *Prediction* → *Which match to play next* → *Todo*. No install line, no "what is this", and no `record_winner → converge → current_skill` block until **line ~226 of 307**, buried in a subsection called "Asking about one competitor". The crate publishes to a private registry, so the git host's README **is** the front door; the good getting-started material exists only in `src/lib.rs`. Names appearing **zero times** in the README: `Joint`, `register`, `converge_partial`, `posterior_of`, `posterior_of_at`, `expected_variance_reduction`, `predict_margin`, `GridTooCoarse`, `rating()`, `learning_curve(s)`, `filtered_*`, `record_draw`, `ConvergenceOptions`, `Game`, `rayon`. The strictness of `converge` — the crate's most opinionated recent decision, with a long rationale in the source — is not mentioned. `rayon` appears 0 times; `approx` appears once, in a ticked Todo box. Feature flags are documented only in `lib.rs`. **The two canonical examples also disagree.** `lib.rs` writes `History::default()` and `current_skill("alice")`; the README writes `History::builder().build()` and `current_skill(&"alice")`. Both compile, and nothing says which is idiomatic. ## 2. `#![deny(missing_docs)]` is off, and 43.6% of the public surface is undocumented `cargo rustdoc --all-features -- -W missing_docs` → **112 warnings**. By file: | File | n | |---|---| | `error.rs` | 27 (the enum itself + all 26 variant *fields*; the variants are documented) | | `lib.rs` | 13 (3 modules; `BETA` `MU` `SIGMA` `GAMMA` `P_DRAW` `EPSILON` `N01` `N00` `N_INF`; `Index`) | | `event.rs` | 11 | | `storage/competitor_store.rs` | 10 | | `time_slice.rs` | 9 | | `convergence.rs` | 8 | | `game.rs` | 8 | | `history.rs` | 8 | | `key_table.rs` | 7 | | `gaussian.rs` | 4 (`pi` `tau` `mu` `sigma`) | | `outcome.rs` | 4 | | `competitor.rs` | 2 | | `event_builder.rs` | 1 | **The worst three are all first-contact:** `History::current_skill` — the method the crate's own first example calls — `EventBuilder`, the type `h.event(t)` hands you, and `Gaussian::mu()`. Cost to turn the lint on: ~62 items are mechanical (the 26 `InferenceError` field docs already exist as prose inside the `Display` format strings). ~50 need real writing. Roughly a day and a half. Worth doing **after** #73 removes six types, which takes the 112 well under 90. Also: `history.rs:1438` links `[History::member_skills]`, which is private → a broken link on the published docs. ## 3. The only `Joint` example does not compile downstream `history.rs:1279` opens `# use smallvec::smallvec;`. `smallvec` is a dependency of the crate but is **not re-exported**: ``` error[E0432]: unresolved import `smallvec` help: if you wanted to use a crate named `smallvec`, use `cargo add smallvec` ``` The root cause is broader — four public items expose `smallvec` types: - `Event::teams: SmallVec<[Team<K>; 4]>` (public field) - `Team::members: SmallVec<[Member<K>; 4]>` (public field) - `Outcome::Ranked(SmallVec<[u32; 4]>)` and `Outcome::Scored { scores: SmallVec<..> }` - `ConvergenceReport::per_iteration_time: SmallVec<[Duration; 32]>` You can *build* an `Event` without the dependency (`vec![..].into()` coerces) and *iterate* the timings via `Deref`. But naming any of these — a helper returning a teams list, a `match` arm binding `ranks` and passing it on — requires adding `smallvec` and keeping its major version in lockstep. **A `smallvec 2.0` becomes a breaking change here.** Both `examples/scored.rs` and `examples/atp.rs` use `smallvec!` directly, so the crate's own worked examples teach the leaking path. The README's `Event` literal dodges it with `.into_iter().collect()` — a third spelling of the same construction. **Fix:** `pub use smallvec;`, or make the fields private behind accessors. `Outcome::as_ranks`/`as_scores` are already written and `pub(crate)` — promoting those two and making the payloads opaque closes it and costs nothing. ## 4. `Game`'s three constructors have `# Errors` as their entire doc `game.rs:456`, `:520`, `:598` — no summary line, so rustdoc's index renders the error list as the summary. And two doc comments put `# Errors` **before** `# Preconditions` with the error list stranded after, so rustdoc renders an empty **Errors** section: `History::expected_information_gain` and `History::predict_ranking`. Free to fix. ## 5. Missing "which entry point?" guidance `Game`, `record_winner`, `EventBuilder` and `add_events` are presented as peers. `Game` is listed in "Core types" beside `History` with no hint that it does not participate in a history at all. A reader has no basis to choose. **Breaks:** nothing in 1, 2, 4, 5. Item 3 breaks struct-literal construction of `Event`/`Team` if the fields go private. Found by an API audit, 2026-09-09; the `smallvec` failure reproduced from a consumer crate.
logaritmisk added the docs label 2026-09-09 17:58:30 +00:00
Author
Owner

Closed by 31564b7 (merged as 251211f). All five items.

1. README

Restructured: what the crate is → install (with both feature flags) → quickstart (record_winnerconvergecurrent_skill, then learning_curve and current_skills) → teams/rankings/draws → a which entry point? table → the converge-is-strict rationale. The link dump moved to the bottom, and the all-ticked Todo list was retired in favour of a short Status paragraph pointing at the tracker.

The two canonical examples disagreed, as reported. They now use one spelling: History::default() and current_skill("alice").

Five new blocks are doctested, taking the suite from 19 to 25.

2. #![deny(missing_docs)] is on

80 items at the start (112 in the audit; #73's removals accounted for the difference), now zero, and the lint is enabled so the next undocumented item is a build failure rather than a warning nobody reads.

Several of the new docs are measurements, not readings:

  • Outcome::Ranked — ranks are used ordinally, so [0, 1, 2] and [0, 5, 90] are the same observation. Measured: bit-identical posteriors for both. Contrasted explicitly with Scored, where the literal adjacent difference is the evidence.
  • OwnedGame::log_evidence — two identically-rated competitors give exactly ln(0.5). Written as a doctest, so the claim runs rather than being asserted from reading ln_sf.
  • Member::weight — zero and negative are deliberately accepted; only non-finite is InvalidParameter. Measured.
  • ConvergenceReport::final_step is (|Δmu|, |Δsigma|) in skill units, not natural parameters. That one had to be traced through Gaussian::delta; the surrounding pi/tau vocabulary makes the wrong reading the natural one.
  • GameOptions::score_sigma rejects non-positive and NaN but accepts +inf — the doc says what the guard says.
  • Event.time — drift is driven by a competitor's consecutive appearances, not by the gap between slices.

The [History::member_skills] link is on a private method, so it never rendered publicly; changed to plain text anyway.

3. pub use smallvec;

The Joint example's unresolved import reproduced. Re-exported rather than making the fields private: the types are already in the public API, so the major version is already a breaking-change vector — re-exporting only makes it usable, and a consumer takes this crate's version instead of pinning a matching one. examples/scored.rs and examples/atp.rs now import through the re-export, so the crate's own examples teach the path that works downstream.

The opaque-payload alternative (Outcome::as_ranks/as_scores promoted, fields private) is still the cleaner end state and is a breaking change worth its own issue if you want it.

4 and 5

Game's constructors have summary lines; the Game type itself now says plainly that it does not participate in a History and that a hand-chained sequence of Games is a forward-only filter, not the same answer. The README's entry-point table covers the same ground for a reader who never opens the docs.

The two empty # Errors sections had already been fixed by the earlier api/cleanup work — see #78 for the two doc defects in that list that were still real.

Closed by 31564b7 (merged as 251211f). All five items. ## 1. README Restructured: what the crate is → install (with both feature flags) → quickstart (`record_winner` → `converge` → `current_skill`, then `learning_curve` and `current_skills`) → teams/rankings/draws → a **which entry point?** table → the `converge`-is-strict rationale. The link dump moved to the bottom, and the all-ticked Todo list was retired in favour of a short Status paragraph pointing at the tracker. The two canonical examples disagreed, as reported. They now use one spelling: `History::default()` and `current_skill("alice")`. Five new blocks are doctested, taking the suite from 19 to **25**. ## 2. `#![deny(missing_docs)]` is on 80 items at the start (112 in the audit; #73's removals accounted for the difference), now zero, and the lint is enabled so the next undocumented item is a build failure rather than a warning nobody reads. Several of the new docs are measurements, not readings: - `Outcome::Ranked` — ranks are used **ordinally**, so `[0, 1, 2]` and `[0, 5, 90]` are the same observation. Measured: bit-identical posteriors for both. Contrasted explicitly with `Scored`, where the literal adjacent difference *is* the evidence. - `OwnedGame::log_evidence` — two identically-rated competitors give exactly `ln(0.5)`. Written as a doctest, so the claim runs rather than being asserted from reading `ln_sf`. - `Member::weight` — zero and negative are deliberately accepted; only non-finite is `InvalidParameter`. Measured. - `ConvergenceReport::final_step` is `(|Δmu|, |Δsigma|)` in **skill units, not natural parameters**. That one had to be traced through `Gaussian::delta`; the surrounding `pi`/`tau` vocabulary makes the wrong reading the natural one. - `GameOptions::score_sigma` rejects non-positive and NaN but **accepts `+inf`** — the doc says what the guard says. - `Event.time` — drift is driven by a competitor's *consecutive appearances*, not by the gap between slices. The `[History::member_skills]` link is on a private method, so it never rendered publicly; changed to plain text anyway. ## 3. `pub use smallvec;` The `Joint` example's `unresolved import` reproduced. Re-exported rather than making the fields private: the types are already in the public API, so the major version is already a breaking-change vector — re-exporting only makes it usable, and a consumer takes this crate's version instead of pinning a matching one. `examples/scored.rs` and `examples/atp.rs` now import through the re-export, so the crate's own examples teach the path that works downstream. The opaque-payload alternative (`Outcome::as_ranks`/`as_scores` promoted, fields private) is still the cleaner end state and is a breaking change worth its own issue if you want it. ## 4 and 5 `Game`'s constructors have summary lines; the `Game` type itself now says plainly that it does not participate in a `History` and that a hand-chained sequence of `Game`s is a forward-only filter, not the same answer. The README's entry-point table covers the same ground for a reader who never opens the docs. The two empty `# Errors` sections had already been fixed by the earlier `api/cleanup` work — see #78 for the two doc defects in that list that were still real.
Sign in to join this conversation.