Initial commit -- did OMO do a good job?
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
F1 Plan Compliance Audit — news-triage
|
||||
======================================
|
||||
Auditor: F1 (plan compliance)
|
||||
Repo: /home/connor/docs/projects/news
|
||||
Date: 2026-09-01
|
||||
Verdict: APPROVE
|
||||
|
||||
All required checks were executed and recorded below. Every check passed.
|
||||
|
||||
================================================================================
|
||||
CHECK 1 — CHECKBOX COUNT (todos 1-21, F1-F4 excluded)
|
||||
================================================================================
|
||||
Command:
|
||||
grep -E '^- \[x\] [0-9]+\.|^- \[ \] [0-9]+\.' /home/connor/docs/projects/news/.omo/plans/news-triage.md | wc -l
|
||||
grep -E '^- \[x\] [0-9]+\.' /home/connor/docs/projects/news/.omo/plans/news-triage.md | wc -l
|
||||
grep -E '^- \[ \] [0-9]+\.' /home/connor/docs/projects/news/.omo/plans/news-triage.md | wc -l
|
||||
|
||||
Output:
|
||||
total matching todo lines: 21
|
||||
checked ([x]): 21
|
||||
unchecked ([ ]): 0
|
||||
|
||||
Result: PASS — all 21 implementation todos are checked.
|
||||
|
||||
================================================================================
|
||||
CHECK 2 — FORBIDDEN STRINGS
|
||||
================================================================================
|
||||
Command (crates):
|
||||
grep -riE "liteLLM|llama.cpp|gpt-oss|gemma3" /home/connor/docs/projects/news/crates
|
||||
|
||||
Output:
|
||||
(no output)
|
||||
|
||||
Command (Dockerfile / CI / deploy):
|
||||
grep -riE "liteLLM|llama.cpp|gpt-oss|gemma3" /home/connor/docs/projects/news/Dockerfile /home/connor/docs/projects/news/.gitea/workflows/ci.yml /home/connor/docs/projects/news/deploy/
|
||||
|
||||
Output:
|
||||
(no output)
|
||||
|
||||
Result: PASS — zero forbidden-string matches in code, build, CI, or deploy artifacts.
|
||||
|
||||
================================================================================
|
||||
CHECK 3 — PROTECTED FILES (mtime baseline audit)
|
||||
================================================================================
|
||||
Implementation began ~2026-09-01 13:40 -0400. Any mtime later than the listed
|
||||
baseline is a REJECT. Measured with `stat --format="%n %y"`.
|
||||
|
||||
Command:
|
||||
stat --format="%n %y" /home/connor/compose.yml /home/connor/Caddyfile \
|
||||
/home/connor/data/ntfy/etc/server.yml /home/connor/config/prometheus/prometheus.yml
|
||||
|
||||
Output:
|
||||
/home/connor/compose.yml 2026-09-01 13:31:00.123329809 -0400 (baseline <= 2026-09-01 13:31:00)
|
||||
/home/connor/Caddyfile 2026-08-28 15:51:59.475726235 -0400 (baseline <= 2026-08-28 15:51:59)
|
||||
/home/connor/data/ntfy/etc/server.yml 2026-08-20 17:25:40.623346035 -0400 (baseline <= 2026-08-20 17:25:40)
|
||||
/home/connor/config/prometheus/prometheus.yml 2026-08-25 22:21:00.811095356 -0400 (baseline <= 2026-08-25 22:21:00)
|
||||
|
||||
Result: PASS — all four protected files have mtimes at or before their baselines.
|
||||
No implementer edits detected.
|
||||
|
||||
================================================================================
|
||||
CHECK 4 — MUST-NOT SWEEP
|
||||
================================================================================
|
||||
|
||||
4a. Article-body scraping / full-text extraction / paywall circumvention
|
||||
------------------------------------------------------------------------
|
||||
Commands:
|
||||
grep -riE "scraper\b|full.?article|article.?body|paywall|readability|html2text|trafilatura|goose|newspaper" \
|
||||
/home/connor/docs/projects/news/crates /home/connor/docs/projects/news/Cargo.toml /home/connor/docs/projects/news/Dockerfile
|
||||
grep -riE "scraper crate|scraper::|full.?text|readability" /home/connor/docs/projects/news/crates
|
||||
|
||||
Output:
|
||||
Only benign matches in generated CSS/JS/dist and a "newspaper" ntfy tag:
|
||||
/home/connor/docs/projects/news/crates/news-server/src/notify/ntfy.rs: /// Uses `Priority: 2`, `Markdown: yes`, and `Tags: newspaper`.
|
||||
/home/connor/docs/projects/news/crates/news-server/src/notify/ntfy.rs: .header("Tags", "newspaper")
|
||||
No scraper crate, no readability crate, no full-article fetching code.
|
||||
|
||||
Inspection:
|
||||
Feed ingestion is limited to parsing RSS/Atom (`crates/news-ingest/src/rss_atom.rs`)
|
||||
and the AP news-sitemap (`crates/news-ingest/src/news_sitemap.rs`).
|
||||
The only text indexed for search is title+summary via FTS5
|
||||
(`crates/news-store/src/items.rs`); the schema stores `title`, `summary`, and `link`
|
||||
only (`crates/news-store/migrations/0001_init.sql`).
|
||||
|
||||
Result: PASS — no article-body scraping code beyond feed-provided lead data.
|
||||
|
||||
4b. Backend serving the frontend dist / static SPA files
|
||||
--------------------------------------------------------
|
||||
Command:
|
||||
grep -riE "ServeDir|ServeFile|include_bytes.*index\.html|/srv/dist|actix.files|tower_http.*fs|warp.*fs" \
|
||||
/home/connor/docs/projects/news/crates/news-server
|
||||
|
||||
Output:
|
||||
(no output)
|
||||
|
||||
Inspection:
|
||||
`crates/news-server/src/health.rs` exposes only `/healthz` and `/metrics`.
|
||||
`crates/news-server/src/api/mod.rs` mounts `/api/stories`, `/api/config`, and
|
||||
`/api/feedback`. No static-file handler is registered anywhere in news-server.
|
||||
`Dockerfile` line 81 copies the built frontend to `/srv/dist` with the explicit
|
||||
comment: "Where the updater looks for the frontend. Nothing serves it from inside
|
||||
this container."
|
||||
|
||||
Result: PASS — backend does not serve dist or news-web assets.
|
||||
|
||||
4c. LLM / embedding / GPU / paid-AI API dependencies
|
||||
----------------------------------------------------
|
||||
Commands:
|
||||
grep -riE "\bcandle\b|\bort\b|\bllama\b|\bopenai\b|\banthropic\b|\btiktoken\b|\btokenizers\b|\bembedding\b|\bembeddings\b|\bbert\b|\bsentence.?transformers\b|\bfastembed\b|\bollama\b|\bgpt-4\b|\bgpt4\b|\bclaude\b" \
|
||||
/home/connor/docs/projects/news/crates /home/connor/docs/projects/news/Cargo.toml \
|
||||
/home/connor/docs/projects/news/Dockerfile /home/connor/docs/projects/news/.gitea/workflows/ci.yml \
|
||||
/home/connor/docs/projects/news/deploy
|
||||
|
||||
Output:
|
||||
No matches in source, Cargo.toml, Dockerfile, CI, or deploy files.
|
||||
(The earlier broad regex caught only false positives: the word "importance",
|
||||
"internal", "newspaper", and references in plan documents / node_modules
|
||||
Playwright READMEs, none of which are code dependencies.)
|
||||
|
||||
Inspection of workspace dependencies (`Cargo.toml` lines 26-73):
|
||||
Internal: news-core, news-store, news-ingest, news-server
|
||||
General: serde, thiserror, uuid, chrono, chrono-tz, serde_json, pretty_assertions
|
||||
Persistence: sqlx, tokio
|
||||
Feed polling: reqwest, url, quick-xml
|
||||
Config/logging: toml, tracing, clap, tracing-subscriber
|
||||
HTTP API: axum, tower
|
||||
Metrics: prometheus
|
||||
news-web: leptos, gloo-net, console_error_panic_hook, serde, serde_json
|
||||
|
||||
Result: PASS — no LLM, embedding, GPU, or paid-AI crates anywhere in the build.
|
||||
|
||||
================================================================================
|
||||
CHECK 5 — SCOPE-IN COVERAGE
|
||||
================================================================================
|
||||
The plan's "Must have" list maps to implemented components as follows:
|
||||
|
||||
1. Cargo workspace with 6 crates, edition 2024, pinned toolchain
|
||||
→ /home/connor/docs/projects/news/Cargo.toml (members lines 3-10, workspace.package, lints)
|
||||
→ /home/connor/docs/projects/news/rust-toolchain.toml (channel = "1.89.0", wasm32 target)
|
||||
→ crates/news-core, crates/news-ingest, crates/news-store, crates/news-server, crates/news-web, crates/news-cli
|
||||
|
||||
2. Source registry: Al Jazeera, BBC World, AP sitemap; per-source weight/enable/poll; conditional GET; honest UA; backoff
|
||||
→ crates/news-store/src/sources.rs (seed_default_sources, SourceRepo CRUD, fixed v1 seed IDs)
|
||||
→ crates/news-ingest/src/lib.rs (FeedPoller, PollResult, ETag/If-Modified-Since, Retry-After, HONEST_USER_AGENT)
|
||||
→ crates/news-ingest/src/rss_atom.rs, crates/news-ingest/src/news_sitemap.rs
|
||||
|
||||
3. Story normalization, dedupe, clustering, cross-source corroboration
|
||||
→ crates/news-store/src/items.rs (insert_if_new dedupes on source_id+link)
|
||||
→ crates/news-core/src/clustering.rs (Jaccard title similarity)
|
||||
→ crates/news-store/src/clusters.rs (assign_or_create, distinct-source source_count)
|
||||
|
||||
4. Two-axis non-LLM scoring + trailing-7-day percentile distributions
|
||||
→ crates/news-core/src/scoring/importance.rs (importance_score: prominence + trust + corroboration)
|
||||
→ crates/news-core/src/scoring/relevance.rs (BM25 + NaiveBayes, cold-start at 20 samples)
|
||||
→ crates/news-server/src/scoring.rs (ImportanceScorer, RelevanceScorer wiring)
|
||||
→ crates/news-store/src/percentile.rs (PercentileTracker with MetricKind::Importance/Relevance, 7-day window)
|
||||
|
||||
5. Notification gate: token bucket, quiet hours, adaptive p90 relevance threshold, p99/importance-or-2-source bypass, suppression
|
||||
→ crates/news-server/src/gate.rs (NotificationGate, TokenBucket, GateDecision, SuppressReason)
|
||||
→ bypass ceiling independent of normal budget is configured and tested
|
||||
|
||||
6. ntfy publishing with title/markdown/priority/click/actions + ACL snippet
|
||||
→ crates/news-server/src/notify/ntfy.rs (NtfyPublisher, NotifyLane, action buttons)
|
||||
→ deploy/ntfy-access-snippet.md (exact `ntfy access news-bot news-triage rw` command)
|
||||
|
||||
7. Feedback capture feeding Naive Bayes
|
||||
→ crates/news-server/src/api/feedback.rs (POST /api/feedback)
|
||||
→ crates/news-store/src/feedback.rs (FeedbackRepo persistence)
|
||||
→ crates/news-core/src/scoring/relevance.rs (incremental train)
|
||||
|
||||
8. Daily digest at 07:00 America/Louisville for 75th-90th relevance band
|
||||
→ crates/news-server/src/notify/digest.rs (build_and_send_digest, DIGEST_HOUR=7, BAND_FLOOR=75, BAND_CEILING=90)
|
||||
|
||||
9. Replay harness and --dry-run flag
|
||||
→ crates/news-cli/src/replay.rs (run replay over fixture XML)
|
||||
→ crates/news-cli/src/main.rs (clap subcommand)
|
||||
→ crates/news-server/src/main.rs (--dry-run CLI flag, dry_run tagging in notification_log)
|
||||
|
||||
10. Operator control surface: config.toml + SIGHUP hot-reload + Leptos web UI
|
||||
→ crates/news-server/src/config.rs (NewsConfig, load, reload_once, watch_sighup)
|
||||
→ deploy/README.md instructs operator to create ./config/news/config.toml
|
||||
→ crates/news-web/src/main.rs + app.rs + story_list.rs + config_view.rs + api.rs
|
||||
→ crates/news-server/src/api/stories.rs (GET /api/stories)
|
||||
→ crates/news-server/src/api/config.rs (GET /api/config)
|
||||
→ crates/news-server/src/api/feedback.rs (POST /api/feedback)
|
||||
|
||||
11. Containerized deployment: one image, dist lifted out, /healthz, /metrics, 90-day pruning
|
||||
→ /home/connor/docs/projects/news/Dockerfile (multi-stage web/server/runtime, COPY dist to /srv/dist)
|
||||
→ crates/news-server/src/health.rs (/healthz, /metrics)
|
||||
→ crates/news-server/src/scheduler/loops.rs (retention loop prunes raw_items > 90 days)
|
||||
|
||||
12. CI: fmt, clippy, tests, cargo-machete, cargo-deny, WASM bundle-size budget
|
||||
→ /home/connor/docs/projects/news/.gitea/workflows/ci.yml
|
||||
- check job: cargo fmt --all --check, cargo clippy -D warnings, cargo test --workspace, wasm check
|
||||
- guardrails job: cargo machete, cargo deny check
|
||||
- bundle job: trunk build --release + 1.8MB WASM size budget
|
||||
|
||||
13. Operator-applied snippets for compose/Caddy/ntfy/Prometheus
|
||||
→ deploy/README.md contains compose service block, Caddy LAN-only vhost block,
|
||||
ntfy ACL grant command, Prometheus scrape-config snippet
|
||||
→ deploy/news-update.service + deploy/news-update.timer (systemd system timer)
|
||||
|
||||
14. TDD throughout
|
||||
→ Every scope component listed above has corresponding tests in the same crate
|
||||
(e.g. model tests, scoring property tests, gate tests, API oneshot tests,
|
||||
healthz oneshot tests, digest mock tests). Evidence files task-1 through
|
||||
task-21 are present in .omo/evidence/.
|
||||
|
||||
Result: PASS — every Scope-IN item has a corresponding implemented component.
|
||||
|
||||
================================================================================
|
||||
ACCEPTED DEVIATIONS (NOT FLAGGED)
|
||||
================================================================================
|
||||
- test names flexed to real module paths (per task instructions)
|
||||
- root package.json (runway layout)
|
||||
- percentile_rank helper duplicated in two modules (F2's concern)
|
||||
- NEWS_DATABASE_URL added as env source (required by todo 20's Dockerfile contract)
|
||||
- The runtime `config/news/config.toml` file is operator-applied per deploy/README.md;
|
||||
the loader/hot-reload machinery is fully implemented and tested.
|
||||
|
||||
================================================================================
|
||||
FINAL VERDICT
|
||||
================================================================================
|
||||
All checks pass:
|
||||
[✓] Checkbox count: 21/21 checked
|
||||
[✓] Forbidden strings: zero matches
|
||||
[✓] Protected files: mtimes unchanged
|
||||
[✓] No article-body scraping
|
||||
[✓] No backend static-file serving of dist
|
||||
[✓] No LLM/embedding/GPU/paid-AI dependencies
|
||||
[✓] Scope-IN fully covered
|
||||
|
||||
F1 verdict: APPROVE
|
||||
Reference in New Issue
Block a user