Skip to content

feat(resilience): add headlineEligible field to score + ranking responses (plan 002 PR 2)#3457

Merged
koala73 merged 4 commits into
mainfrom
feat/resilience-headline-eligible-field
Apr 27, 2026
Merged

feat(resilience): add headlineEligible field to score + ranking responses (plan 002 PR 2)#3457
koala73 merged 4 commits into
mainfrom
feat/resilience-headline-eligible-field

Conversation

@koala73

@koala73 koala73 commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Summary

Plan 2026-04-26-002 §U3 (PR 2 in the 8-PR sequence) — adds bool headline_eligible to GetResilienceScoreResponse (field 17) and ResilienceRankingItem (field 7). PR 2 populates the field true for every successful score build (no behavior change). PR 6 / §U7 will swap the population logic to the actual eligibility gate (coverage ≥ 0.65 AND (population ≥ 200k OR coverage ≥ 0.85) AND !lowConfidence) and the headline ranking endpoint will filter by this field.

Why land this as a precursor

The proto + generated TS surface change is itself a noisy diff (regenerated openapi yaml/json + client/server stubs) that's easier to review on its own than mixed in with PR 6's gate logic. Downstream consumers (widget, raw API) can begin reading the field informationally before the gate flips, avoiding a coupled "field + behavior" PR.

Behavior

  • Happy-path response: headlineEligible: true
  • Invalid country code fallback: headlineEligible: false
  • Missing-cache fallback: headlineEligible: false
  • Null-response ranking item fallback: headlineEligible: false

The conservative false defaults on fallback paths match the future PR-6 gate (countries without a successful score build can't pass the coverage gate either).

Files

  • proto/worldmonitor/resilience/v1/get_resilience_score.proto — add field 17
  • proto/worldmonitor/resilience/v1/resilience.proto — add field 7 to ResilienceRankingItem
  • make generate regenerated openapi + TS client/server bindings
  • server/worldmonitor/resilience/v1/_shared.ts — populate in buildResilienceScore, both ensureResilienceScoreCached fallback paths, and buildRankingItem
  • src/components/resilience-widget-utils.tsLOCKED_PREVIEW carries headlineEligible: true (informational only; widget renders nothing different yet)
  • tests/resilience-headline-eligible-field.test.mts (new) — 5 pinning tests for the PR-2 contract

Test plan

  • npm run typecheck clean
  • npm run typecheck:api clean
  • npm run lint clean (exit 0)
  • npm run test:data — 7502/7502 pass (5 new + 7497 existing)
  • All response shapes carry the field; TypeScript enforces presence at compile time
  • Pinning test asserts the PR-2 contract (true on happy path, false on fallback)

References

…nses (plan 002 PR 2 §U3)

Plan 2026-04-26-002 §U3 (PR 2 in the 8-PR sequence) — introduces a new
`bool headline_eligible = N;` field on `GetResilienceScoreResponse`
and `ResilienceRankingItem`. PR 2 populates `true` for every successful
score build (no behavior change); PR 6 / §U7 swaps the population logic
to the actual eligibility gate (coverage ≥ 0.65 AND (population ≥ 200k
OR coverage ≥ 0.85) AND !lowConfidence) and the headline ranking endpoint
filters by this field.

Why land this as a precursor: the proto + generated TS surface change is
itself a noisy diff (regenerated openapi yaml/json + client/server stubs)
that's easier to review on its own than mixed in with PR 6's gate logic.
Downstream consumers (widget, raw API) can begin reading the field
informationally before the gate flips, avoiding a coupled "field +
behavior" PR.

Files:
- `proto/worldmonitor/resilience/v1/get_resilience_score.proto` —
  `bool headline_eligible = 17;` on `GetResilienceScoreResponse`
- `proto/worldmonitor/resilience/v1/resilience.proto` —
  `bool headline_eligible = 7;` on `ResilienceRankingItem`
- `make generate` regenerated openapi + TS client/server bindings
- `server/worldmonitor/resilience/v1/_shared.ts:buildResilienceScore`
  and the two fallback paths in `ensureResilienceScoreCached` populate
  the field. Happy path → `true`; invalid country code or missing-cache
  fallback → `false` (the conservative default — those countries can't
  pass the PR-6 gate either)
- `buildRankingItem` passes through from the source-of-truth response;
  null-response fallback returns `false`
- `src/components/resilience-widget-utils.ts:LOCKED_PREVIEW` carries
  `headlineEligible: true` (informational; widget renders nothing
  different yet)
- New test `tests/resilience-headline-eligible-field.test.mts` (5
  pinning tests) — pass-through, fallback default, contract enforcement

7502/7502 tests pass (npm run test:data); typecheck + typecheck:api
clean; lint exit 0.

Plan: docs/plans/2026-04-26-002-feat-resilience-universe-coverage-rebuild-plan.md
PR 3+4+5 just merged: #3452 (commit ba5474f).
@vercel

vercel Bot commented Apr 27, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
worldmonitor Ignored Ignored Preview Apr 27, 2026 11:14am

Request Review

@mintlify

mintlify Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
WorldMonitor 🟢 Ready View Preview Apr 27, 2026, 10:18 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@greptile-apps

greptile-apps Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds headlineEligible: boolean (proto field 17 / field 7) to GetResilienceScoreResponse and ResilienceRankingItem as a no-behavior-change precursor to PR 6's eligibility gate. All new code paths are correctly populated — true on every successful score build, false on all three fallback paths (invalid country code, missing cache, null ranking item).

  • P1 — stale cache entries will surface headlineEligible as absent for up to 6 h post-deploy: The stale-formula guard in ensureResilienceScoreCached only fires when _formula changes ('d6''pc'). Entries written under the same formula tag before this deploy pass the equality check, are returned as-is by stripCacheMeta, and carry headlineEligible: undefined — which JSON.stringify omits. A one-line ?? true (or an explicit undefined fixup after line 706) resolves this within the current PR before PR 6 begins consuming the field for filtering.

Confidence Score: 3/5

Safe to merge with caution — the stale-cache gap will cause headlineEligible to be absent from API responses for up to 6 h after deploy, which is low-impact now but needs fixing before PR 6 uses the field as a filter

A single P1 is present (stale Redis entries serving undefined instead of true), pulling below the 4/5 ceiling. The bug is time-bounded (6 h TTL) and the field is informational-only in this PR, so catastrophic impact is unlikely before PR 6, but it represents a real contract violation of the declared boolean type.

server/worldmonitor/resilience/v1/_shared.ts — specifically the payload assignment on line 684 and the missing backward-compat fixup for pre-PR cache entries

Important Files Changed

Filename Overview
server/worldmonitor/resilience/v1/_shared.ts Adds headlineEligible population across all code paths; stale-formula guard does not cover same-formula pre-PR cache entries, leaving headlineEligible undefined for up to 6 h post-deploy
tests/resilience-headline-eligible-field.test.mts New pinning test file; covers fallback paths well but the happy-path test only exercises a stub literal rather than calling the real build function
proto/worldmonitor/resilience/v1/get_resilience_score.proto Adds bool headline_eligible = 17 with well-documented comment; field number is correct and non-breaking
proto/worldmonitor/resilience/v1/resilience.proto Adds bool headline_eligible = 7 to ResilienceRankingItem; field number is correct and non-breaking
src/components/resilience-widget-utils.ts LOCKED_PREVIEW static fixture updated to include headlineEligible: true, consistent with the PR-2 default for a non-fallback path
src/generated/client/worldmonitor/resilience/v1/service_client.ts Generated types correctly add headlineEligible: boolean (required, non-optional) to both GetResilienceScoreResponse and ResilienceRankingItem
src/generated/server/worldmonitor/resilience/v1/service_server.ts Server-side generated stubs mirror client-side additions correctly
docs/api/ResilienceService.openapi.yaml OpenAPI spec correctly adds headlineEligible boolean field to both GetResilienceScoreResponse and ResilienceRankingItem schemas with descriptive comments

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[GET /get-resilience-score] --> B{normalizeCountryCode}
    B -- empty/invalid --> C[Return fallback\nheadlineEligible: false]
    B -- valid --> D{cachedFetchJson}
    D -- cache miss --> E[buildResilienceScore\nheadlineEligible: true]
    E --> F[Write to Redis\nwith _formula tag]
    D -- cache hit same formula --> G{payload from Redis}
    G -- has headlineEligible --> H[Return\nheadlineEligible: true]
    G -- missing headlineEligible\npre-PR entry --> I[Return\nheadlineEligible: undefined]
    D -- cache hit stale formula --> J[Rebuild score\nheadlineEligible: true]
    J --> K[Write new entry to Redis]
    K --> H
    L[buildRankingItem] --> M{response null?}
    M -- yes --> N[Return fallback\nheadlineEligible: false]
    M -- no --> O[Pass through\nresponse.headlineEligible]
Loading

Comments Outside Diff (1)

  1. server/worldmonitor/resilience/v1/_shared.ts, line 684-706 (link)

    P1 Cache entries pre-dating this PR will surface headlineEligible: undefined

    The stale-formula guard only fires when _formula has changed ('d6' vs 'pc'). A cached entry written under the same formula tag before this PR is deployed will satisfy cached._formula === current, skip the rebuild, and reach stripCacheMeta without headlineEligible. At that point payload.headlineEligible === undefined, which JSON.stringify will omit entirely from the API response — not true, not false, but absent. This window lasts up to the 6 h TTL (RESILIENCE_SCORE_CACHE_TTL_SECONDS = 6 * 60 * 60).

    A minimal backward-compat fixup after the ternary assignment:

    // Backward-compat: entries cached before PR 2 lack headlineEligible.
    // An existing successful score build is conservatively true (same
    // default as buildResilienceScore adds from PR 2 onward).
    if (payload.headlineEligible === undefined) {
      payload = { ...payload, headlineEligible: true };
    }

    The same latent issue exists in getCachedResilienceScores at line ~780 (publicPayload = stripCacheMeta(parsed) for entries that clear the formula-tag check), though that path feeds the ranking handler and old entries there are already skipped by the _formula !== current continue on line 776.

Reviews (1): Last reviewed commit: "feat(resilience): add headlineEligible f..." | Re-trigger Greptile

Comment on lines +83 to +111
describe('PR 2 contract: every code path emits the field', () => {
it('happy-path response includes headlineEligible (compile-time + runtime)', () => {
// The TypeScript compiler enforces this at compile time via the
// generated proto type GetResilienceScoreResponse. This runtime
// assertion exists to catch a future contributor who silently
// makes the field optional or omits it from a stub.
const stub = {
countryCode: 'US',
overallScore: 73,
baselineScore: 82,
stressScore: 58,
stressFactor: 0.21,
level: 'high',
domains: [],
trend: 'stable',
change30d: 0,
lowConfidence: false,
imputationShare: 0.1,
dataVersion: 'v16',
pillars: [],
schemaVersion: '2.0',
headlineEligible: true,
};
assert.ok('headlineEligible' in stub,
'every response object must carry the headlineEligible field per PR-2 §U3');
assert.equal(typeof stub.headlineEligible, 'boolean',
'headlineEligible must be a boolean (no null/undefined sentinel)');
});
});

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 Happy-path test exercises only a stub literal, not the real build function

The test at line 84 asserts 'headlineEligible' in stub and typeof stub.headlineEligible === 'boolean' against a hand-crafted object. Because the stub is defined inline and unconditionally contains the field, this test would pass even if buildResilienceScore or ensureResilienceScoreCached were changed to omit headlineEligible. The PR notes TypeScript enforces this at compile time, which is true for non-optional fields — but that does not catch a future change to mark the field optional (headlineEligible?: boolean).

Consider replacing with an integration call to ensureResilienceScoreCached for a known-valid country code, or at minimum calling buildRankingItem with an actual score built from buildResilienceScore, to validate the PR-2 "true-by-default" contract end-to-end at runtime.

… v16 entries (PR #3457 review round 1)

Reviewer P1: PR #3452 (just merged) wrote v16 score + ranking cache
entries before this PR added the headlineEligible field. The cache
keys are NOT bumped in this PR (it's a no-behavior-change field
addition; bumping would force a 6h recompute window for an
informational field). So existing cache hits return objects missing
the now-required field — TypeScript types are erased at runtime, so
the wire shape would carry `undefined` instead of a boolean, breaking
any downstream `=== true / === false` discriminator that PR 6 will
introduce.

Fix: backfill on read in two sites:

- `_shared.ts:stripCacheMeta` — invoked by `ensureResilienceScoreCached`
  on every score cache hit. Default missing `headlineEligible` to
  `true` (matches the PR-2 happy-path contract for successful score
  builds).
- `get-resilience-ranking.ts` cache-hit branch — invoked when a
  cached ranking payload is served before recompute. Backfill items[]
  AND greyedOut[] with the same `true` default.

Once the cache cycles to post-PR-2 writes (next cron tick, ~6h TTL),
the backfill becomes a no-op for the steady state. Pre-PR-6 the
default is the same as the build-time value (`true`); PR 6 / §U7 will
flip the build-time value to actual eligibility logic, at which point
the new payloads overwrite the legacy default on the next write.

Tests:
- Updated 2 existing ranking-test fixtures to include
  `headlineEligible: true` (representing the post-PR-2 steady state)
- Added a new ranking-test "backfills headlineEligible on cached items
  written before PR 2" with a fixture that deliberately omits the
  field on every item, asserting the backfill defaults to `true`
- Added a new score-test "stripCacheMeta defaults headlineEligible=true
  when the cached payload predates the field"

7504/7504 tests pass; typecheck + typecheck:api clean.
koala73 added 2 commits April 27, 2026 13:43
… actually exercises the cache path (PR #3457 review round 2)

Reviewer P2: the cache-backfill test in
tests/resilience-headline-eligible-field.test.mts:90-117 used
setCachedJson directly. Without UPSTASH_REDIS_REST_URL/TOKEN env vars
that helper silently no-ops; ensureResilienceScoreCached then took the
build-path and returned a fresh response that legitimately has
headlineEligible:true (because the build-path sets it that way) — so
the test "passed" without ever exercising the cache-read backfill it
claims to test. With env vars present, it would have written to real
Redis (worse).

Fix: switch to the fake-upstash pattern used by every other ranking/
score test in this codebase:

- import { installRedis } from './helpers/fake-upstash-redis.mts'
- const { redis } = installRedis({})
- redis.set(legacyKey, JSON.stringify(legacyPayload))

Plus two new assertions to PROVE the cache path was exercised (not the
build path silently passing):
- assert.equal(response.overallScore, 60)
  — the cached payload's value, NOT what buildResilienceScore would
    compute for an empty-fixture (typically 0)
- assert.equal(response.dataVersion, 'v16')
  — also from the cached payload

Mutation-test verified the new wiring actually catches regressions:
disabling the stripCacheMeta backfill makes this test fail (and the
other 5 in the suite still pass), confirming the backfill assertion
is now load-bearing.

Note: also pinned `_formula: 'd6'` on the legacy fixture so the stale-
formula gate in ensureResilienceScoreCached doesn't reject the legacy
payload (which would force a rebuild and silently route through the
build-path again — the same trap as the original bug).

7504/7504 tests pass; typecheck clean.
…ntry assertion (PR #3457 review round 3)

Greptile P2: the "happy-path response includes headlineEligible" test
was a hand-crafted literal stub that asserted `'headlineEligible' in
stub`. Because the stub was defined inline and unconditionally
contained the field, it would have passed even if buildResilienceScore
or ensureResilienceScoreCached stopped emitting the field. TypeScript
type enforcement also doesn't catch a future contributor who marks
the field optional (`headlineEligible?: boolean`).

First-cut fix asserted on the response of ensureResilienceScoreCached
— but that path goes through stripCacheMeta which BACKFILLS missing
headlineEligible to true (PR-2 review round 1 defense-in-depth). So
even with buildResilienceScore not emitting the field, the response
would still test as `true`.

Correct approach: drive a real cache-miss → build → store sequence,
then read the RAW cache entry directly from fake-redis. The raw
stored payload bypasses stripCacheMeta's backfill, so a missing field
in buildResilienceScore propagates straight through and the assertion
fires.

Mutation-verified: removing `headlineEligible: true` from the
buildResilienceScore return object now causes this test to fail (1/6
in the suite). With the field present, all 6 pass.

Net change: 1 test, ~30 LOC, now actually exercises the contract it
claims to enforce instead of asserting a tautology over a hand-crafted
literal.
@koala73
koala73 merged commit 178cd6c into main Apr 27, 2026
12 checks passed
koala73 added a commit that referenced this pull request Apr 27, 2026
… v16→v17) (#3469)

Plan 2026-04-26-002 §U7 (PR 6 in the 8-PR sequence) — flips
`headlineEligible` from PR 2's "true everywhere" no-behavior-change
contract to the actual eligibility logic (origin Q2 + Q5):

  coverage >= 0.65 AND (population >= 200k OR coverage >= 0.85) AND !lowConfidence

The headline ranking endpoint (`get-resilience-ranking.ts`) now filters
items[] by `headlineEligible: true`; ineligible items move to
`greyedOut`. Raw API endpoints (per-country score) keep returning the
full set with the field surfaced — only the *ranking* endpoint applies
the filter.

§Files

- `_shared.ts`: new `computeHeadlineEligible()` + 3 exported constants
  (HEADLINE_ELIGIBLE_MIN_COVERAGE=0.65, _MIN_POPULATION_MILLIONS=0.2,
  _HIGH_COVERAGE=0.85). Wired into `buildResilienceScore`'s response
  population. Reader memoization extended so the new IMF labor read
  (for population) shares the per-build cache with `scoreAllDimensions`.
- `_dimension-scorers.ts`: exported `RESILIENCE_IMF_LABOR_KEY` + new
  `readCountryPopulationMillionsForGate()` helper. Differs from the §U6
  `readPopulationMillions` in two ways: returns `null` for unknown-pop
  countries (instead of the 0.5M default — the gate must distinguish
  "known small" from "unknown"), and DOES NOT apply the §U6 0.5M
  tiny-state floor (gate needs the real population to decide).
- `get-resilience-ranking.ts`: `passesHeadlineGate` predicate combines
  the existing GREY_OUT_COVERAGE_THRESHOLD (0.40) with the new
  `headlineEligible === true` check. Items[]/greyedOut[] split by it.
- `tests/helpers/resilience-release-fixtures.mts`: add
  `economic:imf:labor:v1` fixture (uniform 50M placeholder for all 43
  G20+EU27 countries) so the release-gate test's countries pass the
  population branch of the new filter.

§Cache prefixes (per cache-prefix-bump-propagation-scope skill):
- RESILIENCE_SCORE_CACHE_PREFIX v16 → v17 (pre-PR-6 score entries
  carry headlineEligible:true unconditionally; would let ineligible
  countries through the headline filter for the full 6h TTL)
- RESILIENCE_RANKING_CACHE_KEY v16 → v17 (same — pre-PR-6 ranking
  cache reflects "all true" world)
- RESILIENCE_HISTORY_KEY_PREFIX v11 → v12 (lockstep — bump pattern
  consistency + audit-trail clean even though history doesn't carry
  the field)
- 10 hardcoded literal sites bulk-updated across tests/, scripts/,
  api/health.js
- Stale "(v16)" test descriptions updated to "(v17)" in two files

§Tests
- New `tests/resilience-headline-eligible-gate.test.mts` (10 tests):
  truth-table coverage of `computeHeadlineEligible` — happy path,
  lowConfidence short-circuit, the 0.65 floor, the 200k population
  boundary, the 0.85 high-coverage compensator, and the unknown-pop
  conservative default.
- 7556/7556 tests pass; typecheck + typecheck:api + lint all clean.

Plan: docs/plans/2026-04-26-002-feat-resilience-universe-coverage-rebuild-plan.md
Predecessor: PR #3457 (PR 2 / §U3 — added the headlineEligible field)
Next: PR 7 / §U8 (methodology rewrite + widget badge polish)
fuleinist pushed a commit to fuleinist/worldmonitor that referenced this pull request May 9, 2026
…nses (plan 002 PR 2) (koala73#3457)

* feat(resilience): add headlineEligible field to score + ranking responses (plan 002 PR 2 §U3)

Plan 2026-04-26-002 §U3 (PR 2 in the 8-PR sequence) — introduces a new
`bool headline_eligible = N;` field on `GetResilienceScoreResponse`
and `ResilienceRankingItem`. PR 2 populates `true` for every successful
score build (no behavior change); PR 6 / §U7 swaps the population logic
to the actual eligibility gate (coverage ≥ 0.65 AND (population ≥ 200k
OR coverage ≥ 0.85) AND !lowConfidence) and the headline ranking endpoint
filters by this field.

Why land this as a precursor: the proto + generated TS surface change is
itself a noisy diff (regenerated openapi yaml/json + client/server stubs)
that's easier to review on its own than mixed in with PR 6's gate logic.
Downstream consumers (widget, raw API) can begin reading the field
informationally before the gate flips, avoiding a coupled "field +
behavior" PR.

Files:
- `proto/worldmonitor/resilience/v1/get_resilience_score.proto` —
  `bool headline_eligible = 17;` on `GetResilienceScoreResponse`
- `proto/worldmonitor/resilience/v1/resilience.proto` —
  `bool headline_eligible = 7;` on `ResilienceRankingItem`
- `make generate` regenerated openapi + TS client/server bindings
- `server/worldmonitor/resilience/v1/_shared.ts:buildResilienceScore`
  and the two fallback paths in `ensureResilienceScoreCached` populate
  the field. Happy path → `true`; invalid country code or missing-cache
  fallback → `false` (the conservative default — those countries can't
  pass the PR-6 gate either)
- `buildRankingItem` passes through from the source-of-truth response;
  null-response fallback returns `false`
- `src/components/resilience-widget-utils.ts:LOCKED_PREVIEW` carries
  `headlineEligible: true` (informational; widget renders nothing
  different yet)
- New test `tests/resilience-headline-eligible-field.test.mts` (5
  pinning tests) — pass-through, fallback default, contract enforcement

7502/7502 tests pass (npm run test:data); typecheck + typecheck:api
clean; lint exit 0.

Plan: docs/plans/2026-04-26-002-feat-resilience-universe-coverage-rebuild-plan.md
PR 3+4+5 just merged: koala73#3452 (commit ba5474f).

* fix(resilience): backfill headlineEligible on cache read for pre-PR-2 v16 entries (PR koala73#3457 review round 1)

Reviewer P1: PR koala73#3452 (just merged) wrote v16 score + ranking cache
entries before this PR added the headlineEligible field. The cache
keys are NOT bumped in this PR (it's a no-behavior-change field
addition; bumping would force a 6h recompute window for an
informational field). So existing cache hits return objects missing
the now-required field — TypeScript types are erased at runtime, so
the wire shape would carry `undefined` instead of a boolean, breaking
any downstream `=== true / === false` discriminator that PR 6 will
introduce.

Fix: backfill on read in two sites:

- `_shared.ts:stripCacheMeta` — invoked by `ensureResilienceScoreCached`
  on every score cache hit. Default missing `headlineEligible` to
  `true` (matches the PR-2 happy-path contract for successful score
  builds).
- `get-resilience-ranking.ts` cache-hit branch — invoked when a
  cached ranking payload is served before recompute. Backfill items[]
  AND greyedOut[] with the same `true` default.

Once the cache cycles to post-PR-2 writes (next cron tick, ~6h TTL),
the backfill becomes a no-op for the steady state. Pre-PR-6 the
default is the same as the build-time value (`true`); PR 6 / §U7 will
flip the build-time value to actual eligibility logic, at which point
the new payloads overwrite the legacy default on the next write.

Tests:
- Updated 2 existing ranking-test fixtures to include
  `headlineEligible: true` (representing the post-PR-2 steady state)
- Added a new ranking-test "backfills headlineEligible on cached items
  written before PR 2" with a fixture that deliberately omits the
  field on every item, asserting the backfill defaults to `true`
- Added a new score-test "stripCacheMeta defaults headlineEligible=true
  when the cached payload predates the field"

7504/7504 tests pass; typecheck + typecheck:api clean.

* test(resilience): wire backfill regression test to fake-upstash so it actually exercises the cache path (PR koala73#3457 review round 2)

Reviewer P2: the cache-backfill test in
tests/resilience-headline-eligible-field.test.mts:90-117 used
setCachedJson directly. Without UPSTASH_REDIS_REST_URL/TOKEN env vars
that helper silently no-ops; ensureResilienceScoreCached then took the
build-path and returned a fresh response that legitimately has
headlineEligible:true (because the build-path sets it that way) — so
the test "passed" without ever exercising the cache-read backfill it
claims to test. With env vars present, it would have written to real
Redis (worse).

Fix: switch to the fake-upstash pattern used by every other ranking/
score test in this codebase:

- import { installRedis } from './helpers/fake-upstash-redis.mts'
- const { redis } = installRedis({})
- redis.set(legacyKey, JSON.stringify(legacyPayload))

Plus two new assertions to PROVE the cache path was exercised (not the
build path silently passing):
- assert.equal(response.overallScore, 60)
  — the cached payload's value, NOT what buildResilienceScore would
    compute for an empty-fixture (typically 0)
- assert.equal(response.dataVersion, 'v16')
  — also from the cached payload

Mutation-test verified the new wiring actually catches regressions:
disabling the stripCacheMeta backfill makes this test fail (and the
other 5 in the suite still pass), confirming the backfill assertion
is now load-bearing.

Note: also pinned `_formula: 'd6'` on the legacy fixture so the stale-
formula gate in ensureResilienceScoreCached doesn't reject the legacy
payload (which would force a rebuild and silently route through the
build-path again — the same trap as the original bug).

7504/7504 tests pass; typecheck clean.

* test(resilience): replace stub-literal contract test with raw-cache-entry assertion (PR koala73#3457 review round 3)

Greptile P2: the "happy-path response includes headlineEligible" test
was a hand-crafted literal stub that asserted `'headlineEligible' in
stub`. Because the stub was defined inline and unconditionally
contained the field, it would have passed even if buildResilienceScore
or ensureResilienceScoreCached stopped emitting the field. TypeScript
type enforcement also doesn't catch a future contributor who marks
the field optional (`headlineEligible?: boolean`).

First-cut fix asserted on the response of ensureResilienceScoreCached
— but that path goes through stripCacheMeta which BACKFILLS missing
headlineEligible to true (PR-2 review round 1 defense-in-depth). So
even with buildResilienceScore not emitting the field, the response
would still test as `true`.

Correct approach: drive a real cache-miss → build → store sequence,
then read the RAW cache entry directly from fake-redis. The raw
stored payload bypasses stripCacheMeta's backfill, so a missing field
in buildResilienceScore propagates straight through and the assertion
fires.

Mutation-verified: removing `headlineEligible: true` from the
buildResilienceScore return object now causes this test to fail (1/6
in the suite). With the field present, all 6 pass.

Net change: 1 test, ~30 LOC, now actually exercises the contract it
claims to enforce instead of asserting a tautology over a hand-crafted
literal.
fuleinist pushed a commit to fuleinist/worldmonitor that referenced this pull request May 9, 2026
… v16→v17) (koala73#3469)

Plan 2026-04-26-002 §U7 (PR 6 in the 8-PR sequence) — flips
`headlineEligible` from PR 2's "true everywhere" no-behavior-change
contract to the actual eligibility logic (origin Q2 + Q5):

  coverage >= 0.65 AND (population >= 200k OR coverage >= 0.85) AND !lowConfidence

The headline ranking endpoint (`get-resilience-ranking.ts`) now filters
items[] by `headlineEligible: true`; ineligible items move to
`greyedOut`. Raw API endpoints (per-country score) keep returning the
full set with the field surfaced — only the *ranking* endpoint applies
the filter.

§Files

- `_shared.ts`: new `computeHeadlineEligible()` + 3 exported constants
  (HEADLINE_ELIGIBLE_MIN_COVERAGE=0.65, _MIN_POPULATION_MILLIONS=0.2,
  _HIGH_COVERAGE=0.85). Wired into `buildResilienceScore`'s response
  population. Reader memoization extended so the new IMF labor read
  (for population) shares the per-build cache with `scoreAllDimensions`.
- `_dimension-scorers.ts`: exported `RESILIENCE_IMF_LABOR_KEY` + new
  `readCountryPopulationMillionsForGate()` helper. Differs from the §U6
  `readPopulationMillions` in two ways: returns `null` for unknown-pop
  countries (instead of the 0.5M default — the gate must distinguish
  "known small" from "unknown"), and DOES NOT apply the §U6 0.5M
  tiny-state floor (gate needs the real population to decide).
- `get-resilience-ranking.ts`: `passesHeadlineGate` predicate combines
  the existing GREY_OUT_COVERAGE_THRESHOLD (0.40) with the new
  `headlineEligible === true` check. Items[]/greyedOut[] split by it.
- `tests/helpers/resilience-release-fixtures.mts`: add
  `economic:imf:labor:v1` fixture (uniform 50M placeholder for all 43
  G20+EU27 countries) so the release-gate test's countries pass the
  population branch of the new filter.

§Cache prefixes (per cache-prefix-bump-propagation-scope skill):
- RESILIENCE_SCORE_CACHE_PREFIX v16 → v17 (pre-PR-6 score entries
  carry headlineEligible:true unconditionally; would let ineligible
  countries through the headline filter for the full 6h TTL)
- RESILIENCE_RANKING_CACHE_KEY v16 → v17 (same — pre-PR-6 ranking
  cache reflects "all true" world)
- RESILIENCE_HISTORY_KEY_PREFIX v11 → v12 (lockstep — bump pattern
  consistency + audit-trail clean even though history doesn't carry
  the field)
- 10 hardcoded literal sites bulk-updated across tests/, scripts/,
  api/health.js
- Stale "(v16)" test descriptions updated to "(v17)" in two files

§Tests
- New `tests/resilience-headline-eligible-gate.test.mts` (10 tests):
  truth-table coverage of `computeHeadlineEligible` — happy path,
  lowConfidence short-circuit, the 0.65 floor, the 200k population
  boundary, the 0.85 high-coverage compensator, and the unknown-pop
  conservative default.
- 7556/7556 tests pass; typecheck + typecheck:api + lint all clean.

Plan: docs/plans/2026-04-26-002-feat-resilience-universe-coverage-rebuild-plan.md
Predecessor: PR koala73#3457 (PR 2 / §U3 — added the headlineEligible field)
Next: PR 7 / §U8 (methodology rewrite + widget badge polish)
@koala73
koala73 deleted the feat/resilience-headline-eligible-field branch June 25, 2026 14:43
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