Skip to content

ci: PR tier skips slow rows; main and release keep every row (CI-SLIM D1, CR-195) - #449

Merged
luisleo526 merged 3 commits into
mainfrom
ci/d1-policy-v2
Oct 20, 2026
Merged

luisleo526 merged 3 commits into
mainfrom
ci/d1-policy-v2

Conversation

@luisleo526

Copy link
Copy Markdown
Collaborator

Supersedes #448 (same tree cb1fc3d98f75, folded into ONE commit 14c3365f0 on main 18d8f3c96). Why a new PR: scripts/check_doc_reverts.py judges every commit against its own parent with its own message, so the two deleted docs/ci.md sentences had to be named in the commit that deletes them; a commit on top cannot fix that, and the PR branch is never force-pushed. The commit body names both deletions (133f571, b2a578c).

CI-SLIM D1: the PR tier skips slow rows; main and release keep every row (CR-195)

Owner order 10-20: CI simplification first. This is the D1 policy change (ruled by CORE-TOP; L6 reviews the coverage/floors table before merge).

What changes, on pull_request ONLY:

  • PR Release leg + kernel-only run with --exclude-label slow, with registration floors (release 895, kernel 309), so a dropped registration still fails.
  • test_ci_verify is split into 13 FAST / 9 SLOW classes. FAST always runs on PRs. SLOW runs on a PR only when the path helper finds a path the verifier reads; an unknown path runs SLOW.
  • push / dispatch / release are UNCHANGED: every row and class still runs there. New tests assert this: no exclusion and no class filter outside pull_request.

Policy reversal (named on purpose): this reverses the documented "full Release/kernel set on every PR" policy at scripts/ci_preflight.py:618 and :628 and docs/ci.md:715, :1049 and :1051. The docs are updated in this PR.

Measured before (engine PR CI, successful attempt-1 runs, queue included): median 90.8 min, p90 124.8, max 140.3 (n=11). The after-number is measured on the first real PRs after merge and reported; nothing here is an estimate of it.

Coverage and floors table (all 29 slow rows and all 22 verifier classes: floor, where each still runs, delay to MAIN; plus the NOT-covered list and 8 re-check commands):

Engine CI decision D1: coverage and floors table

Status: draft by lane B (authoring seat), 2026-10-20, for L6 review before merge (L6 condition 1). Lane A writes the code, lane B writes this table and docs/ci.md; both follow one contract. Nothing here has run on a real pull request. PR wall time after this change is to be measured on real PR runs (ten runs, typical and tail, cold and warm compiler cache; L6 condition 2). The 15-minute target is not claimed as met.

What changes, in one place

On pull_request only, and nothing on push, workflow_dispatch or release:

  1. The two PR legs that ran their full ctest population now exclude the ctest label slow (the PINEFORGE_PR_SLOW_TESTS population, tests/CMakeLists.txt:4591-4621, label set at :7049-7056): the Release legs of the build matrix and kernel-only. Debug, sanitizers and the native-live native cell already excluded it (scripts/ci_preflight.py:615-623 pins those three).
  2. The CI verifier's own self-test, scripts/test_ci_verify.py (22 TestCase classes), is split into FAST classes, which the preflight stage verifier-tests runs on every PR, and SLOW classes, which it runs on a PR only when the path helper scripts/ci_verifier_paths.py says the PR touches a path the verifier reads. An unknown or unreadable diff runs the SLOW classes. A class that is in neither list runs.
  3. The ctest row test_ci_verify stays in PINEFORGE_PR_SLOW_TESTS: excluded by the label on the PR legs, run in full on push to main.
  4. Floors: EXCLUDED_REGISTERED_MIN gets release and kernel keys so a PR leg that excludes the label also bounds how many rows may leave the registry (section c).

Coverage is unchanged and only its timing moves: every excluded row and class still runs on the push-to-main run for the merge commit. The delay is never zero (section a, note D-push).

Release tier: .github/workflows/release.yml runs no CTest and no ci_verify.py (/usr/bin/grep -c 'ctest\|ci_verify' .github/workflows/release.yml prints 0). Before a tag, today's full-row coverage is the maintainers' lab verification (docs/ci.md: the maintainers run the full ci_verify.py profiles before the merge gate; documented, not verified here). A GitHub verify-release job is CR-195 plan step 4 and is not part of this change. This change touches no release path, so release coverage is unchanged (note R below).

How to read the numbers: every number carries a command from section (e) or a file:line. derived means computed in this table from the sampled data, not measured on the tree this change lands on. not verified means no command was run. Data directory P = /Users/haoliangwen/.local/state/pineforge/runner/ORG-CX/proof/CI-SLIM/:

  • PR-TSV = P/tests-pineforge-engine-ci-pull_request.tsv: ctest rows per CI job, ten PR runs (five on the pool label pf-ci-x64-16, five on hosted ubuntu-24.04), Ubuntu legs only (no macOS rows).
  • PUSH-TSV = P/tests-pineforge-engine-ci-push.tsv: six push-to-main runs (five on the older pf-linux-x64-16 runners, 10-17 and 10-18, one hosted run 39126947405).
  • JOBS-PR, JOBS-PUSH = P/pineforge-engine-ci-pull_request-jobs.tsv, P/pineforge-engine-ci-push-jobs.tsv: job walls.
  • LOG = P/serial-test_ci_verify.log: 214 tests in 2730.345 s, 9 failures, 8 errors, 8 skips (LOG:610, :612). It is a timing log, not a passing proof.

(a) The 29 PINEFORGE_PR_SLOW_TESTS rows

Recount: the set(PINEFORGE_PR_SLOW_TESTS ...) block lists 29 names, all distinct (command a1). scripts/ci_preflight.py:677-686 pins the count at 29 and its sha256.

Legs on pull_request: R = build matrix Release legs (the Ubuntu leg is the one in PR-TSV; the macOS Release leg is not in the TSV), K = kernel-only, D = build matrix Debug legs, S = sanitizers, N = native-live (native). † marks a leg this change newly excludes; a leg without † already excluded the label before this change. A * on a floor marks a derived floor (section c).

Population of each leg on the sampled base tree (commands a2 and a5; all numbers are ran + skipped rows, every skip a pinned private-fixture placeholder):

leg registered labelled slow and registered there selected after -LE slow ran + pinned skips of the selection
R (Release, Ubuntu) 957 29 (26 ran + 3 skipped) 928 624 + 304
K (kernel-only) 371 24 (22 ran + 2 skipped) 347 318 + 29
D (Debug, Ubuntu) 938 (909 selected + 29) 29 909 605 + 304
S (sanitizers) 929 (900 selected + 29) 29 900 605 + 295
N (native-live native) not in TSV not in TSV not in TSV not in TSV

R and K are the minimum over the ten sampled PR runs (957 and 371; the other runs carry up to 977 and 385 because their PR heads add rows) and equal the registries of the full push run 39126947405. D and S PR jobs already exclude, so their registered count is the PR selection plus the 29 rows the same push run registers in them. Five slow rows are not registered in the kernel profile at all (test_adapter_live_state_equivalence, test_adapter_live_state_scaling, test_adapter_reissue_binding, test_chart_day_memo, test_l10recon1_merge_fill_liveness: absent from every kernel job of both TSVs), so the kernel leg excludes 24 rows and the kernel cells below read "not in TSV" for those five.

Three slow rows are pinned private-fixture placeholders: test_l10recon1_merge_fill_liveness, test_local_time_fields, test_native_cpp_abi skip in every public job of both TSVs (result ***Skipped, at most 0.15 s). They run only where the private overlay is present (the maintainers' lab). Excluding them removes no public coverage.

D-push (the delay column): the row next runs, in full, on the push-to-main run for the merge commit, in every leg it is registered in. Measured job walls (command a4): hosted run 39126947405: Release 1876 s, kernel-only 3022 s, Debug 3416 s, sanitizers 2707 s, native-live native 3132 s, preflight 1453 s, whole run 3433 s. Older pf-linux-x64-16 runners, median of five runs: Release 1815 s, kernel-only 1812 s, Debug 1770 s, sanitizers 2050 s, native 1838 s. No push run on today's pf-ci-* pool is in the sample, so the push wall on the current pool is not measured. The delay is not zero and not bounded by these walls: a regression in an excluded row is not seen by the PR, it is seen by that push run, and earlier only if the maintainers' lab verification covers it (which lab step runs which row is not verified here; the lab runs test_ci_verify in its full jobs, tests/CMakeLists.txt:7088-7090).

Note R (release): see "What changes". For every row below, release coverage is unchanged by this change, and today's pre-tag full-row coverage is the maintainers' lab verification, not release.yml.

PR Release job and PR kernel job seconds are the ctest row seconds of PR-TSV (command a3): the median of five runs, pool / hosted. skip means the row skipped (a placeholder). "not in TSV" means the row is not in that job's rows. Floors in force are the PR-leg registered floors of section (c); D, S and N are the existing keys.

# row PR Release job, s (pool / hosted) PR kernel job, s (pool / hosted) excluded on PR legs still runs: push to main floor in force on PR legs delay
1 test_adapter_live_state_equivalence 5.17 / 2.57 not in TSV R† D S N R D S N R≥895* D≥876 S≥867 N≥963 D-push
2 test_adapter_live_state_scaling 63.33 / 59.32 not in TSV R† D S N R D S N R≥895* D≥876 S≥867 N≥963 D-push
3 test_adapter_reissue_binding 4.95 / 2.56 not in TSV R† D S N R D S N R≥895* D≥876 S≥867 N≥963 D-push
4 test_aggregate_cpp_versions 8.39 / 5.88 7.24 / 6.79 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
5 test_broker_state_hash_coverage_mutations 36.15 / 20.61 31.93 / 23.78 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
6 test_chart_day_memo 69.37 / 43.04 not in TSV R† D S N R D S N R≥895* D≥876 S≥867 N≥963 D-push
7 test_ci_verify 3544.58 / 2003.57 3584.52 / 2261.99 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
8 test_l10recon1_merge_fill_liveness skip 0.02 / skip 0.00 not in TSV R† D S N R D S N, SKIP in public CI R≥895* D≥876 S≥867 N≥963 D-push
9 test_local_time_fields skip 0.02 / skip 0.00 skip 0.02 / skip 0.00 R† K† D S N R K D S N, SKIP in public CI R≥895* K≥309* D≥876 S≥867 N≥963 D-push
10 test_magnifier_endpoints4 1.61 / 0.73 1.61 / 0.75 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
11 test_native_bar_open_hook 4.80 / 2.20 4.90 / 2.35 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
12 test_native_command_after 37.73 / 18.84 38.12 / 18.49 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
13 test_native_cpp_abi skip 0.15 / skip 0.07 skip 0.14 / skip 0.08 R† K† D S N R K D S N, SKIP in public CI R≥895* K≥309* D≥876 S≥867 N≥963 D-push
14 test_native_cpp_versions 226.40 / 169.38 221.81 / 191.21 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
15 test_native_definition_index 1.86 / 0.87 1.83 / 0.85 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
16 test_native_direct_mutation 23.68 / 13.47 23.67 / 14.19 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
17 test_native_event_retention 16.02 / 6.57 15.59 / 6.49 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
18 test_native_group_absorption 2.64 / 1.10 2.92 / 1.40 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
19 test_native_group_keep_handle 4.22 / 1.82 3.79 / 2.00 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
20 test_native_handle_stable_replace 11.77 / 7.06 11.64 / 7.46 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
21 test_native_host_reads 1.57 / 0.81 1.67 / 0.99 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
22 test_native_lookup_memos 79.43 / 47.42 73.06 / 46.09 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
23 test_native_match_band_precheck 13.31 / 5.98 13.20 / 6.62 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
24 test_native_match_row_reuse 11.29 / 4.98 11.62 / 5.51 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
25 test_native_precommit_hook 2.89 / 1.32 2.87 / 1.36 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
26 test_native_quiet_point 5.86 / 2.89 6.19 / 2.89 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
27 test_native_runtime_ambient 4.79 / 2.14 4.39 / 2.19 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
28 test_native_script_bucket_completions 207.54 / 138.11 198.83 / 152.87 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push
29 test_native_settlement_carry 2.82 / 1.32 2.84 / 1.37 R† K† D S N R K D S N R≥895* K≥309* D≥876 S≥867 N≥963 D-push

Reading the table: in the sampled PR runs test_ci_verify is 3544.58 s (Release) and 3584.52 s (kernel-only) on the pool, 2003.57 s and 2261.99 s hosted; no other excluded row has a median above 230 s in either job. The N column is the existing exclusion through the exclude_slow input (.github/workflows/ci.yml:403); native-live ctest rows are not in either TSV, so which of the 29 rows the native profile registers is not verified. The macOS Release and macOS Debug legs run on push (JOBS-PUSH) but their rows are not in either TSV, so the macOS registries are not verified.

(b) The 22 TestCase classes of scripts/test_ci_verify.py

Rule: a class is FAST only if its measured total in LOG is under 5 s and its passing runtime is measured; otherwise SLOW; a class in neither list runs (defaults to RUNNING). The 22 classes are the ^class .*TestCase lines of the file (:643 to :3936, command b1): 9 SLOW + 13 FAST, none left out. Seconds are the per-class sums of the durations LOG prints (LOG:431-606, command b2; 176 of 214 tests print one, durations under 0.001 s are hidden, LOG:608). Every per-class sum is recomputed with awk (command b2) and equals the reference design's per-class number; the FAST and SLOW totals (FAST 2.809 s, SLOW 2727.510 s, visible total 2730.319 s) differ from the reference design's 2.818 s and 2727.501 s only because ReceiptReuse moved from FAST to SLOW.

In the PR tier: FAST classes run on every PR in the preflight stage verifier-tests (preflight runs on every event, docs-only changes included). SLOW classes run on a PR only when scripts/ci_verifier_paths.py (the single source of truth for the path list) reports a touched path the verifier reads, or cannot read the diff. The paths are in the helper, not copied here; they are the product inputs the verifier reads (include/pineforge, src/c_abi.cpp, tests/, runner/, the workflows, the CI scripts and a few documents).

Still runs: on push to main, workflow_dispatch and release, verifier-tests runs all 22 classes, and the serial ctest row test_ci_verify runs them again in the full Release, kernel-only, Debug and sanitizers jobs of the push run (PUSH-TSV has the row, Passed, in all four jobs of all six runs, command a3; native-live native: not in TSV). Delay: the SLOW classes, when no touched path triggers them, are next seen on the push-to-main run for the merge commit (D-push; preflight there took 1453 s hosted, whole job, not the stage alone; the stage alone is not verified from these inputs); never zero. Release: note R.

# class FAST / SLOW tests (defs in file) log seconds (command b2) in the PR tier when still runs delay
1 DriverOrderingAndAggregation SLOW 75 defs, 74 collected (one name defined twice) 1675.677 (69 visible) a touched path or an unreadable diff push, dispatch, release: verifier-tests; push: ctest row D-push when the PR triggers no SLOW class; a PR that triggers them runs them in its own preflight
2 PrivateFixtureFloors SLOW 26 370.396 (25) same same D-push
3 RowFloorDriver SLOW 13 240.006 (13) same same D-push
4 ExamplesAssertLive SLOW 5 165.281 (4) same same D-push
5 StaleBinaries SLOW 9 124.121 (9) same same D-push
6 LiveProfilesWithFixturesAbsent SLOW 7 73.318 (7) same same D-push
7 PrExclusionWithFixturesAbsent SLOW 7 64.919 (7) same same D-push
8 ReceiptRecipe SLOW 1 13.783 (1) same same D-push
9 ReceiptReuse SLOW 8 0.009 (7 visible), a time to ERROR: all 8 ERROR entries fail with FileNotFoundError: '/usr/bin/c++' (LOG:165-346), so its passing runtime is unmeasured same same D-push
10 RowFloorConfig FAST 6 1.180 (3) every PR same not excluded from the PR tier (its preflight runs it); the ctest-row copy is next seen on D-push
11 CTestDeadlines FAST 7 1.107 (7) every PR same same
12 DiagnosticsCollection FAST 7 0.364 (7) every PR same same
13 FloorDocsPointAtTheTable FAST 3 0.107 (3) every PR same same
14 GitObjectDiscovery FAST 2 0.015 (1) every PR same same
15 RunnerSanitizerCoverage FAST 3 0.014 (3) every PR same same
16 ProfileOptions FAST 10 0.011 (5) every PR same same
17 CAbiRuntimeInventory FAST 2 0.006 (2) every PR same same
18 ConfigValidation FAST 10 0.004 (2) every PR same same
19 RuntimeBudgetLanes FAST 2 0.001 (1) every PR same same
20 CopyCacheIdentity FAST 3 no visible duration in LOG (every test under 0.001 s); class total not verified every PR same same
21 HistoricalProviderPinTable FAST 2 no visible duration in LOG; class total not verified every PR same same
22 HistoricalProviderPins FAST 7 no visible duration in LOG: the class is skipped without the private overlay (scripts/test_ci_verify.py:1092-1099), so its runtime in this public run is not verified; it runs in the maintainers' overlay world every PR (skipped in public CI) same same

Sums (command b2): SLOW classes (9) 2727.510 s including ReceiptReuse's 0.009 s to error; FAST classes (13) 2.809 s over the ten classes that print a duration, plus three classes with none. Neither is a fresh passing runtime: the log carries nine failures, eight errors and eight skips. The three FAST classes without a printed duration (rows 20-22) are classified FAST by decision: the log prints no duration for them, so their passing runtime is not measured, and the stated rule alone would not classify them FAST.

(c) Floors

Where the numbers live and what the code reads (scripts/ci_verify.py):

key value basis
EXCLUDED_REGISTERED_MIN['debug'] 876 existing, :646; registered count at 873b25da plus the fifteen selected-window rows, counted before -LE (:704-707)
EXCLUDED_REGISTERED_MIN['sanitizers'] 867 existing, :646, same derivation
EXCLUDED_REGISTERED_MIN['native'] 963 existing, :646, same derivation
EXCLUDED_REGISTERED_MIN['release'] 895, derived; to be confirmed by the lab job's ctest -N new key; arithmetic below
EXCLUDED_REGISTERED_MIN['kernel'] 309, derived; to be confirmed by the lab job's ctest -N new key; arithmetic below
run floor, overlay present release 910, kernel 337 (RELEASE_MIN_TESTS, KERNEL_MIN_TESTS, :787-788) measured at 024fdf92c (:767-774)
run floor, overlay absent release 603 = 910 − 307, kernel 306 = 337 − 31 (PRIVATE_FIXTURE_PROFILE_FLOORS, :789-792; overlay_aware_floor, :831-837) constants of the profile

Derivation of the two new numbers (commands a2 and a5; derived, not measured on the tree this change lands on):

  1. Registered rows per profile in the PR-world jobs, minimum over the ten sampled PR runs (ran + skipped; command a2): Release 957, kernel 371 (these two jobs ran their full population in the sample, so ran + skipped is the registered count); Debug 909 and sanitizers 900 (these two already exclude, so ran + skipped is the selected count).
  2. Rows the slow label removes in each profile (command a5, the full push run 39126947405): Debug 29, sanitizers 29, Release 29, kernel 24.
  3. Registered count of the Debug and sanitizers PR-world jobs = selected + labelled: Debug 909 + 29 = 938, sanitizers 900 + 29 = 929. Cross-check (command a5): the same full push run registers Debug 938, Release 957, kernel 371, sanitizers 929.
  4. Existing offset, registered against registered: Debug 938 − 876 = 62; sanitizers 929 − 867 = 62 (the same number for both profiles). The existing keys therefore sit 62 rows below the current registry.
  5. The same offset applied to the two new keys: Release 957 − 62 = 895; kernel 371 − 62 = 309.

The basis matters: the existing keys count rows registered before -LE, so the Debug PR job's 909 (rows after -LE) must not be compared with 876.

What a PR leg enforces once it excludes the label (scripts/ci_verify.py:2255-2311, the exclusion branch, which returns before the run floor):

  • registered ≥ max(EXCLUDED_REGISTERED_MIN[profile], the profile's min_tests) (:2256-2257, :2263). On the public PR legs min_tests is the overlay-absent run floor (603 and 306), so the binding number is 895 (Release) and 309 (kernel), 876 (Debug), 867 (sanitizers), 963 (native). With the overlay present min_tests is 910 and 337, so the two new keys do not bind there.
  • 0 < selected < registered (:2267): the exclusion must remove something and must leave something.
  • overlay absent (the public jobs): ran + pinned skips == selected, and a skipped row outside the pinned list fails (:2271-2283); overlay present: ran == selected, any skip fails (:2299).
  • registered, labelled and ran are recorded in ctest-exclusion.log and ci-summary.json (docs/ci.md).

Slack on the public PR legs, in rows that could vanish from the registry unnoticed (sampled registries 957 and 371):

leg without the new keys (floor = overlay-absent run floor) with the derived keys
Release 957 − 603 = 354 957 − 895 = 62
kernel-only 371 − 306 = 65 371 − 309 = 62

On the sampled data the kernel key narrows the slack by only 3 rows. Both derived keys are lower bounds chosen by the offset rule, not measured floors; the lab job's ctest -N on the head this change lands on is the confirmation and may move them.

The run floors (910 / 337 with the overlay present, 603 / 306 absent) are not read on an excluded run; they are still enforced on push, workflow_dispatch and any other full run (ctest-floor, scripts/ci_verify.py:2312 onward). On those runs the slack in the sample is 650 − 603 = 47 rows (Release) and 340 − 306 = 34 rows (kernel-only), command a2.

(d) NOT covered by this change

  • macOS Debug in the PR tier: decision D3 (the reference design recommends moving it to MAIN). Unchanged here; its existing Debug exclusion stays.
  • The corpus-parity pull_request trigger: decision D2. corpus-parity.yml and the parity subset are unchanged (the subset keeps its full 54-probe population, docs/ci.md).
  • native-live: the native cell already excludes the label on PR; live-sanitizers and live-tsan stay in the PR tier unchanged. No native-live ctest rows are in the TSVs.
  • sanitizers: unchanged (already excludes the label on PR).
  • release.yml: unchanged. It runs no CTest (note R); verify-release is CR-195 step 4.
  • macOS Release leg registry and floor: the leg is one of the Release legs, but no macOS rows are in the TSVs, so whether its registry meets the derived Release key is not verified.
  • Other repositories (codegen-oss, hpo, release, dashboard, data, benchmarks-assets), preflight-lite, the x8 verifier shards, the full-minus-test_ci_verify mode for the full tiers, compiler-cache changes, docs-build path filters, runner routing, and the measurement of PR wall time (to be measured on real PR runs).

(e) How to check this table

Run these from the engine worktree root; P is the data directory named above. Every command is read-only.

P=/Users/haoliangwen/.local/state/pineforge/runner/ORG-CX/proof/CI-SLIM

# a1: the slow population, 29 distinct names
awk '/^set\(PINEFORGE_PR_SLOW_TESTS/{f=1;next} f&&/^\)/{f=0} f&&/^    test_/{n++; s[$1]=1} END{print n, length(s)}' tests/CMakeLists.txt

# a2: per PR job, over the ten runs: rows (ran + skipped) min and max, and rows that ran (Passed) min
#     (Release 957 / 650, kernel-only 371 / 340, Debug 909 and sanitizers 900 after their existing exclusion)
awk -F'\t' 'NR>1 {k=$1 SUBSEP $2; r[k]++; if ($5=="Passed") p[k]++} END {for (x in r) {split(x,a,SUBSEP); j=a[2]; if (!(j in mr) || r[x]<mr[j]) mr[j]=r[x]; if (!(j in Mr) || r[x]>Mr[j]) Mr[j]=r[x]; if (!(j in mp) || p[x]<mp[j]) mp[j]=p[x]} for (j in mr) print j ": rows min=" mr[j] " max=" Mr[j] ", ran min=" mp[j]}' $P/tests-pineforge-engine-ci-pull_request.tsv | sort

# a3: the test_ci_verify row with its seconds, run by run, in the PR jobs and the full push jobs (put another row name in $4==... to read it)
awk -F'\t' 'FNR==1 {n=split(FILENAME,f,"/")} $4=="test_ci_verify" {print f[n], $1, $2, $3, $5, $6}' $P/tests-pineforge-engine-ci-pull_request.tsv $P/tests-pineforge-engine-ci-push.tsv

# a4: push-to-main job walls (successful jobs, seconds)
awk -F'\t' 'NR>1 && $7=="success" && $9>0 {print $1, $5, $6, $9}' $P/pineforge-engine-ci-push-jobs.tsv

# b1: the TestCase classes
/usr/bin/grep -n '^class .*TestCase' scripts/test_ci_verify.py

# b2: per-class seconds from the serial log
awk 'NR>=431 && NR<=606 && /^[0-9]+\.[0-9]+s / {d=$1; sub(/s$/,"",d); if (match($0,/\(__main__\.[A-Za-z0-9_]+\./)) {c=substr($0,RSTART+10,RLENGTH-11); s[c]+=d}} END {for (c in s) printf "%s %.3f\n", c, s[c]}' $P/serial-test_ci_verify.log | sort -k2 -n -r

# a5: registered rows and slow rows per profile in the full push run 39126947405
#     (Debug 938/29, Release 957/29, kernel-only 371/24, sanitizers 929/29)
awk -F'\t' 'FNR==NR {if ($0 ~ /^set\(PINEFORGE_PR_SLOW_TESTS/) f=1; else if (f && $0 ~ /^\)/) f=0; else if (f && $0 ~ /^    test_/) {gsub(/ /,"",$0); s[$0]=1} next} $1=="39126947405" {n[$2]++; if ($4 in s) l[$2]++} END {for (j in n) print j ": registered=" n[j] " slow=" l[j]+0}' tests/CMakeLists.txt $P/tests-pineforge-engine-ci-push.tsv | sort

# code: the floors and the exclusion branch, and the release workflow
sed -n '646p;787,792p;2255,2257p;2263p;2267p;2283p;2311,2312p' scripts/ci_verify.py
/usr/bin/grep -c 'ctest\|ci_verify' .github/workflows/release.yml   # 0

Confirmation of the derived floors is the lab job's ctest -N (registered rows per profile) on the head this change lands on; until then both numbers stay derived.

Expected PR wall and the next step (L6 condition 3)

Expected PR wall for an ordinary src/ PR, and the next step to 15 minutes (L6 condition 3)

ESTIMATES. Nothing here has been measured on a changed workflow; the PR's own CI run is the first measurement and the numbers below are replaced by it.

What this PR changes for an ordinary src/ or include/ PR. The Release leg and kernel-only no longer run the ctest row test_ci_verify (3530 s of a 3608 s Release ctest run, measured on the pool PR job of #412; 2004 s and 2262 s on hosted runs) nor the other 28 slow rows. Their critical path falls to build + the fast ctest set. The preflight verifier-tests stage still runs the FAST verifier classes (about 2.8 s in total) on every PR.

What it does NOT change. scripts/ci_verifier_paths.py returns "run SLOW" for any path under src/, include/, tests/, scripts/, .github/, runner/, docs/, ... and for any unknown path, so the 9 SLOW verifier classes (2727.5 s serial; measured preflight verifier-tests stage 1480-2062 s on earlier hosted and pool PR runs, 4 shards on a 4-vCPU hosted runner) still run inside the preflight job of almost every engine PR. For an ordinary code PR the long pole is therefore still the PREFLIGHT job at roughly 25 minutes (ESTIMATE from those measured stages), not 15. Only a PR that touches nothing but tools/, tutorial/, benchmarks/, .claude/ or top-level prose defers them. This PR alone does not make ordinary code PRs 15 minutes; it removes the Release/kernel ctest row.

Dated next step (live by 10-21 18:00, earlier if the lab is green). Lane D2, branch ci/d2-verifier-shards on top of this PR: move the SLOW classes out of preflight into their own job verifier-slow, a matrix of 8 hosted time-balanced slices (per-test durations from the serial log committed as a table, longest-processing-time assignment, unknown tests round-robin so they always run), and make the aggregate wait for it. ESTIMATE for the slice: the ideal balanced slice is max(200 s slowest single test, 2727/8 = 341 s) plus checkout and setup, so about 6-9 minutes; with preflight reduced to FAST classes, the long pole of an ordinary PR becomes the build legs. Narrowing the SLOW triggers (a path list that is not "everything") is the second lever and is NOT proposed here: it trades detection for time and needs a decision.

Measured PR wall times (successful attempt-1 runs, queue included, last 100 completed PR runs; optimistic): engine CI on code PRs, 11 heads: median 90.8 min, p90 124.8, max 140.3 (proof/CI-SLIM/pr-wall/README.md). Release and data PR tails were attributed to pool queueing, not to run time (TAIL.md). These numbers are reported again after this PR and after D2, from real PR runs, typical and tail.

Rule for the deferred rows (L6 condition 4)

A red on main in an excluded row or class (a Release or kernel-only slow row, or a SLOW verifier class) blocks the next release cut and is fixed or reverted within the hour. It is written into docs/ci.md ("PR tier versus MAIN/RELEASE").

Floors

Release 895 and kernel-only 337 are DERIVED from the sampled public PR/push runs, not measured: the lab job's ctest -N on the head confirms them or the code takes the measured numbers; a macOS Release leg that registers fewer rows than 895 gets a floor per operating system, never a lower one (the PR's own run is that proof; read-based argument: Linux registers 19 example_* rows more than Debug and macOS Debug already passes 876, so macOS Release registers at least 895).

🤖 Generated with Claude Code

…egistration floors, and defers the slow verifier self-test classes (D1, CR-195)

On pull_request the Release matrix leg and the kernel-only job now pass
--exclude-label slow, like Debug and sanitizers already did. Push to main,
workflow_dispatch and release keep every row and every class, so coverage is
unchanged and only its timing moves.

- ci_preflight.py: the two policy checks that forbade this (only PR Debug
  matrix jobs may exclude slow rows; kernel-only must keep its full
  population) become one exact rule with must-fail forms; docs/ci.md says the
  same. This REVERSES the previous policy. The must-fail test for the
  full-population rule edits the `with:` block of the reusable-workflow job
  corpus-parity-subset (it has no `steps:`).
- ci_verify.py: EXCLUDED_REGISTERED_MIN gains release 895 and kernel 337
  (KERNEL_MIN_TESTS), derived on one registered-count basis from the PR-world
  job data, to be confirmed by a lab ctest -N. 337 keeps today's 34-row slack
  and stays below the 371 registered in all 16 sampled pull-request and push
  runs; 'release' stays 895.
- test_ci_verify.py classes are partitioned into FAST (13) and SLOW (9) in
  scripts/ci_verifier_classes.py; the preflight verifier-tests stage runs the
  FAST classes on every pull request and the SLOW ones only when
  scripts/ci_verifier_paths.py finds a path the verifier reads; any unknown
  path or class runs. Non-PR events run everything. HistoricalProviderPins is
  skipped without the private overlay and is FAST by decision.
- preflight has `needs: changes` and `if: ${{ !cancelled() }}` (pinned in
  ci_preflight.py); the docs say so.
- doc anchors: PRIVATE_FIXTURE_PROFILE_FLOORS moved from ci_verify.py:789 to
  ci_verify.py:802 (CONTRIBUTING.md, docs/pages/contributing-llm.md,
  docs/pages/native-engine.md, docs/ci.md).
- docs/ci.md gains L6's rule for the deferred rows: a red on main in an
  excluded row or class blocks the next release cut and is fixed or reverted
  within the hour. AGENTS.md lists Release and kernel-only among the jobs that
  exclude the slow rows.
- New tests assert that push, dispatch and release carry no exclusion and no
  class filter.

Doc sentences this change removes or rewrites ON PURPOSE (doc-reverts check):

- "The PR registration floors after lane FIX-E1E2 are 773 for Debug and
  sanitizers and 782 for native ..." (docs/ci.md), introduced by b2a578c
  ("docs: make every README and doc page true before the release
  (DOCS-ENGINE) (#300)"). Those numbers were already stale against
  scripts/ci_verify.py (876/867/963) and are now the keys of
  EXCLUDED_REGISTERED_MIN.
- "`preflight` waits for nothing." (docs/ci.md), introduced by 133f571 ("1.0
  prep: CI flake fix, docs-only CI skip, benchmark refresh (supersedes #303,
  #304, #307)"): preflight now waits for `changes`.
- "Preflight starts at once, and the proof jobs start alongside it" and
  "`changes` and `preflight` wait for nothing and run on every event"
  (docs/ci.md), same reason as 133f571 above.
- "than documentation runs preflight, both Release jobs, kernel-only and the
  parity subset with their full sets" and "both Debug jobs, sanitizers and
  native-live run the registered set excluding the 29 CTest rows labelled
  `slow`" (docs/ci.md): now stated for Release and kernel-only too.
- "A Release leg runs its full population on every event" and "Every event
  runs the whole kernel set" (the `build` and `kernel-only` rows of the
  timeout table in docs/ci.md): a pull request now excludes the slow rows.
- "The two new keys are derived, not measured: each is the registered count in
  the sampled public pull-request runs" (docs/ci.md): the kernel key differs
  from the Release one.

Note: scripts/release.yml runs no CTest and no ci_verify, so "release keeps
every row" means only that no --exclude-label is present in any release path;
the release workflow itself is unchanged.

The registration floors for Release and kernel-only (895 and 337) are derived,
not measured; a lab run of the release and kernel steps confirms or corrects
them.

This is the squash of 11cd290, 1b01244 and ef8fc5d (doc-reverts judges
every commit with its own message; the three were folded so that one message
names every sentence).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
import os
from pathlib import Path
import tempfile
import unittest
@luisleo526
luisleo526 merged commit 0212cab into main Oct 20, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant