Skip to content

Gate overlap zones on node world - #291

Merged
jehanazad merged 1 commit into
mainfrom
feat/assoc-world-gate-overlap
Sep 6, 2026
Merged

jehanazad merged 1 commit into
mainfrom
feat/assoc-world-gate-overlap

Conversation

@jehanazad

Copy link
Copy Markdown
Contributor

Pairs with offworldlabs/retina-analytics#25 — this PR is the pin bump plus the
status field. Merge the library PR first.

What

  • libs/retina-analytics pinned to 4439c2c (branch feat/world-gate-overlap-zones),
    which stops register_node/rebuild_zones_for building an overlap zone
    between two nodes in known, different worlds, and drops one that already
    exists.
  • /api/radar/association/status gains assoc_world_skipped_pairs next to
    overlap_zones.

Why

PR #267 gated ADS-B claiming and seeding on node_world_provider, but
bottom-up pairing was untouched. This fleet is where that bites: 50 synthetic
nodes and 8 receivers (ret*, radar3*-retnode) share one footprint over
Greenville, so the associator holds a zone for every sim/real pair, and any
pairing drawn from one of those grids can only ever match a simulated tracklet
against a real echo. A read-only audit found real node ids (retf1144fcd,
ret7dd2cb0d) in 48 of 156 synthetic dark solver records over a 7-minute
window.

Nothing new is wired: state.node_world is already the single authority for
the question and is already injected as node_world_provider.

Numbers, measured live on retina-test (read-only /api/radar/association/status)

pairs
registered nodes 58 (50 synth-*, 8 real)
overlap zones today 1653 — every pair, C(58,2)
cross-world (sim × real) pairs 400
...with a non-empty grid, i.e. a live adjacency edge 39
zones after the gate 1253 (1225 sim/sim, 28 real/real)

So the gate removes 24% of the zone table and 39 real neighbour edges, all of
them edges no true pairing can live on.

assoc_world_skipped_pairs is what makes that visible: it counts pairs
considered and refused, so it climbs with re-registrations, and zero means the
fleet is single-world (or untagged) rather than the gate being off.

Verified

  • libs/retina-analytics suite: 420 passed (9 new in tests/test_world_overlap_gate.py).
  • tests/test_analytics_routes.py tests/test_adsb_seed_backend.py: 54 passed
    — new: the status field reflects the counter; a synth-* node and a hardware
    node registered on overlapping coverage get no zone and no adjacency; two
    synthetic nodes on the same coverage still pair.
  • Full backend suite: 2777 passed, 2 skipped in 165.25s (-n 2 -m "not external").
  • pre-commit run --all-files clean in both the worktree and the library.
  • No deploy, no container restart.

🤖 Generated with Claude Code

Bumps libs/retina-analytics to the branch commit that stops register_node and
rebuild_zones_for building an overlap zone between two nodes in different
worlds (offworldlabs/retina-analytics#25).

This fleet is the reason: 50 synthetic nodes and 8 receivers share one
footprint over Greenville, so the associator held a zone for all 400 sim/real
pairs — 39 of them a live adjacency edge — and a pairing drawn from one of
those grids can only ever match a simulated tracklet against a real echo.
Real node ids showed up in 48 of 156 synthetic dark solver records over a
7-minute window.  node_world is already the single authority for the question
and is already injected as node_world_provider, so nothing new is wired here.

/api/radar/association/status gains assoc_world_skipped_pairs next to
overlap_zones: without it a fleet whose cross-world pairs are being refused
reads exactly like a fleet whose pairs never overlapped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown

Claude finished @jehanazad's task in 1m 47s —— View job


Code Review

Todo list

  • Gather context (PR diff, description, CLAUDE.md)
  • Read changed files (backend/routes/analytics.py, test files, library pin)
  • Check the paired library PR / pinned commit for the actual gating logic
  • Review for correctness, security, style
  • Post final review

Summary

This backend-side PR is exactly what it says it is: a submodule pin bump plus one status field. The diff is small (4 files, +50/-1) and low-risk.

Note on scope: libs/retina-analytics is a git submodule and isn't checked out in this environment (empty dir, network fetch unavailable to me here), so I could not inspect the actual world-gating logic in register_node/rebuild_zones_for — that lives in the paired library PR (offworldlabs/retina-analytics#25) and should be reviewed there. This review covers only what's in retina-server: the status field and its tests.

Findings

Nothing blocking. A few observations:

  • backend/routes/analytics.py:135getattr(_a, "assoc_world_skipped_pairs", 0) is defensive against an associator instance that predates the pin, consistent with every other counter in this handler (track_pairs_gated, claim_rounds, etc. all use the same getattr(..., 0) pattern). Good consistency, no issue.
  • The inline comment on assoc_world_skipped_pairs (routes/analytics.py:128-134) is long, but matches the house style already used for every other field in this same function (each counter gets a paragraph explaining what it means and why zero isn't necessarily "off"). Fits the existing pattern.
  • Tests correctly rely on the existing is_synthetic_node prefix rule (synth-, e2e-, realnode-, test-) via state.node_world, so synth-GVL-9001/synth-GVL-9002 resolve to "sim" and hw-9001 resolves to "real" — the fixtures test what they claim to.
  • State isolation between the new tests is handled by the existing autouse _reset_for_tests fixture in conftest.py, plus each test's own finally: state._reset_for_tests() — no leakage risk between tests.
  • No contracts/nodes-v1.openapi.yaml update needed — /api/radar/association/status isn't under /v1/nodes.
  • The counter naming (assoc_world_skipped_pairs) and its cumulative-since-boot semantics are clearly documented in both the route comment and the PR description, so the "zero doesn't mean the gate is off" footgun is well covered.

Overall: clean, well-tested, well-scoped change. The real correctness question (does the gate actually skip the right pairs and drop existing zones correctly) lives in the library PR, which the description says already has 9 new tests and a full green suite.

jehanazad pushed a commit that referenced this pull request Sep 6, 2026
…r-observability

docs/solverflow.md: keep this branch's symbol-named 'Defined in' column and
add #292's SOLVER_ALT_MODE / SOLVER_FREE_ALT_STARTS rows and section in it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jehanazad pushed a commit that referenced this pull request Sep 6, 2026
jehanazad pushed a commit that referenced this pull request Sep 6, 2026
jehanazad pushed a commit that referenced this pull request Sep 6, 2026
jehanazad pushed a commit that referenced this pull request Sep 6, 2026
…ix/cluster-contamination

test_analytics_routes.py: keep both new status tests (live counters from this
branch, world-skipped pairs from #291). libs/retina-analytics: point at
analytics main (#25 world gate + #26 cluster split + #27 epochs merged).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jehanazad pushed a commit that referenced this pull request Sep 6, 2026
…urement-epochs

state.py / routes/test.py / solver.py / test_solver_stats.py: adjacent
additions from both sides kept (epoch-align flag + counters next to the
free-altitude flags, resolve_skips_dark and altitude_mode stamps).
libs/retina-analytics: analytics main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jehanazad pushed a commit that referenced this pull request Sep 6, 2026
…ts-and-dr-budget

.env.example: both new comment blocks, the shared commented knobs once.
docs/solverflow.md: symbol-named constants table from the stack plus this
branch's MN_DR_CAP_S / MN_DARK_EXPIRY_S rows; section 7 kept.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jehanazad pushed a commit that referenced this pull request Sep 6, 2026
…covariance-psd

aircraft_feed.py: #297's MN_DR_CAP_S cap and KF learned-velocity dead
reckoning live inside #298's per-entry _multinode_entry helper, as resolved
on deploy/dark-solver-stack (cedbe69), which ran this exact PR set.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jehanazad pushed a commit that referenced this pull request Sep 6, 2026
…ck-following

state.py / test_solver_stats.py: adjacent additions from both sides kept
(DARK_FOLLOW_MODE beside the epoch-align and altitude-mode flags; follow
counters beside the stale/epoch/skip counters).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jehanazad pushed a commit that referenced this pull request Sep 6, 2026
jehanazad pushed a commit that referenced this pull request Sep 6, 2026
jehanazad pushed a commit that referenced this pull request Sep 6, 2026
…supersession-altitude-gate

solver.py / state.py taken from deploy/dark-solver-stack b0a289f, which merged
this exact PR set: the altitude-gated supersession block re-indented inside
#299's restructured publish path, and mn_superseded_blocked_alt reset beside
the other counters.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jehanazad
jehanazad marked this pull request as ready for review September 6, 2026 02:38
@jehanazad
jehanazad merged commit 69e72a9 into main Sep 6, 2026
15 checks passed
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.

2 participants