chore(nodes): drop the blah2 bridge and its two hardcoded nodes - #340
Conversation
radar3-retnode and radar3a-retnode were polled over HTTP once a second each,
their geometry pinned in backend/config/blah2_nodes.json. Both are being
reflashed and will re-enter through the v1 node API, which reaches the pipeline
by the same calls the bridge made, so the bridge has no remaining job.
It was kept deliberately as the v1 rollback path (see
docs/design-notes/2026-08-06-node-ingest-minimal.md); retiring that rollback is
the point of this change, not an oversight.
Removed with it: the BLAH2_* constants, blah2_nodes.json's place in the
runtime-config overlay and the Dockerfile step that seeded it, the
task-registry note about per-node keys, and the two /api/test/radar3/*
back-compat aliases. The generic /api/test/node/{node_id}/* endpoints those
wrapped are unchanged, and had no callers in the repo.
Comments in node_pipeline, frame_processor and node_stream described their
contract by pointing at the bridge as the working reference. They now state
the contract directly rather than referring to a module that is gone.
The running app reads backend/data/runtime/blah2_nodes.json, seeded on first
boot and authoritative after, so deployed droplets keep that file until it is
cleared by hand. Nothing reads it once this lands.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deleting the bridge left `register_task` with no callers, which fails the dead-code hook and so CI. The bridge was the only thing registering task keys that config, not code, decided; nothing else needs the entry point. Restores the two /api/test/radar3/* aliases. They were added for callers that predate the per-node routes, and those callers are on the droplets rather than in this repo, so a grep proving no in-repo use proves nothing about what would start 404ing. They can go with the node itself. DEFAULT_NODE_CONFIG is the retired radar3 geometry. The deleted node list was the only place saying so, and the coordinates outlived it, so the provenance is now recorded where the numbers are. The doppler triple in node_pipeline points at the same place rather than asserting a provenance a reader cannot check. Runbook command took a <node_id> placeholder, which bash reads as a redirection; uses a variable instead so it survives being pasted. Two solverflow anchors had drifted by a line or two when docstrings shrank. Not changed, recorded here so the next reader does not re-derive it: a frame staleness gate at v1 ingest would contradict the published contract, which says a late frame carries an old `t` and is rejected by the association gate instead. Node geometry is validated by services/node_config.py, which is shared by registration and PUT and is stricter than the bridge's own checks were. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The comment on DEFAULT_NODE_CONFIG named the receive site and its city right above that site's coordinates. services/public_location.py exists because a receiver sits at someone's home, and this repo is public, so which site it is now lives in the ticket and the comment keeps only the provenance. It also claimed a registered node is always solved against its own config, which __init__ does not guarantee: any falsy node_config takes the fallback. The runbook section was retitled to generic per-node guidance but kept two sentences that only described the bridge. Missing from /api/radar/nodes no longer means an entry was skipped and logged at startup; an invalid config is refused at the PUT. The delay-residual check is still the way to catch bad geometry, but it needs a node publishing adsb[].expected_delay, which a v1 node does not, so it is scoped rather than presented as available. Producer anchors in solverflow now name symbols. Renumbering them by hand was not working: the pass that corrected two missed main.py:164, and one of the two pointed at submit_frame's queue push while labelled startup priming. The adsb branch in frame_processor says which source still reaches it, and the radar3 alias comment cites the commits that establish the ordering it asserts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main moved a long way under this branch. Resolutions: blah2_bridge.py and its tests are deleted, against main's changes to them from #278 (null altitudes) and 408e764 (atomic error counters). Only the bridge's share of that work goes; node_config.canonical_config and resolve_altitudes, which #278 added and every node path uses, stay. main relocated _pipeline_frame to node_pipeline.pipeline_frame and its docstring still described the frame shape as the one blah2_bridge puts on the queue. Taken from main and reworded in its new home, since the module it cites is gone. frame_processor keeps main's adsb_capture_ts_ms work; only the comment naming a blah2 node is reworded. solverflow's tables are main's. It converted the whole file from line numbers to symbol references, which this branch had started doing by hand for two anchors; main's version is better and wins. The bridge is then removed from it. services/node_sites.py is left as main wrote it. It reads blah2_nodes.json from the runtime overlay and skips it cleanly when absent, so it keeps working while the droplets still hold their orphaned copies and goes quiet once those are cleared. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
pipeline_frame moved to node_pipeline in #278 and brought its docstring with it, which still described the frame shape as the one blah2_bridge puts on the queue. The merge took main's version of the function and left the text; this finishes that. node_sites reads blah2_nodes.json and keeps doing so: while a droplet still holds a seeded copy, that file is what tells the module radar3 and radar3a share a roof, which is what keeps their published positions fuzzed as one site. Only the comments change, to stop calling it the bridge's list now that nothing writes it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
The comment added with the bridge removal named one of the two other copies of doppler_min/doppler_max/min_doppler. frame_processor carries the same values a third time as cfg.get fallbacks, so an editor following the comment and updating passive_radar would have called it synced and left that one behind, which is the drift the comment exists to prevent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
/api/test/radar3/verification and /api/test/radar3/detection-range wrapped the per-node routes with the node id fixed. They were kept earlier in this branch on the reasoning that their callers were on the droplets, out of a repo grep's reach. That was never checked, and it is wrong: a read-only sweep of staging and production found no reference outside the route definition, in root's crontab, the systemd timers, /root, /opt, /etc/cron* or /usr/local/bin, and production's nginx log recorded no request for either across the hour it holds, a window in which two other /api/test routes were called. An hour is not proof of a rare external caller's absence, but nothing is known to call these, they name a node that is being reflashed out of existence, and GET /api/test/node/<node_id>/... does the same job for any node. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @Babissimo's task in 7m 13s —— View job Tasks
Result: no new issues since the last review passThis synchronize only added comment/docstring rewording on top of the prior commit — One candidate finding came out of the automated pass and didn't survive verification, so I'm not carrying it forward: it read the new Everything else matches the previous pass's conclusion: clean deletion, no dangling references to No new blockers. |
Retires the in-process poller for Gene's two boxes.
radar3-retnodeandradar3a-retnodewere polled over HTTP once a second each, from a hardcoded list, as the rollback path for v1 ingest. Both are being reflashed to re-enter through the v1 node API, which reaches the pipeline by the same calls the bridge made, so the bridge has no remaining job.Ticket: 86cb6385b
What goes
services/blah2_bridge.py,backend/config/blah2_nodes.json, theBLAH2_*constants, themain.pywiring, the file's place in the runtime-config overlay, its tests, and the Dockerfile step that copied it into the pristine config image. That last one settles a worry recorded on the ticket: a fresh droplet or a wipedbackend-datavolume no longer resurrects the poller, because nothing is left to seed it from.core/task_registry.register_taskgoes too. The bridge was its only caller, and thedead-codehook fails on it otherwise.What stays
Both
/api/test/radar3/*aliases go with it. They were kept earlier in this branch on the reasoning that their callers were on the droplets, out of a repo grep's reach; that was never checked and is wrong. A read-only sweep of staging and production found no reference outside the route definition, and production's nginx log recorded no request for either across the hour it holds, a window in which/api/test/dashboardtook 14 and/api/test/mlat-verification3.GET /api/test/node/<node_id>/verificationand.../detection-rangedo the same job for any node and are untouched.services/node_sites.pykeeps readingblah2_nodes.jsonfrom the runtime overlay. Its comments are corrected here to stop describing the file as the bridge's, but the read stays: while a droplet still holds a seeded copy, that file is what tells the module radar3 and radar3a share a roof, which is what keeps their published positions fuzzed as one site. It skips the file cleanly once it is gone.Two things to know before merging
This runs ahead of the ticket's own precondition. The Follow-up section gates deletion on "once every node is across", and neither box has registered over v1 yet. That ordering is deliberate, confirmed 2026-09-08, but it means
radar3/radar3ago dark at deploy rather than at cutover.blah2_nodes.jsonwas deleted against a standing constraint. The ticket says not to delete it until 86cb7fdhg is resolved or the calibrated geometry is accepted as lost. The geometry is recoverable from this branch's history and is captured in full onworktree-blah2-discrepancy-record, which is not merged.After merging
The running app reads
backend/data/runtime/blah2_nodes.json, seeded on first boot and authoritative after, so both droplets keep polling until that file is cleared by hand.Clearing it is safe but not a no-op:
services/node_sites.pyalso reads it, so removing it drops radar3/radar3a's shared-site grouping along with the polling. Both nodes are going away, so that is the intended end state, but do it deliberately rather than as tidying.Verification
pre-commit run --all-filespasses all five hooks; the full backend suite passes, coverage 84.5%.contracts/nodes-v1.openapi.yamlregenerates with no diff.🤖 Generated with Claude Code