Skip to content

Phase 1 PR1: RegionalSnapshot proto + RPC handler#2951

Merged
koala73 merged 4 commits into
mainfrom
feat/regional-intelligence-proto-rpc
Apr 11, 2026
Merged

Phase 1 PR1: RegionalSnapshot proto + RPC handler#2951
koala73 merged 4 commits into
mainfrom
feat/regional-intelligence-proto-rpc

Conversation

@koala73

@koala73 koala73 commented Apr 11, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 1 PR1 of the Regional Intelligence Model. Server-only; ships the proto definition and the RPC handler that reads the canonical snapshots persisted by Phase 0. No UI changes. No LLM calls. PR2 (narrative generator) and PR3 (Intelligence Board panel) follow independently.

What landed

Proto

  • New proto/worldmonitor/intelligence/v1/get_regional_snapshot.proto with the full RegionalSnapshot wire format: 13 top-level fields, 17 nested message types, mirrors shared/regions.types.d.ts that Phase 0 already ships. Every balance axis, actor, scenario lane, transmission path, trigger threshold, mobility substate, evidence item, and narrative section has a typed proto message.
  • GetRegionalSnapshot(GetRegionalSnapshotRequest) returns (GetRegionalSnapshotResponse) added to IntelligenceService. region_id validated via buf.validate regex as lowercase kebab.
  • make generate output committed: generated client/server TypeScript types, OpenAPI JSON/YAML.

Handler

  • New server/worldmonitor/intelligence/v1/get-regional-snapshot.ts implements the two-hop read: :latest pointer -> snapshot_id -> snapshot-by-id payload. Returns empty response on miss or malformed JSON. Does NOT recompute on miss - one writer (the seed bundle), canonical reads, matching the architecture spec.
  • Handler includes a localized snake_case -> camelCase adapter (adaptSnapshot) bridging Phase 0's persisted shape (snake_case from shared/regions.types.d.ts) to the proto's camelCase wire format. Adapter is the single bridge; Phase 0 code stays frozen.
  • Wired into intelligence/v1/handler.ts.

Security

  • Added /api/intelligence/v1/get-regional-snapshot to PREMIUM_RPC_PATHS — gateway enforces Pro subscription or API key.
  • Added to RPC_CACHE_TIER with 'slow' tier (300s browser, 1800s edge) matching similar premium intelligence RPCs.

Tests

  • tests/get-regional-snapshot.test.mts — 29 tests across 5 suites:
    • 18 real unit tests of adaptSnapshot() covering every nested message, with synthetic inputs and empty-default fallbacks
    • 8 structural checks on the handler source (imports, key prefixes, two-hop ordering, fallback paths, export signature)
    • 2 registration checks on handler.ts
    • 2 security-wiring checks (PREMIUM_RPC_PATHS + RPC_CACHE_TIER)
    • 3 proto definition checks (RPC method, regex validation, 7-axis BalanceVector)

Verification

  • npm run test:data: 4293 / 4293 pass
  • npm run typecheck:all: clean
  • biome lint: 1 pre-existing warning on server/gateway.ts:242 complexity (unrelated tech debt)

What's deliberately NOT in this PR

  • LLM narrative generator — PR2. Updates scripts/seed-regional-snapshots.mjs to call callLlmReasoning once per region per 6h cycle, populates narrative_provider + narrative_model in SnapshotMeta.
  • RegionalIntelligenceBoard panel UI — PR3. Depends on this PR's RPC being live. 6 blocks: Regime, Balance, Actors, Scenarios, Transmission, Watchlist.
  • Panel registration — Config, CMD+K, barrel export all live with PR3.
  • Mobility population — Phase 2 per the spec.

Post-Deploy Monitoring & Validation

  • What to monitor/search
    • Logs: Vercel edge function logs for get-regional-snapshot — expect low traffic (premium users only, not bootstrapped)
    • Metrics: /api/intelligence/v1/get-regional-snapshot latency. Should be <100ms (two Redis GETs)
  • Validation checks
    • Curl with API key after merge:
      curl -H "X-WorldMonitor-Key: $KEY" \
        "https://worldmonitor.app/api/intelligence/v1/get-regional-snapshot?region_id=mena" | jq .
      
    • Expect: empty response {} initially (Phase 0 seed may not have persisted anything yet for this specific deployment), then populated snapshot after the next seed cron cycle (~6h).
    • Inspect Redis directly: GET intelligence:snapshot:v1:mena:latest should return a snapshot_id after the first Phase 0 cron fires post-merge.
  • Expected healthy behavior
    • 200 OK on valid region_id (even if snapshot is empty)
    • 400 on malformed region_id (proto validation via buf)
    • 401 on missing auth
  • Failure signal(s) / rollback trigger
    • Sentry errors from the handler: investigate adapter bugs or Redis connectivity
    • Persistent empty responses after 24h: Phase 0 seed isn't running. Check Railway seed-bundle-derived-signals logs.
  • Validation window & owner
    • 24 hours post-merge. Owner: @koala73 monitors initial /api/health + curl checks.

PR sequence

Spec references

  • docs/internal/pro-regional-intelligence-upgrade.md — architecture
  • docs/internal/pro-regional-intelligence-appendix-engineering.md — file/key inventory
  • docs/internal/pro-regional-intelligence-appendix-scoring.md — formulas

koala73 added 3 commits April 11, 2026 18:40
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
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)
…ructural 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.
@mintlify

mintlify Bot commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
WorldMonitor 🟢 Ready View Preview Apr 11, 2026, 2:44 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@vercel

vercel Bot commented Apr 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Apr 11, 2026 4:12pm

Request Review

@greptile-apps

greptile-apps Bot commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR ships the proto definition and RPC handler for GetRegionalSnapshot (Phase 1 PR1), providing a two-hop Redis read (intelligence:snapshot:v1:{region}:latestsnapshot-by-id) with a full snake_case→camelCase adapter bridging Phase 0's persisted shape to the proto wire format. The endpoint is correctly gated behind PREMIUM_RPC_PATHS and the gateway applies the appropriate slow-browser cache tier for premium paths.

Confidence Score: 5/5

Safe to merge — all findings are P2 style suggestions with no functional or security impact.

The two-hop Redis read, adapter logic, auth gating, and handler wiring are all correct. The only findings are: a dead RPC_CACHE_TIER entry (pre-existing pattern for premium paths) and a slightly loose proto regex that permits trailing hyphens. Neither affects runtime behavior.

No files require special attention beyond the two P2 notes on gateway.ts and get_regional_snapshot.proto.

Important Files Changed

Filename Overview
server/worldmonitor/intelligence/v1/get-regional-snapshot.ts Core handler: two-hop Redis read with full snake_case→camelCase adapter; null/missing-data fallbacks are correct; raw=true key reads match seed writer convention.
proto/worldmonitor/intelligence/v1/get_regional_snapshot.proto Full proto definition with 13 top-level fields and 17 nested message types; region_id regex permits trailing hyphens (P2).
server/gateway.ts RPC_CACHE_TIER entry for the new endpoint is dead code for premium paths (gateway overrides to slow-browser); existing pattern across all premium paths.
src/shared/premium-paths.ts Endpoint correctly added to PREMIUM_RPC_PATHS, enforcing Pro subscription or API key authentication.
server/worldmonitor/intelligence/v1/handler.ts getRegionalSnapshot correctly imported and registered on the IntelligenceServiceHandler object.
tests/get-regional-snapshot.test.mts 29 tests covering adapter logic (18 unit), handler structural checks (8), registration (2), security wiring (2), and proto checks (3); the RPC_CACHE_TIER test validates a dead-code entry.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Gateway as Gateway (auth + cache)
    participant Handler as getRegionalSnapshot
    participant Redis as Redis (Upstash)

    Client->>Gateway: GET /api/intelligence/v1/get-regional-snapshot?region_id=mena
    Gateway->>Gateway: Auth check (PREMIUM_RPC_PATHS)
    Gateway->>Gateway: Proto validation (buf.validate regex)
    Gateway->>Handler: invoke(req)
    Handler->>Redis: GET intelligence:snapshot:v1:mena:latest
    Redis-->>Handler: snapshot_id (string) or null
    alt no snapshot_id
        Handler-->>Gateway: {}
    else snapshot_id found
        Handler->>Redis: GET intelligence:snapshot-by-id:v1:{snapshot_id}
        Redis-->>Handler: PersistedSnapshot (snake_case JSON) or null
        alt null / malformed
            Handler-->>Gateway: {}
        else valid
            Handler->>Handler: adaptSnapshot() snake_case→camelCase
            Handler-->>Gateway: { snapshot: RegionalSnapshot }
        end
    end
    Gateway->>Gateway: Apply slow-browser cache headers (max-age=300, no CDN)
    Gateway-->>Client: 200 OK
Loading

Reviews (1): Last reviewed commit: "test(intelligence): unit tests for get-r..." | Re-trigger Greptile

Comment thread server/gateway.ts
'/api/intelligence/v1/get-country-energy-profile': 'slow',
'/api/intelligence/v1/compute-energy-shock': 'fast',
'/api/intelligence/v1/get-country-port-activity': 'slow',
'/api/intelligence/v1/get-regional-snapshot': 'slow',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 RPC_CACHE_TIER entry is dead code for premium paths

Because PREMIUM_RPC_PATHS.has(pathname) is checked first in the cache-tier decision (line 434), the gateway unconditionally uses 'slow-browser' for every premium endpoint — the RPC_CACHE_TIER[pathname] branch is never reached. The effective Cache-Control for this endpoint is max-age=300, stale-while-revalidate=60, stale-if-error=1800 (no public, no s-maxage, no CDN caching), not the 'slow' tier claimed in the PR description ("300s browser, 1800s edge").

All other entries in PREMIUM_RPC_PATHS have the same dead entry in RPC_CACHE_TIER, so this is an established (if confusing) codebase pattern rather than a new mistake. However, the test in tests/get-regional-snapshot.test.mts (line 438–440) validates the presence of the dead entry, which could mislead future maintainers into believing edge caching is active for this endpoint.

Consider removing the entry or adding a comment noting that premium paths ignore RPC_CACHE_TIER.

(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-]*$",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Regex permits trailing hyphens in region_id

The pattern ^[a-z][a-z0-9-]*$ allows values like mena- or east-asia- — a hyphen in any position after the first character is valid, including at the end. This isn't a functional bug (Redis simply returns null for unknown keys), but it's looser than the kebab-case convention used by the canonical region IDs (e.g. mena, east-asia).

A pattern that prevents consecutive or trailing hyphens would match the intent more precisely:

Suggested change
(buf.validate.field).string.pattern = "^[a-z][a-z0-9-]*$",
(buf.validate.field).string.pattern = "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant