source_health: next_due_at = later of streak-backoff and retry_after_at

Per Codex: the Next poll column computed only the streak-backoff time, so a
rate-limited source could show an earlier Next poll than the real gate (which
also requires retry_after_at <= now). Take the later of the two in the Python
post-process so the admin table agrees with due_source_rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-09 11:45:54 -04:00
parent 38abc26ddd
commit 01de5a3ef0
2 changed files with 15 additions and 0 deletions
+4
View File
@@ -327,6 +327,10 @@ def source_health(conn: sqlite3.Connection) -> list[dict]:
# Curation quality: of what this source got ACCEPTED, how much was a # Curation quality: of what this source got ACCEPTED, how much was a
# duplicate of content already served (accepted_total served = accepted dupes). # duplicate of content already served (accepted_total served = accepted dupes).
d["accepted_dup_rate"] = round(100 * (accepted - d["served"]) / accepted) if accepted else None d["accepted_dup_rate"] = round(100 * (accepted - d["served"]) / accepted) if accepted else None
# Match the REAL scheduler gate: due = the later of the streak-backoff time
# and any retry_after_at rest (UTC strings sort chronologically).
due_times = [t for t in (d["next_due_at"], d["retry_after_at"]) if t]
d["next_due_at"] = max(due_times) if due_times else None
out.append(d) out.append(d)
return out return out
+11
View File
@@ -63,3 +63,14 @@ def test_non_429_failure_still_increments_streak(monkeypatch):
res = feeds.poll_source(c, src) res = feeds.poll_source(c, src)
assert res["status"] == "failed" assert res["status"] == "failed"
assert c.execute("SELECT consecutive_failures FROM sources WHERE id=1").fetchone()[0] == 1 assert c.execute("SELECT consecutive_failures FROM sources WHERE id=1").fetchone()[0] == 1
def test_source_health_next_due_uses_later_of_backoff_and_retry_after():
from goodnews import queries
c = connect(":memory:"); init_db(c); _src(c)
# a recent attempt (streak due ~soon) but a far-future retry_after_at
c.execute("INSERT INTO ingest_runs (source_id, finished_at, status) VALUES (1, datetime('now'), 'rate_limited')")
c.execute("UPDATE sources SET retry_after_at = '2099-01-01 00:00:00' WHERE id = 1")
c.commit()
s = next(x for x in queries.source_health(c) if x["id"] == 1)
assert s["next_due_at"] == "2099-01-01 00:00:00" # agrees with the real gate, not the streak time