Runtime audit of the C++ engine, following the static audit that shipped in 1.10.0. This round was driven by instrumented builds (-fsanitize=address,undefined), a degenerate-input stress harness, and a golden-output regression suite covering all 37 exported algorithms (~3,200 result comparisons).
Every fix below is pinned by a test in tests/testthat/test-regression-audit.R, and each of those tests fails on 1.10.0.
Crashes and hangs
-
ob_categorical_ivbcrashed the R session — a segmentation fault, not a catchable error — for any feature with no more categories thanmax_bins. With the defaultmax_bins = 5that meant every 2- to 5-level predictor: sex, marital status, region, education. Thencat <= max_binsfast path skipped the only placestats_cachewas built, while result assembly dereferenced it unconditionally. -
ob_numerical_ir,ob_numerical_jediandob_numerical_jedi_mwoelooped forever whenevermin_binsexceeded the number of distinct feature values — routine when onemin_binsis applied across a whole feature set. The loops contained noR_CheckUserInterrupt(), so Ctrl-C could not interrupt them either; the session had to be killed.
Silent data loss
ob_numerical_cmdiscarded observations. Its equal-frequency pre-binner, on hitting a run of tied values straddling a bin boundary, advanced past those records without assigning them to any bin. In testing this lost 26% of an integer feature and 40% of a coarse one, with the reported WoE/IV computed from the survivors alone.
Interval convention — standardised on (a, b]
The package previously disagreed with itself about what a bin is. Labels advertised (a;b] while many algorithms assigned values as [a;b); ob_apply_woe_num() had its two binary searches swapped, so both settings of include_upper_bound did the opposite of what the argument documents; and several pre-binners split runs of tied values, so their counts could not be derived from their own cutpoints at all. A value landing exactly on a cutpoint — routine for integer, rounded or currency features — could be scored into a different bin than the one it was trained in.
Measured across 63 algorithm/dataset combinations: 31 violated the documented convention before this release, 0 do now.
Reproducibility
-
ob_categorical_sabnow honoursset.seed(). It was seeded fromstd::random_device, so identical input returned a different binning on every call. Four runs under one seed previously gave bins 4/3/5/5 with three distinct IV values — unusable for auditable or regulated models. -
obcorr()now returns a stable row order. Per-thread buffers were spliced together in anomp criticalblock, so row order depended on thread scheduling; two runs with the same data and same thread count could differ. Correlation values are unchanged.
CRAN policy
-
obcorr()no longer seizes every core. With the defaultthreads = 0it calledomp_set_num_threads(std::thread::hardware_concurrency()). CRAN Repository Policy requires never using more than two cores by default; this was an archival risk. The default is now at most 2, honouringOMP_NUM_THREADS, with an explicitthreadsargument still respected. -
Removed eight stale Dropbox conflict copies from
src/thatR CMD buildwould have shipped and compiled, producing duplicate symbols.
Compatibility
This release changes numeric output. Of 3,240 golden comparisons against 1.10.0, 2,353 are unchanged; 12 numerical algorithms plus ob_categorical_sab differ, concentrated in data with ties (integer, rounded or currency features). Continuous features without ties are largely unaffected. No public R API changed.
Users with scorecards in production should expect bin boundaries to shift on discrete features, and re-validate. The previous results were silently incorrect at those boundaries.
Verification
R CMD check green on Ubuntu (devel, release, oldrel-1/2/3) and Windows (release). Test suite runs with 0 failures and 0 skips — the ob_numerical_sketch test disabled since 1.0.7 for a segfault is re-enabled, re-verified clean under -fsanitize=address,undefined. The full suite is also clean under ASan/UBSan.
Known issues
ob_categorical_dpandob_categorical_fetbreportWoE = IV = 0for perfectly separating bins instead of applying smoothing.- Return fields are not uniform across algorithms:
event_rateappears in 13 of 37,total_ivin 28 of 37, andivis missing from both_dmivvariants. ob_apply_woe_num()does not support the multinomial*_jedi_mwoevariants.