Skip to content

chore(nodes): drop the blah2 bridge and its two hardcoded nodes - #340

Merged
Babissimo merged 9 commits into
mainfrom
chore/drop-blah2-bridge
Sep 9, 2026
Merged

Babissimo merged 9 commits into
mainfrom
chore/drop-blah2-bridge

Conversation

@Babissimo

@Babissimo Babissimo commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Retires the in-process poller for Gene's two boxes. radar3-retnode and radar3a-retnode were 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, the BLAH2_* constants, the main.py wiring, 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 wiped backend-data volume no longer resurrects the poller, because nothing is left to seed it from.

core/task_registry.register_task goes too. The bridge was its only caller, and the dead-code hook 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/dashboard took 14 and /api/test/mlat-verification 3. GET /api/test/node/<node_id>/verification and .../detection-range do the same job for any node and are untouched.

services/node_sites.py keeps reading blah2_nodes.json from 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/radar3a go dark at deploy rather than at cutover.

blah2_nodes.json was 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 on worktree-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.py also 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-files passes all five hooks; the full backend suite passes, coverage 84.5%. contracts/nodes-v1.openapi.yaml regenerates with no diff.

🤖 Generated with Claude Code

Babissimo and others added 4 commits September 8, 2026 14:27
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>
@claude

This comment has been minimized.

Babissimo and others added 2 commits September 9, 2026 11:30
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>
@claude

This comment has been minimized.

@claude

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>
@claude

This comment has been minimized.

@claude

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

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown

Claude finished @Babissimo's task in 7m 13s —— View job


Tasks

  • Gather context (PR body, changed files, prior review comment)
  • Understand the request: run /code-review against this PR (per repo custom instructions)
  • Run code review against the current diff (gh pr diff 340, since git diff origin/main...HEAD failed locally due to shallow history)
  • Verify candidate findings against the actual diff/tree
  • Post results

Result: no new issues since the last review pass

This synchronize only added comment/docstring rewording on top of the prior commit — backend/pipeline/passive_radar.py (+8/-0, new header comment on DEFAULT_NODE_CONFIG), backend/services/node_pipeline.py (+12/-14), backend/services/node_sites.py (comment tweak), and the corresponding renames/trims in test_ingest_event_loop.py / test_node_pipeline.py. All of it is non-functional.

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 DEFAULT_NODE_CONFIG comment (backend/pipeline/passive_radar.py:54-61) as claiming the fallback geometry is the retired radar3 site, then flagged that the altitude/tx_lon fields don't match the deleted blah2_nodes.json record. Rereading the actual comment, it does the opposite — it deliberately declines to name the site ("Which site, and the fit behind the numbers, is in ClickUp 86cb6385b rather than here: a receiver position is someone's home and this repo is public"), consistent with this being a public repo. DEFAULT_NODE_CONFIG's values are unchanged by this PR; only the explanatory comment above it is new. No discrepancy to fix here.

Everything else matches the previous pass's conclusion: clean deletion, no dangling references to blah2_bridge/register_task/BLAH2_*, node_sites.py correctly keeps reading blah2_nodes.json defensively (path-exists guard) for droplets that still have a seeded copy, and the kept /api/test/radar3/*-adjacent behavior degrades safely. The frame_processor.py:615-621 comment-accuracy nit noted previously (legacy routes/radar.py frames aren't schema-blocked from carrying an adsb key, so the comment's "carry no list at all" is true in practice but not enforced) still applies and is still non-blocking.

No new blockers.

@Babissimo
Babissimo merged commit 828c0e8 into main Sep 9, 2026
16 checks passed
@Babissimo
Babissimo deleted the chore/drop-blah2-bridge branch September 9, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant