Files
worldmonitor/server
Elie Habib 7da202c25d 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.
2026-04-11 20:19:56 +04:00
..