mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* feat(resilience): expose imputationClass on ResilienceDimension (T1.7 schema pass) Ships the Phase 1 T1.7 schema pass of the country-resilience reference grade upgrade plan. PR #2944 shipped the classifier table foundation (ImputationClass type, ImputationEntry interface, IMPUTATION/IMPUTE tagged with four semantic classes) and explicitly deferred the schema propagation. This PR lands that propagation so downstream consumers can distinguish "country is stable" from "country is unmonitored" from "upstream is down" from "structurally not applicable" on a per-dimension basis. What this PR commits - Proto: new imputation_class string field on ResilienceDimension (empty string = dimension has any observed data; otherwise one of stable-absence, unmonitored, source-failure, not-applicable). - Generated TS types: regenerated service_server.ts and service_client.ts via make generate. - Scorer: ResilienceDimensionScore carries ImputationClass | null. WeightedMetric carries an optional imputationClass that imputation paths populate. weightedBlend aggregates the dominant class by weight when the dimension is fully imputed, returns null otherwise. - All IMPUTE.* early-return paths propagate the class from the table (IMPUTE.bisEer, IMPUTE.wtoData, IMPUTE.ipcFood, IMPUTE.unhcrDisplacement). - Response builder: _shared.ts buildDimensionList passes the class through to the ResilienceDimension proto field. - Tests: weightedBlend aggregation semantics (5 cases), dimension-level propagation from IMPUTE tables, serialized response includes the field. What is deliberately NOT in this PR - No widget icon rendering (T1.6 full grid, PR 3 of 5) - No source-failure seed-meta consultation (PR 4 of 5) - No freshness field (T1.5 propagation, PR 2 of 5) - No cache key bump: the new field is empty-string default, existing cached responses continue to deserialize cleanly Verified - make generate clean - npm run typecheck + typecheck:api clean - tests/resilience-dimension-scorers.test.mts all passing (existing + new) - tests/resilience-*.test.mts + test:data suite passing (4361 tests) - npm run lint exits 0 * fix(resilience): normalize cached score responses on read (#2959 P2) Greptile P2 finding on PR #2959: cachedFetchJson and getCachedResilienceScores return pre-change payloads verbatim, so a resilience:score:v7 entry written before this PR lands lacks the imputationClass field. Downstream consumers that read dim.imputationClass get undefined for up to 6 hours until the cache TTL expires. Fix: add normalizeResilienceScoreResponse helper that defaults missing optional fields in place and apply it at both read sites. Defaults imputationClass to empty string, matching the proto3 default for the new imputation_class field. - ensureResilienceScoreCached applies the normalizer after cachedFetchJson returns. - getCachedResilienceScores applies it after each successful JSON.parse on the pipeline result. - Two new test cases: stale payload without imputationClass gets defaulted, present values are preserved. - Not bumping the cache key: stale-read defaults are safe, the key bump would invalidate every cached score for a 6-hour cold-start cycle. The normalizer is extensible when PR #2961 adds freshness to the same payload. P3 finding (broken docs reference) verified invalid: the proto comment points to docs/methodology/country-resilience-index.mdx, which IS the current file. The .md predecessor was renamed in PR #2945 (T1.3 methodology doc promotion to CII parity). No change needed to the comment. * fix(resilience): bump score cache key v7 to v8, drop normalizer (#2959 P2) Second fixup for the Greptile P2 finding on #2959. The previous fixup (40ea22009) added normalizeResilienceScoreResponse to default missing imputationClass fields on cached payloads to empty string. The reviewer correctly pushed back: defaulting to empty string is the proto3 default for "dimension has observed data", which silently misreports pre-rollout imputed dimensions as observed until the 6h TTL expires. Correct fix: bump RESILIENCE_SCORE_CACHE_PREFIX from resilience:score:v7: to resilience:score:v8:. Invalidates every pre-change cache entry, so the next request per country repopulates with the correct imputationClass written by the scorer. Cost: a 6h warmup cycle where first-request-per-country recomputes the score, ~100ms per country across hundreds of requests. Also deletes the normalizeResilienceScoreResponse helper and its two call sites. It was misleading defense-in-depth that can hide future schema drift bugs. Future additive field additions should bump the key, not silently default fields. - server/worldmonitor/resilience/v1/_shared.ts: prefix v7 to v8, delete normalizer function and both call sites. - scripts/seed-resilience-scores.mjs, validate-resilience-correlation.mjs, validate-resilience-backtest.mjs: mirror constants bumped. - tests/resilience-scores-seed.test.mjs: pin literal v7 to v8. - tests/resilience-ranking.test.mts: 7 hardcoded cache keys bumped. - tests/resilience-handlers.test.mts: stray v7 cache key bumped. - tests/resilience-release-gate.test.mts: the two normalizer test cases from40ea22009deleted along with the helper. - docs/methodology/country-resilience-index.mdx: Redis keys table updated from v7 to v8 to match the canonical constant. P3 (broken docs reference) confirmed invalid a second time. docs/methodology/country-resilience-index.mdx exists on origin/main AND on the PR branch with the same blob hashd2ab1ebad3. docs/methodology/resilience-index.md does not exist on either. No proto comment change.