Skip to content

Commit 497875b

Browse files
test(tiers): return every-run calibration and recovery cells to T1 and T2
Brief 40 of the August 2026 audit. Report: dev/review-2026-08/reports/40-t0-retier.md Constituent changes: - test(tiers): return the every-run tier's calibration and recovery cells to T1 and T2 - docs(tiers): state the every-run budget in the units CI reports
1 parent 6059a8d commit 497875b

13 files changed

Lines changed: 138 additions & 10 deletions

.github/workflows/fast-checks.yaml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,19 @@
1010
# Every push to develop and every pull request. No gate, so the run is
1111
# exactly what a maintainer sees locally: contracts, validation, wiring,
1212
# the product surface, and the tight numerical unit guards that catch a
13-
# shifted constant in one run. Budget ~90 s of tests (~4-6 min a job with
14-
# dependency restore and compilation).
13+
# shifted constant in one run. Budget: <= 15 min of tests in THIS job,
14+
# which keeps the whole job near half its 30-minute timeout.
15+
#
16+
# Read the budget off this job's own test phase. The tiering audit's
17+
# "~90 s" is a different measurement -- an OPTIMIZED build on a developer
18+
# machine -- and nothing this job prints can be compared against it.
19+
# devtools::test() compiles a debug build (pkgbuild::compiler_flags(debug
20+
# = TRUE) gives "-UNDEBUG -Wall -pedantic -g -O0"), and this job keeps it
21+
# that way on purpose (see the extra-packages note below). Measured on
22+
# identical content that build costs 2.38x, and this 2-core runner a
23+
# further ~2.31x over one developer machine. So quote CI minutes here, or
24+
# say which build a local number came from; a bare seconds figure is not
25+
# comparable across the two and let this tier drift unnoticed once.
1526
#
1627
# T1 nightly .github/workflows/nightly-validation.yaml. Daily 03:00 UTC
1728
# on develop, BGMS_RUN_SLOW_TESTS=true, <= 60 min, timeout 90.

dev/review-2026-08/MAINTAINERS.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ be T1. Budget is the tie-breaker, never the criterion.
218218

219219
| Tier | Cadence | Content, by bug class | Budget | Gate |
220220
|---|---|---|---|---|
221-
| **T0** every run | local `devtools::test()`; every push to develop and every PR (`fast-checks.yaml`) | contracts, validation, wiring, unit guards, product surface | ~90 s of tests | none |
221+
| **T0** every run | local `devtools::test()`; every push to develop and every PR (`fast-checks.yaml`) | contracts, validation, wiring, unit guards, product surface | **≤ 15 min of tests** in the `fast-checks` job (`timeout-minutes: 30`) | none |
222222
| **T1** nightly heartbeat | daily 03:00 UTC on **develop** (`nightly-validation.yaml`) | curated calibration subset — graph-law and prior-chain identities, gauge detector, single surface-vs-gold cells, RB saturation, concordance smokes. "Does the settled math still hold tonight" | **≤ 60 min** on the 2-core runner; `timeout-minutes: 90` | `BGMS_RUN_SLOW_TESTS=true` |
223223
| **T2** weekly certification | Sunday 03:00 UTC on **develop** (`weekly-certification.yaml`) | the heavy Monte-Carlo machinery — SBC suites, full parameter-recovery sweeps, full NUTS-vs-MH condition grids, n = 2e6 MC channels, refit cross-validations (incl. the F-049 gate) | `timeout-minutes: 360` | `BGMS_RUN_CERTIFICATION=true` (the T2 workflow sets **both** vars, so a weekly run also carries T1) |
224224

@@ -227,7 +227,19 @@ T2 uses the shared `skip_unless_certification()` in
227227
`tests/testthat/helper-tiers.R`, whose skip message says explicitly that
228228
`BGMS_RUN_SLOW_TESTS` alone does not enable the block.
229229

230-
Two mechanics worth knowing before editing these workflows:
230+
**Budgets are in CI minutes, and a local number must say which build it came
231+
from.** The 2026-07-29 audit measured an *optimized* build on a developer
232+
machine and landed 89.7 s; `devtools::test()` and every CI tier compile a
233+
*debug* build (`pkgbuild::compiler_flags(debug = TRUE)` is `-UNDEBUG -Wall
234+
-pedantic -g -O0`). Measured on byte-identical content the build is worth
235+
**2.38x**, and the 2-core runner a further **~2.31x** over one developer
236+
machine (`dev/review-2026-08/reports/40-t0-retier.md`). So the audit's 89.7 s
237+
and anything CI reports were never comparable, and T0 drifted for five weeks
238+
without the drift being visible to either number. The reference instrument is
239+
the `fast-checks` job's own test phase. When re-tiering, measure from a run
240+
that FINISHED, in that job, and quote minutes.
241+
242+
Three mechanics worth knowing before editing these workflows:
231243

232244
- **Schedules fire from the default branch.** GitHub reads the cron from
233245
`main`'s copy of a workflow file, so the T1/T2 schedules only start firing

tests/testthat/helper-tiers.R

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@
77
# T0 every run (local devtools::test(), and every push/PR)
88
# Contracts, validation, wiring, the product surface, and the tight
99
# numerical unit guards that catch a shifted constant in one run.
10-
# No gate. Budget ~90 s.
10+
# No gate. Budget: <= 15 min of tests in the fast-checks job.
11+
#
12+
# State which build any local number came from. devtools::test()
13+
# compiles a debug build (-O0), the tiering audit measured an optimized
14+
# one, and the two differ by 2.38x on identical content -- so the
15+
# audit's "~90 s" cannot be compared with a devtools::test() wall time
16+
# or with what CI prints. The fast-checks job is the reference
17+
# instrument; see .github/workflows/fast-checks.yaml.
1118
#
1219
# CRAN runs a SUBSET of T0: the heavy internal numerical guard files
1320
# are excluded there via skip_heavy_guard_on_cran() below (maintainer

tests/testthat/test-bgm.R

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@
1818
# This file focuses on tests that require special setup or unique assertions.
1919
# ==============================================================================
2020

21+
# Tiers. The product surface stays local. The estimate-simulate-re-estimate
22+
# cycle is parameter recovery -- it proves the settled numerics still recover
23+
# what they planted -- so it runs nightly.
24+
25+
skip_unless_slow = function() {
26+
skip_if_not(
27+
identical(Sys.getenv("BGMS_RUN_SLOW_TESTS"), "true"),
28+
message = "Set BGMS_RUN_SLOW_TESTS=true to run the parameter-recovery cycle"
29+
)
30+
}
31+
2132
test_that("bgm is reproducible", {
2233
# Use cached fixture as fit1, run one fresh fit as fit2 with same params
2334
fit1 = get_bgms_fit_ordinal()
@@ -235,14 +246,15 @@ test_that("bgm GGM output has correct parameter ordering", {
235246
})
236247

237248
test_that("bgm OMRF output has correct parameter ordering", {
249+
# Ordering is structural: the chain only has to produce the vectors.
238250
skip_on_cran()
239251

240252
data("Wenchuan", package = "bgms")
241253
x = na.omit(Wenchuan[, 1:5]) # p=5 to detect row/column-major bugs
242254

243255
fit = bgm(
244256
x,
245-
iter = 400, warmup = 300, chains = 1,
257+
iter = 60, warmup = 60, chains = 1,
246258
edge_selection = TRUE, seed = 42,
247259
display_progress = "none"
248260
)
@@ -1139,6 +1151,7 @@ test_that("bgm GGM implied regression matches OLS for large n", {
11391151
# Posterior mean parameters from the re-fit should correlate with the original.
11401152

11411153
test_that("estimate-simulate-re-estimate cycle recovers parameters (OMRF)", {
1154+
skip_unless_slow()
11421155
skip_on_cran()
11431156

11441157
data("Wenchuan", package = "bgms")

tests/testthat/test-bgmCompare.R

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@
1717
# This file focuses on tests that require special setup or unique assertions.
1818
# ==============================================================================
1919

20+
# Tiers. Structure, ordering, labels, scale and the selection branches stay
21+
# local. Reading back a planted difference at its planted size is parameter
22+
# recovery, so it runs nightly.
23+
24+
skip_unless_slow = function() {
25+
skip_if_not(
26+
identical(Sys.getenv("BGMS_RUN_SLOW_TESTS"), "true"),
27+
message = "Set BGMS_RUN_SLOW_TESTS=true to run the compare recovery certification"
28+
)
29+
}
30+
2031
# ------------------------------------------------------------------------------
2132
# Reproducibility Tests (using fixtures to save one model fit)
2233
# ------------------------------------------------------------------------------
@@ -246,6 +257,7 @@ test_that("bgmCompare handles more than 2 groups", {
246257
# ==============================================================================
247258

248259
test_that("bgmCompare output has correct parameter ordering", {
260+
# Ordering is structural: the chain only has to produce the vectors.
249261
skip_on_cran()
250262

251263
data("Wenchuan", package = "bgms")
@@ -255,7 +267,7 @@ test_that("bgmCompare output has correct parameter ordering", {
255267
fit = bgmCompare(
256268
x = x, group_indicator = group_ind,
257269
difference_selection = TRUE,
258-
iter = 1000, warmup = 500, chains = 1,
270+
iter = 60, warmup = 60, chains = 1,
259271
seed = 42,
260272
display_progress = "none"
261273
)
@@ -421,6 +433,7 @@ test_that("bgmCompare pairwise effects are on the association scale", {
421433
})
422434

423435
test_that("bgmCompare recovers a planted group difference at its planted size", {
436+
skip_unless_slow()
424437
skip_on_cran()
425438
# The guard above fits two groups drawn from the SAME omega, so every
426439
# difference in it is zero and a difference parameterization off by a factor

tests/testthat/test-calibration-check.R

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
# Tiers. The contract cells -- what fitted_observed_data() returns, the band
2+
# geometry, the panel kinds, the paging and the newdata guards -- stay local.
3+
# The family-breadth cells (Blume-Capel, non-contiguous scores, per-group and
4+
# Blume-Capel compare fits) show the check also covers those families, which
5+
# is calibration breadth rather than wiring, so they are T1.
6+
7+
skip_unless_slow = function() {
8+
skip_if_not(
9+
identical(Sys.getenv("BGMS_RUN_SLOW_TESTS"), "true"),
10+
message = "Set BGMS_RUN_SLOW_TESTS=true to run the calibration breadth cells"
11+
)
12+
}
13+
114
test_that("fitted_observed_data returns the fitted data on the input scale", {
215
skip_on_cran()
316
fit = get_bgms_fit_wenchuan5()
@@ -180,6 +193,7 @@ test_that("plot.bgms_calibration draws small multiples and checks variables", {
180193
})
181194

182195
test_that("calibration_check handles Blume-Capel and mixed Blume-Capel fits", {
196+
skip_unless_slow()
183197
skip_on_cran()
184198
x = Wenchuan[stats::complete.cases(Wenchuan[, 1:5]), 1:5]
185199

@@ -224,6 +238,7 @@ test_that("calibration_check handles Blume-Capel and mixed Blume-Capel fits", {
224238
})
225239

226240
test_that("calibration_check reads non-contiguous ordinal category scores", {
241+
skip_unless_slow()
227242
skip_on_cran()
228243
x = Wenchuan[stats::complete.cases(Wenchuan[, 1:5]), 1:5] * 2L + 1L
229244

@@ -323,6 +338,7 @@ test_that("fitted_observed_data puts a compare fit's Blume-Capel baseline back",
323338
})
324339

325340
test_that("calibration_check runs per group on a compare fit", {
341+
skip_unless_slow()
326342
fit = get_bgmcompare_fit_wenchuan5()
327343
check = calibration_check(fit, nrep = 20, seed = 2)
328344

@@ -374,6 +390,7 @@ test_that("a compare check accepts the fitted rows as newdata, in input order",
374390
})
375391

376392
test_that("calibration_check covers a Blume-Capel compare fit", {
393+
skip_unless_slow()
377394
fit = get_bgmcompare_fit_blumecapel()
378395
check = calibration_check(fit, nrep = 15, seed = 5)
379396
expect_s3_class(check, "bgms_calibration")

tests/testthat/test-ggm-gibbs.R

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,23 @@
1212
# samplers target the same K | graph posterior, so their posterior means
1313
# must agree within MCMC error. This catches a wrong target that a
1414
# "runs without error" check would miss.
15+
#
16+
# Tiers. The closed-form anchors are unit guards against a shifted constant and
17+
# stay local, as does one agreement cell -- Normal, alpha = 1, delta = 0, the
18+
# smoke proving gibbs is wired into bgm() at all. The remaining agreement cells
19+
# sweep the prior conditions (xi shift, gamma shape, Cauchy slab, eta frame),
20+
# which is settled-numerics calibration, so they are T1. The edge-selection
21+
# cells compare two samplers' inclusion probabilities across a condition grid,
22+
# which the tier contract places in T2.
1523
# --------------------------------------------------------------------------- #
1624

25+
skip_unless_slow = function() {
26+
skip_if_not(
27+
identical(Sys.getenv("BGMS_RUN_SLOW_TESTS"), "true"),
28+
message = "Set BGMS_RUN_SLOW_TESTS=true to run the gibbs-vs-AM condition sweep"
29+
)
30+
}
31+
1732

1833
# ---- Closed-form anchors ---------------------------------------------------- #
1934

@@ -137,6 +152,7 @@ test_that("gibbs routes through bgm() and agrees with AM (Normal, alpha=1, delta
137152

138153

139154
test_that("gibbs agrees with AM at delta = 0.5 (xi shape shift)", {
155+
skip_unless_slow()
140156
skip_on_cran()
141157
skip_if_not_installed("MASS")
142158
Y = ggm_agreement_data()
@@ -151,6 +167,7 @@ test_that("gibbs agrees with AM at delta = 0.5 (xi shape shift)", {
151167

152168

153169
test_that("gibbs agrees with AM at alpha = 2 (independent-MH on the diagonal)", {
170+
skip_unless_slow()
154171
skip_on_cran()
155172
skip_if_not_installed("MASS")
156173
Y = ggm_agreement_data()
@@ -165,6 +182,7 @@ test_that("gibbs agrees with AM at alpha = 2 (independent-MH on the diagonal)",
165182

166183

167184
test_that("gibbs agrees with AM under a Cauchy slab (scale mixture of normals)", {
185+
skip_unless_slow()
168186
skip_on_cran()
169187
skip_if_not_installed("MASS")
170188
Y = ggm_agreement_data()
@@ -180,6 +198,7 @@ test_that("gibbs agrees with AM under a Cauchy slab (scale mixture of normals)",
180198

181199

182200
test_that("gibbs agrees with AM under a non-unit Cauchy slab in the eta frame", {
201+
skip_unless_slow()
183202
skip_on_cran()
184203
skip_if_not_installed("MASS")
185204
Y = ggm_agreement_data()
@@ -215,6 +234,7 @@ ggm_pips = function(Y, update_method, iter, warmup, shape = 1,
215234
}
216235

217236
test_that("gibbs edge selection recovers the same inclusion probabilities as NUTS", {
237+
skip_unless_certification()
218238
skip_on_cran()
219239
skip_if_not_installed("MASS")
220240
set.seed(11)
@@ -236,6 +256,7 @@ test_that("gibbs edge selection recovers the same inclusion probabilities as NUT
236256
})
237257

238258
test_that("gibbs edge selection agrees with NUTS at Gamma shape = 2", {
259+
skip_unless_certification()
239260
skip_on_cran()
240261
skip_if_not_installed("MASS")
241262
set.seed(11)
@@ -256,6 +277,7 @@ test_that("gibbs edge selection agrees with NUTS at Gamma shape = 2", {
256277
})
257278

258279
test_that("gibbs edge selection agrees with NUTS under a Cauchy slab", {
280+
skip_unless_certification()
259281
skip_on_cran()
260282
skip_if_not_installed("MASS")
261283
set.seed(11)

tests/testthat/test-prior-inclusion-probabilities.R

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@
1111
# routes against each other.
1212
# --------------------------------------------------------------------------- #
1313

14+
# Tiers. The acceptance-target wiring, the analytic pieces, the extractor
15+
# contracts and the hierarchical pass-through stay local. The cells that
16+
# check one route against another (table vs prior-only chain, the mixed
17+
# per-class split, the hierarchical edge-prior identity) are settled-numerics
18+
# agreement, so they are T1.
19+
20+
skip_unless_slow = function() {
21+
skip_if_not(
22+
identical(Sys.getenv("BGMS_RUN_SLOW_TESTS"), "true"),
23+
message = "Set BGMS_RUN_SLOW_TESTS=true to run the prior-PIP route agreement cells"
24+
)
25+
}
26+
1427
prior_pip_ggm_data = function() {
1528
set.seed(7)
1629
x = matrix(rnorm(80 * 3), 80, 3)
@@ -165,6 +178,7 @@ test_that("the Beta quadrature reduces to the Beta mean without reweighting", {
165178
# --------------------------------------------------------------------------- #
166179

167180
test_that("GGM beta-bernoulli prior PIPs match the prior-only chain", {
181+
skip_unless_slow()
168182
skip_on_cran()
169183

170184
fit = small_fit(prior_pip_ggm_data(), "continuous",
@@ -234,6 +248,7 @@ test_that("GGM SBM prior PIPs come from a cached deterministic chain", {
234248
# --------------------------------------------------------------------------- #
235249

236250
test_that("mixed beta-bernoulli prior PIPs split by edge class", {
251+
skip_unless_slow()
237252
skip_on_cran()
238253

239254
d = prior_pip_mixed_data()
@@ -338,6 +353,7 @@ test_that("posterior extractor maps mixed indicators through block order", {
338353
# --------------------------------------------------------------------------- #
339354

340355
test_that("a hierarchical fit's prior inclusion probability is the edge prior", {
356+
skip_unless_slow()
341357
skip_on_cran()
342358

343359
x = prior_pip_hier_data()

tests/testthat/test-prior-sensitivity.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ test_that("compare_anchor_draws aligns every gated difference with its indicator
361361

362362

363363
test_that("the compare noise yardstick runs per indicator, not per gated parameter", {
364+
skip_unless_slow()
364365
skip_on_cran()
365366
data("Wenchuan", package = "bgms")
366367
fit = bgmCompare(
@@ -533,9 +534,10 @@ test_that("the difference-scale reweighting reproduces a refit at that scale", {
533534

534535

535536
test_that("a warm-start list with the wrong length errors", {
537+
# The guard reads the list's length, not the states it carries.
536538
data("Wenchuan", package = "bgms")
537539
fit = bgm(Wenchuan[, 1:5],
538-
chains = 2, iter = 150, warmup = 200, seed = 3,
540+
chains = 2, iter = 40, warmup = 40, seed = 3,
539541
display_progress = "none"
540542
)
541543
ws = extract_warm_state(fit)

tests/testthat/test-rb-inclusion-probabilities.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,10 +357,11 @@ test_that("extract_inclusion_bf pins the bgmCompare interleaved flattening", {
357357
})
358358

359359
test_that("estimator = 'rb' errors without edge selection", {
360+
# The guard reads edge_selection off the spec, before any draw.
360361
data("Wenchuan", package = "bgms")
361362
fit = bgm(
362363
Wenchuan[, 1:5],
363-
iter = 200, warmup = 150, chains = 1, seed = 7,
364+
iter = 20, warmup = 20, chains = 1, seed = 7,
364365
edge_selection = FALSE, display_progress = "none"
365366
)
366367
expect_error(

0 commit comments

Comments
 (0)