Skip to content

fix(health-freshness): read the keyless compact health endpoint — stop the anon 401-per-minute flood (#4902)#4905

Merged
koala73 merged 1 commit into
mainfrom
fix/4902-health-freshness-keyless-compact
Jul 5, 2026
Merged

fix(health-freshness): read the keyless compact health endpoint — stop the anon 401-per-minute flood (#4902)#4905
koala73 merged 1 commit into
mainfrom
fix/4902-health-freshness-keyless-compact

Conversation

@koala73

@koala73 koala73 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Fixes #4902.

Problem

#4715 key-gated detailed /api/health (operator/enterprise only) but never swept the dashboard caller: src/services/health-freshness.ts kept fetching the bare endpoint anonymously. Since the gate deployed (2026-07-04), every anonymous tab 401s once a minute (REFRESH_INTERVALS.healthFreshness = 60s, runImmediately: true, all variants), each failure flows to Sentry, and the seed-health → data-freshness pipeline is silently dead — refreshDataFreshnessFromHealth() throws before recordSeedHealth(), so panel freshness badges never see STALE_SEED / REDIS_DOWN. Same caller-sweep flood class as #4865 (→ #4869); the #4856 sweep fixed only the advertised consumers of this endpoint.

Fix

  • Default the client to the keyless /api/health?compact=1 (server already pins this variant as public — tests/health-redis-down-status.test.mjs). problems carries the same per-check shape as checks but only non-OK entries; a mapped check absent from it was evaluated server-side and found within budget, so the client synthesizes OK-as-of-checkedAt (seedAgeMin: 0 — an age-less OK would leave lastUpdate null and calculateStatus reports no_data).
  • 401/403 → throw once, then suppress refetch for 15 min: one visible error per window per tab instead of one per tick if the endpoint ever gets (re-)gated.
  • Detailed-checks parsing and the REDIS_DOWN 503 body-first branch are unchanged (REDIS_DOWN short-circuits server-side before the compact branch, still no checks/problems).

Verification

  • 4 new tests in tests/data-freshness-health.test.mts (failing-first): default-endpoint pin, compact problems + synthesized-OK hydration, healthy-compact clears prior errors, 401 suppression window. 13/13 pass; existing detailed-payload tests untouched and green.
  • Exercised end-to-end against live prod: 24 mapped sources hydrate from the real compact payload; the genuinely stale gdeltIntel maps to very_stale, absent checks read fresh as of checkedAt.
  • npm run typecheck clean; tiered pre-push gate green (unit incl. changed tests, edge bundle, boundaries).

https://claude.ai/code/session_01YKrVoDp8TfEKLYXo83kPFV

…p the anon 401-per-minute flood (#4902)

#4715 key-gated detailed /api/health (operator/enterprise only) but the
dashboard client kept fetching it bare: every anonymous tab 401'd once a
minute and the seed-health → data-freshness pipeline went silently dead.

- Default the client to /api/health?compact=1 (public). `problems` carries
  the same per-check shape; mapped checks absent from it were evaluated
  server-side and found within budget, so synthesize OK-as-of-checkedAt
  (seedAgeMin 0 — an age-less update would leave lastUpdate null and
  calculateStatus reports no_data).
- 401/403 → 15-min suppression window: one visible error per window per tab
  instead of one per tick if the endpoint ever gets re-gated (same
  caller-sweep flood class as #4865).
- REDIS_DOWN 503 body-first handling unchanged (short-circuits before the
  compact branch server-side).

Verified against live prod: 24 mapped sources hydrate, stale gdeltIntel
maps very_stale, absent checks read fresh.

Fixes #4902

Claude-Session: https://claude.ai/code/session_01YKrVoDp8TfEKLYXo83kPFV
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Jul 5, 2026 5:32pm

Request Review

@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a 401-per-minute flood caused by the anonymous dashboard calling the key-gated /api/health endpoint: it switches the default to the public compact variant (/api/health?compact=1) and adds a 15-minute suppression window on 401/403 responses so a re-gated endpoint produces one error per window instead of one per scheduler tick.

  • Compact payload handling: since the compact endpoint omits healthy checks (only non-OK entries land in problems), the code synthesizes an OK entry with seedAgeMin: 0 for every mapped check absent from problems, ensuring calculateStatus returns fresh rather than no_data for server-vouched healthy sources.
  • 401/403 suppression: module-level authGateSuppressedUntilMs gates the fetch; on a gated response the suppression timestamp is set and the error is thrown once, then subsequent calls within the window return 0 silently; __resetHealthFreshnessForTests is exported for test isolation.
  • Test coverage: four new tests verify the endpoint pin, compact hydration with synthesized OKs, all-healthy compact snapshot, and the suppression window — all four pass alongside the existing detailed-payload suite.

Confidence Score: 4/5

Safe to merge; the fix directly addresses a live 401 flood and the new compact-payload logic is well-tested and well-commented.

The synthesis guard at line 148 does not exclude Redis-outage statuses, so a hypothetical REDIS_DOWN compact response with non-empty problems would bypass the early-return Redis path and incorrectly synthesize OK for absent mapped sources. The server currently guarantees no problems alongside REDIS_DOWN, but the guard is easy to harden. No other logic concerns found; the 401-suppression and endpoint-switch paths are correct and covered by tests.

src/services/health-freshness.ts — specifically the compact-synthesis guard at line 148.

Important Files Changed

Filename Overview
src/services/health-freshness.ts Switches the default fetch target from the key-gated /api/health to the public compact variant, adds a 15-minute 401/403 suppression window via module-level state, and synthesizes server-vouched OK entries for mapped checks absent from a compact problems payload. The synthesis guard does not exclude Redis-outage statuses, which could theoretically mismark sources as OK during a partial Redis outage if the server contract ever changes.
tests/data-freshness-health.test.mts Adds four new test cases covering the compact endpoint pin, problems-to-degraded hydration with synthesized OK for absent checks, all-healthy compact snapshot, and 401 suppression window. Tests reset module state at the start of each case; the suppression test also resets at the end.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[refreshDataFreshnessFromHealth] --> B{suppressed?}
    B -- Yes --> C[return 0]
    B -- No --> D[fetch /api/health?compact=1]
    D --> E{status 401 or 403?}
    E -- Yes --> F[set suppress window 15min\nthrow error]
    E -- No --> G[parse JSON body]
    G --> H{resp.ok or Redis outage?}
    H -- No --> I[throw fetch error]
    H -- Yes --> J{payload.checks present?}
    J -- Yes: detailed --> K[checks = payload.checks]
    J -- No: compact --> L[checks = spread of problems or empty]
    L --> M{status is string?}
    M -- Yes --> N[synthesize OK for absent\nHEALTH_CHECK_SOURCE_MAP keys]
    M -- No --> O
    K --> O{checks empty and Redis outage?}
    N --> O
    O -- Yes --> P[record all sources as REDIS_DOWN]
    O -- No --> Q[apply highest-rank update per source]
    P --> R[return count]
    Q --> S[recordSeedHealth]
    S --> R
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[refreshDataFreshnessFromHealth] --> B{suppressed?}
    B -- Yes --> C[return 0]
    B -- No --> D[fetch /api/health?compact=1]
    D --> E{status 401 or 403?}
    E -- Yes --> F[set suppress window 15min\nthrow error]
    E -- No --> G[parse JSON body]
    G --> H{resp.ok or Redis outage?}
    H -- No --> I[throw fetch error]
    H -- Yes --> J{payload.checks present?}
    J -- Yes: detailed --> K[checks = payload.checks]
    J -- No: compact --> L[checks = spread of problems or empty]
    L --> M{status is string?}
    M -- Yes --> N[synthesize OK for absent\nHEALTH_CHECK_SOURCE_MAP keys]
    M -- No --> O
    K --> O{checks empty and Redis outage?}
    N --> O
    O -- Yes --> P[record all sources as REDIS_DOWN]
    O -- No --> Q[apply highest-rank update per source]
    P --> R[return count]
    Q --> S[recordSeedHealth]
    S --> R
Loading

Reviews (1): Last reviewed commit: "fix(health-freshness): read the keyless ..." | Re-trigger Greptile

// and found within budget. Synthesize OK-as-of-checkedAt for those:
// seedAgeMin 0 is required because recordSeedHealth keeps lastUpdate null
// on an age-less update and calculateStatus then reports no_data.
if (!payload.checks && typeof payload.status === 'string') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The synthesis guard !payload.checks && typeof payload.status === 'string' does not exclude Redis-outage statuses. If the server ever returns a compact REDIS_DOWN/REDIS_PARTIAL payload with non-empty problems (e.g., a future change to the outage branch), the code would skip the early-return Redis path (because checks is non-empty), then synthesize OK for every absent mapped key alongside the genuinely broken sources — leaving most sources falsely marked fresh during a total Redis outage. Adding !isRedisOutageStatus(payload.status) closes this gap defensively.

Suggested change
if (!payload.checks && typeof payload.status === 'string') {
if (!payload.checks && typeof payload.status === 'string' && !isRedisOutageStatus(payload.status)) {

@koala73
koala73 merged commit e76c3d7 into main Jul 5, 2026
26 checks passed
koala73 added a commit that referenced this pull request Jul 5, 2026
…, 5-min cadence, idle-deferred first hydration (#4907) (#4908)

Every /api/health call runs a ~196-key Upstash pipeline and responses were
no-store end to end, so origin probe cost scaled linearly with open tabs
(60s x per-tab x all variants) and the first fetch competed with the LCP
window (#4890).

- api/health.js: compact 200s (the public keyless form) get
  Cache-Control: public, max-age=0, must-revalidate +
  CDN-Cache-Control: public, s-maxage=60 — all tabs collapse onto ~one
  origin probe per cache window per edge region. Key-gated detailed
  responses, 401s, and the REDIS_DOWN 503 keep no-store (caching a 401
  pins an auth failure; caching a 503 masks recovery).
- REFRESH_INTERVALS.healthFreshness 60s -> 5min: seed cadences are 6-24h
  and badge decay is client-computed; 5min stays safely under the 15min
  FRESH_THRESHOLD so synthesized-OK sources (age-less since #4905) cannot
  decay to stale between polls.
- App.ts: health-freshness registration (and its immediate first run)
  deferred via scheduleAfterFirstPaint — freshness badges are
  below-the-fold decoration and must not compete with LCP requests.

New tests: compact-200 cache headers + no-store pins for detailed/401/503
(handler-level, mocked Upstash pipeline); scheduler-shape pin updated to
require the post-paint defer.

Fixes #4907

Claude-Session: https://claude.ai/code/session_01YKrVoDp8TfEKLYXo83kPFV
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.

fix(health-freshness): dashboard calls gated /api/health anonymously — 401 every 60s per tab + seed-health freshness silently dead since #4715

1 participant