Skip to content

External Normalizer / Adapter Boundary Contract

This page defines the boundary between external gateways or adapters and nats-sinks. It is written for defence and mission-support readers, but the contract is generic enough for business systems, media pipelines, observability feeds, logistics adapters, and enterprise integration services.

nats-sinks is not a tactical protocol stack, media decoder, C2 service, authorization gateway, sanitizer, policy authority, or semantic validator. Those responsibilities stay with systems that are approved for the source domain. nats-sinks consumes already-authorized, already-normalized events from NATS JetStream and persists them with at-least-once, commit-then-ACK delivery semantics.

Boundary Model

flowchart LR
    Source[Source system or sensor] --> Adapter[External gateway / adapter / decoder]
    Adapter --> Normalize[Normalize, authorize, redact, validate]
    Normalize --> JS[NATS JetStream]
    JS --> Runner[nats-sinks core runner]
    Runner --> Policy[Generic validation and policy]
    Policy --> Sink[Configured sink]
    Sink --> Store[Durable store]
    Store --> Ack[ACK after durable commit]

The external component owns:

  • protocol parsing, media decoding, and domain-specific validation;
  • authorization, sanitization, redaction, and release decisions;
  • schema translation from source-specific data into a deployment-approved event shape;
  • classification and security-label assignment according to the deployment policy;
  • payload-reference creation when raw or large artifacts should not be embedded in the JetStream message;
  • semantic interpretation of tactical, media, C2, logistics, or enterprise data.

nats-sinks owns:

  • JetStream consumption through the configured durable consumer;
  • normalized event envelope validation;
  • generic mission metadata validation when configured;
  • generic security-label, classification, priority, and label preservation;
  • optional payload encryption, policy checks, and custody metadata;
  • DLQ handling for permanent validation failures;
  • destination writes through configured sinks;
  • ACK only after durable success for all required sink work.

Adapter Families

The same boundary applies across adapter types:

Adapter family External responsibility nats-sinks responsibility
Enterprise data gateway Map business transactions into stable events. Persist event payload, metadata, lineage, and audit context.
Link 16 / TADIL-J normalizer Interpret authorized J-series-derived data outside nats-sinks. Persist the normalized event and metadata.
VMF / K-series normalizer Interpret authorized VMF-derived data outside nats-sinks. Persist the normalized event and metadata.
MIP4-IES / C2 object adapter Perform MIP-specific exchange, validation, and object mapping. Preserve object custody, version, correlation, and relationship metadata.
NFFI / FFTS / FFI adapter Parse, authorize, and redact tracking data externally. Preserve tracking-event custody and data-quality metadata.
STANAG 4609 / KLV / MISB decoder Demux media, decode approved metadata, and create artifact references. Persist media custody records and decoded metadata placeholders.
Observability or telemetry adapter Normalize operational signals and apply sharing policy. Persist or route approved event records without changing delivery semantics.

For the companion standards overview, see the Interoperability Standards Matrix.

Normalized Event Requirements

Every adapter should publish a bounded, deployment-approved event envelope. The exact profile is deployment-specific, but a durable event should normally include:

Field Purpose
event_id Stable event identifier generated outside nats-sinks.
source_system System that produced or owns the source event.
source_gateway Gateway, decoder, or adapter that normalized the event.
schema_profile Named schema or metadata profile used by the adapter.
schema_version Version of that schema profile.
payload or normalized_payload Approved normalized event body.
raw_payload_reference Optional reference to an external artifact or source export.
payload_sha256 Digest of the normalized payload or referenced artifact.
event_time Time the source event claims or represents.
receive_time Time the adapter or gateway received the event.
classification Deployment-approved classification or sensitivity label.
labels Searchable, low-cardinality labels approved for storage.
security_labels Optional data-centric security label profile.
mission_metadata Optional generic mission or operational metadata.
data_quality Optional quality, confidence, freshness, or completeness metadata.
correlation_id Identifier that links related events or transactions.
idempotency_key Stable duplicate-detection key or fields used to derive one.
custody Optional custody fields or hash references supplied by the adapter.

Use the generic Mission Metadata, Security Label Profile, Idempotency, Tamper-Evident Custody, and Dead Letter Queues guidance when designing a profile.

Idempotency And ACK Behavior

Adapters should publish stable identifiers. nats-sinks assumes at-least-once delivery, so redelivery is normal. A duplicate event should result in an idempotent update or no-op rather than a second mission, media, or business record.

Good idempotency sources include:

  • event_id plus source_system;
  • source stream sequence plus gateway identifier;
  • artifact ID plus artifact hash;
  • object ID plus object version;
  • track ID plus event time and sequence;
  • transaction ID plus source system.

Do not use mutable fields such as priority, classification, labels, or free text as the only idempotency key.

nats-sinks ACKs JetStream only after the required durable sink work succeeds. If validation fails permanently, the configured DLQ behavior must complete before ACK. If sink commit status is uncertain, prefer redelivery over silent loss.

Synthetic Examples

The following examples are illustrative and unclassified. They are not protocol parsers, message catalogues, tactical procedures, or controlled interface specifications.

Generic Business Event

{
  "event_id": "business-event-0001",
  "source_system": "order-service",
  "source_gateway": "enterprise-adapter",
  "schema_profile": "nats-sinks.business-event.v1",
  "schema_version": "1.0",
  "event_time": "2026-05-31T10:00:00Z",
  "receive_time": "2026-05-31T10:00:02Z",
  "correlation_id": "order-correlation-0001",
  "classification": "internal",
  "labels": ["synthetic", "business"],
  "payload_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "normalized_payload": {
    "event_type": "order_created",
    "order_reference": "ORDER-EXAMPLE-0001",
    "status": "created"
  },
  "idempotency_key": "order-service:business-event-0001"
}

Generic Media Artifact Event

{
  "event_id": "media-artifact-0001",
  "source_system": "media-pipeline",
  "source_gateway": "media-normalizer",
  "schema_profile": "nats-sinks.artifact-reference.v1",
  "schema_version": "1.0",
  "event_time": "2026-05-31T10:01:00Z",
  "receive_time": "2026-05-31T10:01:03Z",
  "classification": "internal",
  "labels": ["synthetic", "media"],
  "raw_payload_reference": {
    "artifact_id": "artifact-0001",
    "uri": "object://example-bucket/synthetic/artifact-0001.bin",
    "media_type": "application/octet-stream",
    "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
  },
  "data_quality": {
    "quality_source": "media-normalizer",
    "confidence": 0.98
  },
  "idempotency_key": "artifact-0001"
}
{
  "event_id": "link16-derived-0001",
  "source_system": "authorized-gateway-placeholder",
  "source_gateway": "link16-normalizer-placeholder",
  "schema_profile": "nats-sinks.normalized-link-event.v1",
  "schema_version": "1.0",
  "message_family": "TADIL-J",
  "message_name": "Jx.x",
  "event_time": "2026-05-31T10:02:00Z",
  "receive_time": "2026-05-31T10:02:02Z",
  "correlation_id": "synthetic-track-correlation-0001",
  "classification": "placeholder",
  "labels": ["synthetic", "link16-derived"],
  "payload_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
  "normalized_payload": {
    "synthetic": true,
    "track_reference": "TRACK-EXAMPLE-0001",
    "status": "observed"
  },
  "mission_metadata": {
    "profile": "synthetic-mission-event",
    "phase": "observe"
  },
  "idempotency_key": "authorized-gateway-placeholder:link16-derived-0001"
}

Synthetic VMF-Derived Event

{
  "event_id": "vmf-derived-0001",
  "source_system": "vmf-source-placeholder",
  "source_gateway": "vmf-normalizer-placeholder",
  "schema_profile": "nats-sinks.normalized-vmf-event.v1",
  "schema_version": "1.0",
  "message_family": "VMF",
  "message_name": "K-series-placeholder",
  "event_time": "2026-05-31T10:03:00Z",
  "receive_time": "2026-05-31T10:03:02Z",
  "classification": "placeholder",
  "labels": ["synthetic", "vmf-derived"],
  "payload_sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
  "normalized_payload": {
    "synthetic": true,
    "sender_reference": "SENDER-EXAMPLE",
    "recipient_reference": "RECIPIENT-EXAMPLE",
    "status": "normalized"
  },
  "idempotency_key": "vmf-source-placeholder:vmf-derived-0001"
}

Synthetic MIP4-IES-Derived C2 Object Event

{
  "event_id": "c2-object-0001",
  "source_system": "c2-adapter-placeholder",
  "source_gateway": "mip-adapter-placeholder",
  "schema_profile": "nats-sinks.object-relationship.v1",
  "schema_version": "1.0",
  "event_time": "2026-05-31T10:04:00Z",
  "receive_time": "2026-05-31T10:04:02Z",
  "classification": "placeholder",
  "labels": ["synthetic", "c2-object"],
  "normalized_payload": {
    "object_id": "OBJ-EXAMPLE-0001",
    "object_type": "generic-object",
    "object_operation": "upsert",
    "object_version": "1"
  },
  "data_quality": {
    "quality_source": "mip-adapter-placeholder",
    "completeness": 1.0
  },
  "idempotency_key": "OBJ-EXAMPLE-0001:1"
}

Synthetic FFTS / FFI Tracking Event

{
  "event_id": "tracking-event-0001",
  "source_system": "tracking-source-placeholder",
  "source_gateway": "ffts-adapter-placeholder",
  "schema_profile": "nats-sinks.geo-temporal-track-fix.v1",
  "schema_version": "1.0",
  "event_time": "2026-05-31T10:05:00Z",
  "receive_time": "2026-05-31T10:05:02Z",
  "classification": "placeholder",
  "labels": ["synthetic", "tracking"],
  "normalized_payload": {
    "track_id": "TRACK-EXAMPLE-0002",
    "position_reference": "redacted-position-reference",
    "position_quality": "reduced",
    "synthetic": true
  },
  "data_quality": {
    "quality_source": "ffts-adapter-placeholder",
    "timeliness": 0.95
  },
  "idempotency_key": "TRACK-EXAMPLE-0002:2026-05-31T10:05:00Z"
}

Synthetic FMV / KLV-Derived Event

{
  "event_id": "fmv-klv-event-0001",
  "source_system": "media-source-placeholder",
  "source_gateway": "klv-decoder-placeholder",
  "schema_profile": "nats-sinks.fmv-custody-event.v1",
  "schema_version": "1.0",
  "event_time": "2026-05-31T10:06:00Z",
  "receive_time": "2026-05-31T10:06:03Z",
  "classification": "placeholder",
  "labels": ["synthetic", "fmv", "custody"],
  "raw_payload_reference": {
    "artifact_id": "fmv-segment-0001",
    "uri": "object://example-bucket/synthetic/fmv-segment-0001.ts",
    "media_type": "video/mp2t",
    "sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
  },
  "normalized_payload": {
    "synthetic": true,
    "segment_id": "SEGMENT-EXAMPLE-0001",
    "presentation_timestamp": "2026-05-31T10:06:00Z",
    "decoded_metadata_reference": "klv-placeholder-0001"
  },
  "idempotency_key": "fmv-segment-0001"
}

Subject Naming Guidance

Subject design should remain boring and reviewable. Prefer stable, bounded subject families such as:

  • enterprise.orders.created
  • mission.synthetic.link.normalized
  • mission.synthetic.vmf.normalized
  • mission.synthetic.c2.object
  • mission.synthetic.tracking.fix
  • mission.synthetic.media.custody

Avoid putting sensitive values, raw identifiers, coordinates, unit names, target references, or free-text mission details in subject tokens. Put approved context in metadata fields where validation, redaction, and storage policy are clear.

Failure Handling

Adapters should distinguish temporary source-side delays from permanent normalization failures before publishing to JetStream. Once an event reaches JetStream, nats-sinks applies the configured validation and sink behavior:

  • malformed normalized event metadata should fail closed;
  • permanent validation failures should go to the configured DLQ before ACK;
  • temporary sink failures should result in retry, redelivery, or bounded spool behavior according to the deployment configuration;
  • duplicate delivery should be safe through idempotent sink configuration.

Non-Goals And Safety Notes

This contract does not implement or describe Link 16, VMF, MIP4-IES, NFFI, FFTS, FFI, STANAG 4609, SMPTE 336 KLV, MISB, ADatP, APP, or any other protocol. It does not include controlled field catalogues, classified message semantics, operational coordinates, real tracks, keys, frequencies, target data, platform identifiers, unit identifiers, or tactical procedures.

Use this page only for authorized environments and synthetic examples. Actual source mappings must be defined by approved programme teams and reviewed under their own data-handling, releasability, and accreditation processes.