Product contract¶
Rollup is a local-first personal briefing engine. It reduces a chosen information environment into a bounded reading object — a finite digest worth reading — without becoming the store of record for those sources.
Thunderbird owns the ongoing stream. Rollup takes a time window and produces the rollup. You do not need a special newsletter address, a second mailbox, or to make Rollup authoritative for subscriptions.
Optional LinkedIn, Reddit, and webpage sources add material at digest time. They are ingest transports, not inboxes: no continuous feed, no unread state. They do not replace the mail-store contract.
This is a different product from an RSS reader:
RSS reader |
Rollup |
|
|---|---|---|
Shape |
source → stream → user triages items |
source → collection window → filtering / grouping / synthesis → finished briefing |
Job |
show everything new from these sources |
turn a messy pile you already let into orbit into something finite and worth reading |
The product is not “the self-hosted AI newsletter reader”. In 2026 that niche has competitors. Rollup’s distinction is read-only existing mail + durable source policy + publication integrity + multi-format digest output. See COMPARISON.md and ROADMAP.md.
Who owns what¶
Layer |
Owner |
Responsibility |
|---|---|---|
Filing |
Thunderbird message filters |
Move newsletters into folders under a |
Window & folders |
Run profile / CLI / TOML |
Which calendar days and which folders enter the digest |
LinkedIn / Reddit / webpages |
TOML + GUI + opt-in CLI flags |
Ingest transports for the collection window (not inboxes). Credentials in env only; payloads cached in |
Source policy |
|
Enable/disable, priority, type override, always-surface, grouping |
Summaries |
Preview by default; LLM via |
Entry summaries. Whole-digest QA is |
Attention (planned) |
Interest profile + ratings |
Rank or annotate. Must not silently drop included material |
Invariants¶
These do not change without an explicit contract revision:
Never write, delete, or rename anything under the mail root
Default
rollup digestmakes no network callsLLM summarisation is opt-in (
--ollama).--final-reviewcalls a model independently and does not require--ollama. Google Scholardetailedmode is opt-in HTTPS enrichment of mail already in the window (not an ingest transport)Web UI is loopback and single-user; GET routes are read-only
Secrets never live in TOML or the web UI
Publication is staged + rename;
latest.*and seen-state cross an irreversible boundary only after required artifacts landReader bodies are a capped convenience cache, not a mailbox archive
Ranking, when added, changes attention — not deterministic inclusion
Network sources (LinkedIn, Reddit, webpages; RSS if it ever exists) are ingest transports for the collection window. Rollup never grows an RSS inbox, unread counts, mark-all-read, or a scrolling feed
Non-goals (through 1.0)¶
IMAP / Gmail API / Maildir backends (Gmail API is post-1.0)
Thunderbird add-on (XPI)
Multi-user or non-loopback web UI
In-app digest scheduler (use CRON.md / launchd)
Exposing classifier thresholds as user knobs
Built-in workflow engine, generic plugin-everything, or a mobile app
An RSS reader (unread counts, folders, stars, sync, mark-all-read). RSS as a silent ingest transport is parked, same pattern as LinkedIn / Reddit today
Official LinkedIn API (v1 uses session cookies + Voyager
profileUpdatesV2)
Sensible defaults¶
rollup digestneeds no config file when mail lives under a discoverable layoutDefault profile is weekly (7-day lookback, grouping on)
Folder accents are deterministic from folder names; personal emoji/colors live in TOML
See CONFIG.md for TOML, profiles, and path discovery.
Runtime integrity (persistence and publication)¶
Authoritative types live in rollup.run_contracts. Exit codes: 0 success, 1 hard failure, 2 partial — details in CRON.md.
Irreversible publication boundary¶
Do not update latest.*, seen-state, web index, or derive success until every required dated artifact and every required output writer has landed in its final path. Staged temporary files are never published artifacts.
Ordering after a successful no-input gate:
Summarize / review / render into a run-scoped staging area under the output directory
fsyncstaged files; rename into final namesCross irreversible boundary
Optionally publish
latest.*Update seen-state (only for messages included in the published digest)
Write manifest from accumulated lifecycle state (partial runs included when possible)
Web-index (typed secondary degradation; does not alone force partial)
Derive final status / exit
Publication is staged + rename, not a filesystem-wide atomic multi-file transaction. Crash between renames is recovered on the next run (stale temps discarded; half-published sets reported; latest/seen not advanced incorrectly).
Writers¶
Enabled writers are classified required or optional on EffectiveRun. Core Markdown/HTML are always required. Enabled add-on writers default to required. Optional writer failure yields partial (exit 2) after required publication succeeds.
Empty window vs no-input¶
Distinguish discovered / parse-candidate / parsed-ok / parse-failed / in-window / filtered / included message counts. Empty-window success is allowed when discovery and parsing are healthy and messages_included == 0; latest.* is refused. No-input hard failure (exit 1) when an explicit include matches nothing, no folders are readable, or every parse candidate fails — gated before Ollama, final review, rendering, writers, state mutation, and publication.
Schema version¶
schema_version labels one canonical full database shape (including empty cache/feature tables). The current package version is 16 (Scholar paper-body cache, listing caches for Reddit/LinkedIn, webpage queue, reader bodies, source registry, web archive). Version is never written before migrations complete, never lowered, and future versions are refused before any mutate. See migration tests under tests/test_schema_migrations.py.
Primary summary variant¶
The first entry in the validated summary-plan order is primary for filenames, latest.*, primary manifest fields, returned paths, and web indexing. Duplicate variants are rejected. Variants share the same eligible message identity set and stable group identities.