Carry the node's ADS-B correlation with its position in v1 frames (contract 1.5.0) - #514
Conversation
…5.0) A node that correlates a detection with an ADS-B aircraft knows where that aircraft was — blah2-api's enrichment reports hex, lat, lon, altitude, ground speed, track and its own delay/Doppler residuals — but the v1 contract only had room for the hex. The server then needs a position source of its own to make anything of it, and where it has none, which is any environment that only sees the node second-hand through the detection mirror, the correlation is inert: no claim, no coverage calibration point, and the tracker never sees the tag. Contract 1.5.0 adds an optional `adsb` column to DetectionFrame: an AdsbTag or null per detection, parallel to `adsb_hex` and required to agree with it entry for entry, so one correlation cannot be said twice differently. A hex-only node omits it and is byte-for-byte unchanged. pipeline_frame files the tags as the per-detection `adsb` records the TCP ingest has always produced (`alt` under `alt_baro`, null fields omitted since the geolocator branches on key presence), so frame_processor stores the position in the aircraft cache under the node's world, the known lane claims through path 1, the tracker tags the detection, and the mirror — which uses the same conversion — forwards the position to every target. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
A node sending 1.5.0's `adsb` tags said each correlation twice, once in `adsb_hex` and again as the tag's `hex`, with a validator whose only job was to check the two copies agreed. A node with association switched off still had to send a list of nulls. Nothing on the server reads the frame's `adsb_hex` beyond copying it onto the queue: the tracker takes its hex from the per-detection `adsb` record, and path 1 claiming reads `frame["adsb"]`. So `adsb_hex` becomes optional and is marked deprecated in the contract. `adsb` alone carries the association, neither column means the node correlated nothing, and a frame that sends both must still have them agree. `pipeline_frame` fills the queue's `adsb_hex` from whichever column arrived, so nothing downstream, the detection mirror included, sees the option. It adds nothing to the rollout. Relaxing a required request field breaks no client, and a server below 1.5.0 already refuses a frame carrying `adsb`, so the server-first order this PR needs covers it. It stays within 1.5.0, which has not shipped. retina-telemetry#19 copies the 1.5.0 DetectionFrame and needs this revision of it; it can then stop sending `adsb_hex` beside the tags. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings the branch up to date with main so CI can run on it; a conflicting PR queues no pull_request workflows. The one conflict was the ADS-B extraction comment in frame_processor.py, which this branch rewrites, beside the probation guard #525 added to the line below it. Both are kept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
The agreement check between adsb and adsb_hex is one-directional on purpose: a tag's hex must match the hex beside it, but a hex beside a null tag passes. That is how a node says it matched an aircraft and had no usable position for it, which retina-telemetry#19 sends when lat/lon are not finite. The docstring's 'agree entry for entry' read as symmetric, and the review bot proposed making the check so, which would refuse those frames. The docstring now says what the check allows, and a test pins it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @Babissimo's task in 1m 36s —— View job Review of PR #514 (latest push)
SummaryThis push ( Prior finding resolved by clarification, not code changeThe earlier review flagged that a hex in The fix here is documentation + a pinning test rather than a logic change:
I traced Rest of the diff
I could not run VerdictNo outstanding findings. The one issue from the prior review is resolved appropriately (explained + tested rather than "fixed" into a stricter check that would reject valid frames). |
Summary
A node that correlates a detection with an ADS-B aircraft also knows where that aircraft was, but the v1 contract only had room for the hex. Without a position source of its own the server can do nothing with the hex, and on any environment that sees the node second-hand through the detection mirror that means no claim, no coverage calibration point, and an untagged detection at the tracker. This is why real nodes on test recorded zero calibration points after #513 landed: mirrored frames arrive hex-only and test has no fresh real-world position cache. Contract 1.5.0 adds an optional
adsbcolumn carrying the node's own correlation with its position.Node side: offworldlabs/retina-telemetry#19.
Changes
backend/routes/node_schemas.py: newAdsbTag(hex,lat,lonrequired;altft,gskt,trackdeg and the node's expected delay/Doppler and residuals optional; extra keys forbidden).DetectionFrame.adsb: list[AdsbTag | None] | None, parallel to the four arrays and required to agree withadsb_hexentry for entry. A hex-only node omits it and is unchanged.backend/services/node_pipeline.py:pipeline_framefiles the tags as the per-detectionadsbrecords the TCP ingest has always produced (altunderalt_baro, null fields omitted because the geolocator branches on key presence). From there nothing else changes: frame_processor stores the position in the aircraft cache under the node's world, the known lane claims through path 1, the tracker reads the tag per detection, and the mirror, which uses the same conversion, forwards the position to every target.backend/routes/nodes.py:NODE_API_VERSION1.4.0 → 1.5.0 with the reasoning comment.contracts/nodes-v1.openapi.yaml: regenerated.backend/services/frame_processor.py: the comment claiming v1 frames never carry anadsblist is updated.Test coverage
test_node_schemas.py::TestAdsbTags: round trip, absent is None, minimal tag, length mismatch, hex disagreement, tag on an unassociated detection, unknown keys, out-of-range and non-numeric values.test_node_pipeline.py: conversion shape, null fields omitted, and end to end: a wire tag is a path-1 claim with no aircraft cache involved.test_detection_mirror.py: the batch carries the tags in the pipeline shape; a hex-only frame is byte-identical.generate_openapi --checkpasses.Review notes
adsboutright, and the telemetry client cannot learn the server's contract version. This must reach every environment nodes post to (prod) before retina-telemetry#19 ships to nodes. The mirror path needs it on both the sending and receiving environment.adsb_hexfor hex-only nodes from the aircraft cache; untouched here.🤖 Generated with Claude Code