3 Commits

Author SHA1 Message Date
Elie Habib
e1b3796939 fix(supply-chain): gate get-sector-dependency via premium-paths (#2978)
* fix(supply-chain): gate get-sector-dependency via premium-paths

Non-PRO browser callers were receiving HTTP 200 with an empty payload
from get-sector-dependency.ts instead of HTTP 403 from the gateway.
Root cause: the RPC path was missing from PREMIUM_RPC_PATHS, so the
gateway never enforced the API-key / bearer-token check and the handler
silently returned its empty-shape fallback for non-PRO sessions.

Add /api/supply-chain/v1/get-sector-dependency to PREMIUM_RPC_PATHS so
the gateway forces the legacy-pro-bearer check and returns 401/403 for
unauthenticated / non-PRO callers. Handler-level isCallerPremium stays
as defense-in-depth.

* test(supply-chain): assert get-sector-dependency is in PREMIUM_RPC_PATHS

Guards against accidental removal of the premium gate added in this PR.
Follows the established pattern in tests/supply-chain-sprint2.test.mjs
(get-bypass-options, get-country-cost-shock) and
tests/get-regional-snapshot.test.mts.
2026-04-12 08:36:42 +04:00
Elie Habib
23ed4eba44 fix(supply-chain): address all code review findings from PR #2873 (#2878)
* fix(supply-chain): address all code review findings from PR #2873

- Rename costIncreasePct → supplyDeficitPct (semantic correction)
- Add primaryChokepointWarRiskTier to GetBypassOptionsResponse
- Consolidate ThreatLevel/threatLevelToWarRiskTier into _insurance-tier.ts
- Replace inline CpEntry/ChokepointStatusCacheEntry with ChokepointInfo
- Add outer cachedFetchJson wrapper (3 serial Redis reads → 1 on warm path)
- Add hs2 validation guard matching sibling handler pattern
- Extract CHOKEPOINT_STATUS_KEY constant; eliminate string literal duplication
- Add SCORE_RISK_WEIGHT/SCORE_COST_WEIGHT named constants; clamp liveScore ≥ 0
- Add Math.max(0,...) to liveScore for sub-1.0 cost multiplier corridors
- Fix closurePct: req.closurePct ?? 100 (was || which falsy-coalesced zero)
- Type fetchBypassOptions cargoType as CargoType (was implicit string)
- Add exhaustiveness check to threatLevelToInsurancePremiumBps switch
- Move TIER_RANK to module level in _insurance-tier.ts
- Update WIDGET_PRO_SYSTEM_PROMPT with both new PRO RPCs

* fix(supply-chain): fix supplyDeficitPct averaging and coverageDays sentinel

- Remove .filter(d > 0) from productDeficits: zero-deficit products have demand
  and must stay in the denominator to avoid overstating the average
- Clamp coverageDays = Math.max(0, effectiveCoverDays): prevents -1 net-exporter
  sentinel from leaking into the public API response
- Update proto comment: document 0 for net exporters
- Add test assertions for both contracts

* chore(api-docs): regenerate OpenAPI docs for coverage_days comment update

* refactor(supply-chain): use CHOKEPOINT_STATUS_KEY in chokepoint-status writer

The key was extracted to cache-keys.ts in the previous commit but the primary
writer (getChokepointStatus) and BOOTSTRAP_CACHE_KEYS still embedded the raw
string literal. Import the constant at both sites to complete the refactor.

* test: update supply-chain-v2 assertions for CHOKEPOINT_STATUS_KEY refactor

Handler now imports CHOKEPOINT_STATUS_KEY as REDIS_CACHE_KEY from cache-keys.ts
rather than defining a local constant. BOOTSTRAP_CACHE_KEYS also references the
constant. Update source-string assertions to match the new patterns.

* fix: keep BOOTSTRAP_CACHE_KEYS.chokepoints as string literal

bootstrap.test.mjs enforces string-literal values in BOOTSTRAP_CACHE_KEYS via
regex. CHOKEPOINT_STATUS_KEY is used in handler imports and is the primary dedup
win; the static registry entry stays as-is per test contract.
2026-04-09 21:41:26 +04:00
Elie Habib
bd07829518 feat(supply-chain): Sprint 2 — bypass corridor intelligence + cost shock engine (#2873)
* feat(supply-chain): Sprint 2 — bypass corridor intelligence + cost shock engine

- src/config/bypass-corridors.ts: ~40 bypass corridors for all 13 chokepoints
- server/supply-chain/v1/get-bypass-options.ts: PRO-gated RPC, live bypass scoring from chokepoint status cache
- server/supply-chain/v1/get-country-cost-shock.ts: PRO-gated RPC, war risk premium BPS + energy coverage days (HS 27)
- server/supply-chain/v1/_insurance-tier.ts: pure function, Lloyd's JWC threat → premium BPS
- gateway.ts + premium-paths.ts: registered both RPCs as slow-browser + PRO-gated
- src/services/supply-chain/index.ts: fetchBypassOptions + fetchCountryCostShock client methods
- proto: GetBypassOptions + GetCountryCostShock messages + service registrations
- tests/supply-chain-sprint2.test.mjs: 61 tests covering all new components

Co-Authored-By: Claude Sonnet 4.6 (200K context) <noreply@anthropic.com>

* fix(cost-shock): call computeEnergyShockScenario directly instead of reading wrong cache key

The old code read from `energy:shock:${iso2}:${chokepointId}:v1` which never
matches the actual v2 cache key written by compute-energy-shock.ts. Fix by
calling computeEnergyShockScenario() directly (it handles v2 caching internally)
and mapping effectiveCoverDays + crude product deficitPct to the response fields.

* fix(cost-shock): average refined product deficitPct instead of looking for non-existent 'crude' product

---------

Co-authored-by: Claude Sonnet 4.6 (200K context) <noreply@anthropic.com>
2026-04-09 20:15:41 +04:00