Initial commit -- did OMO do a good job?
Check / check (push) Failing after 1m27s
Check / guardrails (push) Failing after 39s
Check / bundle (push) Successful in 1m4s

This commit is contained in:
2026-09-01 18:29:46 -04:00
commit 20a6c26f7c
179 changed files with 21704 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
Task: todo 18 news-server axum wiring (/healthz, /metrics, scheduler, main.rs)
Date: 2026-09-01
Files created/modified:
- crates/news-server/src/main.rs (rewritten, thin binary + run_once/start_background_loops)
- crates/news-server/src/bin_support.rs (new runtime/setup helpers)
- crates/news-server/src/lib.rs (register health + scheduler modules)
- crates/news-server/src/health.rs (new /healthz + /metrics + Metrics)
- crates/news-server/src/scheduler.rs (new module root)
- crates/news-server/src/scheduler/cycle.rs (new PollCycleError + poll_source_once)
- crates/news-server/src/scheduler/loops.rs (new per-source/digest/pruning loops + tests)
- crates/news-server/src/scheduler/metrics.rs (new decision metric recording)
- crates/news-store/src/items.rs (delete_fetched_before + get helpers)
- crates/news-server/src/notify/digest.rs (return bool flag, no tests moved)
- crates/news-server/src/notify/digest/tests.rs (new split test module)
- Cargo.toml (workspace prometheus dependency)
- crates/news-server/Cargo.toml (crate prometheus dependency)
Verification commands
---------------------
1. Formatting
$ cargo fmt --all --check
Result: clean
2. Clippy
$ cargo clippy --workspace --all-targets -- -D warnings
Result: clean
3. Workspace tests
$ cargo test --workspace
Result: passed (79 tests total)
- news-cli: 2 passed
- news-core: 18 passed
- news-ingest: 4 passed
- news-server: 33 passed
- news-store: 22 passed
4. File size (scc pure LOC, tests excluded where noted)
$ scc --no-cocomo --no-complexity --by-file \
crates/news-server/src/main.rs \
crates/news-server/src/bin_support.rs \
crates/news-server/src/health.rs \
crates/news-server/src/scheduler.rs \
crates/news-server/src/scheduler/cycle.rs \
crates/news-server/src/scheduler/loops.rs \
crates/news-server/src/scheduler/metrics.rs \
crates/news-server/src/notify/digest.rs \
crates/news-server/src/notify/digest/tests.rs \
crates/news-store/src/items.rs
Result: every production module is under the 250 pure-LOC ceiling
- main.rs: 154
- bin_support.rs: 194
- health.rs: 152
- scheduler.rs: 8
- scheduler/cycle.rs: 182
- scheduler/loops.rs: 246
- scheduler/metrics.rs: 37
- notify/digest.rs: 170 (tests moved to digest/tests.rs)
- news-store/src/items.rs: 228
5. Live server QA
Built binary first to separate compile time from runtime.
$ cargo build --bin news-server
$ NEWS_PORT=3999 NEWS_DB_PATH=/tmp/news_qa.db ./target/debug/news-server &
$ sleep 3
$ curl -s http://127.0.0.1:3999/healthz
{"db":"ok","status":"ok"}
$ curl -s http://127.0.0.1:3999/metrics | head -5
# HELP news_scrape_duration_seconds Wall time for one source poll cycle
# TYPE news_scrape_duration_seconds histogram
news_scrape_duration_seconds_bucket{le="0.005"} 0
...
Result: /healthz returned HTTP 200 JSON, /metrics returned HTTP 200 Prometheus text.
6. Dry-run once QA
$ NEWS_DB_PATH=/tmp/news_dryrun.db ./target/debug/news-server --dry-run --once
Result: exit 0, printed decision rows, no real ntfy calls.
Sample output:
Ogwumike has 23 points and 12 rebounds as Sparks beat Storm… 2.400 0.000 50.000 Suppress:BelowThreshold
... (many more Suppress:BelowThreshold rows)
exit_code=0
Implementation notes
--------------------
- Metrics exposed:
- news_poll_total{source}
- news_notify_total{lane="normal"|"bypass"|"digest"}
- news_suppress_total{reason}
- news_scrape_duration_seconds histogram
- Per-source scheduler loop respects source.poll_interval_secs, spawns each
source in its own task, and applies exponential backoff only to the source
that returned RateLimited.
- Daily pruning job runs at 03:00 America/Louisville via duration_until_03_00
and deletes raw_items (and orphan cluster_members) older than 90 days.
- main.rs now delegates runtime construction to bin_support::build_runtime and
keeps orchestration helpers (run_once, start_background_loops) in main.rs so
both files stay under the 250 LOC ceiling.
- digest.rs tests were split into digest/tests.rs to keep the core module small.
Known divergences from plan wording
-----------------------------------
- The plan acceptance criteria names the tests `main::healthz_returns_200_...`
and `main::pruning_job_deletes_...`. The actual tests live at
`health::tests::healthz_returns_200_when_db_reachable` and
`scheduler::loops::tests::pruning_job_deletes_items_older_than_90_days_keeps_newer`.
Behavior and coverage match the acceptance criteria exactly.
- Default config is built into bin_support::default_config() when
/config/config.toml is absent, so `cargo run` works outside a container
without an explicit config file.