Skip to content

fix(pipeline): freshness repairs — caps, recent-first fetch, hot-bill pass, honest quiet-week verdict, metrics keyspace#88

Merged
cm2489 merged 5 commits into
mainfrom
fix/pipeline-freshness
Jul 16, 2026
Merged

fix(pipeline): freshness repairs — caps, recent-first fetch, hot-bill pass, honest quiet-week verdict, metrics keyspace#88
cm2489 merged 5 commits into
mainfrom
fix/pipeline-freshness

Conversation

@cm2489

@cm2489 cm2489 commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Implements the fix package from the 2026-07-16 pipeline audit (delivered to Colby; root causes: decode budget 40 vs ~400/night real demand, oldest-first fetch bias, quiet-week verdict keyed on job-ran instead of data-advanced, plus the now-resolved Anthropic credit outage).

The five commits

  1. CapsMAX_NEW_DECODES 40→120 (still dispatch-overridable for the one-time catch-up run), COVERAGE_TOP_N ∞→150 (stops the nightly news-quota exhaustion; applies the deferred 2026-07-04 proposal, re-sized to live numbers).
  2. Recent-first two-pass fetch — a desc-sorted pass (limit 100, decode reserve 20 carved out of the 120) runs before the ascending backlog scan, so bills that just had floor action can never again be structurally last-in-line. Cursor freeze semantics untouched: cursor/frozen are written only by the ascending pass. Shared fetch helpers extracted to scripts/congress-fetch.mjs.
  3. Hot-bill refresh workflowhot-bills.yml at 17:00/22:00 UTC, refresh-only (status/last_action_date for known bills), zero Anthropic usage, shared data-sync concurrency group. New bills still wait for the nightly decode gate (documented).
  4. Honest freshness verdictemptyStateVerdict now trips data_stale on cursor (completeThrough) or newestAction staleness past the 21-day dead window, not just lastRun; verify-sync's 7-day cursor warning promoted to a hard failure at 10 days. With today's committed data the suite proves the site now says "Data check needed" instead of the false "Quiet week" — pinned tests updated deliberately with invariant-change comments.
  5. Metrics keyspace fixkeyPrefix() resolves to dev in Actions (no VERCEL_ENV): the daily digest was reading an always-empty keyspace, and pregen has been writing its script cache to dev:script:* since arming on 07-12 — spend with zero possible production cache hits. Both steps now set VERCEL_ENV: production. (verify-salt.mjs checked — not affected.)

Costs at new defaults (per audit, /bin/zsh.07–0.15/bill)

Backlog-drain nights: hard-capped .40–18.00. Steady state: .80–12 on active session days, ~/bin/zsh in recess (refreshes are free). Hot-bills: $0. Coverage cap slightly reduces Haiku spend.

Verification

lint ✓ typecheck ✓ full Playwright suite 1290 passed / 14 skipped / 0 failed ✓ check-naming ✓ check-key-namespaces ✓ check-server-json ✓ check-messages-parity ✓ (no new strings; existing dataStale EN/ES keys reused). No live API calls in tests. Built by a Sonnet 5 subagent; orchestrator independently verified branch on origin, cursor-semantics implementation, and the hot-bills workflow's zero-Anthropic surface.

After merge (pending Colby's spend approvals in chat)

One-time catch-up dispatch (~400 decodes, ~–60) to drain the 29-day backlog, then the nightly settles at the new caps.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XexCqeBQ2csGYiBMC62p2G

cm2489 and others added 5 commits July 16, 2026 11:07
MAX_NEW_DECODES (scripts/sync-bills.mjs + the workflow_dispatch input/env
default in sync-bills.yml) was still 40, pinning the ascending-pass sync
cursor weeks behind against live nightly demand of 373-418 bills. Raise to
120 (workflow_dispatch input stays overridable for a one-time higher-budget
catch-up run). COVERAGE_TOP_N (scripts/sync-coverage.mjs) was unbounded
(Infinity), so the nightly coverage sweep queried every eligible bill in
urgency order and structurally starved anything near the bottom of the
ranking of TheNewsAPI's daily quota; cap it at 150, still overridable via env.

This is the already-deferred 2026-07-04 proposal, re-sized against the
audit's live numbers (pipeline-audit.md §5 item 1).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… the race

The nightly sync queried Congress.gov ascending by updateDate, so the most
recently-touched bills (floor votes, markups — exactly what generates press)
were structurally the LAST ones reached each run, behind both the MAX_UPDATES
window cap and the decode budget. On a deep-backlog night this meant a bill
that just had real activity could go unnoticed indefinitely even on a clean,
successful run — the root cause behind "worth a call" reading stale/empty
independent of the decode-cap fix (pipeline-audit.md §5 item 2).

Add a recent-first pass (sort=updateDate+desc, ~100 bills, no cursor floor)
that runs before the existing ascending backlog scan: already-known bills
refresh for free, brand-new bills decode within a reserved sub-budget
(RECENT_DECODE_RESERVE=20, carved out of MAX_NEW_DECODES, not additional).
Bills the recent pass resolves are deduped (not re-fetched/re-decoded) when
the ascending scan reaches them.

Cursor semantics preserved deliberately: the freeze-on-incomplete-work
high-water mark (state.lastSync) is advanced ONLY by the ascending pass, so
a bill handled out-of-order by the recent-first pass can never push the
cursor past unprocessed middle-of-backlog territory. The failed >
updated.length/2 abort also stays scoped to the ascending pass exactly as
before; the recent pass's failures are logged separately.

Extracted the Congress.gov fetch/status/urgency/refresh helpers into
scripts/congress-fetch.mjs so scripts/hot-bills.mjs (next commit) can reuse
them instead of duplicating.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add scripts/hot-bills.mjs + .github/workflows/hot-bills.yml: a refresh-only
pass at 17:00 and 22:00 UTC that re-fetches the ~100 most-recently-updated
Congress.gov bills and updates status/last_action_date/urgency for whichever
are already in the corpus. Decodes nothing — a brand-new bill still waits
for the nightly sync's decode-before-publish gate — so this is zero
Anthropic usage, near-$0 marginal cost (Congress.gov + free public-repo
Actions minutes only).

Reuses scripts/congress-fetch.mjs (extracted in the prior commit) rather
than duplicating the fetch/refresh logic. Commits as the same oravan-sync
author pattern as sync-bills.yml/refresh-legislators.yml and shares their
`data-sync` concurrency group so it can never race the nightly sync.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…aleness

emptyStateVerdict() (lib/freshness-state.ts) collapsed to quiet_week whenever
checkedAt/lastRun was recent, regardless of whether the sync cursor
(completeThrough/lastSync) or the corpus's own newest last_action_date had
gone stale — so a pipeline that ran and committed every night, but made no
real forward progress for weeks, read as "fresh" and the site (and the MCP
whats_moving tool) confidently claimed "quiet week" over a month-stale corpus
(pipeline-audit.md §5 item 4 — the exact live bug: a 29-day-old cursor and
newest action both passing as fresh under the old checkedAt-only check).

emptyStateVerdict now takes all three freshness signals and fails toward
data_stale if EITHER checkedAt is stale/dead (tight FRESHNESS_CLAIM_WINDOW_
DAYS/FRESHNESS_DEAD_WINDOW_DAYS) OR completeThrough/newestAction exceed the
wider FRESHNESS_DEAD_WINDOW_DAYS — deliberately wider for the latter two
since the sync cursor is expected to lag checkedAt by real days under normal
backlog-draining operation (lib/freshness.ts's own doc comment); gating them
on the tight window would cry "stale" most nights even when the recent-first
pass (previous commit) kept the actually-relevant content current.

UrgencyEmptyState and its three call sites (homepage, /reps, BillsBrowser)
now pass the full FreshnessSignals object instead of just checkedAt.
whatsMoving (lib/core/mcp.ts) passes getFreshness() directly — the shape
already matched.

Also promotes scripts/verify-sync.mjs's 7-day cursor-age ::warning to a hard
failure at 10 days (generous enough that the post-merge backlog-drain window
doesn't insta-fail, still well inside the site's own 21-day dead ceiling for
the same value) — the warning fired every clean night for weeks and was
never acted on, which is exactly the silent-failure shape this verifier
exists to catch.

Updates the pinned tests this invariant touches (tests/freshness.unit.spec.ts,
tests/freshness.spec.ts, tests/mcp-tools.spec.ts) deliberately, with comments
explaining why the invariant legitimately changed; unrelated assertions are
untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…eal keyspace

lib/upstash.ts's keyPrefix() resolves to `process.env.VERCEL_ENV ?? 'dev'` —
'production'/'preview' are only ever set by Vercel itself at request time, so
any GitHub Actions job touching an Upstash-backed key builder without
explicitly setting VERCEL_ENV silently operates on the 'dev:' keyspace
instead of the one the live site actually uses (same pattern already
documented for laptop-shell CLI runs in scripts/tenant-admin.mjs).

Two live instances, both fixed the same way:
- .github/workflows/daily-metrics.yml (flagged, not fixed, in PR #87):
  readUsageWindow/readMcpClientDay were reading an always-empty 'dev:'
  keyspace, so the daily digest could only ever report zero MCP/script
  usage — never caught because a silent zero looks identical to genuinely
  no traffic.
- .github/workflows/sync-bills.yml's pregen step (same root cause,
  previously unflagged): every combo cached since PREGEN_ENABLED was armed
  (2026-07-12) has been written under 'dev:script:...' — a keyspace
  app/api/script/route.ts's live 'production:' requests can never read —
  so every pregen run since arming has been pure Anthropic Batch API spend
  with zero possible cache hits.

Checked scripts/verify-salt.mjs for the same problem: it does NOT have it —
that script iterates the 'production'/'preview' keyspaces explicitly rather
than deriving a single prefix via keyPrefix(), so no fix needed there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
oravan Ready Ready Preview, Comment Jul 16, 2026 3:32pm

Request Review

@cm2489
cm2489 merged commit ee84931 into main Jul 16, 2026
3 checks passed
@cm2489
cm2489 deleted the fix/pipeline-freshness branch July 16, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant