feat(resilience): activate headline-eligible gate (plan 002 PR 6, v16→v17)#3469
Conversation
… v16→v17) 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)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR activates the Confidence Score: 4/5Safe to merge — all findings are P2 (dead code, missing integration test, fragile cast); no logic errors found in the eligibility gate or ranking split. Only P2 findings: a redundant dead-code conjunct in
Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant getResilienceRanking
participant buildResilienceScore
participant computeHeadlineEligible
participant Redis
Client->>getResilienceRanking: GET /ranking
getResilienceRanking->>Redis: GET resilience:ranking:v17
alt Cache HIT (tag matches)
Redis-->>getResilienceRanking: cached ranking
getResilienceRanking-->>Client: items[] + greyedOut[] (backfillEligibility on undefined fields)
else Cache MISS / stale formula
getResilienceRanking->>Redis: pipeline GET resilience:score:v17:*
Redis-->>getResilienceRanking: cachedScores map
getResilienceRanking->>buildResilienceScore: warm missing countries
buildResilienceScore->>Redis: read IMF labor seed (RESILIENCE_IMF_LABOR_KEY)
Redis-->>buildResilienceScore: imfLaborRaw
buildResilienceScore->>computeHeadlineEligible: coverage, populationMillions, lowConfidence
computeHeadlineEligible-->>buildResilienceScore: headlineEligible boolean
buildResilienceScore-->>getResilienceRanking: GetResilienceScoreResponse (headlineEligible stamped)
getResilienceRanking->>getResilienceRanking: passesHeadlineGate = headlineEligible===true
getResilienceRanking->>Redis: SET resilience:ranking:v17 (items split by gate)
getResilienceRanking-->>Client: items[] (eligible) + greyedOut[] (ineligible + low-coverage)
end
|
| const passesHeadlineGate = (item: ResilienceRankingItem): boolean => | ||
| item.overallCoverage >= GREY_OUT_COVERAGE_THRESHOLD && item.headlineEligible === true; |
There was a problem hiding this comment.
Redundant coverage check in
passesHeadlineGate
GREY_OUT_COVERAGE_THRESHOLD is 0.40, while HEADLINE_ELIGIBLE_MIN_COVERAGE (the floor inside computeHeadlineEligible) is 0.65. Any item with headlineEligible === true necessarily has overallCoverage >= 0.65, which is already above 0.40. The first conjunct is therefore always true when headlineEligible === true, making it dead code. The predicate is logically equivalent to item.headlineEligible === true.
Keeping it as-is causes no incorrect behavior, but it may mislead future readers into thinking there is a class of headlineEligible: true items that could still fail the coverage check.
| const passesHeadlineGate = (item: ResilienceRankingItem): boolean => | |
| item.overallCoverage >= GREY_OUT_COVERAGE_THRESHOLD && item.headlineEligible === true; | |
| const passesHeadlineGate = (item: ResilienceRankingItem): boolean => | |
| item.headlineEligible === true; |
| // | ||
| // PR 6 swaps `headlineEligible` from the PR-2 default `true` to actual | ||
| // eligibility per origin Q2 + Q5: | ||
| // coverage >= 0.65 AND (population >= 200k OR coverage >= 0.85) AND !lowConfidence |
There was a problem hiding this comment.
Misleading file header — ranking handler filter is not tested here
The comment says the file "assert[s] the ranking handler filters by the field," but the file only contains computeHeadlineEligible truth-table tests — there is no describe block or it case that calls getResilienceRanking and verifies that an item with headlineEligible: false ends up in greyedOut[] rather than items[]. The end-to-end handler path is only exercised indirectly by the version-bumped tests in resilience-ranking.test.mts, none of which introduce an ineligible item to assert the move to greyedOut. Either add a handler-level integration test or update the comment to reflect the actual scope.
…ndant check (PR #3469 follow-up) (#3472) * fix(resilience): apply §U7 gate to cache-hit path + drop redundant coverage check (PR #3469 review round 1) Two Greptile P2s, plus a real bug exposed by writing the missing test: (P2) Redundant coverage check in `passesHeadlineGate`: `headlineEligible: true` already implies `overallCoverage >= 0.65` (HEADLINE_ELIGIBLE_MIN_COVERAGE), which is comfortably above GREY_OUT_COVERAGE_THRESHOLD (0.40). The first conjunct was dead code. Reduced the predicate to the single load-bearing check `item.headlineEligible === true` and dropped the now-unused import. The gate is the single source of truth for headline-ranking inclusion. (P2) Misleading file header — the §U7 file's preamble claimed "asserts the ranking handler filters by the field," but the file only exercised `computeHeadlineEligible` truth-table cases. No test actually invoked `getResilienceRanking` with a mixed-eligibility payload to verify the handler routes ineligible items to greyedOut[]. Added two handler-level integration tests: 1. "moves headlineEligible:false items to greyedOut[], NOT items[]" — seeds a cached payload with one eligible (NO) + one ineligible (TV) item, asserts the split. Mutation-verified: replacing the predicate with `() => true` makes this test fail. 2. "headlineEligible:true items pass even when overallCoverage is below the legacy GREY_OUT threshold" — pins the rationale for the P2 simplification: gate is the SoT, not coverage alone. (BUG, found by writing the test) The cache-hit path in `get-resilience-ranking.ts` did NOT apply the gate. It served whatever items[] the cached payload contained, including ineligible ones. In production the v17 ranking cache would be filled by post-PR-6 builds (which DO apply the gate at write time), so this didn't surface in practice — but a partially-migrated cache or any future writer that forgets to filter would silently serve ineligible items in the headline ranking. Fixed by applying the same predicate to the cache- hit branch: `eligibleItems` go to items[], `ineligibleFromItems` join greyedOut[]. This makes the gate truly the single source of truth across both code paths. 7556+12=7568 net tests pass; typecheck + lint clean. * fix(resilience): conservative-false default for missing headlineEligible at v17 (PR #3472 review round 1) Greptile P2 (PR #3472 review round 1): the cache-hit gate at v17 was still backfilling missing `headlineEligible` to `true`. That defaulting made sense at v16 (PR 2 emitted true unconditionally — pre-field entries matched the contract), but at v17 every legitimate writer stamps the field explicitly via `computeHeadlineEligible`. A v17 cache entry missing the field is anomalous (partially-migrated, manual seed, or a future writer bug), and defaulting to `true` lets the anomaly through to the headline ranking. Per the reviewer: "For the cache-hit gate to be the source of truth, missing should be treated as ineligible or force recompute." Fix: flipped the conservative default to `false` in BOTH backfill sites: - `get-resilience-ranking.ts` cache-hit branch: missing field → false → gate routes the item to greyedOut[] instead of items[] - `_shared.ts:stripCacheMeta` (per-country score): missing field → false (sibling backfill, same v17 semantic) Updated 3 test sites that pinned the v16 `true` default: - `tests/resilience-ranking.test.mts` "backfills headlineEligible on cached items written before PR 2" — now asserts both the false default AND the gate routing (item with missing field → greyedOut[], not items[]). - `tests/resilience-headline-eligible-field.test.mts` cache-backfill test — flipped expected to false, updated comment. - `tests/resilience-ranking.test.mts` auth-fallback test — added `headlineEligible: true` to the cached fixture (the test is about auth, not gate filtering; keep the field present so it exercises the auth path cleanly). Mutation-verified: reverting the backfill to `true` makes the new "backfills headlineEligible on cached items written before PR 2" test fail (the item appears in items[] instead of greyedOut[]). 675/675 resilience tests pass; typecheck clean.
… 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)
…ndant check (PR koala73#3469 follow-up) (koala73#3472) * fix(resilience): apply §U7 gate to cache-hit path + drop redundant coverage check (PR koala73#3469 review round 1) Two Greptile P2s, plus a real bug exposed by writing the missing test: (P2) Redundant coverage check in `passesHeadlineGate`: `headlineEligible: true` already implies `overallCoverage >= 0.65` (HEADLINE_ELIGIBLE_MIN_COVERAGE), which is comfortably above GREY_OUT_COVERAGE_THRESHOLD (0.40). The first conjunct was dead code. Reduced the predicate to the single load-bearing check `item.headlineEligible === true` and dropped the now-unused import. The gate is the single source of truth for headline-ranking inclusion. (P2) Misleading file header — the §U7 file's preamble claimed "asserts the ranking handler filters by the field," but the file only exercised `computeHeadlineEligible` truth-table cases. No test actually invoked `getResilienceRanking` with a mixed-eligibility payload to verify the handler routes ineligible items to greyedOut[]. Added two handler-level integration tests: 1. "moves headlineEligible:false items to greyedOut[], NOT items[]" — seeds a cached payload with one eligible (NO) + one ineligible (TV) item, asserts the split. Mutation-verified: replacing the predicate with `() => true` makes this test fail. 2. "headlineEligible:true items pass even when overallCoverage is below the legacy GREY_OUT threshold" — pins the rationale for the P2 simplification: gate is the SoT, not coverage alone. (BUG, found by writing the test) The cache-hit path in `get-resilience-ranking.ts` did NOT apply the gate. It served whatever items[] the cached payload contained, including ineligible ones. In production the v17 ranking cache would be filled by post-PR-6 builds (which DO apply the gate at write time), so this didn't surface in practice — but a partially-migrated cache or any future writer that forgets to filter would silently serve ineligible items in the headline ranking. Fixed by applying the same predicate to the cache- hit branch: `eligibleItems` go to items[], `ineligibleFromItems` join greyedOut[]. This makes the gate truly the single source of truth across both code paths. 7556+12=7568 net tests pass; typecheck + lint clean. * fix(resilience): conservative-false default for missing headlineEligible at v17 (PR koala73#3472 review round 1) Greptile P2 (PR koala73#3472 review round 1): the cache-hit gate at v17 was still backfilling missing `headlineEligible` to `true`. That defaulting made sense at v16 (PR 2 emitted true unconditionally — pre-field entries matched the contract), but at v17 every legitimate writer stamps the field explicitly via `computeHeadlineEligible`. A v17 cache entry missing the field is anomalous (partially-migrated, manual seed, or a future writer bug), and defaulting to `true` lets the anomaly through to the headline ranking. Per the reviewer: "For the cache-hit gate to be the source of truth, missing should be treated as ineligible or force recompute." Fix: flipped the conservative default to `false` in BOTH backfill sites: - `get-resilience-ranking.ts` cache-hit branch: missing field → false → gate routes the item to greyedOut[] instead of items[] - `_shared.ts:stripCacheMeta` (per-country score): missing field → false (sibling backfill, same v17 semantic) Updated 3 test sites that pinned the v16 `true` default: - `tests/resilience-ranking.test.mts` "backfills headlineEligible on cached items written before PR 2" — now asserts both the false default AND the gate routing (item with missing field → greyedOut[], not items[]). - `tests/resilience-headline-eligible-field.test.mts` cache-backfill test — flipped expected to false, updated comment. - `tests/resilience-ranking.test.mts` auth-fallback test — added `headlineEligible: true` to the cached fixture (the test is about auth, not gate filtering; keep the field present so it exercises the auth path cleanly). Mutation-verified: reverting the backfill to `true` makes the new "backfills headlineEligible on cached items written before PR 2" test fail (the item appears in items[] instead of greyedOut[]). 675/675 resilience tests pass; typecheck clean.
Summary
Plan 2026-04-26-002 §U7 (PR 6 in the 8-PR sequence) — flips
headlineEligiblefrom PR 2's "true everywhere" no-behavior-change contract to the actual eligibility logic:The headline ranking endpoint (
get-resilience-ranking.ts) now filtersitems[]byheadlineEligible: true; ineligible items move togreyedOut. Raw API endpoints (per-country score) keep returning the full set with the field surfaced — only the ranking endpoint applies the filter.Why this matters empirically
coverage >= 0.85branch alone — no inflated permissiveness from a default-pop assumption.Files
_shared.ts: newcomputeHeadlineEligible()+ 3 exported constants. Wired intobuildResilienceScoreresponse. Reader memoization extended._dimension-scorers.ts: exportedRESILIENCE_IMF_LABOR_KEY+ newreadCountryPopulationMillionsForGate()helper (returnsnullfor unknown, no §U6 floor).get-resilience-ranking.ts:passesHeadlineGatepredicate combiningGREY_OUT_COVERAGE_THRESHOLD+headlineEligible === true. Items split.tests/helpers/resilience-release-fixtures.mts: addeconomic:imf:labor:v1fixture (50M placeholder for all 43 G20+EU27 countries).tests/resilience-headline-eligible-gate.test.mts: 10 truth-table tests forcomputeHeadlineEligible— happy path, lowConfidence short-circuit, the 0.65 floor, the 200k boundary, the 0.85 compensator, and the unknown-pop conservative default.Cache prefix bumps (per
cache-prefix-bump-propagation-scopeskill)RESILIENCE_SCORE_CACHE_PREFIXv16 → v17 (pre-PR-6 score entries carryheadlineEligible: trueunconditionally — would let ineligible countries through the headline filter for the full 6h TTL post-deploy)RESILIENCE_RANKING_CACHE_KEYv16 → v17 (same)RESILIENCE_HISTORY_KEY_PREFIXv11 → v12 (lockstep)tests/,scripts/,api/health.js(v16)test descriptions updated to(v17)Test plan
npm run typecheckcleannpm run typecheck:apicleannpm run lintclean (exit 0)npm run test:data— 7556/7556 pass (10 new + 7546 existing)References
docs/plans/2026-04-26-002-feat-resilience-universe-coverage-rebuild-plan.mdheadlineEligiblefield)