Axiom Overwatch maintainsDocumentation Index
Fetch the complete documentation index at: https://docs.axiomancer.io/llms.txt
Use this file to discover all available pages before exploring further.
temporal_edges — a relationship graph that records every observed change in a vessel’s identity over time. Each edge captures a transition between two snapshots of the same IMO (a name change, a flag change, an MMSI change) with a confidence score and an obfuscation flag. Downstream motif detectors (detect-temporal-motifs) traverse this graph to surface shell-hop chains, jurisdiction-shopping patterns, and other multi-step deception signals that a single-event API cannot see.
When to use it
Reach for temporal edges when you need the history of how a vessel’s identity has moved, not just the latest snapshot. Typical use cases:- Tracing a renamed-and-reflagged vessel across multiple identity hops
- Distinguishing a routine rename (high name similarity) from a shell-hop (low similarity)
- Surfacing MMSI changes, which are inherently suspicious because MMSI should be stable for the life of a hull
- Feeding a motif detector that scans for repeated identity churn within a configurable window
vessel_identity_history directly without the edge transform.
How edges are built
For every IMO with new history since the last run, Overwatch fetches the full snapshot history, sorts it byobserved_at, and walks consecutive pairs. Each detected change emits one edge.
Group history by IMO
All rows in
vessel_identity_history for the affected IMOs are loaded and partitioned by imo_number. Spurious IMOs (0, 1) are filtered out at this stage.Walk consecutive snapshots
Within each IMO group, snapshots are sorted by
observed_at. The producer iterates pairwise (prev → curr) and tests three fields independently: name, flag, mmsi.Emit one edge per changed field
A pair can produce zero, one, two, or three edges — a snapshot where the vessel renamed, reflagged, and changed MMSI on the same day produces all three. Each edge carries its own relation type, confidence, and obfuscation flag.
Relation types
| Relation | Trigger | Confidence | Obfuscation flag |
|---|---|---|---|
renamed_to | Name changed and Jaccard similarity ≥ 0.3 | Equal to the similarity score (min 0.1) | false |
shell_hop | Name changed and Jaccard similarity < 0.3 | Equal to the similarity score (min 0.1) | true |
jurisdiction_changed | Flag changed | 0.9 | false |
mmsi_changed | MMSI changed | 0.7 | true |
renamed_to / shell_hop split runs the same Jaccard character-set similarity used by TemporalGraphEngine in @axiom/core, so the live producer and the engine agree on whether a rename looks legitimate. A near-identical name (MV ATLAS → M/V ATLAS) scores high and stays as renamed_to; an unrelated name (MV ATLAS → OCEAN STAR VII) scores low and is promoted to shell_hop with obfuscation_flag = true.
MMSI changes are always flagged as obfuscation because MMSI is meant to be stable for the operational life of the hull. Flag changes are not — vessels legitimately reflag for tax, registry, or charter reasons — so jurisdiction_changed ships with the obfuscation flag clear and lets downstream motif detectors decide based on cadence and combination.
Output shape
Each edge upserted intotemporal_edges:
id—edge_{type}_{imo}_{from_ms}_{to_ms}, deterministic and stablesource_entity_id,target_entity_id—imo_{imo}_{ms}for the two endpointsrelation_type— one of the four values in the table abovevalid_from— theobserved_atof the new snapshot (when the change was first observed)valid_to—null(open-ended; closed implicitly by the next edge from the same source)confidence_decay— name-derived for renames, fixed for flag and MMSI changesobfuscation_flag—trueforshell_hopandmmsi_changedsource_feed,source_system,source_uri,record_id— provenance tags identifying the producer and the underlying history row
Production pipeline
The producer runs as thepopulate-temporal-edges Edge Function rather than ad-hoc. It is the only writer of temporal_edges and is the upstream dependency for any motif-level analysis.
Incremental schedule
The function runs every 4 hours under pg_cron. Each run reads the last successful run’smetadata.last_run_at from ingestion_logs, queries vessel_identity_history for IMOs with new observations since that timestamp, and processes only those IMOs. On the first run (or after a wipe), the function falls back to a 180-day lookback so a fresh deployment populates promptly without scanning the entire history.
To stay inside the 150-second Edge Function timeout, each run caps at 300 IMOs. History fetches are chunked at 75 IMOs per IN() call with a 5,000-row safety cap, which prevents PostgREST’s default page limit from silently dropping rows for IMOs late in the result set. Edge upserts are batched at 500 rows.
Direct invocation
You can call the Edge Function directly to force an incremental pass — for example, after correcting upstream identity data:ingestion_logs with source = 'populate-temporal-edges', the records-fetched and records-stored counts, and the last_run_at watermark used by the next incremental run.
Reference remediation runner
When the status page flagstemporal_edges as stale — typically because the cron job missed a run or hit a transient error — invoke the reference remediation runner to re-trigger the producer and let the freshness gap clear on its own.
The runner follows a strict read → confirm → act → exit contract: it reads the open freshness gap from cockpit_gaps, confirms remediation is actually warranted, posts to the Edge Function, and exits. It does not write to cockpit_gaps directly — cockpit_detect_gaps (every 15 minutes) prunes the gap on its next run once the underlying table is fresh again.
SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in the environment (or --supabase-url / --service-key flags). Exit codes:
| Code | Meaning |
|---|---|
0 | No open gap (table fresh) or remediation succeeded |
1 | Environment / configuration error |
2 | Edge Function returned a non-2xx response |
cockpit_gaps), runners that act but never mutate the gap table, and a detector that prunes on the next sweep — keeps cockpit_gaps durably consistent across multiple remediation runners without any cross-runner locking.
What the producer doesn’t do
- No retroactive edits. Edges are immutable once written. If
vessel_identity_historyis corrected, run a targeted backfill — re-running the function will skip existing IDs because ofignoreDuplicates: true. - No cross-IMO inference. A renamed-and-reflagged vessel that surfaces under a new IMO is not stitched here. Cross-IMO entity resolution lives upstream in the identity-tracking pipeline.
- No motif-level scoring. The producer only emits the building blocks. Multi-hop patterns (rapid name churn, repeated jurisdiction flips, name + MMSI co-changes) are scored by
detect-temporal-motifs, which traverses the edges this function writes. - No alerting. The producer logs to
ingestion_logsand exits. Alerts come from the consumers downstream oftemporal_edges.
Related references
- Risk API — surfaces identity-change events and the underlying
vessel_identity_history - Investigations API — case files that pull identity history into a forensics view
- Status page — health view that surfaces the freshness gap on
temporal_edges