Audit remediation: fix all 64 findings from the July 2026 audit - #36
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…ng, and add known-answer tests (FG-06, FG-07, FG-08, FG-27, FG-28, FG-29, FG-30, FG-32, FG-53) Remove bogus overflow guards in Normal/LogNormal (|z|>37), Gamma (rate*x>700), and Exponential (rate*x>700) — the log-densities are pure log-space and finite everywhere (FG-07, FG-08, FG-30). Fix Beta boundary semantics to match scipy (interior computed exactly with no ln cutoffs; endpoints return the true -inf / finite / +inf limits) (FG-27). Handle Binomial degenerate p=0/p=1 exactly instead of producing NaN (FG-28). Cache the Categorical CDF, validate once at construction, sample via binary search, and index log_prob directly (FG-53). Add infallible constructors Normal::standard/Uniform::unit/Beta::uniform_prior/ Bernoulli::fair (FG-29). Add interior-point known-answer tests for every distribution and exact-value tests for log_sum_exp/normalize_log_probs/ log1p_exp (FG-06, FG-32), plus regression tests at every previously-guarded point. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…guides, ELBO honesty (FG-04, FG-16, FG-17, FG-18, FG-44, FG-45, FG-46, FG-60) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…address collisions, dup detection, fresh score logp, i64 path, pattern binds (FG-19, FG-20, FG-21, FG-26, FG-47, FG-48, FG-52, FG-54, FG-61) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…ence and correct ABC-SMC (FG-03, FG-09, FG-13, FG-34, FG-43, FG-56, FG-58, FG-59) - FG-03: smc_prior_particles weight = log_likelihood + log_factors (prior cancels), no prior-squaring. - FG-59: move the prior trace into the particle instead of cloning it. - FG-43/FG-58: adaptive_smc is now genuine likelihood-tempered SMC (adaptive beta ladder, ESS-triggered systematic resampling, pi_beta-invariant MH rejuvenation) returning SMCResult with an unbiased log-evidence estimate. - FG-13: rejuvenation is a separate pi_beta-invariant move that never reweights; post-move ESS stays == N. - FG-09: abc_smc is a correct importance-weighted ABC-SMC (Beaumont/Toni) with weighted-covariance perturbation kernel, prior-zero rejection, and w ~ pi/sum_j w_j K; abc_smc_weighted exposes the weighted population. - FG-34: bounded per-stage attempts with typed ABCError (EmptyInitialPopulation / StageExhausted); no panic, no infinite loop. - FG-56: is_multiple_of(2) already in place from the trunk migration. Regression tests: tests/f_smc_smc.rs, tests/f_smc_abc.rs (seeded, analytic references). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…n ESS, split-R-hat, autocorr Geweke, and redundant-work fixes (FG-01, FG-02, FG-10, FG-11, FG-12, FG-35, FG-36, FG-37, FG-38, FG-39, FG-40, FG-41, FG-42, FG-57) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…e benches (FG-05, FG-22, FG-24, FG-62, FG-63, FG-64) FG-05: back Address with Arc<str> + precomputed u64 hash (allocation-free clone, O(1) hash). Measured adaptive_mcmc_chain before/after on 20/50-site models: ~1.3% faster at 50 sites (non-overlapping CIs), neutral at 20. The win is from Arc<str> O(1) clone; the cached hash aids HashMap<Address> probe sites (MH kind_cache, adaptation, VI params). Honest note: audit's "address allocation dominates" premise not borne out (trace is BTreeMap/Ord-keyed). FG-22/FG-62: wire-or-cut decided by evidence. f_perf pooling_evidence showed PooledPriorHandler/TracePool only ~3.8% faster than the shipped PriorHandler, below the 10% bar, so CowTrace/TracePool/TraceBuilder/PooledPriorHandler and the lying memory.md module doc were deleted. All in-crate call sites, tests, examples, and rustdoc updated. FG-63/FG-64: TraceBuilder::with_capacity (ignored its argument) resolved fixed-by-removal with the rest of the subsystem. FG-24: benches/f_perf.rs benchmarks the real entry points end-to-end (adaptive_mcmc_chain 20/50-site, tempered adaptive_smc, elbo_with_guide) with committed baseline numbers; deleted memory_benchmarks.rs (benched deleted code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…path (FG-31) Close the FG-31 competitive-ceiling gap: - New src/inference/hmc.rs: Hamiltonian Monte Carlo over the f64 sites of a trace using deterministic central-finite-difference forces. Leapfrog integrator, Hoffman & Gelman (2014) dual-averaging step size to 0.8 target (frozen after warmup), Alg.4 reasonable-epsilon init, configurable path length L, identity mass with optional diagonal mass adaptation. Bounded supports handled by -inf/divergence rejection. Module docs prove exactness: finite-difference forces still yield a volume-preserving, reversible leapfrog map, so the MH accept step (using the exact Hamiltonian) is exactly correct. - 7 new distributions in core/distribution.rs following existing style: StudentT, Cauchy, Laplace, Weibull, ChiSquared, InverseGamma (f64) and DiscreteUniform (i64), each with validated constructors, full normalizing constants in log_prob, and rand_distr / inverse-CDF / reciprocal-Gamma samplers. - DiscreteUniform makes ChoiceValue::I64 live end-to-end through sample/observe/replay/score and MCMC. - Exports wired through lib.rs (hmc_chain, HMCConfig, sample_i64, all 7 dists). Tests (all seeded): interior-point log_prob vs scipy-equivalent closed forms, boundary/support, moment sanity; HMC on 2-D correlated Gaussian (mean within 3 SE, cov within 15%), conjugate Normal-Normal vs analytic, and >=2x ESS per model-evaluation vs adaptive_single_site_mh (measured 5.0x); i64 end-to-end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…y pruning, exact distribution count, verified MSRV (FG-23, FG-25, FG-33, FG-50, FG-51)
- FG-23: drop the "production-ready" tagline (README, mdBook home, and a
stale duplicate landing page) for accurate positioning; add an explicit
pre-1.0 SemVer policy note.
- FG-25: add examples/{smc,abc,vi}_inference.rs -- the first examples/docs
anywhere in the crate to exercise adaptive_smc, abc_smc_weighted, and
optimize_meanfield_vi_with_config, each checked against a closed-form
posterior; wire into a new mdBook "Advanced Inference" tutorial section;
add hmc_chain to the README's example index.
- FG-33: prune 11 of 22 ErrorCode variants (and the FugueError
variants/constructors/macro that existed only to hold them) that no code
path ever constructed; document the live 11 with their construction sites.
- FG-50: state the exact distribution count (17) instead of "10+".
- FG-51: the README's unverified "1.70+" MSRV claim was wrong -- real
rustc 1.70.0 fails to build the crate. Verified the true floor (1.87.0),
pinned rust-version in Cargo.toml, corrected the badges, and added an MSRV
CI job that actually builds against that toolchain.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…arness, tighten e2e tolerances (FG-14, FG-15, FG-49) - FG-14: add tests/f_tests_sampler_validation.rs, a KS (continuous, n=5000, alpha=0.001, one-sample against hand-derived analytic CDFs) / chi-square (discrete) + standardized-moment goodness-of-fit suite covering all 17 exported distributions, not just Normal. Confirmed the Gamma test catches a rate-vs-scale parameterization bug via a manual injection/revert check. - FG-15: extend src/inference/validation.rs with test_conjugate_beta_bernoulli_model/ConjugateBetaBernoulliConfig alongside test_conjugate_normal_model, factor shared scoring logic into validate_against_analytical_posterior, re-export the new items from lib.rs, and add tests/analytical_validation.rs which actually calls both harnesses against adaptive_mcmc_chain. Fixed the stale inference_integration.rs comment claiming ConjugateNormalConfig "isn't exported". - FG-49: replace the self-acknowledged loose/apologetic tolerances in tests/end_to_end_workflows.rs (mse<200, pred_range<100) and inference_integration.rs's test_workflow_parameter_estimation_uncertainty (beta within 1.5, alpha within 2.0) with CLT-derived bounds computed from each model's exact closed-form Bayesian-linear-regression posterior (cross-checked in tests/gen_refs.py) and each chain's measured effective sample size. Diagnosed that the cross-validation test's original 30 samples/10 warmup never reached the asymptotic regime (empirically mse ~27 vs a theoretical ~0.04-0.6); bumped to 150/50, still sub-second, which resolves it without loosening the new bound. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
Address became a struct with private fields plus as_str() (FG-05 Arc<str> remediation); the example still used tuple-field .0 access. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…mple FG-47 duplicate-sample detection newly (and correctly) rejects the hierarchical_models example, which sampled each group's intercept/slope inside the per-observation plate — drawing the same alpha#g/beta#g/class#c address once per observation in that group. Before FG-47 this silently double-counted the group log-prior and dropped all but the last choice. Restructure every affected model to sample each group parameter once in a group-level plate, then index it per observation. This is both the correct partial-pooling model and conflict-free. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…(FG-55) Implement the public `Validate` trait for the ten exported distributions that lacked an impl (LogNormal, Binomial, Poisson, StudentT, Cauchy, Laplace, Weibull, ChiSquared, InverseGamma, DiscreteUniform), bringing coverage to all 17 distributions re-exported at the crate root. Each impl mirrors its `new()` constructor's validation exactly (predicates, messages, error codes, context keys). Add coverage tests: - tests/f_validate_coverage.rs: public-API exhaustiveness/drift guard that calls `.validate()` on a valid instance of every exported distribution (all 17 enumerated); fails to compile if a distribution is exported without a Validate impl. - src/core/distribution.rs: fg55_validate_rejects_invalid_parameters, one invalid-parameter case per newly implemented distribution asserting the correct ErrorCode (uses struct literals for the otherwise-unreachable invalid instances). - src/error.rs: extend validate_trait_on_valid_distributions to all 17. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
FG-10: rewrite the categorical regression to K=8 (and K=12) targets whose posterior mass sits on high indices, with per-category + aggregate-L1 tolerances that fail on the pre-fix asymmetric UniformCategoricalProposal and pass on the prior-resample fix. FG-19: fold sequence_vec/traverse_vec (hence plate!) into a right-nested, Vec-threaded bind chain so interpretation stays O(1) stack for any association; add a 100k-site plate! stack-safety regression (left-fold shape) plus an ordering test. FG-20/FG-21: make single-site MH sample fresh dimensions from the prior as an RJMCMC birth (with the matching q_forward/q_reverse prior terms) and add the site-selection dimension term, so structure-varying models recover the correct trans-dimensional posterior instead of biasing or panicking; refresh the cached site list on any structural change. Add sampler-path regressions: a b~Bernoulli branch model that recovers P(b=1|y) and E[x|b=1], and a branch-address-switching model driven through adaptive_mcmc_chain/adaptive_single_site_mh. FG-22: scrub the remaining fugue::runtime::memory references from how-to/README, production-deployment, trace-manipulation, and .github/CHANGELOG; add a docs guard test so a reintroduced reference fails CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…, truthful perf claims (re-verification lows)
ITEM 1 (src/core/distribution.rs) — DiscreteUniform::len() truncation:
The full i64 domain has 2^64 support points; `(high - low + 1) as u64`
truncated that to 0, so sample() panicked on gen_range(0..0) and log_prob()
returned +INF for in-range x. Keep the count in u128 (new private count()),
add is_full_i64_range(), sample the full domain via a raw uniform i64 draw,
and score it as -64*ln2. len() now saturates to u64::MAX for the full range
(documented); every other range is unchanged. Regressions: full-range
sample()/log_prob() (no panic, -64*ln2 within 1e-12) and near-full ranges
(MIN..=MAX-1, MIN+1..=MAX) scoring -ln(2^64 - 1).
ITEM 2 (src/inference/mh.rs) — site-list cache invalidation on equal-count
set swaps: the length-based check the finding describes was already replaced
(FG-20/21) by a born/died `structure_changed` flag that fires on ANY
address-set change, including swaps that keep the site count constant; both
the warmup and sampling loops honor it and the cache-site comment documents
it. Added a regression that drives the exact equal-count switching model
(bool `sw` selecting sample("a") vs sample("b")) and asserts both branches
are visited, both value marginals move (variance > 0), no ghost choices, and
finite weights.
ITEM 3 (docs) — stale claims for the deleted memory subsystem: README.md
and docs/src/home.md still advertised "memory optimization throughout" and
"memory pooling, copy-on-write traces"; the how-to index still labeled the
performance guide "Memory pooling". Replaced with truthful claims
(numerically stable log-space computations; O(1), allocation-free Arc<str>
address clones with a cached hash — what FG-05 shipped). The remaining
"Memory Optimization" mentions in the trace-manipulation tutorial describe
generic techniques (fresh traces, pre-sizing, batching) with no
deleted-subsystem references and were left as-is.
Also wrapped two pre-existing over-width assert! lines in
tests/f_mcmc_proposals.rs so `cargo fmt --all -- --check` passes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
…iation complete) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5821d267c7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| qq[i] = orig + h; | ||
| let lp = log_joint_at(model_fn, base, sites, &qq); | ||
| qq[i] = orig - h; | ||
| let lm = log_joint_at(model_fn, base, sites, &qq); |
There was a problem hiding this comment.
Avoid absorbing HMC states near bounded supports
For bounded or positive f64 sites, a current value within finite_diff_eps of the boundary (for example a Gamma, LogNormal, Uniform, or Beta draw with q < 1e-5, or a Beta draw near 1) makes the q - h probe score -inf. That marks the very first gradient as divergent, so every HMC transition from that valid state rejects before moving; chains initialized in this boundary layer get stuck rather than merely having lower acceptance. Consider a support-aware one-sided/adaptive finite difference or transforming constrained sites before applying HMC.
Useful? React with 👍 / 👎.
| let delta = (self.scale * gaussian_z(self.rng)).round() as i64; | ||
| current + delta |
There was a problem hiding this comment.
Prevent overflow in i64 random-walk proposals
When an i64 latent can sit near i64::MAX or i64::MIN (for example DiscreteUniform over an extreme range), adding the rounded Gaussian step can overflow in debug builds or wrap in optimized builds. That either panics during MCMC or jumps to a value far across the support while the code still treats the proposal as symmetric with no Hastings correction. Compute the proposal in a wider type or use checked/saturating arithmetic with an explicit reject/reflect path.
Useful? React with 👍 / 👎.
…ssion test (#37) Rebased onto main after the July 2026 audit remediation (#36), which independently fixed the same stack overflow via an iterative trampoline in the interpreter (FG-19) and a stack-safe sequence_vec. This keeps the still-novel parts of the original PR: the criterion benchmark for large observe sequences, and a 100k-observe regression test exercising the sequence_vec + observe path (the existing FG-19 test covers sample+bind). Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2 Co-authored-by: Brendan Ashworth <brendan.ashworth@me.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Complete remediation of the July 2026 ecosystem audit (see AUDIT-2026-07.md, included and fully resolved in this PR). All 64 findings are fixed — none deferred — and every fix was independently re-verified by an adversarial verifier against the final code (64/64 verified).
Highlights
Silent-wrong-answer fixes (all four inference engines):
adaptive_smcis now genuine likelihood-tempered SMC with an unbiased log-evidence estimate (FG-43/58); rejuvenation no longer re-skews weights (FG-13)Runtime & correctness: stack-safe trampolined interpreter (FG-19), Result-based structure-varying trace scoring (FG-20/21), duplicate-address detection (FG-47), collision-free address encoding (FG-26/52), irrefutable-pattern support in
prob!(FG-61)New capability (FG-31): finite-difference HMC with dual-averaging step-size adaptation, plus 7 new distributions (StudentT, Cauchy, Laplace, Weibull, ChiSquared, InverseGamma, DiscreteUniform) — 17 distributions total, all with scipy-derived known-answer tests and
Validatecoverage (FG-55)Performance (FG-05/22/24/62-64):
Addressis nowArc<str>with a cached hash (O(1) clone); the dead memory-pooling subsystem (CowTrace/TracePool) was benchmarked (<10% win), deleted, and docs de-advertised; benches now measure the real inference entry pointsTests & docs: sampler KS/chi-square validation for every distribution (FG-14), the conjugate-posterior harness is finally called from CI (FG-15), CLT-justified tolerances (FG-49), honest README claims + MSRV pin + CHANGELOG (FG-23/50/51), error-code reconciliation (FG-33)
Verification
🤖 Generated with Claude Code
https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2