Skip to content

feat(resilience): activate headline-eligible gate (plan 002 PR 6, v16→v17)#3469

Merged
koala73 merged 1 commit into
mainfrom
feat/resilience-headline-eligible-gate
Apr 27, 2026
Merged

feat(resilience): activate headline-eligible gate (plan 002 PR 6, v16→v17)#3469
koala73 merged 1 commit into
mainfrom
feat/resilience-headline-eligible-gate

Conversation

@koala73

@koala73 koala73 commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Summary

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:

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.

Why this matters empirically

  • Tiny states with sub-200k population are excluded from the headline ranking unless data quality is exceptional (≥85% coverage). This compounds with the §U6 per-capita normalization that already shipped in feat(resilience): coverage penalty + source-comprehensive + per-capita (plan 002 PR 3+4+5, v15→v16) #3452 to address the small-state bias the entire plan was scoped to fix.
  • Unknown-population countries (no IMF labor entry) default to the conservative path: they only earn headline status via the coverage >= 0.85 branch alone — no inflated permissiveness from a default-pop assumption.
  • The data-quality compensator (coverage ≥ 0.85) preserves Iceland-class small states with high coverage in the headline.

Files

  • _shared.ts: new computeHeadlineEligible() + 3 exported constants. Wired into buildResilienceScore response. Reader memoization extended.
  • _dimension-scorers.ts: exported RESILIENCE_IMF_LABOR_KEY + new readCountryPopulationMillionsForGate() helper (returns null for unknown, no §U6 floor).
  • get-resilience-ranking.ts: passesHeadlineGate predicate combining GREY_OUT_COVERAGE_THRESHOLD + headlineEligible === true. Items split.
  • tests/helpers/resilience-release-fixtures.mts: add economic:imf:labor:v1 fixture (50M placeholder for all 43 G20+EU27 countries).
  • New tests/resilience-headline-eligible-gate.test.mts: 10 truth-table tests for computeHeadlineEligible — 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-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 post-deploy)
  • RESILIENCE_RANKING_CACHE_KEY v16 → v17 (same)
  • RESILIENCE_HISTORY_KEY_PREFIX v11 → v12 (lockstep)
  • 10 hardcoded literal sites bulk-updated across tests/, scripts/, api/health.js
  • Stale (v16) test descriptions updated to (v17)

Test plan

  • npm run typecheck clean
  • npm run typecheck:api clean
  • npm run lint clean (exit 0)
  • npm run test:data — 7556/7556 pass (10 new + 7546 existing)
  • All response shapes carry the field; the new gate truth-table is fully covered

References

… 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)
@vercel

vercel Bot commented Apr 27, 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 Apr 27, 2026 3:10pm

Request Review

@greptile-apps

greptile-apps Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR activates the headlineEligible gate (plan §U7) by replacing the PR-2 true-everywhere stub with real eligibility logic (coverage >= 0.65 AND (population >= 200k OR coverage >= 0.85) AND !lowConfidence) and applying it in get-resilience-ranking.ts to split items into items[] vs greyedOut[]. The v16→v17 cache prefix bumps are correctly scoped and the readCountryPopulationMillionsForGate helper correctly returns null for unknown-population countries instead of the §U6 0.5M floor, ensuring the conservative-default path.

Confidence Score: 4/5

Safe 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 passesHeadlineGate, a misleading test file comment, and a fragile type cast. Core gate logic, cache-prefix bumps, and population null-handling are all correct.

get-resilience-ranking.ts (dead-code predicate conjunct) and tests/resilience-headline-eligible-gate.test.mts (missing handler-level integration test).

Important Files Changed

Filename Overview
server/worldmonitor/resilience/v1/_shared.ts Core change: adds computeHeadlineEligible() + 3 constants, wires the gate into buildResilienceScore, bumps cache prefix v16→v17. Fragile as GetResilienceScoreResponse cast on the computeOverallCoverage call for the gate (P2).
server/worldmonitor/resilience/v1/get-resilience-ranking.ts Splits allItems into items / greyedOut via passesHeadlineGate; the redundant overallCoverage >= GREY_OUT_COVERAGE_THRESHOLD conjunct is dead code since headlineEligible: true already implies coverage ≥ 0.65 > 0.40 (P2).
server/worldmonitor/resilience/v1/_dimension-scorers.ts Exports RESILIENCE_IMF_LABOR_KEY and adds readCountryPopulationMillionsForGate() — returns null for unknown population, no 0.5M floor. Logic is correct and well-documented.
tests/resilience-headline-eligible-gate.test.mts 10 truth-table tests for computeHeadlineEligible; file header claims to test the ranking handler filter but no such handler-level test exists (P2 misleading comment).
tests/helpers/resilience-release-fixtures.mts Adds economic:imf:labor:v1 fixture with 50M placeholder population for all 43 G20+EU27 countries, satisfying the headline gate's population branch for release-gate tests.
tests/resilience-ranking.test.mts All changes are v16→v17 cache key literal updates; no behavioral changes to test logic.

Sequence Diagram

sequenceDiagram
    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
Loading

Comments Outside Diff (1)

  1. server/worldmonitor/resilience/v1/_shared.ts, line 268 (link)

    P2 Fragile as GetResilienceScoreResponse cast

    computeOverallCoverage today only accesses response.domains, so the partial-object cast { domains } as GetResilienceScoreResponse is safe. However, the cast silently bypasses TypeScript's type checker — if computeOverallCoverage is extended to access any other field of GetResilienceScoreResponse, this call site will silently produce undefined at runtime with no compile error. Consider either accepting a narrower type in computeOverallCoverage (e.g. { domains: ResilienceDomain[] }) or extracting overallCoverage earlier in the pipeline where the full response is already assembled.

Reviews (1): Last reviewed commit: "feat(resilience): activate headline-elig..." | Re-trigger Greptile

Comment on lines +184 to +185
const passesHeadlineGate = (item: ResilienceRankingItem): boolean =>
item.overallCoverage >= GREY_OUT_COVERAGE_THRESHOLD && item.headlineEligible === true;

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 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.

Suggested change
const passesHeadlineGate = (item: ResilienceRankingItem): boolean =>
item.overallCoverage >= GREY_OUT_COVERAGE_THRESHOLD && item.headlineEligible === true;
const passesHeadlineGate = (item: ResilienceRankingItem): boolean =>
item.headlineEligible === true;

Comment on lines +3 to +6
//
// 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

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 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.

@koala73
koala73 merged commit eaf0fc9 into main Apr 27, 2026
12 checks passed
@koala73
koala73 deleted the feat/resilience-headline-eligible-gate branch April 27, 2026 15:48
koala73 added a commit that referenced this pull request Apr 27, 2026
…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.
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)
fuleinist pushed a commit to fuleinist/worldmonitor that referenced this pull request May 9, 2026
…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.
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