Fix 1 (CRITICAL): Proposal.__post_init__ coerces all param values to float so
{"window": 20} and {"window": 20.0} produce the same key(); key() uses :.10g
format for stable float rendering. Prevents the same hypothesis being counted
twice when analysts use int vs float params.
Fix 2 (CRITICAL): AnalystFleet.gather restructured to count-before-mark: fresh
proposals are collected without marking, add_trials(len(fresh)) fires FIRST, then
mark_seen loops. A crash between count and mark causes a transient over-count
(bar slightly high, safe) instead of a permanent under-count (bar low → false
discoveries reach capital).
Fix 3 (IMPORTANT): LlmAnalyst.propose rejects lines with empty kind (|| guard)
and drops non-finite param values (inf/nan) via math.isfinite; replaces the
contextlib.suppress pattern with explicit try/except + isfinite check.
Fix 4 (tests): test_cross_analyst_dedup, test_cross_cycle_dedup_does_not_recount,
test_llm_drops_empty_kind_and_nonfinite added to test_analyst_fleet.py.
New file tests/unit/test_proposal_key.py covers int==float key stability and
distinct-param separation. 224 passed, ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>