MIK North Sea operating picture
An experimental ontology extension and marimo app for correlated maritime tracks, governed zones, and watch detections.
The MIK example extension declares a maritime ontology with a correlated MaritimeTrack, vessel registry, restricted MaritimeZone, two watches, and a human detection-acknowledgement action. Download the operator notebook, the exposure pipeline notebook and the app permit template. The full-width notebook uses scrydon.apps components for a moving map, zone overlay, selected-track provenance, linked vessel identity, and alert review.
The contact table and selected-track details show the stable trackId even when an uncorrelated radar contact has no vessel identity. The map labels known vessels by vesselId and falls back to the track object ID for unknown contacts.
The map draws two zone layers — the watched restricted area and approximate zones of interest (the Belgian EEZ, the western Channel approaches, a demo subsea cable corridor, the Strait of Hormuz, the southern Red Sea, the Gulf of Guinea and the Mozambique Channel) — and a point layer with each vessel of interest as a ship at its last reported position, turned to its last course and coloured by risk. Selecting a ship selects it in the Vessels of interest tab, which lists twelve fictional vessels with a risk level, a score and the reasons behind it, and which vessels sit inside a governed area; the Detections tab adds three replayed events beside the live watch detections. Each layer is its own permitted query: the two zone layers read the same MaritimeZone type with a category filter.
This is an experimental authoring reference, not an operational MIK installation. Every vessel, score and replayed event is fictional and every polygon is approximate; the rows say so in their provenance and names. They arrive the way a live feed would — as poll-table data sources bound to MaritimeVessel, MaritimeZone and MaritimeReplayEvent — so a real registry, risk service or zone catalogue replaces a source without changing the notebook. Replayed events are a separate MaritimeReplayEvent type: the live watches alone write MaritimeDetection, and only those can be acknowledged. The AIS/radar replay tests deterministic source identity, correlation, late and future observations, entry semantics, and reporting silence. It does not connect provider credentials or submit real reports.
Zone exposure from a pipeline notebook
exposure.py is an authoring notebook, never published. It reads the vessel registry table with scrydon.sql.query, reads the MaritimeZone objects with scrydon.ontology.read, joins them in pandas (a vessel is exposed to an area when its last position lies inside the area's boundary), and writes the result to the managed table mik_vessel_zone_exposure. The MaritimeZoneExposure type is bound to that table, and the app shows it through its declared exposure query. Run the notebook once in the workspace environment the app is published from; each later run replaces the rows. Until the first run the exposure table in the app stays empty. The notebook declares duckdb and sqlglot in its dependency block: the runtime ships scrydon without its SQL engine, and the packages install from your package index under your organization's egress policy.
The 0.4.0 example replaces the vessel source with mik-vessel-registry-v4 (table mik_vessel_registry), adds the vessel heading, drops the drawn last-position ring, and adds MaritimeZoneExposure. The 0.3.0 example adds the demo vessel, zone and replay sources under new source IDs and table names; the 0.2.x fixture tables are no longer bound. The 0.2.1 example introduced a distinct extension identity, source IDs, and schema declaration names. Earlier test installations used the legacy default classification scheme; an in-place update cannot silently relabel their governed state. Use an explicit scheme migration before retiring an earlier installation.
The published app must pin the exact installed ontology revision and content hash. createMikAppPermit(revisionId, contentHash) validates that permit before it is entered in Analytics. Copy app.py into a marimo notebook, save it, and use the Analytics publication panel only after the governed north-sea-v2 track source has been provisioned and admitted. See app release manifests and marimo app components.
The ontology manifest declares trackPolicies: [{ streamId: "north-sea-v2", policy: mikFixturePolicy }] separately from the public MaritimeTrack binding. Installing it pins the immutable AIS/radar correlation policy in the private ontology track store. The fixture uses Scrydon's default scrydon.default classification scheme; customize its markings for organizations using another scheme. The installer must have clearance for every identity and mapping marking. The installed extension record and readable binding expose only the stream ID and policy revision, never the restricted radar mapping. Reinstalling the same policy is safe; changing its content requires an explicit migration and new revision.
Installed organization-scoped poll sources can now declare a trackProjection that names the north-sea-v2 stream, exact policy revision, marking, and table columns for event ID, source entity ID, observation time, latitude, and longitude. A complete poll writes its table rows first; Platform then submits bounded observation batches under the installed source ID and exact workspace environment. Failed or partial polls do not qualify as healthy source evidence. The private track store accepts only sources in the pinned policy and deduplicates immutable event IDs. After a successful or failed source-sync record commits, Platform sends an empty checkpoint that wakes the independent watch scheduler and refreshes connected governed track queries. If a later batch fails, any earlier accepted observations remain durable, but source health is degraded. If the health record cannot be committed, no checkpoint is sent. A row that carries row markings (the table's markings column) is set aside instead of published and counted in the poll's sync warning: the stream governs reads by one scheme and rank and cannot enforce row-marking slugs, so publishing it at the projection's marking would show it to readers the table hides it from. The fixture's rows carry no row markings, so all of them are published.
The test-only source-fixture.ts defines two organization-scoped poll manifests with normalized AIS and radar columns. They pin the installed track projection to the extension's stream and policy revision, but are deliberately excluded from the installable extension. Run bun apps/docs/examples/mik-north-sea/fixture-server.ts from the repository root to inspect synthetic /ais and /radar responses on port 4019. MIK_FIXTURE_MODE=load serves a synthetic reference load instead (AIS only, MIK_LOAD_TRACKS tracks at MIK_LOAD_RATE updates per second, an optional burst from MIK_LOAD_BURST_AT_SEC); it is a measurement fixture, not a scenario. To install the sources, expose the fixture through a public HTTPS test endpoint. If your organization has an egress policy, add that host to its allowlist (Settings → Governance → Egress); a poll to an unlisted host is refused with "Outbound request blocked by organization egress policy" and the reason appears on the source. Loopback, private-host, and plain-HTTP source URLs are rejected. Pass those HTTPS URLs to createMikFixtureTrackSources, then install both manifests in the exact organization and workspace environment. The server binds to localhost by default; MIK_FIXTURE_HOST and MIK_FIXTURE_PORT configure it behind the test ingress. Separate two-second polls show moving contacts and later reporting silence. An installed-source end-to-end test routes its approved HTTPS manifest URL to the local HTTP fixture through the poll runtime's test transport, then drives Platform's tick, authenticated ontology ingestion, a durable track store, governed read, and zone planning. It observes moving positions and one restricted-area entry without a Horizon viewer, but does not qualify production DNS-pinned egress. A PGlite test also drives installed AIS/radar polls through the watch evaluator and action pipeline. After both complete polls without new observations qualify, it persists one clearance-governed missing-report detection for two silent contacts with no viewer open; repeating the checkpoint does not duplicate it. The composed test uses controlled API Table write and health responses. It does not qualify real provider HTTP, API Table persistence, a Dapr callback, or a deployed COP.
To install the two live sources through the product, build a one-off extension for your HTTPS host and upload it in Settings → Extensions, then install fixture-ais and fixture-radar per environment from the Analytics extension catalog:
bun apps/docs/examples/mik-north-sea/live-sources.ts \
--ais https://<fixture-host>/ais --radar https://<fixture-host>/radar \
--version 0.1.0 --out ./mik-liveA new host needs a new --version. Restarting fixture-server.ts is safe: each run scopes its AIS event ids.
The governed track reader gets configuration-bound source health from API Table for every source in the policy. A recent position alone cannot establish health. This example does not ship operational AIS/radar poll adapters or credentials, so its synthetic replay does not make the MIK watches live. Their five-minute repair sweep, live movement and detection latency, and the proposed load targets still need a running multi-source scenario and measurement. The watches declare high and medium severity respectively; the alert component filters that governed detection property.
For missing-report watches, the platform also schedules one tenant-scoped evaluation at the next reporting deadline. A fresh complete poll after that deadline must still qualify source health before a detection can be persisted. This timer has focused tests; the composed MIK callback and five-second target have not been measured.