Phase 1 PR1: RegionalSnapshot proto + RPC handler (#2951)

* feat(intelligence): add RegionalSnapshot proto definition

Defines the canonical RegionalSnapshot wire format for Phase 1 of the
Regional Intelligence Model. Mirrors the TypeScript contract in
shared/regions.types.d.ts that Phase 0 landed with.

New proto file: proto/worldmonitor/intelligence/v1/get_regional_snapshot.proto

Messages:
  - RegionalSnapshot (13 top-level fields matching the spec)
  - SnapshotMeta (11 fields including snapshot_id, narrative_provider,
    narrative_model, trigger_reason, snapshot_confidence, missing_inputs,
    stale_inputs, valid_until, versions)
  - RegimeState (label + transition history)
  - BalanceVector (7 axes: 4 pressures + 3 buffers + net_balance + decomposed
    drivers)
  - BalanceDriver (axis, magnitude, evidence_ids, orientation)
  - ActorState (leverage_score, role, domains, delta, evidence_ids)
  - LeverageEdge (actor-to-actor directed influence)
  - ScenarioSet + ScenarioLane (per-horizon distribution normalizing to 1.0)
  - TransmissionPath (typed fields: severity, confidence, latency_hours,
    magnitude range, asset class, template provenance)
  - TriggerLadder + Trigger + TriggerThreshold (structured operator/value/
    window/baseline)
  - MobilityState + AirspaceStatus + FlightCorridorStress + AirportNodeStatus
  - EvidenceItem (typed origin for the trust trail)
  - RegionalNarrative + NarrativeSection (LLM-synthesized text with
    evidence_ids on every section)

RPC: GetRegionalSnapshot(GetRegionalSnapshotRequest) -> GetRegionalSnapshotResponse
  - GET /api/intelligence/v1/get-regional-snapshot
  - region_id validated as lowercase kebab via buf.validate regex
  - No other parameters; the handler reads canonical state

Generated code committed alongside:
  - src/generated/client/worldmonitor/intelligence/v1/service_client.ts
  - src/generated/server/worldmonitor/intelligence/v1/service_server.ts
  - docs/api/IntelligenceService.openapi.{json,yaml}

The generated TypeScript types use camelCase per standard buf codegen, while
Phase 0 persists snapshots in Redis using the snake_case shape from
shared/regions.types.d.ts. The handler lands in a follow-up commit with a
localized snake_case -> camelCase adapter so Phase 0 code stays frozen.

Spec: docs/internal/pro-regional-intelligence-upgrade.md

* feat(intelligence): get-regional-snapshot RPC handler

Reads canonical persisted RegionalSnapshot for a region via the two-hop
lookup pattern established by the Phase 0 persist layer:

  1. GET intelligence:snapshot:v1:{region}:latest -> snapshot_id
  2. GET intelligence:snapshot-by-id:v1:{snapshot_id} -> full snapshot JSON

Returns empty response (snapshot omitted) when:
  - No latest pointer exists (seed has never run or unknown region)
  - Latest pointer references a pruned or TTL-expired snapshot
  - Snapshot JSON is malformed

The handler does NOT recompute on miss. One writer (the seed bundle),
canonical reads. Matches the architecture commitment in the spec.

Includes a full snake_case -> camelCase adapter so the persisted Phase 0
shape (shared/regions.types.d.ts) maps cleanly onto the camelCase proto
wire format generated by buf. The adapter is the single bridge between
the two shapes; Phase 0 code stays frozen. Adapter handles every nested
message: SnapshotMeta, RegimeState, BalanceVector (+pressures/buffers
drivers), ActorState, LeverageEdge, ScenarioSet (+lanes +transmissions),
TransmissionPath, TriggerLadder (+triggers +thresholds), MobilityState
(+airspace +flight corridors +airports), EvidenceItem, RegionalNarrative
(+5 sections +watch items).

Wiring:
  - Registered on intelligenceHandler in handler.ts
  - Added to PREMIUM_RPC_PATHS (src/shared/premium-paths.ts) so the
    gateway enforces Pro subscription or API key
  - Added to RPC_CACHE_TIER with 'slow' tier (300s browser, 1800s edge)
    matching similar premium intelligence RPCs

Not in this PR:
  - LLM narrative generator (follow-up PR2, wires into snapshot writer)
  - RegionalIntelligenceBoard panel UI (follow-up PR3)
  - ENDPOINT_ENTITLEMENTS tier-specific enforcement (PREMIUM_RPC_PATHS
    alone is the Pro gate; only stock-analysis endpoints currently use
    tier-specific enforcement)

* test(intelligence): unit tests for get-regional-snapshot adapter + structural checks

29 tests across 5 suites covering:

adaptSnapshot (18 tests): real unit tests of the snake_case -> camelCase
adapter with synthetic persisted snapshots. Covers every nested message
(SnapshotMeta, RegimeState, BalanceVector with 7 axes + decomposed drivers,
ActorState, LeverageEdge, ScenarioSet with nested lanes and transmissions,
TriggerLadder with all 3 buckets + TriggerThreshold, MobilityState with
airspace/flights/airports, EvidenceItem, RegionalNarrative with all 5
sections + watch_items). Also asserts empty-default behavior when
nested fields are missing.

Handler structural checks (8 tests): validates import of getCachedJson,
canonical key prefixes, two-hop lookup ordering, empty-response fallbacks
on missing pointer or malformed snapshot, and export signature matching
the service interface.

Registration (2 tests): confirms getRegionalSnapshot is imported and
registered on the intelligenceHandler object.

Security wiring (2 tests): confirms the endpoint is in PREMIUM_RPC_PATHS
and RPC_CACHE_TIER with 'slow' tier.

Proto definition (3 tests): confirms the RPC method declaration, region_id
validation regex, RegionalSnapshot top-level field layout, and
BalanceVector 7-axis declaration.

* fix(intelligence): address Greptile P2 review findings on #2951

Two P2 findings from Greptile on the RegionalSnapshot proto+RPC PR.

1) region_id regex permitted trailing and consecutive hyphens
   Old: ^[a-z][a-z0-9-]*$ — accepted "mena-", "east-asia-", "foo--bar"
   New: ^[a-z][a-z0-9]*(-[a-z0-9]+)*$ — strict kebab-case, every hyphen must be
   followed by at least one alphanumeric character. Regenerated openapi JSON/YAML
   via `make generate`. Test assertion updated to match.

2) RPC_CACHE_TIER entry looked like dead code for premium paths
   Greptile flagged that `isPremium` short-circuits the tier lookup to
   'slow-browser' before RPC_CACHE_TIER is consulted, so the entry is never read
   at runtime. Kept the entry because `tests/route-cache-tier.test.mjs` enforces
   a parity contract requiring every generated GET route to have an explicit
   tier. Added a NOTE comment in gateway.ts explaining the policy, and updated
   the security-wiring test with a rationale comment so future maintainers know
   the entry is intentional documentation, not a stale wire.
This commit is contained in:
Elie Habib
2026-04-11 20:19:56 +04:00
committed by GitHub
parent da45e830c9
commit 7da202c25d
11 changed files with 2273 additions and 1 deletions

File diff suppressed because one or more lines are too long

View File

@@ -790,6 +790,45 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/intelligence/v1/get-regional-snapshot:
get:
tags:
- IntelligenceService
summary: GetRegionalSnapshot
description: |-
GetRegionalSnapshot returns the latest persisted RegionalSnapshot for a
region. The snapshot is written every 6h by scripts/seed-regional-snapshots.mjs;
this handler only reads canonical state. Premium-gated.
operationId: GetRegionalSnapshot
parameters:
- name: region_id
in: query
description: |-
Display region id (e.g. "mena", "east-asia", "europe"). See shared/geography.js.
Kebab-case: lowercase alphanumeric groups separated by single hyphens, no
trailing or consecutive hyphens.
required: false
schema:
type: string
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/GetRegionalSnapshotResponse'
"400":
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
default:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Error:
@@ -2414,3 +2453,490 @@ components:
format: double
anomalySignal:
type: boolean
GetRegionalSnapshotRequest:
type: object
properties:
regionId:
type: string
maxLength: 32
minLength: 1
pattern: ^[a-z][a-z0-9]*(-[a-z0-9]+)*$
description: |-
Display region id (e.g. "mena", "east-asia", "europe"). See shared/geography.js.
Kebab-case: lowercase alphanumeric groups separated by single hyphens, no
trailing or consecutive hyphens.
required:
- regionId
description: |-
GetRegionalSnapshotRequest asks for the latest persisted RegionalSnapshot
for a given region. See shared/geography.ts for the canonical region ids.
GetRegionalSnapshotResponse:
type: object
properties:
snapshot:
$ref: '#/components/schemas/RegionalSnapshot'
description: |-
GetRegionalSnapshotResponse returns the latest RegionalSnapshot for the
requested region. The snapshot is written by scripts/seed-regional-snapshots.mjs
on a 6h cron; this handler only reads canonical state.
RegionalSnapshot:
type: object
properties:
regionId:
type: string
generatedAt:
type: integer
format: int64
description: 'Warning: Values > 2^53 may lose precision in JavaScript'
meta:
$ref: '#/components/schemas/SnapshotMeta'
regime:
$ref: '#/components/schemas/RegimeState'
balance:
$ref: '#/components/schemas/BalanceVector'
actors:
type: array
items:
$ref: '#/components/schemas/ActorState'
leverageEdges:
type: array
items:
$ref: '#/components/schemas/LeverageEdge'
scenarioSets:
type: array
items:
$ref: '#/components/schemas/ScenarioSet'
transmissionPaths:
type: array
items:
$ref: '#/components/schemas/TransmissionPath'
triggers:
$ref: '#/components/schemas/TriggerLadder'
mobility:
$ref: '#/components/schemas/MobilityState'
evidence:
type: array
items:
$ref: '#/components/schemas/EvidenceItem'
narrative:
$ref: '#/components/schemas/RegionalNarrative'
description: |-
RegionalSnapshot is the canonical intelligence object for one region.
See docs/internal/pro-regional-intelligence-upgrade.md for the full spec
and shared/regions.types.d.ts for the authoritative TypeScript contract.
SnapshotMeta:
type: object
properties:
snapshotId:
type: string
modelVersion:
type: string
scoringVersion:
type: string
geographyVersion:
type: string
snapshotConfidence:
type: number
format: double
missingInputs:
type: array
items:
type: string
staleInputs:
type: array
items:
type: string
validUntil:
type: integer
format: int64
description: 'Warning: Values > 2^53 may lose precision in JavaScript'
triggerReason:
type: string
description: |-
trigger_reason: scheduled_6h | regime_shift | trigger_activation |
corridor_break | leverage_shift
narrativeProvider:
type: string
narrativeModel:
type: string
description: |-
SnapshotMeta carries the trust trail (versions, confidence, input freshness,
narrative provenance, idempotency id).
RegimeState:
type: object
properties:
label:
type: string
description: |-
label: calm | stressed_equilibrium | coercive_stalemate |
fragmentation_risk | managed_deescalation | escalation_ladder
previousLabel:
type: string
transitionedAt:
type: integer
format: int64
description: 'Warning: Values > 2^53 may lose precision in JavaScript'
transitionDriver:
type: string
description: RegimeState captures the current regime label and transition history.
BalanceVector:
type: object
properties:
coercivePressure:
type: number
format: double
description: Pressures (high = bad)
domesticFragility:
type: number
format: double
capitalStress:
type: number
format: double
energyVulnerability:
type: number
format: double
allianceCohesion:
type: number
format: double
description: Buffers (high = good)
maritimeAccess:
type: number
format: double
energyLeverage:
type: number
format: double
netBalance:
type: number
format: double
description: 'Derived: mean(buffers) - mean(pressures), range [-1, +1]'
pressures:
type: array
items:
$ref: '#/components/schemas/BalanceDriver'
buffers:
type: array
items:
$ref: '#/components/schemas/BalanceDriver'
description: |-
BalanceVector is the 7-axis regional balance score with pressures/buffers
split. See docs/internal/pro-regional-intelligence-appendix-scoring.md for
the per-axis formulas.
BalanceDriver:
type: object
properties:
axis:
type: string
description:
type: string
magnitude:
type: number
format: double
evidenceIds:
type: array
items:
type: string
orientation:
type: string
description: 'orientation: "pressure" | "buffer"'
description: BalanceDriver is one contributor to an axis score. Links back to evidence.
ActorState:
type: object
properties:
actorId:
type: string
name:
type: string
role:
type: string
description: 'role: aggressor | stabilizer | swing | broker'
leverageDomains:
type: array
items:
type: string
description: 'leverage_domains: energy | military | diplomatic | economic | maritime'
leverageScore:
type: number
format: double
delta:
type: number
format: double
evidenceIds:
type: array
items:
type: string
description: ActorState is one geopolitical actor's leverage score in the region.
LeverageEdge:
type: object
properties:
fromActorId:
type: string
toActorId:
type: string
mechanism:
type: string
description: 'mechanism: sanctions | naval_posture | energy_supply | alliance_shift | trade_friction'
strength:
type: number
format: double
evidenceIds:
type: array
items:
type: string
description: LeverageEdge is a directed influence relationship between two actors.
ScenarioSet:
type: object
properties:
horizon:
type: string
description: 'horizon: 24h | 7d | 30d'
lanes:
type: array
items:
$ref: '#/components/schemas/ScenarioLane'
description: |-
ScenarioSet bundles scenario lanes for one time horizon. Lane probabilities
sum to 1.0 within a set.
ScenarioLane:
type: object
properties:
name:
type: string
description: 'name: base | escalation | containment | fragmentation'
probability:
type: number
format: double
triggerIds:
type: array
items:
type: string
consequences:
type: array
items:
type: string
transmissions:
type: array
items:
$ref: '#/components/schemas/TransmissionPath'
description: ScenarioLane is one outcome branch within a horizon set.
TransmissionPath:
type: object
properties:
start:
type: string
mechanism:
type: string
end:
type: string
severity:
type: string
description: 'severity: critical | high | medium | low'
corridorId:
type: string
confidence:
type: number
format: double
latencyHours:
type: integer
format: int32
impactedAssetClass:
type: string
description: 'impacted_asset_class: crude | lng | container | fx | equity | agri | metals | ...'
impactedRegions:
type: array
items:
type: string
magnitudeLow:
type: number
format: double
magnitudeHigh:
type: number
format: double
magnitudeUnit:
type: string
description: 'magnitude_unit: usd_bbl | pct | usd_teu | basis_points | ...'
templateId:
type: string
templateVersion:
type: string
description: |-
TransmissionPath describes how a regional event propagates to markets,
logistics, mobility, or other domains. Typed for ranking and calibration.
TriggerLadder:
type: object
properties:
active:
type: array
items:
$ref: '#/components/schemas/Trigger'
watching:
type: array
items:
$ref: '#/components/schemas/Trigger'
dormant:
type: array
items:
$ref: '#/components/schemas/Trigger'
description: TriggerLadder buckets triggers by activation state.
Trigger:
type: object
properties:
id:
type: string
description:
type: string
threshold:
$ref: '#/components/schemas/TriggerThreshold'
activated:
type: boolean
activatedAt:
type: integer
format: int64
description: 'Warning: Values > 2^53 may lose precision in JavaScript'
scenarioLane:
type: string
description: 'scenario_lane: base | escalation | containment | fragmentation'
evidenceIds:
type: array
items:
type: string
description: Trigger is a structured threshold assertion against a named metric.
TriggerThreshold:
type: object
properties:
metric:
type: string
operator:
type: string
description: 'operator: gt | gte | lt | lte | delta_gt | delta_lt'
value:
type: number
format: double
windowMinutes:
type: integer
format: int32
baseline:
type: string
description: 'baseline: trailing_7d | trailing_30d | fixed'
description: |-
TriggerThreshold defines the metric/operator/value/window/baseline for
deterministic trigger evaluation.
MobilityState:
type: object
properties:
airspace:
type: array
items:
$ref: '#/components/schemas/AirspaceStatus'
flightCorridors:
type: array
items:
$ref: '#/components/schemas/FlightCorridorStress'
airports:
type: array
items:
$ref: '#/components/schemas/AirportNodeStatus'
rerouteIntensity:
type: number
format: double
notamClosures:
type: array
items:
type: string
description: |-
MobilityState captures airspace, flight corridor, and airport node status
for the region. Phase 0 ships empty; Phase 2 wires the data plane.
AirspaceStatus:
type: object
properties:
airspaceId:
type: string
status:
type: string
description: 'status: open | restricted | closed'
reason:
type: string
description: AirspaceStatus captures FIR-level airspace state.
FlightCorridorStress:
type: object
properties:
corridor:
type: string
stressLevel:
type: number
format: double
reroutedFlights24h:
type: integer
format: int32
description: FlightCorridorStress captures per-corridor reroute intensity.
AirportNodeStatus:
type: object
properties:
icao:
type: string
name:
type: string
status:
type: string
description: 'status: normal | disrupted | closed'
disruptionReason:
type: string
description: AirportNodeStatus captures airport-level disruption state.
EvidenceItem:
type: object
properties:
id:
type: string
type:
type: string
description: |-
type: vessel_track | flight_surge | news_headline | cii_spike |
chokepoint_status | sanctions_move | market_signal | mobility_disruption
source:
type: string
description: 'source: AIS | ADSB | GDELT | ACLED | Yahoo | OREF | NOTAM | ...'
summary:
type: string
confidence:
type: number
format: double
observedAt:
type: integer
format: int64
description: 'Warning: Values > 2^53 may lose precision in JavaScript'
theater:
type: string
corridor:
type: string
description: |-
EvidenceItem is one upstream data point linked from balance drivers,
narrative sections, and triggers.
RegionalNarrative:
type: object
properties:
situation:
$ref: '#/components/schemas/NarrativeSection'
balanceAssessment:
$ref: '#/components/schemas/NarrativeSection'
outlook24h:
$ref: '#/components/schemas/NarrativeSection'
outlook7d:
$ref: '#/components/schemas/NarrativeSection'
outlook30d:
$ref: '#/components/schemas/NarrativeSection'
watchItems:
type: array
items:
$ref: '#/components/schemas/NarrativeSection'
description: |-
RegionalNarrative is the LLM-synthesized narrative layer. Every section
links back to evidence via evidence_ids.
NarrativeSection:
type: object
properties:
text:
type: string
evidenceIds:
type: array
items:
type: string
description: NarrativeSection is one block of narrative text plus its supporting evidence.

View File

@@ -0,0 +1,266 @@
syntax = "proto3";
package worldmonitor.intelligence.v1;
import "buf/validate/validate.proto";
import "sebuf/http/annotations.proto";
// GetRegionalSnapshotRequest asks for the latest persisted RegionalSnapshot
// for a given region. See shared/geography.ts for the canonical region ids.
message GetRegionalSnapshotRequest {
// Display region id (e.g. "mena", "east-asia", "europe"). See shared/geography.js.
// Kebab-case: lowercase alphanumeric groups separated by single hyphens, no
// trailing or consecutive hyphens.
string region_id = 1 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 32,
(buf.validate.field).string.pattern = "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
(sebuf.http.query) = {name: "region_id"}
];
}
// GetRegionalSnapshotResponse returns the latest RegionalSnapshot for the
// requested region. The snapshot is written by scripts/seed-regional-snapshots.mjs
// on a 6h cron; this handler only reads canonical state.
message GetRegionalSnapshotResponse {
RegionalSnapshot snapshot = 1;
}
// RegionalSnapshot is the canonical intelligence object for one region.
// See docs/internal/pro-regional-intelligence-upgrade.md for the full spec
// and shared/regions.types.d.ts for the authoritative TypeScript contract.
message RegionalSnapshot {
string region_id = 1;
int64 generated_at = 2 [(sebuf.http.int64_encoding) = INT64_ENCODING_NUMBER];
SnapshotMeta meta = 3;
RegimeState regime = 4;
BalanceVector balance = 5;
repeated ActorState actors = 6;
repeated LeverageEdge leverage_edges = 7;
repeated ScenarioSet scenario_sets = 8;
repeated TransmissionPath transmission_paths = 9;
TriggerLadder triggers = 10;
MobilityState mobility = 11;
repeated EvidenceItem evidence = 12;
RegionalNarrative narrative = 13;
}
// SnapshotMeta carries the trust trail (versions, confidence, input freshness,
// narrative provenance, idempotency id).
message SnapshotMeta {
string snapshot_id = 1;
string model_version = 2;
string scoring_version = 3;
string geography_version = 4;
double snapshot_confidence = 5;
repeated string missing_inputs = 6;
repeated string stale_inputs = 7;
int64 valid_until = 8 [(sebuf.http.int64_encoding) = INT64_ENCODING_NUMBER];
// trigger_reason: scheduled_6h | regime_shift | trigger_activation |
// corridor_break | leverage_shift
string trigger_reason = 9;
string narrative_provider = 10;
string narrative_model = 11;
}
// RegimeState captures the current regime label and transition history.
message RegimeState {
// label: calm | stressed_equilibrium | coercive_stalemate |
// fragmentation_risk | managed_deescalation | escalation_ladder
string label = 1;
string previous_label = 2;
int64 transitioned_at = 3 [(sebuf.http.int64_encoding) = INT64_ENCODING_NUMBER];
string transition_driver = 4;
}
// BalanceVector is the 7-axis regional balance score with pressures/buffers
// split. See docs/internal/pro-regional-intelligence-appendix-scoring.md for
// the per-axis formulas.
message BalanceVector {
// Pressures (high = bad)
double coercive_pressure = 1;
double domestic_fragility = 2;
double capital_stress = 3;
double energy_vulnerability = 4;
// Buffers (high = good)
double alliance_cohesion = 5;
double maritime_access = 6;
double energy_leverage = 7;
// Derived: mean(buffers) - mean(pressures), range [-1, +1]
double net_balance = 8;
// Decomposition
repeated BalanceDriver pressures = 9;
repeated BalanceDriver buffers = 10;
}
// BalanceDriver is one contributor to an axis score. Links back to evidence.
message BalanceDriver {
string axis = 1;
string description = 2;
double magnitude = 3;
repeated string evidence_ids = 4;
// orientation: "pressure" | "buffer"
string orientation = 5;
}
// ActorState is one geopolitical actor's leverage score in the region.
message ActorState {
string actor_id = 1;
string name = 2;
// role: aggressor | stabilizer | swing | broker
string role = 3;
// leverage_domains: energy | military | diplomatic | economic | maritime
repeated string leverage_domains = 4;
double leverage_score = 5;
double delta = 6;
repeated string evidence_ids = 7;
}
// LeverageEdge is a directed influence relationship between two actors.
message LeverageEdge {
string from_actor_id = 1;
string to_actor_id = 2;
// mechanism: sanctions | naval_posture | energy_supply | alliance_shift | trade_friction
string mechanism = 3;
double strength = 4;
repeated string evidence_ids = 5;
}
// ScenarioSet bundles scenario lanes for one time horizon. Lane probabilities
// sum to 1.0 within a set.
message ScenarioSet {
// horizon: 24h | 7d | 30d
string horizon = 1;
repeated ScenarioLane lanes = 2;
}
// ScenarioLane is one outcome branch within a horizon set.
message ScenarioLane {
// name: base | escalation | containment | fragmentation
string name = 1;
double probability = 2;
repeated string trigger_ids = 3;
repeated string consequences = 4;
repeated TransmissionPath transmissions = 5;
}
// TransmissionPath describes how a regional event propagates to markets,
// logistics, mobility, or other domains. Typed for ranking and calibration.
message TransmissionPath {
string start = 1;
string mechanism = 2;
string end = 3;
// severity: critical | high | medium | low
string severity = 4;
string corridor_id = 5;
double confidence = 6;
int32 latency_hours = 7;
// impacted_asset_class: crude | lng | container | fx | equity | agri | metals | ...
string impacted_asset_class = 8;
repeated string impacted_regions = 9;
double magnitude_low = 10;
double magnitude_high = 11;
// magnitude_unit: usd_bbl | pct | usd_teu | basis_points | ...
string magnitude_unit = 12;
string template_id = 13;
string template_version = 14;
}
// TriggerLadder buckets triggers by activation state.
message TriggerLadder {
repeated Trigger active = 1;
repeated Trigger watching = 2;
repeated Trigger dormant = 3;
}
// Trigger is a structured threshold assertion against a named metric.
message Trigger {
string id = 1;
string description = 2;
TriggerThreshold threshold = 3;
bool activated = 4;
int64 activated_at = 5 [(sebuf.http.int64_encoding) = INT64_ENCODING_NUMBER];
// scenario_lane: base | escalation | containment | fragmentation
string scenario_lane = 6;
repeated string evidence_ids = 7;
}
// TriggerThreshold defines the metric/operator/value/window/baseline for
// deterministic trigger evaluation.
message TriggerThreshold {
string metric = 1;
// operator: gt | gte | lt | lte | delta_gt | delta_lt
string operator = 2;
double value = 3;
int32 window_minutes = 4;
// baseline: trailing_7d | trailing_30d | fixed
string baseline = 5;
}
// MobilityState captures airspace, flight corridor, and airport node status
// for the region. Phase 0 ships empty; Phase 2 wires the data plane.
message MobilityState {
repeated AirspaceStatus airspace = 1;
repeated FlightCorridorStress flight_corridors = 2;
repeated AirportNodeStatus airports = 3;
double reroute_intensity = 4;
repeated string notam_closures = 5;
}
// AirspaceStatus captures FIR-level airspace state.
message AirspaceStatus {
string airspace_id = 1;
// status: open | restricted | closed
string status = 2;
string reason = 3;
}
// FlightCorridorStress captures per-corridor reroute intensity.
message FlightCorridorStress {
string corridor = 1;
double stress_level = 2;
int32 rerouted_flights_24h = 3;
}
// AirportNodeStatus captures airport-level disruption state.
message AirportNodeStatus {
string icao = 1;
string name = 2;
// status: normal | disrupted | closed
string status = 3;
string disruption_reason = 4;
}
// EvidenceItem is one upstream data point linked from balance drivers,
// narrative sections, and triggers.
message EvidenceItem {
string id = 1;
// type: vessel_track | flight_surge | news_headline | cii_spike |
// chokepoint_status | sanctions_move | market_signal | mobility_disruption
string type = 2;
// source: AIS | ADSB | GDELT | ACLED | Yahoo | OREF | NOTAM | ...
string source = 3;
string summary = 4;
double confidence = 5;
int64 observed_at = 6 [(sebuf.http.int64_encoding) = INT64_ENCODING_NUMBER];
string theater = 7;
string corridor = 8;
}
// RegionalNarrative is the LLM-synthesized narrative layer. Every section
// links back to evidence via evidence_ids.
message RegionalNarrative {
NarrativeSection situation = 1;
NarrativeSection balance_assessment = 2;
NarrativeSection outlook_24h = 3;
NarrativeSection outlook_7d = 4;
NarrativeSection outlook_30d = 5;
repeated NarrativeSection watch_items = 6;
}
// NarrativeSection is one block of narrative text plus its supporting evidence.
message NarrativeSection {
string text = 1;
repeated string evidence_ids = 2;
}

View File

@@ -24,6 +24,7 @@ import "worldmonitor/intelligence/v1/get_social_velocity.proto";
import "worldmonitor/intelligence/v1/get_country_energy_profile.proto";
import "worldmonitor/intelligence/v1/compute_energy_shock.proto";
import "worldmonitor/intelligence/v1/get_country_port_activity.proto";
import "worldmonitor/intelligence/v1/get_regional_snapshot.proto";
// IntelligenceService provides APIs for technical and strategic intelligence.
service IntelligenceService {
@@ -171,4 +172,11 @@ service IntelligenceService {
rpc GetCountryPortActivity(GetCountryPortActivityRequest) returns (CountryPortActivityResponse) {
option (sebuf.http.config) = {path: "/get-country-port-activity", method: HTTP_METHOD_GET};
}
// GetRegionalSnapshot returns the latest persisted RegionalSnapshot for a
// region. The snapshot is written every 6h by scripts/seed-regional-snapshots.mjs;
// this handler only reads canonical state. Premium-gated.
rpc GetRegionalSnapshot(GetRegionalSnapshotRequest) returns (GetRegionalSnapshotResponse) {
option (sebuf.http.config) = {path: "/get-regional-snapshot", method: HTTP_METHOD_GET};
}
}

View File

@@ -220,6 +220,12 @@ const RPC_CACHE_TIER: Record<string, CacheTier> = {
'/api/intelligence/v1/get-country-energy-profile': 'slow',
'/api/intelligence/v1/compute-energy-shock': 'fast',
'/api/intelligence/v1/get-country-port-activity': 'slow',
// NOTE: get-regional-snapshot is premium-gated via PREMIUM_RPC_PATHS; the
// gateway short-circuits to 'slow-browser' before consulting this map. The
// entry below exists to satisfy the parity contract enforced by
// tests/route-cache-tier.test.mjs (every generated GET route needs a tier)
// and documents the intended tier if the endpoint ever becomes non-premium.
'/api/intelligence/v1/get-regional-snapshot': 'slow',
'/api/resilience/v1/get-resilience-score': 'slow',
'/api/resilience/v1/get-resilience-ranking': 'slow',
};

View File

@@ -0,0 +1,515 @@
import type {
IntelligenceServiceHandler,
ServerContext,
GetRegionalSnapshotRequest,
GetRegionalSnapshotResponse,
RegionalSnapshot,
SnapshotMeta,
RegimeState,
BalanceVector,
BalanceDriver,
ActorState,
LeverageEdge,
ScenarioSet,
ScenarioLane,
TransmissionPath,
TriggerLadder,
Trigger,
TriggerThreshold,
MobilityState,
AirspaceStatus,
FlightCorridorStress,
AirportNodeStatus,
EvidenceItem,
RegionalNarrative,
NarrativeSection,
} from '../../../../src/generated/server/worldmonitor/intelligence/v1/service_server';
import { getCachedJson } from '../../../_shared/redis';
const LATEST_KEY_PREFIX = 'intelligence:snapshot:v1:';
const BY_ID_KEY_PREFIX = 'intelligence:snapshot-by-id:v1:';
// The set of valid region ids is defined in shared/geography.js. We don't
// import the shared module here to avoid cross-directory ESM-in-TS friction;
// the server handler accepts any lowercase kebab id and lets Redis return
// null for unknown regions. Validation happens at the proto layer via the
// regex pattern on region_id.
// ---------------------------------------------------------------------------
// Phase 0 snake_case shape (as persisted by scripts/seed-regional-snapshots.mjs)
// ---------------------------------------------------------------------------
//
// The seed writer in scripts/regional-snapshot/ constructs snapshots with
// snake_case field names matching shared/regions.types.d.ts. The proto layer
// uses camelCase per standard buf codegen. This handler reads the snake_case
// payload from Redis and translates to the camelCase wire format on the way
// out. The adapter is the single source of truth for the field mapping.
/** Phase 0 persisted shape. Only the fields we consume are typed. */
interface PersistedSnapshot {
region_id?: string;
generated_at?: number;
meta?: PersistedMeta;
regime?: PersistedRegime;
balance?: PersistedBalance;
actors?: PersistedActor[];
leverage_edges?: PersistedLeverageEdge[];
scenario_sets?: PersistedScenarioSet[];
transmission_paths?: PersistedTransmissionPath[];
triggers?: PersistedTriggerLadder;
mobility?: PersistedMobility;
evidence?: PersistedEvidence[];
narrative?: PersistedNarrative;
}
interface PersistedMeta {
snapshot_id?: string;
model_version?: string;
scoring_version?: string;
geography_version?: string;
snapshot_confidence?: number;
missing_inputs?: string[];
stale_inputs?: string[];
valid_until?: number;
trigger_reason?: string;
narrative_provider?: string;
narrative_model?: string;
}
interface PersistedRegime {
label?: string;
previous_label?: string;
transitioned_at?: number;
transition_driver?: string;
}
interface PersistedBalance {
coercive_pressure?: number;
domestic_fragility?: number;
capital_stress?: number;
energy_vulnerability?: number;
alliance_cohesion?: number;
maritime_access?: number;
energy_leverage?: number;
net_balance?: number;
pressures?: PersistedBalanceDriver[];
buffers?: PersistedBalanceDriver[];
}
interface PersistedBalanceDriver {
axis?: string;
description?: string;
magnitude?: number;
evidence_ids?: string[];
orientation?: string;
}
interface PersistedActor {
actor_id?: string;
name?: string;
role?: string;
leverage_domains?: string[];
leverage_score?: number;
delta?: number;
evidence_ids?: string[];
}
interface PersistedLeverageEdge {
from_actor_id?: string;
to_actor_id?: string;
mechanism?: string;
strength?: number;
evidence_ids?: string[];
}
interface PersistedScenarioSet {
horizon?: string;
lanes?: PersistedScenarioLane[];
}
interface PersistedScenarioLane {
name?: string;
probability?: number;
trigger_ids?: string[];
consequences?: string[];
transmissions?: PersistedTransmissionPath[];
}
interface PersistedTransmissionPath {
start?: string;
mechanism?: string;
end?: string;
severity?: string;
corridor_id?: string;
confidence?: number;
latency_hours?: number;
impacted_asset_class?: string;
impacted_regions?: string[];
magnitude_low?: number;
magnitude_high?: number;
magnitude_unit?: string;
template_id?: string;
template_version?: string;
}
interface PersistedTriggerLadder {
active?: PersistedTrigger[];
watching?: PersistedTrigger[];
dormant?: PersistedTrigger[];
}
interface PersistedTrigger {
id?: string;
description?: string;
threshold?: PersistedTriggerThreshold;
activated?: boolean;
activated_at?: number;
scenario_lane?: string;
evidence_ids?: string[];
}
interface PersistedTriggerThreshold {
metric?: string;
operator?: string;
value?: number;
window_minutes?: number;
baseline?: string;
}
interface PersistedMobility {
airspace?: PersistedAirspace[];
flight_corridors?: PersistedFlightCorridor[];
airports?: PersistedAirport[];
reroute_intensity?: number;
notam_closures?: string[];
}
interface PersistedAirspace {
airspace_id?: string;
status?: string;
reason?: string;
}
interface PersistedFlightCorridor {
corridor?: string;
stress_level?: number;
rerouted_flights_24h?: number;
}
interface PersistedAirport {
icao?: string;
name?: string;
status?: string;
disruption_reason?: string;
}
interface PersistedEvidence {
id?: string;
type?: string;
source?: string;
summary?: string;
confidence?: number;
observed_at?: number;
theater?: string;
corridor?: string;
}
interface PersistedNarrative {
situation?: PersistedNarrativeSection;
balance_assessment?: PersistedNarrativeSection;
outlook_24h?: PersistedNarrativeSection;
outlook_7d?: PersistedNarrativeSection;
outlook_30d?: PersistedNarrativeSection;
watch_items?: PersistedNarrativeSection[];
}
interface PersistedNarrativeSection {
text?: string;
evidence_ids?: string[];
}
// ---------------------------------------------------------------------------
// Adapters: snake_case persisted shape -> camelCase proto shape
// ---------------------------------------------------------------------------
function adaptMeta(raw: PersistedMeta | undefined): SnapshotMeta {
return {
snapshotId: raw?.snapshot_id ?? '',
modelVersion: raw?.model_version ?? '',
scoringVersion: raw?.scoring_version ?? '',
geographyVersion: raw?.geography_version ?? '',
snapshotConfidence: raw?.snapshot_confidence ?? 0,
missingInputs: raw?.missing_inputs ?? [],
staleInputs: raw?.stale_inputs ?? [],
validUntil: raw?.valid_until ?? 0,
triggerReason: raw?.trigger_reason ?? '',
narrativeProvider: raw?.narrative_provider ?? '',
narrativeModel: raw?.narrative_model ?? '',
};
}
function adaptRegime(raw: PersistedRegime | undefined): RegimeState {
return {
label: raw?.label ?? '',
previousLabel: raw?.previous_label ?? '',
transitionedAt: raw?.transitioned_at ?? 0,
transitionDriver: raw?.transition_driver ?? '',
};
}
function adaptBalanceDriver(raw: PersistedBalanceDriver): BalanceDriver {
return {
axis: raw.axis ?? '',
description: raw.description ?? '',
magnitude: raw.magnitude ?? 0,
evidenceIds: raw.evidence_ids ?? [],
orientation: raw.orientation ?? '',
};
}
function adaptBalance(raw: PersistedBalance | undefined): BalanceVector {
return {
coercivePressure: raw?.coercive_pressure ?? 0,
domesticFragility: raw?.domestic_fragility ?? 0,
capitalStress: raw?.capital_stress ?? 0,
energyVulnerability: raw?.energy_vulnerability ?? 0,
allianceCohesion: raw?.alliance_cohesion ?? 0,
maritimeAccess: raw?.maritime_access ?? 0,
energyLeverage: raw?.energy_leverage ?? 0,
netBalance: raw?.net_balance ?? 0,
pressures: (raw?.pressures ?? []).map(adaptBalanceDriver),
buffers: (raw?.buffers ?? []).map(adaptBalanceDriver),
};
}
function adaptActor(raw: PersistedActor): ActorState {
return {
actorId: raw.actor_id ?? '',
name: raw.name ?? '',
role: raw.role ?? '',
leverageDomains: raw.leverage_domains ?? [],
leverageScore: raw.leverage_score ?? 0,
delta: raw.delta ?? 0,
evidenceIds: raw.evidence_ids ?? [],
};
}
function adaptLeverageEdge(raw: PersistedLeverageEdge): LeverageEdge {
return {
fromActorId: raw.from_actor_id ?? '',
toActorId: raw.to_actor_id ?? '',
mechanism: raw.mechanism ?? '',
strength: raw.strength ?? 0,
evidenceIds: raw.evidence_ids ?? [],
};
}
function adaptTransmissionPath(raw: PersistedTransmissionPath): TransmissionPath {
return {
start: raw.start ?? '',
mechanism: raw.mechanism ?? '',
end: raw.end ?? '',
severity: raw.severity ?? '',
corridorId: raw.corridor_id ?? '',
confidence: raw.confidence ?? 0,
latencyHours: raw.latency_hours ?? 0,
impactedAssetClass: raw.impacted_asset_class ?? '',
impactedRegions: raw.impacted_regions ?? [],
magnitudeLow: raw.magnitude_low ?? 0,
magnitudeHigh: raw.magnitude_high ?? 0,
magnitudeUnit: raw.magnitude_unit ?? '',
templateId: raw.template_id ?? '',
templateVersion: raw.template_version ?? '',
};
}
function adaptScenarioLane(raw: PersistedScenarioLane): ScenarioLane {
return {
name: raw.name ?? '',
probability: raw.probability ?? 0,
triggerIds: raw.trigger_ids ?? [],
consequences: raw.consequences ?? [],
transmissions: (raw.transmissions ?? []).map(adaptTransmissionPath),
};
}
function adaptScenarioSet(raw: PersistedScenarioSet): ScenarioSet {
return {
horizon: raw.horizon ?? '',
lanes: (raw.lanes ?? []).map(adaptScenarioLane),
};
}
function adaptTriggerThreshold(raw: PersistedTriggerThreshold | undefined): TriggerThreshold {
return {
metric: raw?.metric ?? '',
operator: raw?.operator ?? '',
value: raw?.value ?? 0,
windowMinutes: raw?.window_minutes ?? 0,
baseline: raw?.baseline ?? '',
};
}
function adaptTrigger(raw: PersistedTrigger): Trigger {
return {
id: raw.id ?? '',
description: raw.description ?? '',
threshold: adaptTriggerThreshold(raw.threshold),
activated: raw.activated ?? false,
activatedAt: raw.activated_at ?? 0,
scenarioLane: raw.scenario_lane ?? '',
evidenceIds: raw.evidence_ids ?? [],
};
}
function adaptTriggerLadder(raw: PersistedTriggerLadder | undefined): TriggerLadder {
return {
active: (raw?.active ?? []).map(adaptTrigger),
watching: (raw?.watching ?? []).map(adaptTrigger),
dormant: (raw?.dormant ?? []).map(adaptTrigger),
};
}
function adaptAirspace(raw: PersistedAirspace): AirspaceStatus {
return {
airspaceId: raw.airspace_id ?? '',
status: raw.status ?? '',
reason: raw.reason ?? '',
};
}
function adaptFlightCorridor(raw: PersistedFlightCorridor): FlightCorridorStress {
return {
corridor: raw.corridor ?? '',
stressLevel: raw.stress_level ?? 0,
reroutedFlights24h: raw.rerouted_flights_24h ?? 0,
};
}
function adaptAirport(raw: PersistedAirport): AirportNodeStatus {
return {
icao: raw.icao ?? '',
name: raw.name ?? '',
status: raw.status ?? '',
disruptionReason: raw.disruption_reason ?? '',
};
}
function adaptMobility(raw: PersistedMobility | undefined): MobilityState {
return {
airspace: (raw?.airspace ?? []).map(adaptAirspace),
flightCorridors: (raw?.flight_corridors ?? []).map(adaptFlightCorridor),
airports: (raw?.airports ?? []).map(adaptAirport),
rerouteIntensity: raw?.reroute_intensity ?? 0,
notamClosures: raw?.notam_closures ?? [],
};
}
function adaptEvidence(raw: PersistedEvidence): EvidenceItem {
return {
id: raw.id ?? '',
type: raw.type ?? '',
source: raw.source ?? '',
summary: raw.summary ?? '',
confidence: raw.confidence ?? 0,
observedAt: raw.observed_at ?? 0,
theater: raw.theater ?? '',
corridor: raw.corridor ?? '',
};
}
function adaptNarrativeSection(raw: PersistedNarrativeSection | undefined): NarrativeSection {
return {
text: raw?.text ?? '',
evidenceIds: raw?.evidence_ids ?? [],
};
}
function adaptNarrative(raw: PersistedNarrative | undefined): RegionalNarrative {
return {
situation: adaptNarrativeSection(raw?.situation),
balanceAssessment: adaptNarrativeSection(raw?.balance_assessment),
outlook24h: adaptNarrativeSection(raw?.outlook_24h),
outlook7d: adaptNarrativeSection(raw?.outlook_7d),
outlook30d: adaptNarrativeSection(raw?.outlook_30d),
watchItems: (raw?.watch_items ?? []).map((section) => adaptNarrativeSection(section)),
};
}
/** Full snake_case -> camelCase adapter for RegionalSnapshot. */
export function adaptSnapshot(raw: PersistedSnapshot): RegionalSnapshot {
return {
regionId: raw.region_id ?? '',
generatedAt: raw.generated_at ?? 0,
meta: adaptMeta(raw.meta),
regime: adaptRegime(raw.regime),
balance: adaptBalance(raw.balance),
actors: (raw.actors ?? []).map(adaptActor),
leverageEdges: (raw.leverage_edges ?? []).map(adaptLeverageEdge),
scenarioSets: (raw.scenario_sets ?? []).map(adaptScenarioSet),
transmissionPaths: (raw.transmission_paths ?? []).map(adaptTransmissionPath),
triggers: adaptTriggerLadder(raw.triggers),
mobility: adaptMobility(raw.mobility),
evidence: (raw.evidence ?? []).map(adaptEvidence),
narrative: adaptNarrative(raw.narrative),
};
}
// ---------------------------------------------------------------------------
// Handler
// ---------------------------------------------------------------------------
/**
* Reads the latest persisted RegionalSnapshot for a region.
*
* Pipeline:
* 1. Read `intelligence:snapshot:v1:{region}:latest` -> snapshot_id (string)
* 2. Read `intelligence:snapshot-by-id:v1:{snapshot_id}` -> full snapshot (JSON)
* 3. Adapt snake_case persisted shape to camelCase proto shape
* 4. Return
*
* Returns an empty response (snapshot omitted) when:
* - No `:latest` pointer exists (seed has never run or region is unknown)
* - The `:latest` pointer references a snapshot that was pruned or TTL'd
* - The snapshot JSON is malformed
*
* This handler is premium-gated at the gateway layer (see
* src/shared/premium-paths.ts and server/gateway.ts RPC_CACHE_TIER).
*/
export const getRegionalSnapshot: IntelligenceServiceHandler['getRegionalSnapshot'] = async (
_ctx: ServerContext,
req: GetRegionalSnapshotRequest,
): Promise<GetRegionalSnapshotResponse> => {
const regionId = req.regionId;
if (!regionId || typeof regionId !== 'string') {
return {};
}
// Step 1: resolve latest pointer -> snapshot_id
const latestKey = `${LATEST_KEY_PREFIX}${regionId}:latest`;
const latestRaw = await getCachedJson(latestKey, true);
// The seed writer stores the id as a bare string (JSON-encoded). getCachedJson
// returns whatever the JSON parser produced, so we handle both shapes.
let snapshotId: string | null = null;
if (typeof latestRaw === 'string') {
snapshotId = latestRaw;
} else if (latestRaw && typeof latestRaw === 'object' && 'snapshot_id' in latestRaw) {
const candidate = (latestRaw as { snapshot_id?: unknown }).snapshot_id;
if (typeof candidate === 'string') snapshotId = candidate;
}
if (!snapshotId) {
return {};
}
// Step 2: resolve snapshot_id -> full snapshot
const snapKey = `${BY_ID_KEY_PREFIX}${snapshotId}`;
const persisted = await getCachedJson(snapKey, true) as PersistedSnapshot | null;
if (!persisted || typeof persisted !== 'object') {
return {};
}
// Step 3: adapt snake_case -> camelCase
const snapshot = adaptSnapshot(persisted);
return { snapshot };
};

View File

@@ -22,6 +22,7 @@ import { getSocialVelocity } from './get-social-velocity';
import { getCountryEnergyProfile } from './get-country-energy-profile';
import { computeEnergyShockScenario } from './compute-energy-shock';
import { getCountryPortActivity } from './get-country-port-activity';
import { getRegionalSnapshot } from './get-regional-snapshot';
export const intelligenceHandler: IntelligenceServiceHandler = {
getRiskScores,
@@ -46,4 +47,5 @@ export const intelligenceHandler: IntelligenceServiceHandler = {
getCountryEnergyProfile,
computeEnergyShockScenario,
getCountryPortActivity,
getRegionalSnapshot,
};

View File

@@ -628,6 +628,196 @@ export interface PortActivityEntry {
anomalySignal: boolean;
}
export interface GetRegionalSnapshotRequest {
regionId: string;
}
export interface GetRegionalSnapshotResponse {
snapshot?: RegionalSnapshot;
}
export interface RegionalSnapshot {
regionId: string;
generatedAt: number;
meta?: SnapshotMeta;
regime?: RegimeState;
balance?: BalanceVector;
actors: ActorState[];
leverageEdges: LeverageEdge[];
scenarioSets: ScenarioSet[];
transmissionPaths: TransmissionPath[];
triggers?: TriggerLadder;
mobility?: MobilityState;
evidence: EvidenceItem[];
narrative?: RegionalNarrative;
}
export interface SnapshotMeta {
snapshotId: string;
modelVersion: string;
scoringVersion: string;
geographyVersion: string;
snapshotConfidence: number;
missingInputs: string[];
staleInputs: string[];
validUntil: number;
triggerReason: string;
narrativeProvider: string;
narrativeModel: string;
}
export interface RegimeState {
label: string;
previousLabel: string;
transitionedAt: number;
transitionDriver: string;
}
export interface BalanceVector {
coercivePressure: number;
domesticFragility: number;
capitalStress: number;
energyVulnerability: number;
allianceCohesion: number;
maritimeAccess: number;
energyLeverage: number;
netBalance: number;
pressures: BalanceDriver[];
buffers: BalanceDriver[];
}
export interface BalanceDriver {
axis: string;
description: string;
magnitude: number;
evidenceIds: string[];
orientation: string;
}
export interface ActorState {
actorId: string;
name: string;
role: string;
leverageDomains: string[];
leverageScore: number;
delta: number;
evidenceIds: string[];
}
export interface LeverageEdge {
fromActorId: string;
toActorId: string;
mechanism: string;
strength: number;
evidenceIds: string[];
}
export interface ScenarioSet {
horizon: string;
lanes: ScenarioLane[];
}
export interface ScenarioLane {
name: string;
probability: number;
triggerIds: string[];
consequences: string[];
transmissions: TransmissionPath[];
}
export interface TransmissionPath {
start: string;
mechanism: string;
end: string;
severity: string;
corridorId: string;
confidence: number;
latencyHours: number;
impactedAssetClass: string;
impactedRegions: string[];
magnitudeLow: number;
magnitudeHigh: number;
magnitudeUnit: string;
templateId: string;
templateVersion: string;
}
export interface TriggerLadder {
active: Trigger[];
watching: Trigger[];
dormant: Trigger[];
}
export interface Trigger {
id: string;
description: string;
threshold?: TriggerThreshold;
activated: boolean;
activatedAt: number;
scenarioLane: string;
evidenceIds: string[];
}
export interface TriggerThreshold {
metric: string;
operator: string;
value: number;
windowMinutes: number;
baseline: string;
}
export interface MobilityState {
airspace: AirspaceStatus[];
flightCorridors: FlightCorridorStress[];
airports: AirportNodeStatus[];
rerouteIntensity: number;
notamClosures: string[];
}
export interface AirspaceStatus {
airspaceId: string;
status: string;
reason: string;
}
export interface FlightCorridorStress {
corridor: string;
stressLevel: number;
reroutedFlights24h: number;
}
export interface AirportNodeStatus {
icao: string;
name: string;
status: string;
disruptionReason: string;
}
export interface EvidenceItem {
id: string;
type: string;
source: string;
summary: string;
confidence: number;
observedAt: number;
theater: string;
corridor: string;
}
export interface RegionalNarrative {
situation?: NarrativeSection;
balanceAssessment?: NarrativeSection;
outlook24h?: NarrativeSection;
outlook7d?: NarrativeSection;
outlook30d?: NarrativeSection;
watchItems: NarrativeSection[];
}
export interface NarrativeSection {
text: string;
evidenceIds: string[];
}
export type SeverityLevel = "SEVERITY_LEVEL_UNSPECIFIED" | "SEVERITY_LEVEL_LOW" | "SEVERITY_LEVEL_MEDIUM" | "SEVERITY_LEVEL_HIGH";
export type TrendDirection = "TREND_DIRECTION_UNSPECIFIED" | "TREND_DIRECTION_RISING" | "TREND_DIRECTION_STABLE" | "TREND_DIRECTION_FALLING";
@@ -1248,6 +1438,31 @@ export class IntelligenceServiceClient {
return await resp.json() as CountryPortActivityResponse;
}
async getRegionalSnapshot(req: GetRegionalSnapshotRequest, options?: IntelligenceServiceCallOptions): Promise<GetRegionalSnapshotResponse> {
let path = "/api/intelligence/v1/get-regional-snapshot";
const params = new URLSearchParams();
if (req.regionId != null && req.regionId !== "") params.set("region_id", String(req.regionId));
const url = this.baseURL + path + (params.toString() ? "?" + params.toString() : "");
const headers: Record<string, string> = {
"Content-Type": "application/json",
...this.defaultHeaders,
...options?.headers,
};
const resp = await this.fetchFn(url, {
method: "GET",
headers,
signal: options?.signal,
});
if (!resp.ok) {
return this.handleError(resp);
}
return await resp.json() as GetRegionalSnapshotResponse;
}
private async handleError(resp: Response): Promise<never> {
const body = await resp.text();
if (resp.status === 400) {

View File

@@ -628,6 +628,196 @@ export interface PortActivityEntry {
anomalySignal: boolean;
}
export interface GetRegionalSnapshotRequest {
regionId: string;
}
export interface GetRegionalSnapshotResponse {
snapshot?: RegionalSnapshot;
}
export interface RegionalSnapshot {
regionId: string;
generatedAt: number;
meta?: SnapshotMeta;
regime?: RegimeState;
balance?: BalanceVector;
actors: ActorState[];
leverageEdges: LeverageEdge[];
scenarioSets: ScenarioSet[];
transmissionPaths: TransmissionPath[];
triggers?: TriggerLadder;
mobility?: MobilityState;
evidence: EvidenceItem[];
narrative?: RegionalNarrative;
}
export interface SnapshotMeta {
snapshotId: string;
modelVersion: string;
scoringVersion: string;
geographyVersion: string;
snapshotConfidence: number;
missingInputs: string[];
staleInputs: string[];
validUntil: number;
triggerReason: string;
narrativeProvider: string;
narrativeModel: string;
}
export interface RegimeState {
label: string;
previousLabel: string;
transitionedAt: number;
transitionDriver: string;
}
export interface BalanceVector {
coercivePressure: number;
domesticFragility: number;
capitalStress: number;
energyVulnerability: number;
allianceCohesion: number;
maritimeAccess: number;
energyLeverage: number;
netBalance: number;
pressures: BalanceDriver[];
buffers: BalanceDriver[];
}
export interface BalanceDriver {
axis: string;
description: string;
magnitude: number;
evidenceIds: string[];
orientation: string;
}
export interface ActorState {
actorId: string;
name: string;
role: string;
leverageDomains: string[];
leverageScore: number;
delta: number;
evidenceIds: string[];
}
export interface LeverageEdge {
fromActorId: string;
toActorId: string;
mechanism: string;
strength: number;
evidenceIds: string[];
}
export interface ScenarioSet {
horizon: string;
lanes: ScenarioLane[];
}
export interface ScenarioLane {
name: string;
probability: number;
triggerIds: string[];
consequences: string[];
transmissions: TransmissionPath[];
}
export interface TransmissionPath {
start: string;
mechanism: string;
end: string;
severity: string;
corridorId: string;
confidence: number;
latencyHours: number;
impactedAssetClass: string;
impactedRegions: string[];
magnitudeLow: number;
magnitudeHigh: number;
magnitudeUnit: string;
templateId: string;
templateVersion: string;
}
export interface TriggerLadder {
active: Trigger[];
watching: Trigger[];
dormant: Trigger[];
}
export interface Trigger {
id: string;
description: string;
threshold?: TriggerThreshold;
activated: boolean;
activatedAt: number;
scenarioLane: string;
evidenceIds: string[];
}
export interface TriggerThreshold {
metric: string;
operator: string;
value: number;
windowMinutes: number;
baseline: string;
}
export interface MobilityState {
airspace: AirspaceStatus[];
flightCorridors: FlightCorridorStress[];
airports: AirportNodeStatus[];
rerouteIntensity: number;
notamClosures: string[];
}
export interface AirspaceStatus {
airspaceId: string;
status: string;
reason: string;
}
export interface FlightCorridorStress {
corridor: string;
stressLevel: number;
reroutedFlights24h: number;
}
export interface AirportNodeStatus {
icao: string;
name: string;
status: string;
disruptionReason: string;
}
export interface EvidenceItem {
id: string;
type: string;
source: string;
summary: string;
confidence: number;
observedAt: number;
theater: string;
corridor: string;
}
export interface RegionalNarrative {
situation?: NarrativeSection;
balanceAssessment?: NarrativeSection;
outlook24h?: NarrativeSection;
outlook7d?: NarrativeSection;
outlook30d?: NarrativeSection;
watchItems: NarrativeSection[];
}
export interface NarrativeSection {
text: string;
evidenceIds: string[];
}
export type SeverityLevel = "SEVERITY_LEVEL_UNSPECIFIED" | "SEVERITY_LEVEL_LOW" | "SEVERITY_LEVEL_MEDIUM" | "SEVERITY_LEVEL_HIGH";
export type TrendDirection = "TREND_DIRECTION_UNSPECIFIED" | "TREND_DIRECTION_RISING" | "TREND_DIRECTION_STABLE" | "TREND_DIRECTION_FALLING";
@@ -709,6 +899,7 @@ export interface IntelligenceServiceHandler {
getCountryEnergyProfile(ctx: ServerContext, req: GetCountryEnergyProfileRequest): Promise<GetCountryEnergyProfileResponse>;
computeEnergyShockScenario(ctx: ServerContext, req: ComputeEnergyShockScenarioRequest): Promise<ComputeEnergyShockScenarioResponse>;
getCountryPortActivity(ctx: ServerContext, req: GetCountryPortActivityRequest): Promise<CountryPortActivityResponse>;
getRegionalSnapshot(ctx: ServerContext, req: GetRegionalSnapshotRequest): Promise<GetRegionalSnapshotResponse>;
}
export function createIntelligenceServiceRoutes(
@@ -1731,6 +1922,53 @@ export function createIntelligenceServiceRoutes(
}
},
},
{
method: "GET",
path: "/api/intelligence/v1/get-regional-snapshot",
handler: async (req: Request): Promise<Response> => {
try {
const pathParams: Record<string, string> = {};
const url = new URL(req.url, "http://localhost");
const params = url.searchParams;
const body: GetRegionalSnapshotRequest = {
regionId: params.get("region_id") ?? "",
};
if (options?.validateRequest) {
const bodyViolations = options.validateRequest("getRegionalSnapshot", body);
if (bodyViolations) {
throw new ValidationError(bodyViolations);
}
}
const ctx: ServerContext = {
request: req,
pathParams,
headers: Object.fromEntries(req.headers.entries()),
};
const result = await handler.getRegionalSnapshot(ctx, body);
return new Response(JSON.stringify(result as GetRegionalSnapshotResponse), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (err: unknown) {
if (err instanceof ValidationError) {
return new Response(JSON.stringify({ violations: err.violations }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
if (options?.onError) {
return options.onError(err, req);
}
const message = err instanceof Error ? err.message : String(err);
return new Response(JSON.stringify({ message }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
},
},
];
}

View File

@@ -12,6 +12,7 @@ export const PREMIUM_RPC_PATHS = new Set<string>([
'/api/market/v1/list-stored-stock-backtests',
'/api/intelligence/v1/deduct-situation',
'/api/intelligence/v1/list-market-implications',
'/api/intelligence/v1/get-regional-snapshot',
'/api/resilience/v1/get-resilience-score',
'/api/resilience/v1/get-resilience-ranking',
'/api/supply-chain/v1/get-country-chokepoint-index',

View File

@@ -0,0 +1,495 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { adaptSnapshot } from '../server/worldmonitor/intelligence/v1/get-regional-snapshot';
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, '..');
const handlerSrc = readFileSync(
resolve(root, 'server/worldmonitor/intelligence/v1/get-regional-snapshot.ts'),
'utf-8',
);
const handlerIndexSrc = readFileSync(
resolve(root, 'server/worldmonitor/intelligence/v1/handler.ts'),
'utf-8',
);
const premiumPathsSrc = readFileSync(
resolve(root, 'src/shared/premium-paths.ts'),
'utf-8',
);
const gatewaySrc = readFileSync(
resolve(root, 'server/gateway.ts'),
'utf-8',
);
const protoSrc = readFileSync(
resolve(root, 'proto/worldmonitor/intelligence/v1/get_regional_snapshot.proto'),
'utf-8',
);
// ────────────────────────────────────────────────────────────────────────────
// adaptSnapshot: snake_case -> camelCase adapter (the substantive logic)
// ────────────────────────────────────────────────────────────────────────────
describe('adaptSnapshot', () => {
it('maps all top-level region fields', () => {
const result = adaptSnapshot({
region_id: 'mena',
generated_at: 1_700_000_000_000,
});
assert.equal(result.regionId, 'mena');
assert.equal(result.generatedAt, 1_700_000_000_000);
});
it('defaults missing top-level fields to empty values', () => {
const result = adaptSnapshot({});
assert.equal(result.regionId, '');
assert.equal(result.generatedAt, 0);
assert.deepEqual(result.actors, []);
assert.deepEqual(result.leverageEdges, []);
assert.deepEqual(result.scenarioSets, []);
assert.deepEqual(result.transmissionPaths, []);
assert.deepEqual(result.evidence, []);
});
it('adapts SnapshotMeta fields', () => {
const result = adaptSnapshot({
meta: {
snapshot_id: 'abc123',
model_version: '0.1.0',
scoring_version: '1.0.0',
geography_version: '1.0.0',
snapshot_confidence: 0.85,
missing_inputs: ['forecast:predictions:v2'],
stale_inputs: [],
valid_until: 1_700_021_600_000,
trigger_reason: 'scheduled_6h',
narrative_provider: 'groq',
narrative_model: 'mixtral-8x7b',
},
});
assert.ok(result.meta);
assert.equal(result.meta.snapshotId, 'abc123');
assert.equal(result.meta.modelVersion, '0.1.0');
assert.equal(result.meta.scoringVersion, '1.0.0');
assert.equal(result.meta.snapshotConfidence, 0.85);
assert.deepEqual(result.meta.missingInputs, ['forecast:predictions:v2']);
assert.equal(result.meta.validUntil, 1_700_021_600_000);
assert.equal(result.meta.triggerReason, 'scheduled_6h');
assert.equal(result.meta.narrativeProvider, 'groq');
assert.equal(result.meta.narrativeModel, 'mixtral-8x7b');
});
it('adapts RegimeState', () => {
const result = adaptSnapshot({
regime: {
label: 'stressed_equilibrium',
previous_label: 'calm',
transitioned_at: 1_700_000_000_000,
transition_driver: 'diff-engine',
},
});
assert.ok(result.regime);
assert.equal(result.regime.label, 'stressed_equilibrium');
assert.equal(result.regime.previousLabel, 'calm');
assert.equal(result.regime.transitionedAt, 1_700_000_000_000);
assert.equal(result.regime.transitionDriver, 'diff-engine');
});
it('adapts BalanceVector with all 7 axes', () => {
const result = adaptSnapshot({
balance: {
coercive_pressure: 0.72,
domestic_fragility: 0.58,
capital_stress: 0.45,
energy_vulnerability: 0.3,
alliance_cohesion: 0.62,
maritime_access: 0.55,
energy_leverage: 0.8,
net_balance: 0.12,
pressures: [
{ axis: 'coercive_pressure', description: 'IRGC naval', magnitude: 0.72, evidence_ids: ['xss:1'], orientation: 'pressure' },
],
buffers: [
{ axis: 'energy_leverage', description: '6 producers', magnitude: 1.0, evidence_ids: [], orientation: 'buffer' },
],
},
});
assert.ok(result.balance);
assert.equal(result.balance.coercivePressure, 0.72);
assert.equal(result.balance.domesticFragility, 0.58);
assert.equal(result.balance.capitalStress, 0.45);
assert.equal(result.balance.energyVulnerability, 0.3);
assert.equal(result.balance.allianceCohesion, 0.62);
assert.equal(result.balance.maritimeAccess, 0.55);
assert.equal(result.balance.energyLeverage, 0.8);
assert.equal(result.balance.netBalance, 0.12);
assert.equal(result.balance.pressures.length, 1);
assert.equal(result.balance.pressures[0]?.axis, 'coercive_pressure');
assert.deepEqual(result.balance.pressures[0]?.evidenceIds, ['xss:1']);
assert.equal(result.balance.buffers.length, 1);
});
it('adapts ActorState array', () => {
const result = adaptSnapshot({
actors: [
{
actor_id: 'iran',
name: 'Iran',
role: 'aggressor',
leverage_domains: ['military', 'energy'],
leverage_score: 0.68,
delta: 0,
evidence_ids: ['forecast:f1'],
},
],
});
assert.equal(result.actors.length, 1);
const iran = result.actors[0];
assert.ok(iran);
assert.equal(iran.actorId, 'iran');
assert.equal(iran.name, 'Iran');
assert.equal(iran.role, 'aggressor');
assert.deepEqual(iran.leverageDomains, ['military', 'energy']);
assert.equal(iran.leverageScore, 0.68);
assert.deepEqual(iran.evidenceIds, ['forecast:f1']);
});
it('adapts LeverageEdge array', () => {
const result = adaptSnapshot({
leverage_edges: [
{
from_actor_id: 'russia',
to_actor_id: 'germany',
mechanism: 'energy_supply',
strength: 0.75,
evidence_ids: ['e1'],
},
],
});
assert.equal(result.leverageEdges.length, 1);
const edge = result.leverageEdges[0];
assert.ok(edge);
assert.equal(edge.fromActorId, 'russia');
assert.equal(edge.toActorId, 'germany');
assert.equal(edge.mechanism, 'energy_supply');
assert.equal(edge.strength, 0.75);
});
it('adapts ScenarioSet with nested lanes and transmissions', () => {
const result = adaptSnapshot({
scenario_sets: [
{
horizon: '24h',
lanes: [
{
name: 'escalation',
probability: 0.45,
trigger_ids: ['t1', 't2'],
consequences: ['price spike'],
transmissions: [
{
start: 'Hormuz threat',
mechanism: 'insurance spike',
end: 'Brent +$10',
severity: 'critical',
corridor_id: 'hormuz',
confidence: 0.85,
latency_hours: 24,
impacted_asset_class: 'crude',
impacted_regions: ['mena', 'east-asia'],
magnitude_low: 10,
magnitude_high: 25,
magnitude_unit: 'usd_bbl',
template_id: 'hormuz_blockade',
template_version: '1.0.0',
},
],
},
],
},
],
});
assert.equal(result.scenarioSets.length, 1);
const set = result.scenarioSets[0];
assert.ok(set);
assert.equal(set.horizon, '24h');
assert.equal(set.lanes.length, 1);
const lane = set.lanes[0];
assert.ok(lane);
assert.equal(lane.name, 'escalation');
assert.equal(lane.probability, 0.45);
assert.deepEqual(lane.triggerIds, ['t1', 't2']);
assert.equal(lane.transmissions.length, 1);
const trans = lane.transmissions[0];
assert.ok(trans);
assert.equal(trans.corridorId, 'hormuz');
assert.equal(trans.latencyHours, 24);
assert.equal(trans.impactedAssetClass, 'crude');
assert.deepEqual(trans.impactedRegions, ['mena', 'east-asia']);
assert.equal(trans.magnitudeLow, 10);
assert.equal(trans.magnitudeHigh, 25);
assert.equal(trans.templateId, 'hormuz_blockade');
assert.equal(trans.templateVersion, '1.0.0');
});
it('adapts TriggerLadder with all three buckets and nested TriggerThreshold', () => {
const result = adaptSnapshot({
triggers: {
active: [
{
id: 'hormuz_transit_drop',
description: 'Hormuz transit drops',
threshold: {
metric: 'chokepoint:hormuz:transit_count',
operator: 'delta_lt',
value: -0.20,
window_minutes: 1440,
baseline: 'trailing_7d',
},
activated: true,
activated_at: 1_700_000_000_000,
scenario_lane: 'escalation',
evidence_ids: ['e1'],
},
],
watching: [],
dormant: [],
},
});
assert.ok(result.triggers);
assert.equal(result.triggers.active.length, 1);
assert.equal(result.triggers.watching.length, 0);
assert.equal(result.triggers.dormant.length, 0);
const trigger = result.triggers.active[0];
assert.ok(trigger);
assert.equal(trigger.id, 'hormuz_transit_drop');
assert.equal(trigger.activated, true);
assert.equal(trigger.activatedAt, 1_700_000_000_000);
assert.equal(trigger.scenarioLane, 'escalation');
assert.ok(trigger.threshold);
assert.equal(trigger.threshold.metric, 'chokepoint:hormuz:transit_count');
assert.equal(trigger.threshold.operator, 'delta_lt');
assert.equal(trigger.threshold.value, -0.20);
assert.equal(trigger.threshold.windowMinutes, 1440);
assert.equal(trigger.threshold.baseline, 'trailing_7d');
});
it('adapts MobilityState with nested airspace/flight/airport arrays', () => {
const result = adaptSnapshot({
mobility: {
airspace: [{ airspace_id: 'LLLL', status: 'restricted', reason: 'conflict' }],
flight_corridors: [{ corridor: 'Tehran-Baghdad', stress_level: 0.8, rerouted_flights_24h: 42 }],
airports: [{ icao: 'OIIE', name: 'Imam Khomeini', status: 'disrupted', disruption_reason: 'drills' }],
reroute_intensity: 0.35,
notam_closures: ['OIIX-A0042'],
},
});
assert.ok(result.mobility);
assert.equal(result.mobility.airspace.length, 1);
const airspace = result.mobility.airspace[0];
assert.ok(airspace);
assert.equal(airspace.airspaceId, 'LLLL');
assert.equal(result.mobility.flightCorridors.length, 1);
const corr = result.mobility.flightCorridors[0];
assert.ok(corr);
assert.equal(corr.reroutedFlights24h, 42);
assert.equal(result.mobility.airports.length, 1);
const airport = result.mobility.airports[0];
assert.ok(airport);
assert.equal(airport.disruptionReason, 'drills');
assert.equal(result.mobility.rerouteIntensity, 0.35);
assert.deepEqual(result.mobility.notamClosures, ['OIIX-A0042']);
});
it('adapts EvidenceItem array', () => {
const result = adaptSnapshot({
evidence: [
{
id: 'cii:IR',
type: 'cii_spike',
source: 'risk-scores',
summary: 'IR CII 78 (UP)',
confidence: 0.9,
observed_at: 1_700_000_000_000,
theater: '',
corridor: '',
},
],
});
assert.equal(result.evidence.length, 1);
const ev = result.evidence[0];
assert.ok(ev);
assert.equal(ev.id, 'cii:IR');
assert.equal(ev.type, 'cii_spike');
assert.equal(ev.source, 'risk-scores');
assert.equal(ev.observedAt, 1_700_000_000_000);
});
it('adapts RegionalNarrative with all 5 sections plus watch_items', () => {
const result = adaptSnapshot({
narrative: {
situation: { text: 'Situation text', evidence_ids: ['s1'] },
balance_assessment: { text: 'Balance text', evidence_ids: ['b1'] },
outlook_24h: { text: '24h outlook', evidence_ids: ['o24'] },
outlook_7d: { text: '7d outlook', evidence_ids: ['o7'] },
outlook_30d: { text: '30d outlook', evidence_ids: ['o30'] },
watch_items: [
{ text: 'Item 1', evidence_ids: ['w1'] },
{ text: 'Item 2', evidence_ids: ['w2'] },
],
},
});
assert.ok(result.narrative);
assert.equal(result.narrative.situation?.text, 'Situation text');
assert.deepEqual(result.narrative.situation?.evidenceIds, ['s1']);
assert.equal(result.narrative.balanceAssessment?.text, 'Balance text');
assert.equal(result.narrative.outlook24h?.text, '24h outlook');
assert.equal(result.narrative.outlook7d?.text, '7d outlook');
assert.equal(result.narrative.outlook30d?.text, '30d outlook');
assert.equal(result.narrative.watchItems.length, 2);
assert.equal(result.narrative.watchItems[0]?.text, 'Item 1');
});
it('is robust to missing nested fields (empty array defaults)', () => {
const result = adaptSnapshot({
region_id: 'mena',
generated_at: 1_700_000_000_000,
balance: {},
triggers: {},
mobility: {},
narrative: {},
});
assert.ok(result.balance);
assert.equal(result.balance.coercivePressure, 0);
assert.deepEqual(result.balance.pressures, []);
assert.ok(result.triggers);
assert.deepEqual(result.triggers.active, []);
assert.ok(result.mobility);
assert.deepEqual(result.mobility.airspace, []);
assert.ok(result.narrative);
assert.deepEqual(result.narrative.watchItems, []);
});
});
// ────────────────────────────────────────────────────────────────────────────
// Handler structural checks (static analysis)
// ────────────────────────────────────────────────────────────────────────────
describe('get-regional-snapshot handler: structural checks', () => {
it('imports getCachedJson from redis helpers', () => {
assert.match(handlerSrc, /import\s*\{\s*getCachedJson\s*\}\s*from\s*'\.\.\/\.\.\/\.\.\/_shared\/redis'/);
});
it('uses the canonical :latest key prefix', () => {
assert.match(handlerSrc, /'intelligence:snapshot:v1:'/);
});
it('uses the canonical snapshot-by-id key prefix', () => {
assert.match(handlerSrc, /'intelligence:snapshot-by-id:v1:'/);
});
it('reads latest pointer then snapshot-by-id (two-hop lookup)', () => {
// latest resolved before snapKey construction
const latestIdx = handlerSrc.indexOf('latestKey');
const snapKeyIdx = handlerSrc.indexOf('snapKey');
assert.ok(latestIdx > 0 && snapKeyIdx > latestIdx, 'latest must resolve before snap lookup');
});
it('returns empty response on missing snapshot id', () => {
assert.match(handlerSrc, /if \(!snapshotId\) \{\s*return \{\}/);
});
it('returns empty response on missing persisted snapshot', () => {
assert.match(handlerSrc, /if \(!persisted \|\| typeof persisted !== 'object'\) \{\s*return \{\}/);
});
it('calls adaptSnapshot to produce the wire shape', () => {
assert.match(handlerSrc, /adaptSnapshot\(persisted\)/);
});
it('exports getRegionalSnapshot handler matching the service interface', () => {
assert.match(handlerSrc, /export const getRegionalSnapshot: IntelligenceServiceHandler\['getRegionalSnapshot'\]/);
});
});
describe('intelligence handler registration', () => {
it('imports getRegionalSnapshot from get-regional-snapshot module', () => {
assert.match(handlerIndexSrc, /import \{ getRegionalSnapshot \} from '\.\/get-regional-snapshot'/);
});
it('registers getRegionalSnapshot on the handler object', () => {
assert.match(handlerIndexSrc, /\s+getRegionalSnapshot,/);
});
});
describe('security wiring', () => {
it('adds the endpoint to PREMIUM_RPC_PATHS', () => {
assert.match(premiumPathsSrc, /'\/api\/intelligence\/v1\/get-regional-snapshot'/);
});
it('has a RPC_CACHE_TIER entry for route-parity (even though premium paths bypass it)', () => {
// At runtime the gateway checks PREMIUM_RPC_PATHS first and short-circuits
// to 'slow-browser' regardless of RPC_CACHE_TIER. The entry exists to satisfy
// tests/route-cache-tier.test.mjs which enforces that every generated GET
// route has an explicit tier, and documents the intended tier if the endpoint
// ever becomes non-premium.
assert.match(gatewaySrc, /'\/api\/intelligence\/v1\/get-regional-snapshot':\s*'slow'/);
});
});
describe('proto definition', () => {
it('declares the GetRegionalSnapshot RPC method', () => {
const serviceProtoSrc = readFileSync(
resolve(root, 'proto/worldmonitor/intelligence/v1/service.proto'),
'utf-8',
);
assert.match(serviceProtoSrc, /rpc GetRegionalSnapshot\(GetRegionalSnapshotRequest\) returns \(GetRegionalSnapshotResponse\)/);
});
it('validates region_id as strict lowercase kebab pattern (no trailing or consecutive hyphens)', () => {
// Pattern: ^[a-z][a-z0-9]*(-[a-z0-9]+)*$
// - Starts with a lowercase letter
// - Each hyphen must be followed by at least one alphanumeric character
// - Rejects "mena-", "east-asia-", "foo--bar"
assert.match(
protoSrc,
/buf\.validate\.field\)\.string\.pattern = "\^\[a-z\]\[a-z0-9\]\*\(-\[a-z0-9\]\+\)\*\$"/,
);
});
it('defines RegionalSnapshot with all 13 top-level fields', () => {
assert.match(protoSrc, /message RegionalSnapshot \{/);
assert.match(protoSrc, /string region_id = 1/);
assert.match(protoSrc, /int64 generated_at = 2/);
assert.match(protoSrc, /SnapshotMeta meta = 3/);
assert.match(protoSrc, /RegimeState regime = 4/);
assert.match(protoSrc, /BalanceVector balance = 5/);
assert.match(protoSrc, /repeated ActorState actors = 6/);
assert.match(protoSrc, /repeated LeverageEdge leverage_edges = 7/);
assert.match(protoSrc, /repeated ScenarioSet scenario_sets = 8/);
assert.match(protoSrc, /repeated TransmissionPath transmission_paths = 9/);
assert.match(protoSrc, /TriggerLadder triggers = 10/);
assert.match(protoSrc, /MobilityState mobility = 11/);
assert.match(protoSrc, /repeated EvidenceItem evidence = 12/);
assert.match(protoSrc, /RegionalNarrative narrative = 13/);
});
it('defines BalanceVector with all 7 axes plus net_balance and drivers', () => {
assert.match(protoSrc, /double coercive_pressure = 1/);
assert.match(protoSrc, /double domestic_fragility = 2/);
assert.match(protoSrc, /double capital_stress = 3/);
assert.match(protoSrc, /double energy_vulnerability = 4/);
assert.match(protoSrc, /double alliance_cohesion = 5/);
assert.match(protoSrc, /double maritime_access = 6/);
assert.match(protoSrc, /double energy_leverage = 7/);
assert.match(protoSrc, /double net_balance = 8/);
});
});