Skip to content

Commit 2e5723c

Browse files
committed
Normalize producer status and receiver stats
1 parent 543b528 commit 2e5723c

37 files changed

Lines changed: 2913 additions & 367 deletions

docs/architecture.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,19 @@ sequenceDiagram
6969

7070
## Startup order
7171

72-
`identd` does not assume it knows which decoder it is reading. It watches the
73-
receiver metadata file first and waits to classify the producer from it before
74-
starting the aircraft, stats, and outline watchers. Running those watchers
75-
before classification would emit a warning on every file change while the type
76-
is still unknown, so they are held back until there is an answer.
77-
78-
The HTTP server starts before that gate, so the web app is reachable while the
79-
receiver file is still missing or malformed. A waiting receiver shows the UI and
80-
a diagnostic rather than a dead page, and the service log notes that it is still
81-
waiting at a steady interval so a long wait stays visible.
72+
`identd` does not assume it knows which decoder it is reading. It starts the
73+
HTTP server and the producer-file watchers during startup, then lets each file
74+
add evidence toward selecting a supported adapter. Receiver metadata is still
75+
the strongest signal when it names the decoder clearly, but aircraft,
76+
statistics, and outline files can help classify setups whose receiver metadata
77+
is generic.
78+
79+
Until one adapter is selected, producer files are not published as normalized
80+
aircraft, status, or range data. The web app is reachable while that wait plays
81+
out, and the diagnostic bell explains whether the producer is still unknown or
82+
whether several adapters match equally well. This keeps unsupported or unusual
83+
stacks visible without guessing a schema and leaking raw decoder semantics into
84+
the Ident wire contract.
8285

8386
## Why this shape
8487

docs/backend/diagnostics.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,19 @@ responsible piece is more actionable than a generic one.
102102
## Startup conditions
103103

104104
Configuration problems, such as an upstream type the backend does not recognize
105-
or an override that disagrees with the detected setup, are caught the first time the
106-
receiver configuration is read at startup and raised with the longer
107-
receiver-tier TTL. Because that configuration is event-driven and may not change
108-
for hours, a heartbeat re-raises any active receiver-derived condition every few
105+
or an override that disagrees with the detected setup, are raised as soon as
106+
there is enough information to identify the problem. Producer identification can
107+
use receiver, aircraft, statistics, or outline files, so a setup with generic
108+
receiver metadata may stay unknown until another file provides enough evidence.
109+
An unknown or ambiguous producer is itself a diagnostic condition rather than a
110+
stream of per-file warnings.
111+
112+
Because receiver and producer-identification conditions are event-driven and may
113+
not change for hours, a heartbeat re-raises active startup conditions every few
109114
minutes so a stable misconfiguration does not quietly expire between file
110-
changes. The heartbeat starts only after the initial producer classification
111-
finishes, so it does not double-emit during the window when classification is
112-
already raising and clearing conditions of its own.
115+
changes. The heartbeat is deduplicated through the same condition identity rules
116+
as every other diagnostic, so refreshing a condition does not create a second
117+
notification.
113118

114119
## Expiry must not be mistaken for success
115120

docs/backend/producer-normalization.md

Lines changed: 69 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ to be kept honest against each decoder's actual output.
2323
## The adapter shape
2424

2525
Each decoder is represented by an adapter that knows how to do a few things:
26-
recognize its own decoder from the receiver file, describe which pieces of
27-
operational data that decoder actually provides, and translate each of the four
28-
files into Ident types. An adapter that cannot supply a given file says so
29-
rather than guessing; the UAT decoder, for instance, emits no statistics file
30-
and no range outline, and its adapter reports both as absent.
26+
recognize its own decoder from the files that have arrived, describe which
27+
pieces of operational data that decoder actually provides, and translate each
28+
of the four files into Ident types. An adapter that cannot supply a given file
29+
says so rather than guessing; the UAT decoder, for instance, emits no
30+
statistics file and no range outline, and its adapter reports both as absent.
3131

3232
The receiver file translates into a small record holding the decoder name, a
3333
version string, and the receiver's coordinates when present. The statistics
@@ -38,40 +38,48 @@ file becomes a polygon.
3838

3939
## How a decoder is identified
4040

41-
Identification runs when the receiver file changes, not on every aircraft frame.
42-
Aircraft data arrives at roughly one update per second; re-deciding the decoder
43-
identity that often would be wasted work, and the identity rarely changes once a
44-
deployment is running. The decision is made from the content of the receiver
45-
file rather than from file paths, because an operator can mount a decoder's
46-
output wherever they like.
47-
48-
There is no scoring. Adapters are tried in a fixed order and the first one that
49-
recognizes the receiver file wins; nothing weighs how well several adapters
50-
match. readsb is tried first and is identified by an explicit flag it sets in
51-
its receiver file. The UAT decoder is tried next and is identified by a version
52-
string that begins with its name. dump1090-fa is tried last and is identified by
53-
its version string containing a known marker. A version string that is merely
54-
present, or non-empty, does not identify dump1090-fa; the marker has to be there.
41+
Identification is content-based, not path-based. Operators can mount a decoder's
42+
output wherever they like, and some stacks write receiver metadata that is too
43+
generic to identify the stack by itself. `identd` therefore considers the
44+
receiver, aircraft, statistics, and outline files together as evidence for the
45+
adapter selection.
46+
47+
Receiver metadata still carries the strongest signals when it includes an
48+
explicit decoder marker. When it does not, the shape of the statistics and
49+
aircraft files can still be enough to identify a supported decoder. This is
50+
deliberately a "what can this adapter safely normalize?" decision rather than a
51+
"what product name is this directory?" decision. The result is one of four
52+
states:
53+
54+
- An operator override names a supported decoder, so that adapter is selected.
55+
- Exactly one adapter has enough evidence, so that adapter is selected.
56+
- No adapter has enough evidence, so the producer remains unknown.
57+
- More than one adapter has equally strong evidence, so `identd` reports an
58+
ambiguous producer. If no adapter has been selected yet, producer data waits
59+
until more evidence arrives or an override is set. If the currently selected
60+
adapter is still one of the tied candidates, `identd` keeps that adapter for
61+
normalization and treats the tie as a diagnostic rather than a reason to
62+
demote the stream.
5563

5664
```mermaid
5765
flowchart TD
58-
C[receiver file changes] --> R{readsb flag set?}
59-
R -->|yes| RB[readsb]
60-
R -->|no| U{version begins with the UAT decoder name?}
61-
U -->|yes| UAT[dump978 / skyaware978]
62-
U -->|no| D{version contains the dump1090-fa marker?}
63-
D -->|yes| DF[dump1090-fa]
64-
D -->|no| UN[unclassified]
66+
F[receiver, aircraft, stats, or outline file changes] --> E[update observed evidence]
67+
E --> O{operator override set?}
68+
O -->|yes| S[select named supported adapter]
69+
O -->|no| A[adapters evaluate evidence]
70+
A --> R{classification result}
71+
R -->|one supported adapter| S
72+
R -->|not enough evidence| N[unknown]
73+
R -->|several adapters tie| M[ambiguous]
6574
```
6675

67-
Order is the only tie-breaker, and that has a consequence worth stating: a feed
68-
whose version string begins with the UAT decoder's name is claimed by that
69-
adapter before the dump1090-fa check ever runs. Keeping order as the sole
70-
priority mechanism avoids a configuration table that most deployments would
71-
never touch, but it does mean the order itself is load-bearing, including in
72-
tests, where reordering the adapters changes which one claims an ambiguous fixture.
76+
Unknown and ambiguous states are surfaced as diagnostics, including the
77+
strongest evidence `identd` has seen so far. That keeps an unsupported or
78+
unusual stack visible to the operator without guessing a decoder. Once a decoder
79+
has already been selected, later ambiguous evidence only blocks a switch away
80+
from that decoder unless the operator overrides it or stronger evidence appears.
7381

74-
A decoder that no adapter recognizes stays unclassified. Some decoders write
82+
A decoder that no adapter recognizes stays unknown. Some decoders write
7583
aircraft JSON that Ident could in principle read but are simply not recognized
7684
by any adapter and so never get classified. Others are structurally
7785
incompatible: a decoder whose aircraft file is a bare array, without the
@@ -85,20 +93,26 @@ published.
8593
An operator can override automatic identification and name the decoder
8694
explicitly through an environment variable or its matching command-line flag,
8795
with a few accepted spellings per decoder. The override selects the adapter, but
88-
automatic identification still runs on every receiver-file change. When the two
89-
disagree, `identd` emits a diagnostic rather than quietly trusting the override,
90-
so a misconfiguration is visible instead of hidden. If the named decoder is one
91-
this build does not support, that is reported too.
92-
93-
## Nothing is published before classification
94-
95-
`identd` starts the receiver-file watcher right away but holds off on the
96-
aircraft, statistics, and outline watchers until a decoder has been identified.
97-
Until then there is no adapter to translate those files, and starting their
98-
watchers early would produce a steady stream of "waiting for classification"
99-
diagnostics, one per file update. The HTTP server starts before this gate so the
100-
interface stays reachable while the wait plays out. No decoder-shaped data
101-
reaches the hub until classification succeeds.
96+
automatic identification still evaluates the observed files. When the observed
97+
evidence points at a different decoder, `identd` emits a diagnostic rather than
98+
quietly trusting the override, so a misconfiguration is visible instead of
99+
hidden. If the named decoder is one this build does not support, that is
100+
reported too.
101+
102+
## Nothing is published before selection
103+
104+
`identd` starts all producer-file watchers during startup so each file can add
105+
evidence as soon as it appears. When no adapter has been selected yet, unknown
106+
or ambiguous producer files are parsed only far enough to help identify a
107+
supported adapter. They are not published as normalized aircraft, status, or
108+
range data until one adapter can safely handle them.
109+
110+
That distinction matters for compatibility. An unknown decoder may write
111+
aircraft JSON that looks close to a supported shape, but publishing it before an
112+
adapter is selected would let raw decoder semantics leak into Ident's wire
113+
contract. The HTTP server still starts immediately, so the interface stays
114+
reachable while classification is waiting for enough evidence, and the
115+
diagnostic bell explains why live producer data is not flowing yet.
102116

103117
## Where the decoders actually differ
104118

@@ -155,11 +169,11 @@ This single on-ground value is what trail segmentation reads (see
155169

156170
Alongside the status and aircraft data, `identd` publishes a description of which
157171
features a decoder supports. Each feature is marked as provided by the decoder,
158-
derived by Ident, or unavailable. The adapter sets a conservative baseline for
159-
its decoder when the receiver file is read, and live data can promote a feature
160-
as statistics or aircraft frames confirm it is actually present. Promotion is
161-
one-directional within a single decoder: a later receiver-file update does not
162-
demote a feature that earlier live data already established, so the interface
163-
does not flicker a capability off and on as files arrive in different orders.
164-
A demotion happens only on a real change of circumstance, such as the decoder
165-
itself changing.
172+
derived by Ident, or unavailable. The selected adapter sets a conservative
173+
baseline from the evidence it has seen, and live data can promote a feature as
174+
statistics, aircraft frames, or outline files confirm it is actually present.
175+
Promotion is one-directional within a single selected decoder: a later file
176+
update does not demote a feature that earlier live data already established, so
177+
the interface does not flicker a capability off and on as files arrive in
178+
different orders. A demotion happens only on a real change of circumstance, such
179+
as the decoder itself changing or the producer becoming ambiguous again.

docs/getting-started/configuration.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,14 @@ when it is not one of the paths Ident already checks.
3232

3333
## Upstream type
3434

35-
Ident usually detects the upstream type from `receiver.json`. If a receiver
36-
setup needs an explicit selection, set `IDENT_UPSTREAM_TYPE` or pass
37-
`--upstream-type`.
35+
Ident usually detects the upstream type from the contents of the receiver data
36+
directory. Explicit receiver metadata is used when present, and aircraft or
37+
statistics files can also provide enough evidence for stacks whose receiver file
38+
is generic. If automatic detection is insufficient or ambiguous, the UI shows a
39+
diagnostic notification instead of guessing.
40+
41+
If a receiver setup needs an explicit selection, set `IDENT_UPSTREAM_TYPE` or
42+
pass `--upstream-type`.
3843

3944
```sh
4045
IDENT_UPSTREAM_TYPE=dump1090-fa
@@ -43,7 +48,9 @@ IDENT_UPSTREAM_TYPE=dump1090-fa
4348
Supported values are `readsb`, `dump1090-fa`, and `skyaware978`. The aliases
4449
`piaware`, `dump978-fa`, and `dump978` are also accepted. An invalid value is
4550
ignored and surfaced as a diagnostic notification while Ident falls back to
46-
automatic detection.
51+
automatic detection. A valid override wins over automatic detection, but a
52+
diagnostic is still raised when the observed files appear to describe a
53+
different supported upstream.
4754

4855
## Station identity and overlays
4956

ident/src/data/feed.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,6 @@ describe("startFeed route envelopes", () => {
736736
uptime: "producer_provided",
737737
maxRange: "producer_provided",
738738
rangeOutline: "producer_provided",
739-
signalDiagnostics: "producer_provided",
740739
meteorology: "unavailable",
741740
replay: "ident_derived",
742741
trails: "ident_derived",

0 commit comments

Comments
 (0)