-
Notifications
You must be signed in to change notification settings - Fork 0
Behavioural Contract
This page is an explanatory map of the tested Python reference behavior. The canonical document in the main repository is normative:
If this Wiki and the canonical contract differ, use the current production code and canonical contract, then correct the Wiki. Future implementation changes must update tests and the contract together.
The contract covers observable behavior for:
- ingress event acceptance;
- supported AIS NMEA extraction;
- multipart assembly and lifecycle outcomes;
- forwarding-owned TAG metadata;
- single and multipart deduplication;
- secure replay, session, and nonce state;
- routing snapshot timing; and
- forwarding and cleanup boundaries.
It is not an AIS semantic decoder, storage or analytics specification, spoof-detection specification, native interface, or ABI.
IngressEvent
|
accepted-string boundary
|
one routing snapshot and optional source match
|
NMEA and ingress TAG extraction
|
multipart assembly
|
forwarding metadata selection
|
global or target-scoped deduplication
|
outbound TAG construction and UDP forwarding
Unless an explicitly documented wall-clock protocol rule applies, process-local TTL state is:
Live while
age < ttland expired whenage >= ttl.
Exact duplicates do not refresh dedup entries, multipart groups, handshake replay records, or data-nonce records. Unique multipart progress and valid secure-session activity are the relevant refresh cases.
See State, Lifecycle, and Limits for the comparative owner model.
IngressEvent.raw_line must satisfy isinstance(raw_line, str). Non-string values are ignored before routing and extraction, and later queued events continue normally.
The extractor accepts supported VDM and VDO talker/family combinations in input order. It requires checksum-field syntax of * followed by two hexadecimal characters but does not verify checksum arithmetic. A TAG block is associated only when its closing backslash immediately precedes the sentence.
The public identity is:
AssemblyKey = tuple[str, str, str, int]
# (assembler identity, sequential ID, channel, declared total)The runtime assembler identity is the ingress peer IP and port. It is distinct from routing source_id. TAG g and the current fragment ordinal are not AssemblyKey fields.
feed_outcome() distinguishes:
| Status | Meaning |
|---|---|
INVALID |
Input cannot enter a valid assembly lifecycle. |
SINGLE |
One valid sentence is immediately ready without multipart state or clock use. |
LIMIT_EXCEEDED |
A valid multipart declaration exceeds the configured fragment limit. |
PENDING |
Unique progress was accepted but the group remains incomplete. |
DUPLICATE |
The exact sentence already occupies that ordinal. |
CONFLICT |
Different content occupied the ordinal, invalidating the generation. |
COMPLETE |
Every ordinal is present and output is materialized in order. |
Fragments may arrive fully out of order. Exact duplicates are idempotent and do not refresh group lifetime. Unique progress does refresh it. A conflict removes the live generation and does not seed a replacement from the conflicting arrival.
discarded_keys reports expiry, conflict, and capacity removals in deterministic order. Completion consumes its own context separately; cleanup_expired() and reset() also return keys for external owners to consume. Together, these surfaces let forwarding metadata follow assembler lifecycle boundaries without reading assembler internals.
Blank sequential IDs remain supported. Fragments from separate physical transmissions can therefore form a synthetic group when all other identity fields collide within one live window; completion is not proof of common physical origin.
The Python assembler supports optional max_fragments_per_group and max_pending_groups. Current service wiring leaves both as None; they are not YAML configuration keys.
The assembler owns fragment state. The forwarding loop separately owns multipart TAG s, c, and g context under the same AssemblyKey.
Every assembler-reported discarded key clears all three contexts before metadata on the current arrival is considered. Completion consumes them even when no route matches or deduplication suppresses all output.
- TAG
s: the non-empty completion-arrival value can override earlier cached ingress metadata; final output selection still follows configured station/input policy. - TAG
c: multipart selection uses the minimum valid decimal observation. A duplicate may lower but not raise it. Multipartc:0is preserved; single-sentencec:0retains the server-time compatibility fallback. - TAG
g: candidates are non-empty decimal strings compared exactly. One observed value can be preserved; none or disagreement produces one generated ID for the completed group. TAGgdoes not define assembler identity.
See Multipart NMEA Assembly and TAG Handling.
A single message uses its exact extracted sentence as the logical key. A multipart message uses the ordinal-ordered tuple of exact extracted sentences. Ingress TAG metadata is not part of either key.
The multipart tuple is decided once before any fragment is emitted:
- legacy mode uses one global scope;
- routing mode uses one independent scope per
target_id; and - ingress source identity does not add another scope for a target.
The Python Deduplicator supports optional max_entries. Current service wiring uses None, so the running service does not impose that capacity through this object and YAML does not expose it.
See Deduplication.
SecureState is the process-local owner for:
- verified handshake replay records;
- active secure sessions; and
- accepted data nonces scoped to each session.
Network policy is checked before secure-state clocks, cryptography, cleanup, or mutation. Allowed packets use one monotonic observation for local lifecycle decisions. Wall time remains separate for handshake freshness, pong timestamps, and diagnostics.
Replay and nonce duplicates do not refresh retention. Sessions are touched only by valid matching keepalive activity or fully validated secure ping/NMEA traffic. Secure state is hard-bounded, traffic-cleaned, non-durable, and lost at restart.
See UDPSEC Security and State for verified limits, replacement rules, and trust boundaries.
When routing state is present, the consumer captures one immutable snapshot for each accepted string event. If it contains a table, the event's source_id is matched once. All supported sentences from that event use the same result.
A concurrent replacement affects the next accepted string event. A non-string event acquires no snapshot. A missing table uses legacy broadcast and global deduplication.
Deduplication, assembly, and secure state expose frozen point-in-time statistics objects. Reading them:
- does not read a clock;
- does not perform cleanup;
- does not expose mutable state; and
- cannot change an earlier snapshot.
Counters keep lifecycle reasons separate. These objects support regression and future differential-conformance testing; they are not a complete runtime metrics-export system.
Deduplication and assembly provide explicit reset behavior. Secure state has no public reset operation.
Multipart fragments and target destinations are sent sequentially. Dedup admission and metadata cleanup are not rolled back after a send failure. The contract does not promise transactional delivery, replay, or recovery after partial output.
Python remains the implemented and normative reference. A future implementation can be compared through ordered sentences, TAG metadata, routing targets, dedup decisions, lifecycle outcomes and discarded keys, explicit no-output cases, and contract-relevant statistics.
No native processor, C or C++ API, ABI, binding technology, or performance claim is defined. See Native-Ready Reference Foundation.
Campaign A consolidated observable Python behavior and differential-test seams. Campaign B made state ownership, clocks, TTLs, limits, lifecycle outcomes, and immutable statistics explicit. Neither history changes the rule that current production code and the canonical contract are the source of truth.
- Quick Start
- Installation and Operations
- OpenWrt Deployment
- Configuration and Examples
- Inputs and Outputs
- Architecture Overview
- Data Representation, Processor, and Runtime Boundary
- Processing and Routing Model
- State, Lifecycle, and Limits
- Multipart NMEA Assembly
- TAG Handling
- Deduplication
- Routing Engine
- Behavioural Contract
- Native-Ready Reference Foundation