Conversation
Since supersession stopped popping another aircraft's key on a bare shared source-track id, a fragment key that the guard refuses now lives out its full 30 s expiry beside the real track. When an established followed key publishes a solve built from the very node tracks a younger key was last built from, that younger key has no evidence of its own left, so it can be retired at publish time instead. Offline replay of this exact predicate over three 20-min captures says the benefit is small and two-sided (cap17: 7 of 38 ghost keys retired, 0-34 ghost frames removed against 13-21 correct frames lost; cap15: 5 ghost frames removed against 21 correct frames lost), so the rule ships behind DARK_FOLLOW_ORPHAN_MODE, default off, to be measured live rather than assumed. Nothing changes with the flag off. The removal is factored into solver._forget_mn_key so the orphan path erases exactly the four stores supersession does; supersession's own behaviour is untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Claude finished @jehanazad's task in 4m 9s —— View job Claude is reviewing this PR
|
Live measurement of PR #303's orphan rule against the cap17 overlay. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Live measurement on the test droplet (cap19, 20 min, stack 6593a6e = cap17 overlay + DARK_FOLLOW_ORPHAN_MODE=on, MN_DARK_EXPIRY_S back at default):
Verdict: consistent with the offline replay in the description. The rule is inert at best and its rare firings were cross-aircraft. Recommend leaving this closed/unmerged; the ghost lever is upstream (bad low-node solves), not key lifecycle. 🤖 Generated with Claude Code |
|
Closing per the 2026-09-10 review against main 886a342. The cap19 measurement above stands (two firings in 20 min, both cross-aircraft), and #331 now joins bottom-up solves onto followed keys on the same >=2-shared-node-track signal at keying time, so the fragment population this rule targeted is mostly never minted. The predicate is also stale against main: it judges A by the last solve's n_nodes while the follow lane's bar uses max(max_n_nodes, n_nodes) (dark_follow.py:410). The |

Stacked on #299 (
feat/dark-track-following) — review that first; this branch's own diff is the last commit.What
A new rule in the dark-follow lane, behind a new env flag
DARK_FOLLOW_ORPHAN_MODE(off|on, default off): when an established (followed) dark key A publishes a solve built from the same node tracks that a younger key B was last built from, B has no evidence of its own left, so it is retired at publish time instead of drifting to its 30 s expiry.Since #302 stopped supersession from popping another aircraft's key on a bare shared source-track id, fragment keys that used to be popped now live out that full expiry beside the real track. This is the narrow, provable slice of them.
Why it is flagged off
Offline replay of this exact predicate over three 20-min captures says the benefit is small:
So it ships default off, to be measured live on the test droplet without changing any default behaviour. The predicate is implemented as specified and deliberately not tuned to look better — the live capture is the verdict. With the flag off nothing about the publish path changes.
The predicate —
dark_follow.find_orphans(tracks, key, new_ids, ts_s, solve_count, n_nodes)Returns
[]unlessorphan_mode() == "on",mode() == "binding"(the rule is a key-ownership rule, and ownership only exists when the lane binds),keyismn-dark-*,new_idsis non-empty, and A is established (solve_count >= DARK_FOLLOW_MIN_SOLVES,n_nodes >= DARK_FOLLOW_MIN_NODES, both as stored on the entry after this publish).A key B is then retired iff its
source_track_idsare non-empty; B is strictly older than the solve and withinDARK_FOLLOW_ORPHAN_WINDOW_S(default 30 s) of it; at least_ORPHAN_MIN_SHARED= 2 of B's ids are innew_idsand they are a majority of B's; B has no more solves than A; and B is notrecently_followed(a key the lane is itself refreshing has evidence that is not intracksat all).Changes
services/dark_follow.py—orphan_mode()accessor (shaped likemode(), same defensive parsing),find_orphans(), the two constants with their WHY comments, and a docstring paragraph placing the rule against ownership.services/tasks/solver.py— new_forget_mn_key()factors the four-store removal (entry, anomaly hex, smoother position history, KF state) that supersession already did; the supersession loop now calls it, with no behaviour change. The orphan call site sits in_process_solver_itemunder_MN_TRACKS_LOCK, right after the entry is stored, so the counts it judges by are the ones this publish wrote; each orphan is removed, put in follow cooldown (drop_target), and counted._record_solve_historygains anorphaned_keyskwarg plumbed likesuperseded_keys.core/state.py—DARK_FOLLOW_ORPHAN_MODEparsed besideDARK_FOLLOW_MODE;dark_follow_orphanedcounter besidedark_follow_dropped, reset with it.routes/test.py—dark_follow_orphanedin thefragmentationblock of/api/test/solver-stats, beside the supersession counters it is read against.tests/test_dark_follow.py—TestOrphanRule(14 predicate tests: off by default, unreadable flag, both inert follow modes, the positive case, 1-of-3 and 2-of-5 majority refusals, newer-than-solve, past the window, better-established B, recently-followed B, unestablished A by solves and by nodes, ADS-B keys on both sides, no provenance) andTestOrphanRuleInTheSolver(end-to-end through_process_solver_item: with the flag on the fragment leavesmultinode_tracks,dark_follow_orphanedbumps, the key enters follow cooldown and the history record lists it inorphaned_keyswithsuperseded_keysempty; with the flag off the same publish leaves it in place).tests/test_solver_stats.py— the two exact-key assertions on thefragmentationblock updated for the new counter.Verified
tests/test_dark_follow.py tests/test_mn_lifetime.py tests/test_solver_anchor.py tests/test_solver_stats.py: 168 passed.pytest tests/ -n 2 -m "not external"): 2834 passed, 2 skipped in 164.88s.pre-commit run --all-files: all five hooks passed (ruff check, ruff format, vulture, both ruff-config checks).🤖 Generated with Claude Code