Skip to content

fix(intelligence): region-scope signals and chokepoint evidence#2952

Merged
koala73 merged 2 commits into
mainfrom
fix/regional-snapshot-theater-matching
Apr 11, 2026
Merged

fix(intelligence): region-scope signals and chokepoint evidence#2952
koala73 merged 2 commits into
mainfrom
fix/regional-snapshot-theater-matching

Conversation

@koala73

@koala73 koala73 commented Apr 11, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes two review findings on PR #2940 that caused MENA/SSA snapshots to silently drop broad cross-source signals and leak foreign chokepoint events into every region's evidence chain.

P1 — theater label matching

scripts/seed-cross-source-signals.mjs normalizes raw values to broad display labels like "Middle East" and "Sub-Saharan Africa". The consumer side (balance-vector.mjs:70 and evidence-collector.mjs:28) compared these against region.theaters kebab IDs (levant, persian-gulf, horn-of-africa, sahel, etc.).

"middle east" does not substring-match any of those even after kebab-to-space transform, so every MENA signal emitted with the broad label was silently dropped from coercive_pressure and the evidence chain. Same for SSA's "Sub-Saharan Africa".

Added signalAliases per region and a shared isSignalInRegion helper in shared/geography.js. Both balance-vector.mjs and evidence-collector.mjs now route signals through the helper, which normalizes both sides (lowercase, trim, collapse dashes) and matches against theater IDs or region aliases.

P2 — chokepoint region leak

evidence-collector.mjs:62 iterated every chokepoint in the payload without filtering by regionId, so Taiwan Strait, Baltic, and Panama threat events surfaced in MENA and SSA evidence chains. Now derives the allowed chokepoint ID set from getRegionCorridors(regionId) and skips anything not owned by the region.

Testing

  • 15 new unit tests in tests/regional-snapshot.test.mjs:
    • isSignalInRegion: theater IDs, broad aliases, case/whitespace tolerance, cross-region rejection, empty/null handling, region-object accepted
    • collectEvidence region scoping: MENA/East Asia/Europe get only their own chokepoints, SSA gets none (tier-3 Cape only), normal-threat skip preserved, broad MENA/SSA cross-source signals land in evidence
  • Full data test suite: 4288 pass, 0 fail
  • npm run typecheck and npm run typecheck:api: clean
  • JSDoc tsc -p scripts/jsconfig.json: clean for touched files (remaining errors are pre-existing in seed-forecasts.mjs / _r2-storage.mjs)
  • Biome lint on touched files: clean

Post-Deploy Monitoring & Validation

  • What to monitor/search
    • Logs: Railway `regional-snapshot` cron output for `persisted` count and per-region driver counts
    • Metrics/Dashboards: MENA and SSA `coercive_pressure` axis values on the next cron run (should pick up previously-dropped broad signals)
  • Validation checks (queries/commands)
    • `redis-cli GET intelligence:snapshot:v1:mena:latest` then fetch the snapshot payload and inspect `balance.pressures` for coercive-pressure drivers sourced from cross-source
    • `redis-cli GET intelligence:snapshot:v1:sub-saharan-africa:latest` and verify `evidence` contains no entries with `corridor` set to `hormuz`/`taiwan_strait`/`bosphorus`/`panama`/etc.
  • Expected healthy behavior
    • MENA and SSA snapshots show non-zero cross-source signal contribution to `coercive_pressure` when the seed emits broad `"Middle East"`/`"Sub-Saharan Africa"` labels
    • Evidence chains per region contain only that region's own chokepoints
  • Failure signal(s) / rollback trigger
    • MENA snapshot shows `coercive_pressure: 0` despite `intelligence:cross-source-signals:v1` having elevated MENA signals, OR SSA evidence still lists Taiwan/Baltic corridors → rollback
  • Validation window & owner
    • Window: next 2 Railway cron cycles (~12h) after merge
    • Owner: Elie

Test plan

  • Unit tests for isSignalInRegion
  • Unit tests for collectEvidence chokepoint region filter
  • Full `npm run test:data` suite passes (4288/4288)
  • Typecheck clean
  • Post-deploy: verify MENA/SSA snapshots pick up broad-label cross-source signals on next cron

@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:28pm

Request Review

@greptile-apps

greptile-apps Bot commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two correctness bugs on the MENA/SSA regional snapshot pipeline: (1) broad display labels emitted by the cross-source signal seed ("Middle East", "Sub-Saharan Africa") were silently dropped because they failed substring-matching against fine-grained theater IDs like levant/horn-of-africa; (2) the chokepoint loop in evidence-collector.mjs iterated the entire payload without region-scoping, leaking Taiwan Strait, Baltic, and Panama events into every region's evidence chain. Both bugs are correctly fixed via the new isSignalInRegion helper and the regionChokepointIds allowlist; the 15 new unit tests are well-targeted regression coverage.

Confidence Score: 5/5

Safe to merge — both bugs are correctly fixed, tests are comprehensive, and no P0/P1 issues remain.

All findings are P2 (pre-existing data model inconsistency in THEATERS.corridorIds, theoretical false-positive risk from the short 'eu' alias). The core logic of isSignalInRegion and the regionChokepointIds allowlist is correct, the type declarations match, and the 15 unit tests directly cover the regression cases.

shared/geography.js — the THEATERS.corridorIds inconsistency for horn-of-africa and caribbean is worth cleaning up to avoid misleading future developers.

Important Files Changed

Filename Overview
shared/geography.js Adds signalAliases to each region and the isSignalInRegion helper. Two pre-existing inconsistencies in THEATERS.corridorIds (horn-of-africa claiming babelm, caribbean claiming panama) are now more visible because this file is the canonical source of region-scoping logic.
scripts/regional-snapshot/evidence-collector.mjs Fixes both bugs: cross-source signals now routed through isSignalInRegion, and chokepoints filtered to regionChokepointIds derived from getRegionCorridors. Logic is clean.
scripts/regional-snapshot/balance-vector.mjs Imports and applies isSignalInRegion in computeCoercivePressure; the region object is already in scope and passed correctly. No other changes.
shared/geography.d.ts Adds signalAliases: string[] to RegionDef and declares isSignalInRegion. Matches implementation exactly.
tests/regional-snapshot.test.mjs 15 new tests covering both regression cases with good boundary conditions (case/whitespace tolerance, cross-region rejection, empty inputs, object vs string region argument, broad-label matching). All assertions are correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Cross-source signal / Chokepoint event] --> B{Signal type?}

    B -- cross-source signal --> C[isSignalInRegion theater region]
    C --> D{theater matches region.theaters? kebab to space normalized}
    D -- Yes --> G[Include in output]
    D -- No --> E{theater matches region.signalAliases? e.g. middle east sub-saharan africa}
    E -- Yes --> G
    E -- No --> H[Skip - silent drop fixed]

    B -- chokepoint event --> I[Build regionChokepointIds from getRegionCorridors regionId]
    I --> J{cpId in regionChokepointIds?}
    J -- No --> K[Skip - cross-region leak fixed]
    J -- Yes --> L{threatLevel != normal or empty?}
    L -- No --> K
    L -- Yes --> G

    G --> M[evidence-collector.mjs collectEvidence output]
    G --> N[balance-vector.mjs computeCoercivePressure drivers]
Loading

Comments Outside Diff (1)

  1. shared/geography.js, line 168 (link)

    P2 THEATERS.corridorIds inconsistent with CORRIDORS.theaterId

    horn-of-africa declares corridorIds: ['babelm'], but babelm's theaterId is 'red-sea' (a MENA theater). Since getRegionCorridors derives corridors exclusively from CORRIDORS.theaterId, THEATERS.corridorIds is effectively dead data for this entry — calling getRegionCorridors('sub-saharan-africa') returns only cape-of-good-hope, not babelm, regardless of what corridorIds says. The same inconsistency exists for 'caribbean' (which lists corridorIds: ['panama'] but panama's theaterId is 'north-america'). A developer reading THEATERS today would expect SSA to surface Bab el-Mandeb events after this PR's chokepoint fix — they won't. Consider either aligning CORRIDORS.theaterId to match the THEATERS.corridorIds intent, or clearing the stale corridorIds entries.

Reviews (1): Last reviewed commit: "fix(intelligence): region-scope signals ..." | Re-trigger Greptile

Comment thread shared/geography.js Outdated
forecastLabel: 'Europe',
wbCode: 'ECS',
theaters: ['eastern-europe', 'western-europe', 'baltic', 'arctic'],
signalAliases: ['europe', 'eu'],

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 Short alias "eu" risks substring false-positives

The matching in isSignalInRegion checks t.includes(alias), meaning any theater string that contains "eu" as a substring would match the Europe region. Two-character aliases like "eu" are particularly broad — e.g., a hypothetical theater label "Neutral Zone" or the word "fuel" would satisfy "neutral zone".includes("eu")true. In practice the seed emits well-formed geographic labels, so this likely won't fire incorrectly, but it's worth either replacing "eu" with "european union" (which maps to a real seed label) or switching short aliases to exact equality instead of substring matching.

Suggested change
signalAliases: ['europe', 'eu'],
signalAliases: ['europe', 'european union'],

koala73 added 2 commits April 11, 2026 20:22
Two review findings on PR #2940 caused MENA/SSA snapshots to silently
drop broad cross-source signals and leak foreign chokepoint events into
every region's evidence chain.

P1 - theater label matching
  seed-cross-source-signals.mjs normalizes raw values to broad display
  labels like "Middle East" and "Sub-Saharan Africa". The consumer side
  compared these against region.theaters kebab IDs (levant, persian-gulf,
  horn-of-africa). "middle east" does not substring-match any of those,
  so every MENA signal emitted with the broad label was silently dropped
  from coercive_pressure and the evidence chain. Same for SSA.

  Added signalAliases per region and a shared isSignalInRegion helper in
  shared/geography.js. Both balance-vector.mjs and evidence-collector.mjs
  now route signals through the helper, which normalizes both sides and
  matches against theater IDs or region aliases.

P2 - chokepoint region leak
  evidence-collector.mjs:62 iterated every chokepoint in the payload
  without filtering by regionId, so Taiwan Strait, Baltic, and Panama
  threat events surfaced in MENA and SSA evidence chains. Now derives
  the allowed chokepoint ID set from getRegionCorridors(regionId) and
  skips anything not owned by the region.

Added 15 unit tests covering: broad-label matching, kebab/spaced input,
cross-region rejection, and the chokepoint filter for MENA/East Asia/
Europe/SSA.
Two P2 findings from Greptile on the region-scoping PR.

1) Drop 'eu' short alias from europe.signalAliases
   `isSignalInRegion` uses substring matching, and bare 'eu' would match
   any theater label containing those two letters ('fuel', 'neutral zone',
   'feudal'). Replaced with 'european union' which is long enough to be
   unambiguous. No seed currently emits a bare 'eu' label, so this is
   pure hardening.

2) Make THEATERS.corridorIds live data via getRegionCorridors union
   horn-of-africa declared corridorIds: ['babelm'] and caribbean declared
   corridorIds: ['panama'], but `getRegionCorridors` only consulted
   CORRIDORS.theaterId — so those entries were dead data. After yesterday's
   region-scoped chokepoint filter, Bab el-Mandeb threat events landed
   ONLY in MENA evidence (via the primary red-sea theater), never in SSA,
   even though the corridor physically borders Djibouti/Eritrea. Same for
   Panama missing from LatAm.

   `getRegionCorridors` now unions direct theater membership (via
   CORRIDORS.theaterId) with indirect claims (via THEATERS.corridorIds),
   de-duplicated by corridor id. This reflects geopolitical reality:
     - MENA + SSA both see babelm threat events
     - NA + LatAm both see panama threat events
   Scoring impact: SSA maritime_access now weighs babelm (weight 0.9),
   LatAm maritime_access now weighs panama (weight 0.6). These were
   missing buffers under the pre-fix model.

   Added regression tests for both new paths. The existing
   "SSA evidence has no chokepoints" test was inverted to assert SSA now
   DOES include babelm (and excludes MENA/East Asia corridors).
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