feat(supply-chain): Sprint D — GetSectorDependency RPC + vendor route-intelligence API + webhooks (#2905)

* feat(supply-chain): Sprint D — GetSectorDependency RPC + vendor route-intelligence API + webhooks

* fix(supply-chain): move bypass-corridors + chokepoint-registry to server/_shared to fix api/ boundary violations

* fix(supply-chain): webhooks — persist secret, fix sub-resource routing, add ownership check

* fix(supply-chain): address PR #2905 review findings

- Use SHA-256(apiKey) for ownerTag instead of last-12-chars (unambiguous ownership)
- Implement GET /api/v2/shipping/webhooks list route via per-owner Redis Set index
- Tighten SSRF: https-only, expanded metadata hostname blocklist, document DNS rebinding edge-runtime limitation
- Fix get-sector-dependency.ts stale src/config/ imports → server/_shared/ (Greptile P1)

* fix(supply-chain): getSectorDependency returns blank primaryChokepointId for landlocked countries

computeExposures() previously mapped over all of CHOKEPOINT_REGISTRY even
when nearestRouteIds was empty, producing a full array of score-0 entries
in registry insertion order. The caller's exposures[0] then picked the
first registry entry (Suez) as the "primary" chokepoint despite
primaryChokepointExposure = 0. LI, AD, SM, BT and other landlocked
countries were all silently assigned a fake chokepoint.

Fix: guard at the top of computeExposures() -- return [] when input is
empty so primaryChokepointId stays '' and primaryChokepointExposure stays 0.
This commit is contained in:
Elie Habib
2026-04-10 17:12:29 +04:00
committed by GitHub
parent 9f5da40ff1
commit a742537ae5
15 changed files with 1566 additions and 2 deletions

View File

@@ -0,0 +1,52 @@
syntax = "proto3";
package worldmonitor.supply_chain.v1;
import "buf/validate/validate.proto";
import "sebuf/http/annotations.proto";
// DependencyFlag classifies how a country+sector dependency can fail.
enum DependencyFlag {
DEPENDENCY_FLAG_UNSPECIFIED = 0;
// >80% of imports come from a single exporter country.
DEPENDENCY_FLAG_SINGLE_SOURCE_CRITICAL = 1;
// >80% of imports transit a single chokepoint with no viable bypass route.
DEPENDENCY_FLAG_SINGLE_CORRIDOR_CRITICAL = 2;
// Both SINGLE_SOURCE_CRITICAL and SINGLE_CORRIDOR_CRITICAL apply.
DEPENDENCY_FLAG_COMPOUND_RISK = 3;
// Viable bypass exists AND multiple exporters are available.
DEPENDENCY_FLAG_DIVERSIFIABLE = 4;
}
message GetSectorDependencyRequest {
string iso2 = 1 [
(buf.validate.field).required = true,
(buf.validate.field).string.len = 2,
(buf.validate.field).string.pattern = "^[A-Z]{2}$",
(sebuf.http.query) = {name: "iso2"}
];
// HS2 chapter code, e.g. "27" (mineral fuels), "85" (electronics)
string hs2 = 2 [
(buf.validate.field).required = true,
(sebuf.http.query) = {name: "hs2"}
];
}
message GetSectorDependencyResponse {
string iso2 = 1;
string hs2 = 2;
// Human-readable HS2 chapter name.
string hs2_label = 3;
// One or more dependency flags; empty means no critical dependency identified.
repeated DependencyFlag flags = 4;
// ISO2 of the country supplying the largest share of this sector's imports.
string primary_exporter_iso2 = 5;
// Share of imports from the primary exporter (01). 0 = no Comtrade data available.
double primary_exporter_share = 6;
// Chokepoint ID with the highest exposure score for this country+sector.
string primary_chokepoint_id = 7;
// Exposure score for the primary chokepoint (0100).
double primary_chokepoint_exposure = 8;
// Whether at least one viable bypass corridor exists for the primary chokepoint.
bool has_viable_bypass = 9;
string fetched_at = 10;
}

View File

@@ -10,6 +10,7 @@ import "worldmonitor/supply_chain/v1/get_shipping_stress.proto";
import "worldmonitor/supply_chain/v1/get_country_chokepoint_index.proto";
import "worldmonitor/supply_chain/v1/get_bypass_options.proto";
import "worldmonitor/supply_chain/v1/get_country_cost_shock.proto";
import "worldmonitor/supply_chain/v1/get_sector_dependency.proto";
service SupplyChainService {
option (sebuf.http.service_config) = {base_path: "/api/supply-chain/v1"};
@@ -45,4 +46,9 @@ service SupplyChainService {
rpc GetCountryCostShock(GetCountryCostShockRequest) returns (GetCountryCostShockResponse) {
option (sebuf.http.config) = {path: "/get-country-cost-shock", method: HTTP_METHOD_GET};
}
// GetSectorDependency returns dependency flags and risk profile for a country+HS2 sector. PRO-gated.
rpc GetSectorDependency(GetSectorDependencyRequest) returns (GetSectorDependencyResponse) {
option (sebuf.http.config) = {path: "/get-sector-dependency", method: HTTP_METHOD_GET};
}
}