Phase 4: delivery -- ntfy, working feedback buttons, digest page

ntfy push is deliberately short: a title, one line, and a Click through to the
story. The digest is a WEB PAGE, not a push payload -- the old system sent the
whole report as one long markdown message and ntfy turned it into a .md
attachment, which is what made it miserable to read on a phone.

Feedback buttons finally work. The old ones never fired once: NEWS_API_BASE_URL
was unset so they were dropped behind a single boot WARN, and /api/feedback had
no auth anyway. ntfy runs http actions FROM THE PHONE, so the endpoint must be
public -- which is why each button now carries an HMAC signed over
(cluster_id, kind). A leaked URL authorises one vote on one story rather than
acting as a bearer token for the whole model. Missing config drops the buttons
loudly instead of silently.

Digest prose is one LLM call per day through LiteLLM, and that is the entire
generative-model surface of this system -- clustering, relevance, importance and
vetoes are all embeddings and arithmetic. Feed text is passed as JSON data,
never interpolated into prompt prose, so a headline cannot read as an
instruction. Any failure falls back to the plain list; the digest never depends
on a model being reachable. Verified against the live proxy: 20s, themed
paragraphs, no invented facts.

Digest page is mobile-first, dark-mode aware, and renders from notification_log
so it is a record of what was actually sent.

deploy/README.md documents the Caddy vhost and env the buttons require, and uses
a python healthcheck rather than curl -- the v0.2 image shipped no curl, so its
healthcheck failed forever while the service was fine.

62 tests.
This commit is contained in:
connor committed 2026-09-08 16:27:27 -04:00
1 parent b852840729
commit dc6b6fea7e
9 files changed
+829

No files matched your search

+13
View File
@@ -119,3 +119,16 @@ corroboration = 0.4
velocity = 0.5
prominence = 0.3
aggregator = 0.3
[llm]
# The ENTIRE generative-model surface of this system: one call per day to write
# the digest prose. Clustering, relevance, importance and vetoes are all
# embeddings and arithmetic -- no model in the scoring path.
#
# Cost on this alias is pinned to 0.0 in the LiteLLM config, so a NONZERO spend
# row is the deliberate quota-overflow alarm, not a billing surprise.
enabled = true
base_url = "http://litellm:4000"
model = "news/glm-5.3-flash"
api_key_env = "NEWS_LITELLM_KEY"
timeout_secs = 60