← All findingssource · memory/feedback_feature_change_traps.md
I shipped three commits on feat/xgboost-plus-v5-missingness that passed 665 tests and read as correct. A 4-skeptic adversarial review found blockers in all of them; one commit had to be reverted outright. Tests passing is not evidence a feature change is sound — nothing in the suite compared the two feature builders.
1. xgboost.core.XGBoostError SUBCLASSES ValueError. (LightGBMError does not.) So except ValueError around any train call swallows every real backend failure — sample-weight mismatch, DMatrix errors — and reports it as a benign skip with exit 0. Verified MRO: XGBoostError -> ValueError -> Exception. Never catch bare ValueError around a call into a C++ backend. Raise a dedicated exception (InsufficientTrainingData(ValueError)) at the specific sites and catch only that.
Why: the blanket catch recreates the exact silent-freeze it was written to prevent.
How to apply: grep for except ValueError near any .fit(/train( call in every plugin before trusting a skip path.
2. Key missingness indicators on DATA PRESENCE, never on the VALUE. has_x = 1.0 if avg > 0 else 0.0 cannot tell "no games" from "genuinely averaged zero" — and a genuine 0.0 is routine for blocks/steals/three_pm/ftm, where it is the most informative value the feature can take. Correct form: any(g for g in logs if <qualifies>).
3. Verify train/serve parity PER STAT TYPE, not by vector width. An AST width check proves nothing. NBA's trainer _get_home_away_avg computes a real venue split while the inference version silently falls back to the SEASON average for steals/blocks/three_pa/ftm — so a new indicator read 0 in training and 1 at serve for 4 of 10 markets. Also: three_pa is missing from STAT_ATTR_MAP (trainer returns None → fabricated −1.0), and _generate_combo_props never forwards game_logs, so ra/pr/pa serve with minutes features zeroed. Pre-existing and worse than anything I touched: pace_factor is ~−6 on every training row and exactly 0.0 on every served row (trainer builds it from one player's line and centers on 80, a team constant; neither serve call site passes it).
4. "Append-only" is false if you change an in-span feature's ENCODING. The deployed 29-wide artifact zero-pads forward and keeps serving; a new flag pads to weight exactly 0.0, so it cannot compensate. Re-encoding feature 10 flipped OVER/UNDER on 39.3% of affected rows under the live artifact. Either leave the old formula alone and let only the appended flag carry new information, or gate the encoding on the artifact's recorded feature version.
Also: holdout_evaluated = holdout_samples > 0 lets a 1-row holdout at 100% stamp the ratchet at 1.0 and lock the gate forever. Require a real minimum (200). Same bug exists at wnba/batch.py:461.
Related: project_nba_season_readiness, project_xgboost_v5_ship_path, feedback_verify_before_extend.