Carry forward _forget_mn_key and the KF kf_action diagnostics - #352
Conversation
The two reusable pieces of the closed drafts #303 and #305, rebased onto main without either PR's policy change. From #303: `_forget_mn_key(old_key)` factors the four-store removal (entry, anomaly hex, smoother position history, Kalman state) that the supersession loop did inline. No behaviour change; the loop now calls it. From #305: every return path of `track_filter._smooth_kf` stamps `kf_action` / `kf_d2` / `kf_innov_m`, and `_record_solve_history` copies them onto the history record. Extended for #310: a gate breach the manoeuvre retry accepts is stamped `manoeuvre_rescued`, distinct from `smoothed` and `reanchored`, and `kf_d2` is the base-Q surprise that triggered the retry. `_predict_update` also returns the innovation magnitude for the stamp. The dark-lane kf_held/kf_reanchored counters from #305 are dropped: `filter_stats()` already counts re-anchors. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
|
Claude finished @jehanazad's task in 1m 23s —— View job Todo
SummaryRe-reviewed against
Tests — CLAUDE.md gate — no Not independently verified: no No changes requested. |
…eploy `deploy-staging` was the only job in the `staging-deploy` concurrency group. `staging-smoke-tests` and `e2e-staging` carried no concurrency at all, so the deploy was serialised while everything that verifies the deploy was not: a following run's deploy was free to land between one run's smoke tests and its E2E, restarting the box underneath a suite that was testing it. Three consecutive merges failed that way on 2026-09-10 (#348, #351, #352), and four runs within three minutes produced the same thing on 2026-08-21 (#223). None of them failed for a fault in the change. The failure does not present as a flake either: a test hitting a half-swapped application gets a specific, plausible assertion failure, which reads as a content regression and costs someone real time before they conclude otherwise. It also blocks releases, as a red staging E2E skips `deploy-production`. Adding the group to the two verification jobs does not close it. Concurrency is acquired and released per job, so a deploy can still slot in between one run's smoke tests and its E2E. The environment has to stay held across the sequence. So the three jobs move to `staging-deploy-verify.yml` and ci.yml calls it from a single `Staging` job that carries the group. A calling job is not complete until every job in the called workflow has finished, so the group is held from the first byte of the deploy to the last E2E assertion. The step bodies move unchanged; only the job headers differ. Two things the move forced: - `env` does not cross a `workflow_call` boundary and `with:` cannot read the `env` context, so APP_DIR is re-exported as a `changes` output rather than restated in the new file, keeping one definition of the deploy directory. - `playwright-image` joins the staging job's prerequisites. An unpublished image now fails the run before the droplet is touched rather than after a deploy and a smoke suite have landed on it. Two comments that named the old job IDs move with them: the DNS-tolerance notes in frontend/e2e/dashboard.spec.ts and deploy/staging-smoke-test.sh both explained themselves by their job being a direct `needs:` of deploy-production, which is now indirect through the calling job. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Why
#303 and #305 were closed on 2026-09-10: their policy changes (the orphan rule, the KF hold and uncertainty-sourced R) were measured against their own live captures and against what main did since (#310, #329, #331) and lost. Each carried one piece that is worth having regardless of policy. This is those two pieces, written against current main rather than rebased through the conflicts.
What
_forget_mn_key(old_key)inbackend/services/tasks/solver.py— factors the four-store removal (entry, anomaly hex,_MN_POS_HISTORY, Kalman state) the supersession loop did inline. The loop now calls it; no behaviour change. Any future removal path goes through it, or the next key minted at the same place inherits the dead one's filter.kf_action/kf_d2/kf_innov_mstamped on every return path oftrack_filter._smooth_kf(init,passthrough,smoothed,manoeuvre_rescued,reanchored) and copied onto the solve-history record besidekf_pos_sigma_m.manoeuvre_rescuedis new relative to Hold established dark tracks against outlier solves (TRACK_KF_OUTLIER_MODE, TRACK_KF_R_SOURCE) #305 and names Display filter: adapt process noise to manoeuvres instead of re-anchoring #310's retry branch;kf_d2is the base-Q innovation that triggered the retry, so a rescued update still shows the surprise._predict_updatereturns the innovation magnitude as a fourth value for the stamp. None on rejects and in off/ewma mode. Stamped in place on the passthrough paths, which return the caller's dict by identity.kf_held/kf_reanchoredcounters (filter_stats()already counts re-anchors and rescues) and Retire orphaned dark keys behind DARK_FOLLOW_ORPHAN_MODE #303'sorphaned_keyshistory field.What this buys: a capture on today's main can say how each published dark position came about — how many bad joins on established keys arrive via
manoeuvre_rescuedvsreanchoredvssmoothed— without flipping any policy. That is the measurement #305's cap20 could not make, and the one any future outlier policy should be designed from.Verified
tests/test_track_filter.py— newTestKfActionStamp(4): every path stamped, the ten-kilometre re-anchor carries its breaching d2 and ~10 km innovation, the manoeuvre rescue is stamped as such with d2 > gate, off mode does not stamp.tests/test_mn_lifetime.py— newTestForgetMnKey(2) andTestHistoryRecordKfAction(2).test_track_filter test_mn_lifetime test_solver_anchor test_solver_stats test_dark_follow test_dark_key_track_continuity test_mlat_history test_feed_multinode test_known_track_hold test_known_track_follow): 405 passed, 1 skipped (Stone-Soup oracle, not installed locally), exit 0.pre-commit runon the four changed files: all five hooks pass.🤖 Generated with Claude Code