chore(resilience): address Ship 1 self-review findings (P2 + P3)

P2: scoreTradePolicy header comment claimed RESILIENCE_SANCTIONS_KEY
and normalizeSanctionCount were "retained" but both were actually
removed during the typecheck pass (TS6133). Replaced with an accurate
one-line pointer to the retire-tag locations at line ~263 (constant)
and line ~542 (helper).

P3: Added a positive-coverage assertion to the formula test —
"DOES read every expected component seed key" — to defend against
accidental component drops in the opposite direction. Symmetric to
the existing "does NOT read sanctions:country-counts:v1" guard.

P3: Cross-linked the methodology doc's "Trade Policy" section to the
resolved-construct paragraph in known-limitations.md so a reader
curious about the dropped sanctions component lands on the rationale
in one click.

🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via [Claude Code](https://claude.com/claude-code) + Compound Engineering v3.0.0

Co-Authored-By: Claude Opus 4.7 (1M context, extended thinking) <noreply@anthropic.com>
This commit is contained in:
Elie Habib
2026-04-25 18:35:46 +04:00
parent f09581bf9b
commit b43d1ca6c7
3 changed files with 38 additions and 6 deletions

View File

@@ -107,6 +107,10 @@ to total 1.0. A separate `financialSystemExposure` dim (plan Phase 2) will
add structural sanctions exposure via BIS Locational Banking Statistics +
WB IDS short-term external debt + FATF AML/CFT listing status.
For the full construct rationale and the rejected alternatives (program-
weight categorization, transit-hub exclusion lists), see
[known-limitations.md § tradeSanctions → tradePolicy](./known-limitations.md#tradesanctions--tradepolicy-ofac-domicile-component-dropped-ship-1-2026-04-25).
| Indicator | Description | Direction | Goalposts (worst-best) | Weight | Source | Cadence |
|---|---|---|---|---|---|---|
| tradeRestrictions | WTO trade restrictions count (IN_FORCE weighted 3x) | Lower is better | 30 - 0 | 0.30 | WTO | Weekly |

View File

@@ -1068,12 +1068,14 @@ export async function scoreCurrencyExternal(
// WTO restrictions count → 0.30 (was 0.15)
// WTO barriers count → 0.30 (was 0.15)
// applied tariff rate → 0.40 (was 0.25)
// `RESILIENCE_SANCTIONS_KEY` and `normalizeSanctionCount` are retained
// (no longer read here) pending plan 2026-04-25-004 Phase 2, which adds the
// `financialSystemExposure` dim built from BIS LBS + WB IDS + FATF status —
// a structural-exposure construct that does not rely on the OFAC count.
// `scripts/seed-sanctions-pressure.mjs` continues to write the seed key for
// other consumers (country-brief generation, ad-hoc analysis).
// The `sanctions:country-counts:v1` seed key is no longer read by this
// module; only `scripts/seed-sanctions-pressure.mjs` continues to WRITE it
// for country-brief generation and ad-hoc analysis. The retired
// `RESILIENCE_SANCTIONS_KEY` constant and `normalizeSanctionCount` helper
// were removed in this PR (see retire-tag at lines ~263 and ~542).
// Phase 2 (Ship 2) adds the `financialSystemExposure` dim built from
// BIS LBS + WB IDS + FATF status — a structural-exposure construct that
// does not rely on the OFAC count.
export async function scoreTradePolicy(
countryCode: string,
reader: ResilienceSeedReader = defaultSeedReader,

View File

@@ -63,6 +63,32 @@ describe('scoreTradePolicy — 3-component weighted-blend formula (Ship 1 contra
);
});
it('DOES read every expected component seed key (defends against accidental drops)', async () => {
// Symmetric counter-positive: if a future refactor accidentally
// drops one of the 3 remaining components, this test names the
// missing reader call directly. The static-record key is templated
// by `readStaticCountry` (resilience:static:{ISO2}); we accept any
// read that includes that prefix.
const observed = new Set<string>();
const reader: ResilienceSeedReader = async (key) => {
observed.add(key);
return null;
};
await scoreTradePolicy(TEST_ISO2, reader);
assert.ok(
observed.has('trade:restrictions:v1:tariff-overview:50'),
'scoreTradePolicy must call reader(trade:restrictions:v1:tariff-overview:50) — WTO restrictions component (weight 0.30)',
);
assert.ok(
observed.has('trade:barriers:v1:tariff-gap:50'),
'scoreTradePolicy must call reader(trade:barriers:v1:tariff-gap:50) — WTO barriers component (weight 0.30)',
);
assert.ok(
[...observed].some((k) => k.startsWith('resilience:static:')),
'scoreTradePolicy must read a resilience:static:{ISO2} key for the applied tariff rate component (weight 0.40)',
);
});
it('reporter-set country with zero restrictions/barriers and no tariff scores 100', async () => {
// Restrictions = 0 → 100 (lowerBetter at the best anchor).
// Barriers = 0 → 100.