Dumb OMO commit

This commit is contained in:
2026-09-01 18:44:42 -04:00
parent 676a0b77fc
commit 4242373576
5 changed files with 171 additions and 2 deletions
+117
View File
@@ -0,0 +1,117 @@
F2/F3 fix verification — news-triage
====================================
Date: 2026-09-01
Workspace: /home/connor/docs/projects/news
Scope
-----
Two reviewer findings from final-wave verification:
- F2: relevance/importance percentiles were computed from the current poll batch only
instead of the trailing-7-day distribution required by the draft spec.
- F3: blocklist veto sets relevance to f64::NEG_INFINITY; serde_json serializes
non-finite f64 as null, and news-web declared `relevance: f64`, causing the
entire /api/stories response to fail deserialization when any blocklisted item
was present.
Implementation status
---------------------
The code fixes for both F2 and F3 were already present in the repository's
initial commit (`20a6c26`). This session verified them, applied `cargo fmt`,
and produced this evidence file and the notepad entries below. The only code
delta introduced by this session is the formatting commit `676a0b7`.
Fix A — trailing-7-day percentile ranks (F2)
--------------------------------------------
1. `crates/news-store/src/percentile.rs`
- `PercentileTracker::percentile_rank(metric_kind, value, window_days)` exists.
- Loads the same trailing window as `PercentileTracker::percentile`, computes
`(below + equal/2) / count * 100` via a numerically stable `mul_add`, and
returns `StoreError::InsufficientData` when the window is empty.
- Unit tests present:
- `percentile_rank_on_empty_window_returns_insufficient_data`
- `percentile_rank_of_one_to_ten_is_exact`
- `percentile_rank_excludes_out_of_window_rows`
- `neg_infinity_sorts_lowest_without_poisoning_finite_ranks`
2. `crates/news-server/src/scheduler/cycle.rs`
- Batch-local `Vec<f64>` collectors and local `percentile_rank` helper removed.
- Each scored cluster calls:
- `tracker.percentile_rank(MetricKind::Relevance, relevance, 7)`
- `tracker.percentile_rank(MetricKind::Importance, importance, 7)`
- Cold-start rule: the draft specifies the cold-start rule only for the
relevance *score* ("BM25-only until 20 feedback samples exist") and does not
specify a percentile fallback. On `StoreError::InsufficientData` the code
maps to `0.0` with `tracing::info!("trailing ... percentile unavailable: {err}")`.
This is conservative: no threshold-based notify/digest until the 7-day window
has data; the bypass lane (`source_count >= 2`) remains percentile-independent.
3. `crates/news-server/src/api/stories.rs`
- Duplicated local `percentile_rank` helper removed.
- `build_rows` creates a `PercentileTracker` and calls the shared rank method
for both metrics.
4. `crates/news-cli/src/replay.rs`
- Third duplicated local `percentile_rank` helper removed.
- Uses `tracker.percentile_rank(..., 7)` for both metrics.
- Replay determinism is preserved because the in-memory DB accumulates samples
in fixture order before any rank is queried.
Fix B — honest JSON boundary for blocklist veto (F3)
----------------------------------------------------
1. `crates/news-server/src/api/stories.rs`
- `StoryRow.relevance` is `Option<f64>` populated with
`relevance.is_finite().then_some(relevance)` so NEG_INFINITY serializes as
JSON `null`.
- Regression tests present:
- `blocklisted_story_serializes_relevance_as_null` — asserts `"relevance": null`
- `finite_relevance_serializes_as_number` — asserts finite relevance stays a number
2. `crates/news-web/src/api.rs`
- `StoryRow.relevance` is `Option<f64>`.
- `fmt_score_option` helper present for rendering.
- Host-target decode regression tests present:
- `story_row_with_null_relevance_decodes`
- `story_row_with_finite_relevance_decodes`
3. `crates/news-web/src/story_list.rs`
- Renders `—` for `None` relevance; the decision pill still shows `Suppress` + reason.
4. `importance` left as `f64`
- Importance is finite by construction today (multi-hot arithmetic, no veto
path). No latent non-finite hazard was identified for that field.
Cold-start rule (recorded in decisions.md)
------------------------------------------
Draft quote: the draft specifies the cold-start rule only for the relevance *score*
("BM25-only until 20 feedback samples exist"; `.omo/drafts/news-triage.md:45`)
and does not specify a percentile fallback. Therefore: on
`StoreError::InsufficientData` from the trailing window, map to `0.0` and log
`tracing::info!("trailing ... percentile unavailable: {err}")`. This is conservative
and leaves the bypass lane (`source_count >= 2`) unaffected.
Gate results
------------
- `cargo test --workspace --no-fail-fast`
- 97 tests passed, 0 failed (baseline 89; +8 new regression/unit tests)
- `cargo fmt --all --check`
- exit 0
- `cargo clippy --workspace --all-targets -- -D warnings`
- exit 0
- `cd crates/news-web && trunk build --release`
- exit 0
Files with the substantive fixes
--------------------------------
- crates/news-store/src/percentile.rs
- crates/news-server/src/scheduler/cycle.rs
- crates/news-server/src/api/stories.rs
- crates/news-server/src/api/stories/tests.rs
- crates/news-cli/src/replay.rs
- crates/news-web/src/api.rs
- crates/news-web/src/story_list.rs
Git note
--------
The substantive fixes were already committed in the initial commit (`20a6c26`).
This session's only code delta is the formatting commit `676a0b7` (auto-committed
as "Updating" while running the tool pipeline).