mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-26 01:24:59 +02:00
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:
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user