fix(news): separate digest retention from forecast evidence and prune tombstones
Author: koala73Created Aug 22, 2026Updated Sep 12, 2026
Labelsbugarea: AI/intelHigh ValueP0area:seedersarea:newsagent-readiness
Parent
#7080
Priority
P0 — the current structure is unbounded and shares one key between incompatible 48-hour and 7–14-day contracts.
What to build
Give digest accumulation and forecast judging separate retention contracts. The digest accumulator must stay bounded to 48 hours. Forecast judging must use a dedicated, self-contained archive that remains usable for its declared maximum lookback and does not depend on seven-day story-track rows.
Why
The current writer refreshes the accumulator key TTL on every build but never removes old sorted-set members. Production contains millions of expired members and tombstones. A direct 48-hour trim is unsafe because the forecast resolver reads the same key for up to 14 days.
Implementation plan
1. Freeze reader and data contracts
- Inventory every in-repo reader of digest accumulators and record its key scope, lookback, fields, and failure behavior.
- Define the digest contract as a 48-hour rolling index used by briefs, watchlists, and spike calculations that request no longer history.
- Define the forecast archive as a versioned, self-contained evidence record with the minimum fields required to judge a forecast. Its maximum retention must cover the 14-day reader contract plus a small cleanup guard band.
- Do not make the forecast archive depend on a story hash whose record expires after seven days.
2. Add the archive before pruning
- Add dual publication of eligible full-English evidence to the new forecast archive.
- Backfill the still-readable portion of the last 14 days before any destructive cleanup. Record unreadable tombstones and missing fields instead of silently omitting them.
- Keep archive writes bounded by explicit record, byte, and command budgets.
- Add archive version, publication time, source digest time, and retention metadata.
3. Migrate and verify the forecast reader
- Move forecast judging to the dedicated archive.
- Compare old and new reader results over the same eligible window while both paths exist.
- Fail closed when archive coverage is truncated, unreadable, or outside the declared window. Do not convert missing evidence into a judged negative.
- Remove the old accumulator dependency only after the comparison passes.
4. Enforce digest retention
- Add member-level pruning for scores older than 48 hours as part of normal digest publication.
- Keep key TTL as abandoned-key cleanup, not as member retention.
- Add a bounded cleanup tool with dry-run output. It must enumerate exact keys and report cardinality and oldest/newest score before mutation.
- Run the one-time production sweep only after the forecast cutover is deployed and verified. Capture the pre-sweep evidence needed for recovery or audit.
5. Observe steady state
- Record accumulator cardinality, oldest age, prune count, and command latency in operator monitoring.
- Keep ordinary cardinality drift out of GitHub status checks. Integrity, malformed archive records, and retention-contract violations must still fail closed where they affect judging.
Acceptance criteria
- All accumulator readers and their required lookbacks are listed in the pull-request handoff.
- Forecast judging uses a dedicated archive and no longer reads a digest accumulator.
- A forecast evidence item remains readable at day 13 and is ineligible after the declared maximum retention.
- The archive remains usable after the corresponding story-track row expires.
- A digest accumulator member older than 48 hours is removed during normal publication.
- Boundary tests keep exactly eligible members at the 48-hour edge and do not remove newer members.
- The migration reports old/new reader parity, archive truncation, missing evidence, and tombstones.
- The cleanup tool defaults to dry-run and requires explicit key scope before mutation.
- The production sweep records UTC time, key count, member counts, oldest ages, and the remaining forecast-archive coverage.
- Post-deploy observation shows bounded cardinality for at least three consecutive digest publication cycles.
Test plan
- Use a fake clock to test 48-hour digest and 14-day forecast boundaries.
- Test archive reads after story-track expiry.
- Test malformed, truncated, duplicate, and partially backfilled archive records.
- Test that a failed archive write does not advance coverage metadata.
- Test the cleanup dry-run, exact-key allowlist, and idempotent second run.
- Run focused news, forecast-resolution, Redis-cache, and story-tracking tests, followed by the API typecheck and diff hygiene checks.
Blocked by
None — can start immediately.
Out of scope
- Changing forecast scoring or judgment policy.
- Extending digest retention beyond 48 hours.
- Treating tombstones as negative forecast evidence.
- Pruning production before the new reader is deployed and verified.
- Hiding ordinary storage health inside pull-request CI.
Source: koala73/worldmonitor