Maritime: dark-fleet detection from AIS

The problem

Vessels evading sanctions have a well-known playbook: turn off the AIS transponder in open water, transfer cargo ship-to-ship, re-flag, rename, and re-appear somewhere else. Catching this today means stitching together an AIS aggregator subscription, a vessel registry, a sanctions screening tool, and a GIS package — four vendors, four exports, and no single place where "this vessel went dark near a sanctioned port, and its beneficial owner is designated" is one traceable answer.

The scenario

An analyst is reviewing a tanker with a history of AIS gaps in the Gulf of Oman. Before escalating to enforcement, they need three things joined together: confirmation the vessel (or a vessel in the same footprint) was actually in the area during the gap, a sanctions screen on the vessel and its registered owner, and the ownership chain traced up to whoever ultimately controls it — because the vessel itself is rarely the sanctioned party.

Geofence, screen, and trace ownership — one engine

GEOFENCE, SANCTIONS_SCREEN, and BENEFICIAL_OWNERSHIP_CHAIN are all SQL-reachable operators against the same governed store — there's no export between the AIS platform and the sanctions tool.

PURPOSE 'maritime:sanctions-investigation'
 
-- 1. What else was moving through the same waters during the AIS gap window?
-- (fence is a pre-registered geofence name; from_ts/to_ts are epoch-ns)
SELECT * FROM GEOFENCE(
  'gulf-of-oman-50km',
  target_type => 'VesselPositionReport',
  from_ts => 1773327600000000000,
  to_ts   => 1773597600000000000
);
 
-- 2. Screen the vessel's registered owner against sanctions lists
SELECT * FROM SANCTIONS_SCREEN('Meridian Shipping SA')
WITH PROVENANCE;
 
-- 3. Trace beneficial ownership to the natural person or ultimate parent
SELECT * FROM BENEFICIAL_OWNERSHIP_CHAIN('Meridian Shipping SA', 7)
WITH PROVENANCE;

Typed SDK snippet

from relata import RelataClient
 
with RelataClient(
    "http://localhost:9090",
    bearer_token="relata-dev",
    purpose="maritime:sanctions-investigation",
) as client:
    # Vessels sharing the geofence during the AIS gap window
    # (from_ts / to_ts are epoch-ns; fence is a pre-registered geofence name)
    nearby = client.query(
        "SELECT * FROM GEOFENCE('gulf-of-oman-50km', "
        "target_type => 'VesselPositionReport', "
        "from_ts => 1773327600000000000, to_ts => 1773597600000000000)"
    )
    for vessel in nearby:
        print(vessel["mmsi"], vessel["last_position"])
 
    # Screen the registered owner
    hits = client.query("SELECT * FROM SANCTIONS_SCREEN('Meridian Shipping SA')")
    for hit in hits:
        print(f"Sanctions hit: {hit['list_id']} ({hit['designation_date']})")
 
    # Walk the ownership chain looking for the actual designated party
    chain = client.query(
        "SELECT * FROM BENEFICIAL_OWNERSHIP_CHAIN('Meridian Shipping SA', 7)"
    )
    for link in chain:
        print(f"depth {link['depth']}: {link['owner_name']} ({link['ownership_pct']}%)")

Why the same engine wins on this workload

  • Geospatial, graph, and identity queries are one plan, not three tools. GEOFENCE (S2-cell indexed), the ownership graph, and sanctions screening all run against the same governed store — no CSV round-trip between the AIS aggregator and the sanctions vendor. See the Query Cookbook for the full operator surface.
  • The vessel isn't the sanctioned party — the owner usually is. BENEFICIAL_OWNERSHIP_CHAIN traces through shell-company layers the same way it would for a wire transfer, because it's the same operator, not a maritime-specific reimplementation.
  • Every hit carries provenance. WITH PROVENANCE on the sanctions screen and ownership trace means the eventual enforcement package cites exactly which list, which designation date, and which ownership record produced each finding. See Governance.
  • Bi-temporal by default. Flag changes, renames, and ownership transfers are tracked over time, not overwritten — AS OF reconstructs the ownership structure as it stood on the date of the incident, not as it stands today.

See also