Fix 1 (HRP, IMPORTANT): both the main bisection path and the except-branch fallback in
hrp_weights now guard the weight vector after computation — if any weight is non-finite,
negative, or the sum is ≤ 0 (triggered by a zero-variance asset causing 1/0→inf in _ivp),
the result is replaced with a uniform distribution (1/n each), which is always a valid
allocation. Prevents NaN weights from silently propagating to capital allocation.
Fix 2 (correlation, MINOR): replace exact `std() == 0` zero-vol guards in full_correlation
and left_tail_correlation with `std() < 1e-12`, so arrays with near-constant float-noise
(std ~1e-17) return NaN instead of a garbage corrcoef value.
Fix 3 (gate, MINOR): split the formerly combined `not isfinite(mg) or mg < min_marginal`
branch into two: a non-finite marginal now reports "marginal Sharpe unestimable — fail-safe
reject" (distinct from the "< threshold" message), making log/alert triage unambiguous.
Admit/reject outcome is unchanged.
Fix 4 (correlation, MINOR): _aligned now returns two empty arrays immediately when either
input has length 0, guarding against the a[-0:] == a[:] footgun that would otherwise return
the full array. Downstream len < 2 guards then produce NaN as designed.
New tests: test_hrp_zero_vol_asset_falls_back_to_valid, test_near_constant_returns_nan,
test_empty_input_is_nan_not_raise, test_unestimable_marginal_reason_is_distinct.
All 203 tests pass; ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>