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
+359
View File
@@ -0,0 +1,359 @@
F4 Scope-Fidelity Review — news-triage
Reviewer: Sisyphus-Junior (F4)
Date: 2026-09-01
Project: /home/connor/docs/projects/news
Standard: Every direct dependency, route, and config key must trace to a specific plan todo; every Scope-OUT bullet must be confirmed unviolated.
================================================================================
FINAL VERDICT: APPROVE
================================================================================
All required traces succeed and all eight Scope-OUT bullets are unviolated.
No unexplained direct dependency, no extra route, and no extra config key was
found. Minor accepted deviations (NEWS_DATABASE_URL env support, root
package.json, --once dry-run helper) are already recorded in the project
notepads/ledger and are not flagged.
================================================================================
CHECK 1 — DIRECT-DEPENDENCY TRACE
================================================================================
Command:
$ cargo tree --workspace -e normal --depth 1
Output (direct dependencies only):
news-cli v0.1.0
├── chrono v0.4.45
├── chrono-tz v0.10.4
├── clap v4.6.6
├── news-core v0.1.0
├── news-ingest v0.1.0
├── news-server v0.1.0
├── news-store v0.1.0
├── serde v1.0.229
├── sqlx v0.8.6
├── thiserror v2.0.20
├── tokio v1.53.1
├── toml v0.8.23
└── uuid v1.26.0
news-core v0.1.0
(no extra direct deps beyond workspace deps)
news-ingest v0.1.0
(no extra direct deps beyond workspace/internal deps)
[dev-dependencies: wiremock]
news-server v0.1.0
(no extra direct deps beyond workspace/internal deps)
[dev-dependencies: tower, tracing-test, tempfile, wiremock]
news-store v0.1.0
(no extra direct deps beyond workspace/internal deps)
news-web v0.1.0
├── console_error_panic_hook v0.1.7
├── gloo-net v0.6.0
├── leptos v0.8.20
├── serde v1.0.229
└── serde_json v1.0.151
Workspace / root direct dependencies:
Internal: news-core, news-store, news-ingest, news-server
serde, thiserror, uuid, chrono, chrono-tz, serde_json, pretty_assertions,
sqlx, tokio, reqwest, url, quick-xml, toml, tracing, tracing-subscriber,
clap, tracing-test, tempfile, axum, tower, prometheus
Root package.json devDependencies:
@playwright/test, @tailwindcss/cli, tailwindcss
--------------------------------------------------------------------------------
Traceability table — every direct dependency maps to a todo
--------------------------------------------------------------------------------
Dependency Justifying todo / evidence
-------------------------- ----------------------------------------------------
serde Todo 1 (workspace skeleton, serde derive), Todo 2
(model structs), Todo 14 (NewsConfig). Used in
every crate.
thiserror Todo 1 (workspace skeleton). Error types in all
crates.
uuid Todo 2 (domain ids). Used in news-core, news-store,
news-ingest, news-server, news-cli.
chrono Todo 2 (timestamps). Used throughout.
chrono-tz Todo 14/16 (quiet hours America/Louisville, digest
07:00 Louisville). Also news-cli replay uses the
gate (todo 17). Recorded as accepted deviation.
serde_json Todo 2 (round-trip tests), Todo 13 (feedback JSON),
Todo 19 (frontend JSON). Also workspace tests.
pretty_assertions Todo 1 ("JSON round-trip assertions + readable
diffs").
sqlx Todo 5 (SQLite schema/migrations). Used in
news-store, news-server, news-cli.
tokio Todo 5 (async runtime), Todo 18 (scheduler/signal).
reqwest Todo 7 (feed polling HTTP client), Todo 15 (ntfy
publisher HTTP POST).
url Todo 7 (reqwest), Todo 15 (NtfyPublisher base_url
is a Url).
quick-xml Todo 7 (RSS/Atom + news-sitemap parsing).
toml Todo 14 (TOML config loader), Todo 17 (replay CLI
may use config). Also news-cli.
tracing Todo 14 (SIGHUP reload logging), Todo 18
(scheduler logging).
tracing-subscriber Todo 18 (binary logging init).
clap Todo 17 (--dry-run server flag, news-cli replay
subcommand), Todo 18 (server CLI).
tracing-test Todo 14 (capture tracing logs in tests). Dev-only.
tempfile Todo 14 (temp config fixtures). Dev-only.
axum Todo 13 (POST /api/feedback), Todo 18 (/healthz,
/metrics), Todo 19a (/api/stories, /api/config).
tower Todo 13 (tower::ServiceExt::oneshot in-process
tests). Dev-only in news-server.
prometheus Todo 18 (Prometheus text exposition for /metrics).
console_error_panic_hook Todo 19 (Leptos 0.8 CSR app, matching runway-web
toolchain; runway-web Cargo.toml also lists this
dep).
gloo-net Todo 19b (frontend fetch layer). Recorded as
accepted deviation.
leptos Todo 19 (Leptos 0.8 CSR web UI).
@playwright/test Todo 19b (browser QA). Recorded as accepted
deviation (root package.json).
@tailwindcss/cli Todo 19b (Tailwind v4 build). Accepted deviation.
tailwindcss Todo 19b (Tailwind v4). Accepted deviation.
wiremock Todo 7 (feed polling fixtures), Todo 15 (ntfy
publisher mock). Dev-only.
Observed, not present:
rusqlite, feed-rs, scraper, regex — these are NOT direct dependencies.
(regex appears only transitively via leptos.)
Check 1 result: PASS — every direct dependency traces to a todo; no unexplained
direct dependency found.
================================================================================
CHECK 2 — ENDPOINT TRACE
================================================================================
All routes are registered in crates/news-server/src/main.rs:
let app = axum::Router::new()
.merge(news_server::api::feedback::router(rt.pool.clone()))
.merge(news_server::api::stories::router(
rt.pool.clone(),
Arc::clone(&rt.gate),
Arc::clone(&config),
))
.merge(news_server::api::config::router(Arc::clone(&config)))
.merge(news_server::health::router(rt.pool, rt.metrics));
Routers and their paths:
feedback.rs: POST /api/feedback
stories.rs: GET /api/stories
config.rs: GET /api/config
health.rs: GET /healthz
health.rs: GET /metrics
Trace to todos:
/healthz → Todo 18 ("GET /healthz returning 200 {status:ok,db:ok}")
/metrics → Todo 18 ("GET /metrics returning Prometheus text format")
POST /api/feedback → Todo 13 ("POST /api/feedback ... persists feedback")
GET /api/stories → Todo 19a ("add this read-only endpoint alongside todo 13's
feedback endpoint")
GET /api/config → Todo 19a ("add this endpoint too")
No other .route(...) calls exist in news-server/src. No fallback or static-file
route is mounted.
Check 2 result: PASS — exactly the five routes implied by the plan are present;
no extra route found.
================================================================================
CHECK 3 — CONFIG-KEY TRACE
================================================================================
NewsConfig fields (crates/news-server/src/config.rs):
pub struct NewsConfig {
pub sources: Vec<SourceConfig>,
pub notify: NotifySection,
pub topics: TopicsSection,
}
pub struct SourceConfig {
pub url: String,
pub kind: String,
pub weight: f64,
pub enabled: bool,
pub poll_interval_secs: Option<u64>,
}
pub struct NotifySection {
pub notify_opinions: bool,
pub quiet_hours_start: String,
pub quiet_hours_end: String,
pub budget_refill_per_day: f64,
pub budget_burst: f64,
pub bypass_ceiling_per_day: u32,
}
pub struct TopicsSection {
pub interests: Vec<String>,
pub blocklist: Vec<String>,
}
Trace to plan/draft:
sources[] → Todo 14/18 ("[[sources]] url/kind/weight/enabled/
poll_interval_secs override")
SourceConfig.url → Todo 14
SourceConfig.kind → Todo 14
SourceConfig.weight → Todo 14 (source trust weight)
SourceConfig.enabled → Todo 14
SourceConfig.poll_interval_secs → Todo 14 (optional override)
notify.notify_opinions → Todo 14/16 (digest opinion filtering)
notify.quiet_hours_start → Todo 14
notify.quiet_hours_end → Todo 14
notify.budget_refill_per_day → Todo 12/14 (token bucket 2/day)
notify.budget_burst → Todo 12/14 (burst 3)
notify.bypass_ceiling_per_day → Todo 12/14 (bypass cap 4/24h)
topics.interests → Todo 14
topics.blocklist → Todo 10/14 (blocklist veto)
Digest fields: there is NO dedicated [digest] config section. Digest time
(07:00 America/Louisville) and percentile band (75th90th) are hardcoded in
crates/news-server/src/notify/digest.rs and crates/news-server/src/gate.rs,
matching Todo 16.
Dry-run: this is a CLI flag (--dry-run), not a NewsConfig key. Implemented in
Todo 17 (crates/news-server/src/main.rs Cli struct).
Check 3 result: PASS — every NewsConfig field traces to the plan; no extra
config key found.
================================================================================
CHECK 4 — SCOPE-OUT RE-CHECK (8 bullets)
================================================================================
Bullet 1 — "No LLM, LiteLLM, llama.cpp, or GPU dependency anywhere in the pipeline."
Evidence: grep -riE across crates/ for llm|litellm|llama\.cpp|gpt-oss|gemma3|
openai|anthropic|claude|gemini|moonshot|kimi|deepseek|huggingface|transformers|
ollama|vllm|tensorrt|cuda|gpu|nvidia returned ZERO matches.
Result: UNVIOLATED.
Bullet 2 — "No full-article scraping, paywall circumvention, or republishing
article text; headline + feed summary + link only."
Evidence: Ingest parsers only extract title, summary, link, published date,
and category:
- crates/news-ingest/src/rss_atom.rs fields: Title, Link, PublishedAt,
Summary, Category.
- crates/news-ingest/src/news_sitemap.rs fields: Loc, Title,
PublicationDate; summary is explicitly empty string.
grep for scraper|scrape|full.?article|paywall|bypass.*pay|article.?body|
readability in crates/ returned only metric names ("scrape_duration_seconds",
"poll" in scheduler/cycle.rs) — no article-scraping code.
Result: UNVIOLATED.
Bullet 3 — "No paid API, no hosted AI service, no new Postgres/Redis/Kafka/
Kubernetes."
Evidence: grep for postgres|redis|kafka|kubernetes|k8s|mysql|mariadb|mongodb|
dynamodb|bigquery|gcs|s3\.amazon|azure|openai.*api|paid.*api returned ZERO
matches. Persistence is SQLite via sqlx (Todo 5). Notifications use self-hosted
ntfy configured via env vars (Todo 15).
Result: UNVIOLATED.
Bullet 4 — "No public consumer-facing product, mobile app, new public DNS record,
or Authelia gate — LAN/VPN-only posture, matching grafana/llama-swap."
Evidence: deploy/README.md states:
"TLS and auth | The root Caddy, on news.rcjohnstone.com, LAN/VPN-only behind
the internal snippet"
Caddyfile snippet uses `tls internal`, `import internal`, `import
secure_headers`, `import accesslog`, and explicitly comments:
"Internal CA and no # ddns: public marker. Publishing this name would put
it in Certificate Transparency permanently and make a personal dashboard
reachable from the internet."
No mobile app source exists; the only UI is the Leptos CSR app served by
Caddy, not by the backend.
Result: UNVIOLATED.
Bullet 5 — "No implementer edits to ~/compose.yml, ~/Caddyfile,
~/data/ntfy/etc/server.yml, or ~/config/prometheus/prometheus.yml
— snippets only."
Evidence:
- grep for news-triage|news-backend|news.rcjohnstone.com in
/home/connor/compose.yml returned ZERO matches.
- grep for news-triage|news.rcjohnstone.com|news-backend|/srv/news in
/home/connor/Caddyfile returned ZERO matches (the lone `path /api /api/*`
match is inside runway.rcjohnstone.com, not news-triage).
- grep for news-triage|news-backend in
/home/connor/config/prometheus/prometheus.yml returned ZERO matches.
- /home/connor/data/ntfy/etc/server.yml was not touched for news-triage
configuration; the deploy README only instructs the operator to run an
`ntfy access` command.
- Note: /home/connor/compose.yml mtime is 2026-09-01, but its content
contains no news-triage snippets, so no implementer edit for this project
is present.
Result: UNVIOLATED.
Bullet 6 — "No unbounded breaking-news notifications — the bypass lane is capped
independently of the normal budget."
Evidence: crates/news-server/src/gate.rs:
- NotifyConfig::default sets bypass_daily_ceiling: 4.
- decide() checks bypass_count_last_24h() < bypass_daily_ceiling before
emitting a bypass Notify.
- bypass_count_last_24h queries `lane = 'bypass' AND dry_run = 0`.
- TokenBucket is only decremented in the normal lane (`if lane == "normal"`
then budget.tokens -= 1.0); the bypass lane never touches it.
- Test `bypass_ceiling_is_independent_of_normal_budget` verifies exactly 4
bypass notifications with an exhausted normal bucket.
Result: UNVIOLATED.
Bullet 7 — "No serving the frontend SPA from the backend process — the image
only lifts dist out for Caddy."
Evidence:
- crates/news-server/src/main.rs mounts only API/health routers; no static
file server, no ServeDir, no fallback to index.html.
- Dockerfile:
"COPY --from=web /app/crates/news-web/dist /srv/dist"
with comment: "Where the updater looks for the frontend. Nothing serves
it from inside this container."
- deploy/news-update copies /srv/dist out of the image onto the host at
~/data/news/web/dist for Caddy to serve.
Result: UNVIOLATED.
Bullet 8 — "No invented MVP/phase-1 reduction of any of the above."
Evidence: All major Scope-IN items are present:
- 6-crate workspace, rust-toolchain.toml, CI (Todo 1)
- Domain model, scoring, clustering (Todos 2-4, 9-11)
- SQLite + FTS5, source registry, feed polling (Todos 5-8)
- Relevance/importance scorers, percentile tracker (Todos 10-11)
- Notification gate with budget/quiet-hours/bypass/suppression (Todo 12)
- Feedback endpoint + Bayes retrain (Todo 13)
- SIGHUP config reload (Todo 14)
- ntfy publisher + ACL snippet (Todo 15)
- Daily digest (Todo 16)
- Replay harness + --dry-run (Todo 17)
- /healthz, /metrics, scheduler, pruning (Todo 18)
- Leptos web UI + API read endpoints (Todo 19)
- Dockerfile + CI bundle gate (Todo 20)
- Operator snippets (Todo 21)
No Scope-IN item was dropped or reduced.
Result: UNVIOLATED.
Check 4 result: PASS — all 8 Scope-OUT bullets confirmed unviolated.
================================================================================
SUMMARY
================================================================================
Check 1 (Dependency trace): PASS
Check 2 (Endpoint trace): PASS
Check 3 (Config-key trace): PASS
Check 4 (Scope-OUT re-check): PASS
No unexplained direct dependencies, no extra routes, no extra config keys, and
no Scope-OUT violations were found. Accepted deviations recorded in
.omo/notepads/news-triage/learnings.md and .omo/start-work/ledger.jsonl were
not flagged.
FINAL VERDICT: APPROVE