feat(resilience): T1.6 full grid with imputation + freshness columns#2962
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR completes the Phase 1 T1.6 full grid by wiring the Confidence Score: 5/5Safe to merge once the stacked PRs (#2959, #2961) land; no runtime bugs found. All findings are P2 (stale comments, fixture convention deviation). The normalization functions, type guards, h()-prop skipping of undefined, and CSS layout are all correct. Tests cover the new paths thoroughly. resilience-widget-utils.ts (stale forward-looking comment block + LOCKED_PREVIEW fixture semantic mismatch); ResilienceWidget.ts (stale block comment around renderDimensionConfidenceGrid call) Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[ResilienceScoreResponse] --> B[collectDimensionConfidences]
B --> C[formatDimensionConfidence per dimension]
C --> D1[normalizeImputationClass empty/unknown → null]
C --> D2[normalizeStaleness empty/unknown → null]
C --> D3[normalizeLastObservedAtMs string → number or null]
D1 & D2 & D3 --> E[DimensionConfidence imputationClass / staleness / lastObservedAtMs]
E --> F[renderDimensionConfidenceCell]
F --> G1[label span]
F --> G2[coverage bar]
F --> G3[imputation icon getImputationClassIcon]
F --> G4[pct span]
F --> G5[freshness dot getStalenessLabel]
G1 & G2 & G3 & G4 & G5 --> H[5-column CSS grid cell]
|
| { id: 'cyberDigital', score: 72, coverage: 0.85, observedWeight: 0.85, imputedWeight: 0.15, imputationClass: '', freshness: { lastObservedAtMs: LOCKED_PREVIEW_FRESH_AT_MS, staleness: 'fresh' } }, | ||
| { id: 'logisticsSupply', score: 70, coverage: 0.8, observedWeight: 0.8, imputedWeight: 0.2, imputationClass: 'stable-absence', freshness: { lastObservedAtMs: LOCKED_PREVIEW_FRESH_AT_MS, staleness: 'fresh' } }, | ||
| { id: 'infrastructure', score: 65, coverage: 0.9, observedWeight: 0.9, imputedWeight: 0.1, imputationClass: '', freshness: { lastObservedAtMs: LOCKED_PREVIEW_FRESH_AT_MS, staleness: 'fresh' } }, |
There was a problem hiding this comment.
Fixture
imputationClass set on non-fully-imputed dimensions
The DimensionConfidenceInput JSDoc states: "Empty string when the dimension has any observed data (i.e. the taxonomy only applies to fully-imputed dimensions)." Two dimensions in the fixture break this convention:
logisticsSupply(observedWeight: 0.8) → classifies asobserved, yet carriesimputationClass: 'stable-absence'socialCohesion(observedWeight: 0.9) → same issue
For a blurred locked-preview this is harmless, but it makes the fixture a misleading reference for anyone studying the intended semantics. borderSecurity (observedWeight: 0, imputedWeight: 1) is the correct pattern for carrying an imputation class.
Ships the final slice of Phase 1 T1.7. The foundation (PR #2944) tagged every imputation with a four-class taxonomy at definition time and explicitly deferred the source-failure wiring as a follow-up. PR #2959 exposed the imputationClass field on the ResilienceDimension response. This PR consults the seed-meta failedDatasets array and re-tags imputed dimensions as source-failure when the underlying adapter fetch failed. What this PR commits - New module server/worldmonitor/resilience/v1/_source-failure.ts: readFailedDatasets(reader) reads seed-meta:resilience:static, extracts failedDatasets string[], defensive against missing, null, malformed, non-string entries, and reader throws. failedDimensionsFromDatasets(keys) expands adapter keys into the ResilienceDimensionId set via DATASET_TO_DIMENSIONS. The map is 1-to-1 with the 11 adapters in scripts/seed-resilience-static.mjs fetchAllDatasetMaps(). - scoreAllDimensions decorates the per-dimension score Record after the 13 scorers run: for each dimension in the affected set, if imputationClass != null (i.e. already imputed), overwrite to 'source-failure'. Real-data dimensions keep their existing null class. - Info-level log at the top of the decoration pass when failedDatasets.length > 0, listing failed adapter keys + affected dimensions so ops can see which adapters went down without combing through Redis. - scoreCurrencyExternal: deleted the old absence-based branch that returned imputationClass=null when BIS EER + IMF inflation + WB reserves are all absent. Replaced with an IMPUTE.bisEer fall-through (curated_list_absent, unmonitored) so the taxonomy is the single source of truth. This is the last place in the scorer where a null imputationClass escaped on an imputed return path. - 13 new unit tests in tests/resilience-source-failure.test.mts covering readFailedDatasets defensive branches, failedDimensionsFromDatasets expansion and deduplication, and DATASET_TO_DIMENSIONS coverage against the seed adapter list. - 5 new integration cases in tests/resilience-dimension-scorers.test.mts inside a new describe('resilience source-failure aggregation (T1.7)') block: end-to-end source-failure propagation via tradeSanctions, real-data countries not re-tagged, unaffected dimensions untouched, healthy-seed no-op path. - Updated tests/resilience-scorers.test.mts BIS-null assertion: the "coverage=0 when all seeds missing" contract now carries an explicit exemption for currencyExternal because the legacy null return path is gone (replaced with the taxonomy fall-through). The exemption verifies the new tag is unmonitored. - tests/resilience-dimension-scorers.test.mts 'both BIS and IMF null' case updated to assert the new curated_list_absent semantics. Phase 1 acceptance delta '4-class imputation taxonomy live end-to-end; old absence-based path deleted' now has all four classes emittable and every imputed return path carries a non-null imputationClass. What is deliberately NOT in this PR - No widget rendering changes (PR #2962 already renders the source-failure icon). - No new adapters to DATASET_TO_DIMENSIONS beyond the current 11. - No INDICATOR_REGISTRY sourceKey renames; that is a separate cleanup. - No cache key bump (response shape unchanged). Verified - npm run typecheck and typecheck:api clean - tests/resilience-source-failure.test.mts 13/13 passing - tests/resilience-dimension-scorers.test.mts 63/63 passing - full resilience suite (tests/resilience-*.test.mts + .mjs) passing - npm run test:data 4379/4379 passing - npm run lint exit 0
…ass) Ships the Phase 1 T1.5 propagation pass of the country-resilience reference-grade upgrade plan. PR #2947 shipped the staleness classifier foundation (classifyStaleness, cadence taxonomy, three staleness levels) and explicitly deferred the dimension-level propagation. This PR consumes the classifier and surfaces per dimension freshness on the ResilienceDimension response. What this PR commits - Proto: new DimensionFreshness message + `freshness` field on ResilienceDimension (last_observed_at_ms, staleness string). - New module server/worldmonitor/resilience/v1/_dimension-freshness.ts that reads seed-meta values for every sourceKey in INDICATOR_REGISTRY and aggregates the worst staleness + oldest fetchedAt across the constituent indicators of each dimension. - scoreAllDimensions decorates each dimension score with its freshness result before returning. The 13 dimension scorer function bodies are untouched: aggregation is a decoration pass at the caller level so this PR stays mechanical. - Response builder: _shared.ts buildDimensionList propagates the freshness field to the proto output. - Tests: 10 classifyDimensionFreshness + readFreshnessMap cases in a new test file + response-shape case on the release-gate test. Aggregation rules - last_observed_at_ms: MIN fetchedAt across the dimension's indicators (oldest signal = most conservative bound). 0 when no signal has ever been observed. - staleness: MAX staleness level across the dimension's indicators (stale > aging > fresh). Empty string when the dimension has no indicators in the registry (defensive path). What is deliberately NOT in this PR - No changes to the 13 individual dimension scorer function bodies. Per-signal freshness inside scorers is a future enhancement. - No widget rendering of the freshness badge (T1.6 full grid, PR 3). - No cache key bump: additive int64/string fields with zero defaults. Verified - make generate clean, new interface in regenerated types - typecheck + typecheck:api clean - tests/resilience-dimension-freshness.test.mts all new cases pass - tests/resilience-*.test.mts full suite pass - test:data clean - lint exits 0 on touched files
…P1) Greptile P1 finding on PR #2961: readFreshnessMap() assumed every INDICATOR_REGISTRY sourceKey could be fetched as seed-meta:<sourceKey>, but most entries use placeholder templates like resilience:static:{ISO2}, energy:mix:v1:{ISO2}, and displacement:summary:v1:{year}. Those produce literal lookups like seed-meta:resilience:static:{ISO2} which don't exist in Redis, so the freshness map missed every templated entry and classifyDimensionFreshness marked the affected dimensions stale even with healthy seeds. Most Phase 1 T1.5 freshness badges were broken on arrival. Fix: two-layer resolution in _dimension-freshness.ts. Layer 1 stripTemplateTokens: drop :{placeholder} and :* segments. 'resilience:static:{ISO2}' -> 'resilience:static' 'resilience:static:*' -> 'resilience:static' 'energy:mix:v1:{ISO2}' -> 'energy:mix:v1' 'displacement:summary:v1:{year}' -> 'displacement:summary:v1' Layer 2 stripTrailingVersion: strip trailing :v\d+, mirroring writeExtraKeyWithMeta + runSeed() in scripts/_seed-utils.mjs which never persist the trailing version in seed-meta keys. Handles cyber:threats:v2, infra:outages:v1, unrest:events:v1, conflict:ucdp-events:v1, sanctions:country-counts:v1, and the displacement v1 case above. Layer 3 SOURCE_KEY_META_OVERRIDES: explicit table for drift cases where the two strips still do not match the real seed-meta key. Verified against api/seed-health.js, api/health.js, and scripts/seed-*. Drift cases covered: economic:imf:macro -> economic:imf-macro economic:bis:eer -> economic:bis economic:energy:v1:all -> economic:energy-prices energy:mix -> economic:owid-energy-mix energy:gas-storage -> energy:gas-storage-countries news:threat:summary -> news:threat-summary intelligence:social:reddit -> intelligence:social-reddit readFreshnessMap now deduplicates reads by resolved meta key (so the 15+ resilience:static indicators share one Redis read) and projects per-meta-key results back onto per-sourceKey map entries so classifyDimensionFreshness can keep its existing interface. Regression coverage: - stripTemplateTokens cases for {ISO2}, {year}, and *. - stripTrailingVersion cases for :v1 / :v2 suffixes. - Embedded :v1 carve-out (trade:restrictions:v1:tariff-overview:50 stays unchanged because :v1 is not trailing). - Override cases for the seven drift entries. - Integration test that proves every resilience:static:* / {ISO2} registry entry resolves to the same seed-meta and is marked fresh when that one key has a recent fetchedAt. - healthPublicService end-to-end test: classifies fresh when seed-meta:resilience:static is recent (was stale before the fix). - Registry-coverage assertion: every INDICATOR_REGISTRY sourceKey must resolve to a seed-meta key that either lives in api/seed-health.js, api/health.js, or the test's KNOWN_SEEDS_NOT_IN_HEALTH allowlist (which covers the four seeds written by writeExtraKeyWithMeta / runSeed that no health monitor tracks yet: trade:restrictions, trade:barriers, sanctions:country-counts, economic:energy-prices). Fails loudly if a future registry entry introduces an unknown sourceKey. Note on P1 #2 (scoreCurrencyExternal absence-branch delete): that is PR #2964's scope (T1.7 source-failure wiring), not #2961 (T1.5 propagation pass). #2961 never claimed to delete the fallback branch; no test in this branch expects the new IMPUTE.bisEer fallback. The reviewer conflated the two stacked PRs. #2964 owns the delete.
5dff6be to
45114e1
Compare
Ships the final slice of Phase 1 T1.7. The foundation (PR #2944) tagged every imputation with a four-class taxonomy at definition time and explicitly deferred the source-failure wiring as a follow-up. PR #2959 exposed the imputationClass field on the ResilienceDimension response. This PR consults the seed-meta failedDatasets array and re-tags imputed dimensions as source-failure when the underlying adapter fetch failed. What this PR commits - New module server/worldmonitor/resilience/v1/_source-failure.ts: readFailedDatasets(reader) reads seed-meta:resilience:static, extracts failedDatasets string[], defensive against missing, null, malformed, non-string entries, and reader throws. failedDimensionsFromDatasets(keys) expands adapter keys into the ResilienceDimensionId set via DATASET_TO_DIMENSIONS. The map is 1-to-1 with the 11 adapters in scripts/seed-resilience-static.mjs fetchAllDatasetMaps(). - scoreAllDimensions decorates the per-dimension score Record after the 13 scorers run: for each dimension in the affected set, if imputationClass != null (i.e. already imputed), overwrite to 'source-failure'. Real-data dimensions keep their existing null class. - Info-level log at the top of the decoration pass when failedDatasets.length > 0, listing failed adapter keys + affected dimensions so ops can see which adapters went down without combing through Redis. - scoreCurrencyExternal: deleted the old absence-based branch that returned imputationClass=null when BIS EER + IMF inflation + WB reserves are all absent. Replaced with an IMPUTE.bisEer fall-through (curated_list_absent, unmonitored) so the taxonomy is the single source of truth. This is the last place in the scorer where a null imputationClass escaped on an imputed return path. - 13 new unit tests in tests/resilience-source-failure.test.mts covering readFailedDatasets defensive branches, failedDimensionsFromDatasets expansion and deduplication, and DATASET_TO_DIMENSIONS coverage against the seed adapter list. - 5 new integration cases in tests/resilience-dimension-scorers.test.mts inside a new describe('resilience source-failure aggregation (T1.7)') block: end-to-end source-failure propagation via tradeSanctions, real-data countries not re-tagged, unaffected dimensions untouched, healthy-seed no-op path. - Updated tests/resilience-scorers.test.mts BIS-null assertion: the "coverage=0 when all seeds missing" contract now carries an explicit exemption for currencyExternal because the legacy null return path is gone (replaced with the taxonomy fall-through). The exemption verifies the new tag is unmonitored. - tests/resilience-dimension-scorers.test.mts 'both BIS and IMF null' case updated to assert the new curated_list_absent semantics. Phase 1 acceptance delta '4-class imputation taxonomy live end-to-end; old absence-based path deleted' now has all four classes emittable and every imputed return path carries a non-null imputationClass. What is deliberately NOT in this PR - No widget rendering changes (PR #2962 already renders the source-failure icon). - No new adapters to DATASET_TO_DIMENSIONS beyond the current 11. - No INDICATOR_REGISTRY sourceKey renames; that is a separate cleanup. - No cache key bump (response shape unchanged). Verified - npm run typecheck and typecheck:api clean - tests/resilience-source-failure.test.mts 13/13 passing - tests/resilience-dimension-scorers.test.mts 63/63 passing - full resilience suite (tests/resilience-*.test.mts + .mjs) passing - npm run test:data 4379/4379 passing - npm run lint exit 0
Ships the Phase 1 T1.6 full grid slice of the country-resilience reference-grade upgrade plan. PR #2949 shipped the per-dimension confidence grid scaffold (label + coverage bar + percentage) and explicitly deferred two columns because their proto fields had not landed yet. PR #2959 (imputationClass) and #2961 (freshness) added those fields to the ResilienceDimension response type. This PR wires both into the widget, completing the grid and satisfying the Phase 1 acceptance criterion "Widget shows per-dimension coverage + imputation class + freshness badge". What this PR commits - DimensionConfidence type gains imputationClass and staleness plus a numeric lastObservedAtMs coerced from the proto int64 string. - formatDimensionConfidence normalizes empty string, unknown, and missing fields into typed nulls so downstream rendering can branch safely. - New helpers getImputationClassIcon, getImputationClassLabel, getStalenessLabel for compact glyphs and tooltip strings. - ResilienceWidget.renderDimensionConfidenceCell renders two new columns: * imputation-icon column between the coverage bar and the percentage (color-coded per class, empty when null) * freshness-dot column at the far right (green/yellow/red dot per staleness, empty when null) aria-label and title attributes explain each glyph for a11y. - country-deep-dive.css: cell grid template expanded from 3 columns to 5. New .resilience-widget__dimension-imputation and .resilience-widget__dimension-freshness rules with per-class and per-level color modifiers. Mobile media-query adjusted to keep the 5-column layout readable below 480px. - LOCKED_PREVIEW fixture populated with a realistic mix of classes and staleness levels so the gated-UI preview shows off the new columns. - Tests: normalization cases for imputationClass and freshness, glyph / label helpers, LOCKED_PREVIEW smoke test. What is deliberately NOT in this PR - No scorer changes. source-failure class is exposed for rendering but no scorer path emits it yet; PR 4 of 5 wires the seed-meta failedDatasets consultation. - formatResilienceConfidence single-label fallback is untouched; the cleanup is out of scope. - No new SVG assets; text glyphs and CSS colors only. Verified - typecheck + typecheck:api clean - tests/resilience-widget-utils.test.mts passing - tests/resilience-*.test.mts full suite passing - test:data clean - lint exit 0
cdc323b to
58b2540
Compare
Ships the final slice of Phase 1 T1.7. The foundation (PR #2944) tagged every imputation with a four-class taxonomy at definition time and explicitly deferred the source-failure wiring as a follow-up. PR #2959 exposed the imputationClass field on the ResilienceDimension response. This PR consults the seed-meta failedDatasets array and re-tags imputed dimensions as source-failure when the underlying adapter fetch failed. What this PR commits - New module server/worldmonitor/resilience/v1/_source-failure.ts: readFailedDatasets(reader) reads seed-meta:resilience:static, extracts failedDatasets string[], defensive against missing, null, malformed, non-string entries, and reader throws. failedDimensionsFromDatasets(keys) expands adapter keys into the ResilienceDimensionId set via DATASET_TO_DIMENSIONS. The map is 1-to-1 with the 11 adapters in scripts/seed-resilience-static.mjs fetchAllDatasetMaps(). - scoreAllDimensions decorates the per-dimension score Record after the 13 scorers run: for each dimension in the affected set, if imputationClass != null (i.e. already imputed), overwrite to 'source-failure'. Real-data dimensions keep their existing null class. - Info-level log at the top of the decoration pass when failedDatasets.length > 0, listing failed adapter keys + affected dimensions so ops can see which adapters went down without combing through Redis. - scoreCurrencyExternal: deleted the old absence-based branch that returned imputationClass=null when BIS EER + IMF inflation + WB reserves are all absent. Replaced with an IMPUTE.bisEer fall-through (curated_list_absent, unmonitored) so the taxonomy is the single source of truth. This is the last place in the scorer where a null imputationClass escaped on an imputed return path. - 13 new unit tests in tests/resilience-source-failure.test.mts covering readFailedDatasets defensive branches, failedDimensionsFromDatasets expansion and deduplication, and DATASET_TO_DIMENSIONS coverage against the seed adapter list. - 5 new integration cases in tests/resilience-dimension-scorers.test.mts inside a new describe('resilience source-failure aggregation (T1.7)') block: end-to-end source-failure propagation via tradeSanctions, real-data countries not re-tagged, unaffected dimensions untouched, healthy-seed no-op path. - Updated tests/resilience-scorers.test.mts BIS-null assertion: the "coverage=0 when all seeds missing" contract now carries an explicit exemption for currencyExternal because the legacy null return path is gone (replaced with the taxonomy fall-through). The exemption verifies the new tag is unmonitored. - tests/resilience-dimension-scorers.test.mts 'both BIS and IMF null' case updated to assert the new curated_list_absent semantics. Phase 1 acceptance delta '4-class imputation taxonomy live end-to-end; old absence-based path deleted' now has all four classes emittable and every imputed return path carries a non-null imputationClass. What is deliberately NOT in this PR - No widget rendering changes (PR #2962 already renders the source-failure icon). - No new adapters to DATASET_TO_DIMENSIONS beyond the current 11. - No INDICATOR_REGISTRY sourceKey renames; that is a separate cleanup. - No cache key bump (response shape unchanged). Verified - npm run typecheck and typecheck:api clean - tests/resilience-source-failure.test.mts 13/13 passing - tests/resilience-dimension-scorers.test.mts 63/63 passing - full resilience suite (tests/resilience-*.test.mts + .mjs) passing - npm run test:data 4379/4379 passing - npm run lint exit 0
…2964) * feat(resilience): T1.7 source-failure wiring + delete absence branch Ships the final slice of Phase 1 T1.7. The foundation (PR #2944) tagged every imputation with a four-class taxonomy at definition time and explicitly deferred the source-failure wiring as a follow-up. PR #2959 exposed the imputationClass field on the ResilienceDimension response. This PR consults the seed-meta failedDatasets array and re-tags imputed dimensions as source-failure when the underlying adapter fetch failed. What this PR commits - New module server/worldmonitor/resilience/v1/_source-failure.ts: readFailedDatasets(reader) reads seed-meta:resilience:static, extracts failedDatasets string[], defensive against missing, null, malformed, non-string entries, and reader throws. failedDimensionsFromDatasets(keys) expands adapter keys into the ResilienceDimensionId set via DATASET_TO_DIMENSIONS. The map is 1-to-1 with the 11 adapters in scripts/seed-resilience-static.mjs fetchAllDatasetMaps(). - scoreAllDimensions decorates the per-dimension score Record after the 13 scorers run: for each dimension in the affected set, if imputationClass != null (i.e. already imputed), overwrite to 'source-failure'. Real-data dimensions keep their existing null class. - Info-level log at the top of the decoration pass when failedDatasets.length > 0, listing failed adapter keys + affected dimensions so ops can see which adapters went down without combing through Redis. - scoreCurrencyExternal: deleted the old absence-based branch that returned imputationClass=null when BIS EER + IMF inflation + WB reserves are all absent. Replaced with an IMPUTE.bisEer fall-through (curated_list_absent, unmonitored) so the taxonomy is the single source of truth. This is the last place in the scorer where a null imputationClass escaped on an imputed return path. - 13 new unit tests in tests/resilience-source-failure.test.mts covering readFailedDatasets defensive branches, failedDimensionsFromDatasets expansion and deduplication, and DATASET_TO_DIMENSIONS coverage against the seed adapter list. - 5 new integration cases in tests/resilience-dimension-scorers.test.mts inside a new describe('resilience source-failure aggregation (T1.7)') block: end-to-end source-failure propagation via tradeSanctions, real-data countries not re-tagged, unaffected dimensions untouched, healthy-seed no-op path. - Updated tests/resilience-scorers.test.mts BIS-null assertion: the "coverage=0 when all seeds missing" contract now carries an explicit exemption for currencyExternal because the legacy null return path is gone (replaced with the taxonomy fall-through). The exemption verifies the new tag is unmonitored. - tests/resilience-dimension-scorers.test.mts 'both BIS and IMF null' case updated to assert the new curated_list_absent semantics. Phase 1 acceptance delta '4-class imputation taxonomy live end-to-end; old absence-based path deleted' now has all four classes emittable and every imputed return path carries a non-null imputationClass. What is deliberately NOT in this PR - No widget rendering changes (PR #2962 already renders the source-failure icon). - No new adapters to DATASET_TO_DIMENSIONS beyond the current 11. - No INDICATOR_REGISTRY sourceKey renames; that is a separate cleanup. - No cache key bump (response shape unchanged). Verified - npm run typecheck and typecheck:api clean - tests/resilience-source-failure.test.mts 13/13 passing - tests/resilience-dimension-scorers.test.mts 63/63 passing - full resilience suite (tests/resilience-*.test.mts + .mjs) passing - npm run test:data 4379/4379 passing - npm run lint exit 0 * docs(resilience): update plan doc T1.7 PR number to #2964
Summary
Phase 1 T1.6 full grid slice. PR #2949 shipped the per-dimension confidence grid scaffold and explicitly deferred two columns because their proto fields had not landed yet. PR #2959 (
imputationClass) and #2961 (freshness) added those fields. This PR wires both into the widget, completing the grid and satisfying the Phase 1 acceptance criterion "Widget shows per-dimension coverage + imputation class + freshness badge".Depends on #2959 and #2961
This PR is stacked on top of PR #2961 (T1.5 propagation), which is itself stacked on PR #2959 (T1.7 schema pass). Base branch is
fix/t1-5-freshness-propagation, notmain. Do not merge until the stack lands in order: #2959, then #2961, then this.What this PR commits
DimensionConfidencetype gainsimputationClass,staleness, andlastObservedAtMs(number | null, coerced from proto int64 string).formatDimensionConfidencenormalizes empty string, unknown values, and missing fields into typed nulls.getImputationClassIcon,getImputationClassLabel,getStalenessLabelfor compact glyph rendering and a11y labels.ResilienceWidget.renderDimensionConfidenceCellrenders two new columns:aria-labelandtitleattributes describe each glyph for a11y.country-deep-dive.css: cell grid template expanded from 3 columns to 5 with per-class and per-level color modifiers. Mobile media-query adjusted to keep the 5-column layout readable below 480px.LOCKED_PREVIEWfixture populated with a realistic mix of imputation classes and staleness levels so the gated-UI preview shows off the new columns.formatDimensionConfidencenormalization cases, glyph/label helpers,LOCKED_PREVIEWsmoke test.What is NOT in this PR
source-failureicon/color is rendered but no scorer path emits that class yet; PR 4 of 5 wires the seed-metafailedDatasetsconsultation.formatResilienceConfidencesingle-label fallback. Cleanup out of scope.Phase 1 dependency chain
Test plan
npm run typecheck+typecheck:apicleantests/resilience-widget.test.mtspasses new casestest:dataclean (4384 tests)lintexit 0npm run dev(reviewer)Post-Deploy Monitoring & Validation
titletooltips are non-empty and descriptive.