cargo test on main has failed continuously since 2026-09-17T04:08Z. Every PR in this repository inherits the red rust job. One test is responsible:
crates/mlsirm-core/tests/bifactor_oakes_mirt.rs:178
rust_oakes_se_matches_mirt_oakes_fixture ... FAILED
info at mirt's converged MLE must be PD; reason:
observed information is not positive definite (cholesky pivot 29 = -1.623e1 <= 1e-12)
This is not an estimator defect. The Rust Oakes assembly is correct; the committed oracle is stale.
What the evidence shows
The failure is deterministic, not numerical. Sweeping fd_step over three orders of magnitude and quadrature over 15/31/61 nodes moves the negative pivot only in the third digit:
fd_step |
q=15 |
q=31 |
q=61 |
| 1e-3 |
-1.597e1 |
-1.598e1 |
-1.599e1 |
| 1e-4 |
-1.621e1 |
-1.623e1 |
-1.623e1 |
| 1e-6 |
-1.623e1 |
-1.625e1 |
-1.625e1 |
So it is neither finite-difference noise nor quadrature error. The next check explains it: the fixture point is not a stationary point of our likelihood.
| point |
our marginal loglik |
worst |∂loglik| |
information PD |
mirt_d as committed |
-8304.664 |
64.04 |
no |
-d, reversed within each item |
-8192.986 |
0.0875 |
yes |
The fixture records mirt's own converged loglik as -8192.99570. Our value at the transformed point is -8192.986 — the 0.0095 residual is the quadpts 15 → 61 difference. At the point the test actually passes in, we are 112 nats away from mirt's optimum.
(y reversed, d as-is) and (y as-is, -d reversed) both give -8192.9862, which is the exact category-reversal symmetry of the graded model: reversing category coding maps d_k → -d_{K-k}. The empirical marginals break the tie. For items whose middle threshold is not near-symmetric, the sign matches the transformed vector, not the committed one:
| item |
empirical logit P(Y≥2) |
committed d2 |
-d reversed |
| 0 |
+0.035 |
-0.049 |
+0.049 |
| 5 |
+0.211 |
-0.423 |
+0.423 |
| 7 |
-0.030 |
+0.048 |
-0.048 |
The oracle was fitted on category-reversed data relative to the dataset the test now reads.
Root cause
f7cd5450 (09-17 06:27) generated tests/fixtures/bifactor_grm_stage3_oakes/mirt_oakes_fixture.json from the stage-1 dataset as it stood then, which the generator's own "Orientation note" documents as category-reversed.
0781051b (09-17 11:47, five hours later) rewrote tests/fixtures/bifactor_grm_stage1/dataset.csv (1570 lines) to correct that reversal, and regenerated bifactor_grm_stage1/mirt_fixture.json — but not the stage-3 Oakes fixture. 0781051b is not an ancestor of f7cd5450; the two landed on separate lines.
The generator's note reasoned that "both estimators fit the same committed data, so the implementation comparison is unaffected". That was true when it was written. Correcting the dataset underneath the oracle invalidated it, and nothing re-ran the R script.
Fix
Re-run the oracle against the corrected dataset:
Rscript tests/fixtures/bifactor_grm_stage3_oakes/generate_mirt_oakes_fixture.R
with R 4.x and mirt 1.46.1, as the script header pins. The regenerated mirt_d, mirt_se_* and mirt_vcov should land near the transformed values above, and the PD assertion, the 5% SE band and the 10% vcov band should all hold unchanged.
Please do not patch the test by negating and reversing mirt_d in Rust. The identity d_k → -d_{K-k} happens to reproduce the right point here, but baking it into the test would hard-code a data-orientation accident as if it were a parameterization convention, and the next fixture regeneration would silently break it. crates/mlsirm-core/src/bifactor_grm.rs:12 and crates/mlsirm-core/src/poly.rs:129 both use P(Y≥k) = logistic(aθ + d_k) — the same intercept convention as mirt — and that is correct as written.
While regenerating, check whether tests/fixtures/two_tier_grm_oakes/ shares the exposure: it reads the stage-4 dataset, a separate lineage, and its test currently passes, but it was produced by the same pattern.
I could not regenerate it here — no R toolchain on this host.
Why this is urgent rather than cosmetic
main has been red for ~44 hours. Every PR in this repository shows a failing required rust check regardless of its contents, which makes the signal useless and blocks the merge queue.
🤖 Generated with Claude Code
https://claude.ai/code/session_01YHVBDaZS5NZT9aQcbRg9Av
cargo testonmainhas failed continuously since 2026-09-17T04:08Z. Every PR in this repository inherits the redrustjob. One test is responsible:This is not an estimator defect. The Rust Oakes assembly is correct; the committed oracle is stale.
What the evidence shows
The failure is deterministic, not numerical. Sweeping
fd_stepover three orders of magnitude and quadrature over 15/31/61 nodes moves the negative pivot only in the third digit:fd_stepSo it is neither finite-difference noise nor quadrature error. The next check explains it: the fixture point is not a stationary point of our likelihood.
mirt_das committed-d, reversed within each itemThe fixture records mirt's own converged loglik as -8192.99570. Our value at the transformed point is -8192.986 — the 0.0095 residual is the quadpts 15 → 61 difference. At the point the test actually passes in, we are 112 nats away from mirt's optimum.
(y reversed, d as-is)and(y as-is, -d reversed)both give -8192.9862, which is the exact category-reversal symmetry of the graded model: reversing category coding mapsd_k → -d_{K-k}. The empirical marginals break the tie. For items whose middle threshold is not near-symmetric, the sign matches the transformed vector, not the committed one:d2-dreversedThe oracle was fitted on category-reversed data relative to the dataset the test now reads.
Root cause
f7cd5450(09-17 06:27) generatedtests/fixtures/bifactor_grm_stage3_oakes/mirt_oakes_fixture.jsonfrom the stage-1 dataset as it stood then, which the generator's own "Orientation note" documents as category-reversed.0781051b(09-17 11:47, five hours later) rewrotetests/fixtures/bifactor_grm_stage1/dataset.csv(1570 lines) to correct that reversal, and regeneratedbifactor_grm_stage1/mirt_fixture.json— but not the stage-3 Oakes fixture.0781051bis not an ancestor off7cd5450; the two landed on separate lines.The generator's note reasoned that "both estimators fit the same committed data, so the implementation comparison is unaffected". That was true when it was written. Correcting the dataset underneath the oracle invalidated it, and nothing re-ran the R script.
Fix
Re-run the oracle against the corrected dataset:
with R 4.x and mirt 1.46.1, as the script header pins. The regenerated
mirt_d,mirt_se_*andmirt_vcovshould land near the transformed values above, and the PD assertion, the 5% SE band and the 10% vcov band should all hold unchanged.Please do not patch the test by negating and reversing
mirt_din Rust. The identityd_k → -d_{K-k}happens to reproduce the right point here, but baking it into the test would hard-code a data-orientation accident as if it were a parameterization convention, and the next fixture regeneration would silently break it.crates/mlsirm-core/src/bifactor_grm.rs:12andcrates/mlsirm-core/src/poly.rs:129both useP(Y≥k) = logistic(aθ + d_k)— the same intercept convention as mirt — and that is correct as written.While regenerating, check whether
tests/fixtures/two_tier_grm_oakes/shares the exposure: it reads the stage-4 dataset, a separate lineage, and its test currently passes, but it was produced by the same pattern.I could not regenerate it here — no R toolchain on this host.
Why this is urgent rather than cosmetic
mainhas been red for ~44 hours. Every PR in this repository shows a failing requiredrustcheck regardless of its contents, which makes the signal useless and blocks the merge queue.🤖 Generated with Claude Code
https://claude.ai/code/session_01YHVBDaZS5NZT9aQcbRg9Av