Retrospective: R020 (release preparation for 0.2.0rc1)#
Status: ready for the release candidate on the branch release-0.2.0 (from main at
8b9067e, the merge of pull request #10 with M6b). Release 0.2.0 is M7 + M6 (M6a, M6b; PLAN
Appendix F); R020 makes its release candidate 0.2.0rc1: the mosp API review and freeze (mosp
is stable from 0.2.0, the author’s decision of 2026-10-07), the mosp benchmark suite, the release
changes and two informational investigations, then the release certificate and the
distributions. Nothing is published by this milestone: the orchestrator pushes the branch; the
author merges the release pull request; the AI assistant pushes the signed tag v0.2.0rc1 and
creates the GitHub Release on the author’s behalf (GOVERNANCE.md, 2026-10-06 and 2026-10-07).
Each of the five steps has its section here: step 5 fixed what the independent reviews of the
branch found and measured the certificate again on the reviewed code; it ends with the readiness
of 0.2.0rc1 and the open items.
Step 1: the mosp API review and freeze (mosp-freeze)#
What changed#
Commit |
What |
|---|---|
|
The review’s documentation amendments of |
|
|
|
The mosp manifest says |
|
ADR 0035 “The mosp API review and freeze (0.2)”: 20 findings in ADR 0023’s format, five documentation amendments, the rest justified; Accepted under delegation for the review, the maturity recorded as the author’s decision; the two demonstrations (removing the default of |
Deviations (each with the same intent)#
The frozen label of an algorithm header now comes from its manifest (ADR 0035, decision 2), instead of relabelling one entry of a hand-written list.
The demonstrations ran once in a throwaway clone and are recorded in ADR 0035 (decision 3); the permanent tests are a synthetic-XML case and a griffe comparison of two copies of
python/dyng, which need neither a build nor git.
Step 2: the mosp benchmark suite (mosp-suite)#
What changed#
Commit |
What |
|---|---|
|
|
|
|
|
|
|
|
Deviations (each with the same intent)#
--keep-contaminatedwas added because another user’s long job (about 22 cores for 36 hours, at times on GPU 0) made the contamination monitor reject every round of the plumbing check; the gate protocol is unchanged (a record that kept contaminated rounds is refused).The suite is
ipdps25_dynamosp_mosp.yaml(R010 split_sosp/_mosp); the TPDS K sweep is listed underdeferred, as in M7.The mosp memory reading uses GPU 1, as the sssp suite’s does (M7 took it on GPU 0; the two A5000s are identical and the reading takes the exclusive lock).
Step 3: the release changes and two informational investigations (release-changes)#
This step was interrupted once by a usage limit (2026-10-07, about 16:20 UTC) and resumed the same day; the work in progress was checked against acceptance criterion 5 and kept.
What changed#
Commit |
What |
|---|---|
|
The approvals log of |
|
|
|
This retrospective, the investigations’ record |
The CUDA toolkits of the plugins (criterion 4’s check). NVIDIA’s RHEL 8 repository
(developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64, read 2026-10-07) lists nvcc up
to cuda-nvcc-12-9 12.9.86 and cuda-nvcc-13-4 13.4.92, the pinned versions. A fresh
ci/cuda_toolkit.py lock --plugin cu12 --release 12.9 and --plugin cu13 --release 13.4 into a
copy reproduce ci/cuda_toolkits.toml byte for byte, and ci/cuda_toolkit.py verify --signatures
passes for the cached RPMs of both. Nothing is re-pinned, so no plugin is rebuilt in this step and
the documents naming 12.9 / 13.4 stay as they are.
Investigation (a): the cycle_count update of the CUDA 13.4 cu13 wheel#
M6a’s informational wheel-vs-parity row (open item 4 of its retrospective) read the DD k = 4 update (25K + 25K) of the cu13 wheel built with the pinned CUDA 13.4 at 1.088x the parity build (CUDA 13.1), while the wheel built with 13.1 read 0.988x.
The kernels are the same. The update kernels of the parity overlays built with 13.1 and 13.4
(parity/wheel_vs_parity.py build-parity, the same commit, sm_86, the same GCC 12.2 host
compiler):
SASS (
cuobjdump -sass, per kernel):count_owned_cycles_kernel<4, unsigned>, the kernel of this update, has the same 560 instructions and 20 registers instead of 22; the othercount_owned_cycles_kernelwidths differ by -64 to +8 instructions (fewer moves and IMADs under 13.4);item_counts_kernelandmark_owners_kernelare identical.Nsight Compute 2025.4.1 (GPU 0,
--clock-control base, the kernels inside the NVTX rangecycle_count.update, 6 updates per side): the kernel time per update is 689.2 us (parity 13.1), 687.0 us (parity 13.4), 691.7 us (wheel 13.1) and 686.2 us (wheel 13.4). 13.4 is 0.3-0.8 % faster;build_next_rows_kernel(224 us) andcount_owned_cycles_kernel<4>(2 x 180-183 us) dominate, and the CUB scan kernels of the newer CCCL have other policy names and 48 -> 58 registers but the same time.
The difference was a process mode of a host stage. About 70 % of the update is
cycle_count.normalize, the framework’s normalization of the batch on the host (ADR 0020); it is
compiled by the host compiler, not by nvcc. wheel_vs_parity.py keeps one long-lived process per
side, so a side reads one process (one heap layout and process mode, parity/results/M3.md
section 6.3) for a whole session. The attempt before the interruption read the update side by
side in such sessions (GPU 0 and, while GPU 0 had a foreign job, GPU 1; the GPU 1 readings are
informational only): the same build against itself read 3.73 against 6.39 ms (13.4 vs 13.4,
GPU 0) and 4.01 against 5.73 ms (13.1 vs 13.1, GPU 1), and 13.4 against 13.1 read anywhere from
0.81x to 1.60x depending on the session. The final reading therefore runs every side in 8
separate processes (15 rounds each after a warm-up, the side order rotating, GPU 0, the exclusive
lock, the boost clock lock; final_a.py, record in parity/results/R020-investigations.json):
Stage (device ms, median over 8 processes [min-max]) |
parity 13.1 |
parity 13.4 |
wheel 13.1 |
wheel 13.4 |
|---|---|---|---|---|
|
2.209 [2.165-2.237] |
2.103 [2.072-2.125] |
2.151 [2.100-2.189] |
2.126 [2.116-2.155] |
|
0.279 |
0.278 |
0.281 |
0.279 |
|
0.299 |
0.300 |
0.297 |
0.296 |
|
0.240 |
0.237 |
0.238 |
0.233 |
|
3.118 [3.085-3.156] |
3.010 [2.965-3.037] |
3.025 [2.961-3.055] |
2.984 [2.960-3.016] |
13.4 / 13.1: 0.965 for the parity builds, 0.986 for the wheels; every device stage is within
1.5 % (13.4 slightly faster), and the spread is in the host stage. (The wheels are M6a’s,
476ab72; since then the cycle_count sources changed only in one error message.)
Recommendation. Keep CUDA 13.4 for the cu13 wheel: there is no 13.4 slowdown to fix, so no
library change and no ADR. A future wheel-vs-parity row of a short, host-heavy update should be
read over several processes per side, as here, not from one long-lived worker per side (a note for
parity/wheel_vs_parity.py; the row is informational, PLAN 7.7, and no gate reads it).
Investigation (b): layout control for the 0.2 gates (a measured proposal)#
The open decision “Layout control for the 0.2 gates” (GOVERNANCE.md; parity/results/M3.md
sections 5.4 and 6.3) asks whether the gates against the originals should control code placement.
Measured on a representative subset of the gate readings, at the release library code (sources of
1474a92 = the release commits’), GPU 0 / the Xeon, the exclusive lock, the contamination monitor
(at most 1 round repeated per reading), outputs byte-identical / histograms equal to the goldens in
every reading. Variants of the host code, built from the parity / parity-cuda presets with:
L0 no flag (the accepted presets); F -falign-functions=64; FB F plus
-falign-loops=32 and the assembler’s -mbranches-within-32B-boundaries (the mitigation of the
Skylake-derived cores’ jump-conditional-code erratum). “A” is the original: A0 the unpatched build
of the harness, AF / AFB the same unpatched sources rebuilt with the same flags in a separate work
area (an experiment, never a gate reference). B0-BF and B0-BFB are dynG against dynG (sssp over 5
heap layouts, ADR 0024). Ratios dynG / original (or variant / L0), medians:
Reading |
A0-B0 (the protocol) |
A0-BF |
AF-BF |
A0-BFB |
AFB-BFB |
B0-BF |
B0-BFB |
|---|---|---|---|---|---|---|---|
sssp OpenMP roadNet-CA local10k, sosp_update obj0 / obj1 / obj2 |
0.741 / 0.730 / 0.854 |
0.755 / 0.748 / 0.855 |
0.640 / 0.611 / 0.659 |
0.733 / 0.700 / 0.796 |
0.716 / 0.677 / 0.730 |
0.992 / 1.010 / 1.006 |
0.976 / 0.983 / 0.992 |
… apply / end to end |
0.888 / 0.880 |
0.872 / 0.907 |
0.869 / 0.880 |
0.860 / 0.668 |
0.842 / 0.734 |
0.974 / 0.983 |
0.948 / 0.789 |
sssp OpenMP roadNet-CA safe50k, sosp_update obj0 / obj1 / obj2 |
0.819 / 0.915 / 0.926 |
0.787 / 0.918 / 0.933 |
0.834 / 0.898 / 0.901 |
0.819 / 0.904 / 0.917 |
0.798 / 0.896 / 0.897 |
1.015 / 1.003 / 0.999 |
0.986 / 0.989 / 0.993 |
… apply / end to end |
0.865 / 0.887 |
0.911 / 0.855 |
0.892 / 0.812 |
0.851 / 0.727 |
0.861 / 0.738 |
1.019 / 0.992 |
0.997 / 0.809 |
sssp OpenMP rgg local10k, sosp_update obj0 / obj1 / obj2 |
0.756 / 0.733 / 0.763 |
0.707 / 0.671 / 0.710 |
0.753 / 0.736 / 0.751 |
0.717 / 0.684 / 0.720 |
0.713 / 0.685 / 0.715 |
1.010 / 1.004 / 1.005 |
1.006 / 0.994 / 1.000 |
… apply / end to end |
0.768 / 0.893 |
0.789 / 0.910 |
0.797 / 0.831 |
0.783 / 0.705 |
0.786 / 0.744 |
0.986 / 0.998 |
1.024 / 0.791 |
cycle_count CUDA DD 25K update, original scope / end to end / resident |
0.683 / 0.966 / 0.621 |
0.681 / 0.969 / 0.613 |
0.673 / 0.949 / 0.614 |
0.662 / 0.958 / 0.601 |
0.669 / 0.966 / 0.604 |
0.993 / 1.000 / 1.011 |
0.981 / 1.001 / 0.963 |
cycle_count OpenMP github 25K update / end to end |
0.447 / 0.573 |
0.441 / 0.568 |
0.443 / 0.531 |
0.404 / 0.558 |
0.398 / 0.626 |
0.999 / 1.002 |
0.899 / 0.982 |
(Compact records in parity/results/R020-investigations.json; the full records in
$DYNG_SCRATCH/r020/inv-b/records-final.)
What the readings say:
dynG against dynG, F changes nothing beyond noise: every region of B0-BF is within 0.974-1.019 (ADR 0024’s reading over heap layouts). Function alignment alone is not a speed-up; it would only fix where the hot functions fall.
FB is a real speed-up of dynG’s text I/O: dynG’s sssp end to end is 0.79-0.81x of L0 in every round (21 / 21, unimodal; the compute regions within 0.976-1.024), and the cycle_count OpenMP update is 0.90x. The branch-boundary mitigation helps the branchy readers and loops on this Cascade Lake CPU.
The gate ratios move with the originals’ layout as much as with dynG’s: rebuilding the original with F moves the roadNet-CA local10k per-objective ratios from 0.73-0.85 (A0-B0) to 0.61-0.66 (AF-BF), i.e. the original’s short updates got 15-25 % slower from its own placement. Controlling only dynG’s layout (A0-BF) moves the ratios by up to 0.06 either way; controlling both sides is a change to the originals’ build, which the parity rules pin (unpatched, as the original’s build files build it).
Every gated reading passes in every variant with a wide margin: the largest gated ratio in the table is 0.969 (cycle_count CUDA end to end, gate 1.10) and the largest per-objective one 0.933 (gate 1.05). Layout control is not needed for the 0.2 gates to hold, and no variant comes near a gate.
The proposal for the author (attached to the open decision; the decision stays open and criterion 3’s gates use the accepted one-layout protocol of ADRs 0018 / 0021):
For the gates against the originals: keep the one-layout protocol (the default), with the originals built exactly as their build files build them. Reason: reading 3; a flag in the parity presets alone would shift the ratios by about as much as the noise it is meant to remove, and aligning the originals changes the pinned baselines. The layout spread (about ±0.06 on the short OpenMP sssp updates, ±0.02 elsewhere) is the known uncertainty of a single reading and is far inside every gate (reading 4).
For dynG-against-dynG bars (refactors): keep ADR 0024 (reading over heap layouts and modes).
Separately, as a performance option for the release builds (not a protocol change): FB in the release presets and the wheels would make dynG’s end-to-end runs about 20 % faster on this CPU family with no regression elsewhere; it needs its own ADR, a parity run of the release builds, a check with the wheels’ toolchain (gcc-toolset 14 in manylinux_2_28) and a reading on a CPU without the erratum. Recommended for 0.3, not 0.2.0 (it would change the measured library after the certificate’s protocol was fixed).
Deviations and decisions (each with the same intent)#
(a) is read over processes, not with the M6a harness. The M6a row’s tool reads one long-lived process per side; its result for the DD update depends on the process mode of a host stage (above), so the final reading uses 8 processes per side with the same batch, plus Nsight Compute on the kernels. The harness itself is unchanged.
(b)’s aligned originals are rebuilt from the unpatched sources in a separate work area (
$DYNG_SCRATCH/r020/inv-b/scratch-F,scratch-FB), never in$DYNG_SCRATCH/ref, so no gate reference changed.No library change came out of either investigation, so the certificate measures the release code as committed here.
The GPU 1 readings of the interrupted attempt are kept only as the evidence of the process-mode spread, labelled informational; every reading of the tables above is on GPU 0.
Results#
Check |
Result |
|---|---|
|
VERSION, CHANGELOG.md and CITATION.cff agree (0.2.0rc1) |
|
valid (schema 1.2.0) |
|
7 generated files up to date (5 algorithms) |
actionlint, zizmor (the two workflows) |
passed |
|
all checks passed: |
Portability pre-checks |
not applicable: no C++ or CUDA file changed in this step |
Step 4: the release certificate and the distributions (certify)#
What was done#
Commit |
What |
|---|---|
|
|
|
CHANGELOG: the section “R020: the release certificate” |
|
This section and the status of this retrospective; the final newline of |
|
|
|
The wording of deviation 6 and of that comment (codespell, deviation 6) |
(this commit) |
The storm window checked against every gate reading; the three mosp CUDA readings at the boost lock that ran in its tail were taken again (deviation 1); the certificate summary, the readiness and the open items |
(the next commit) |
The certificate |
How it was measured. Every measured build comes from a clean git clone of the branch.
The gates, the golden replays and the golden mutations were measured at 8bc3e61 (clone
$DYNG_SCRATCH/r020/cert/clone-a, presets parity and parity-cuda), except the three mosp
CUDA readings at the boost lock of roadNet-CA, rgg and road_usa, which were taken again at
afcfb0f (clone $DYNG_SCRATCH/r020/cert/clone-r, preset parity-cuda; deviation 1); the
commits after 8bc3e61 change no path of the library scope (parity/, CHANGELOG.md,
documents), which certify.py write checks. The C++ test suites (the sanitizer presets, the mutation CTests), the
portability pre-checks and the distributions ran at bc1304f, whose tests and packaging
paths are the release’s; ci/check.sh --parity and ci/gpu_local.sh (the repo scope, every
tracked file but benchmarks/results/) ran at this commit, the last one outside
benchmarks/results/, from fresh clones. Logs: $DYNG_SCRATCH/runs/r020-certify-phase-a.log
(the suites, replays and mutations), r020-certify-mosp-retake.log,
r020-certify-mosp-retake2.log and r020-certify-mosp-retake3.log (the readings taken again),
$DYNG_SCRATCH/runs/r020-cert/
(the checks); each check names its log with its SHA-256 in checks.json.
The first certificate (f90fb9c; superseded by step 5)#
Golden parity (byte equality with the originals; CUDA on GPU 1):
Golden set |
Original |
Cases |
Configurations |
Result |
|---|---|---|---|---|
|
MOSP-OpenMP@c352151 (= MOSP-CUDA@e220ee2) |
495 |
sequential, openmp:1 / 4 / 16 / 28 / 56, sequential/int64, openmp:4/int64; cuda, cuda-fused, cuda-operators, cuda/int64, cuda-operators/int64 |
6435 / 6435 equal |
|
CycleEnumeration-GPU@0a976ad |
24 |
sequential, openmp:4, openmp:56 |
72 / 72 equal |
|
CycleEnumeration-GPU@0a976ad |
24 |
cuda, cuda:resident, cuda:int64 |
72 / 72 equal |
|
MOSP-OpenMP@c352151 (= MOSP-CUDA@e220ee2) |
20 |
sequential, openmp:28; cuda, cuda-operators |
80 / 80 equal |
Performance gates (PLAN 8.6 against the unpatched originals; the exclusive lock; OpenMP sssp and mosp with 28 pinned threads, cycle_count with 56; CUDA on GPU 0 at the boost clock lock, the COLLAB update at the base lock (ADR 0021), the default-clock readings recorded and not gated (ADR 0018); device memory on GPU 1). Every gated region of the three suites is within its gate: 270 / 270. The worst (largest) ratio port / original per algorithm, backend and gate kind:
Algorithm (suite) |
Backend (clocks) |
Compute (gate) |
End to end (gate 1.10) |
Device memory (gate 1.05) |
|---|---|---|---|---|
|
OpenMP |
0.991 (1.05) |
0.976 |
- |
CUDA (boost) |
1.009 (1.10, region under 10 ms) |
0.975 |
0.858 |
|
CUDA (default clocks, not gated) |
1.062 |
0.966 |
- |
|
|
OpenMP (incl. COLLAB) |
0.906 (1.05) |
0.855 |
- |
CUDA (boost) |
1.003 (1.05) |
0.982 |
1.001 |
|
CUDA (base, COLLAB update) |
0.999 (1.05) |
0.984 |
- |
|
CUDA (default clocks, not gated) |
1.001 |
0.974 |
- |
|
|
OpenMP |
0.926 (1.05) |
0.955 |
- |
CUDA (boost) |
1.005 (1.05) |
0.946 |
1.019 |
|
CUDA (default clocks, not gated) |
1.009 |
0.970 |
- |
The best ratios (where dynG is fastest): sssp OpenMP 0.643, cycle_count CUDA 0.223 (the COLLAB
k = 3 count, resident), mosp OpenMP 0.648; the full tables are generated at the end of the
certificate’s README.md. No reading has a flagged or kept contaminated round, and every record’s
inputs were hashed by the harness when its measurement started and equal the suite’s digests.
The one default-clock region above 1.05 (sssp CUDA, road_usa local10k, objective 2, 1.062) is
the DVFS effect ADR 0018 describes (the original read 20.8 ms there, 22.2 ms at the lock); at the
locked clocks the same region reads 0.995.
Checks (checks.json):
Check |
Scope |
Commit |
Result |
|---|---|---|---|
|
library |
|
the control passes; 11 / 11 mutations detected (sssp: the skipped subtree invalidation on sequential, OpenMP and CUDA; cycle_count: the double-counted 5-cycles and the weakened ownership rule, host and CUDA; mosp: the combined-edge weight from one tree on each backend, the path costs from one objective’s weights) |
|
tests |
|
835 / 835 tests passed, no report |
|
tests |
|
742 / 742 |
|
tests |
|
835 / 835 |
|
tests |
|
7 / 7 |
|
packaging |
|
passed |
|
repo |
this commit |
all checks passed: |
|
repo |
this commit |
every step passed: |
The distributions of v0.2.0rc1, built locally as release.yml would build them for that tag
(docs/developer/release.md, “Building a release locally”; no tag was created):
select(its run block extracted fromrelease.yml,GITHUB_REF_NAME=v0.2.0rc1):kind=package,version=0.2.0rc1,prerelease=true,plugins=true, the distributionsdyng,dyng-cu12(-cu12) anddyng-cu13(-cu13);v0.2.0andv0.2.0-rc.1refused.ci/wheel_check.py --release-metadatapasses.The toolkits: step 3 checked on 2026-10-07 that CUDA 12.9 (nvcc 12.9.86) and 13.4 (nvcc 13.4.92) are still the newest 12.x and 13.x of NVIDIA’s RHEL 8 repository (a fresh
lockreproducesci/cuda_toolkits.toml;verify --signaturespasses), so nothing was re-pinned.ci/wheels.sh:dyng-0.2.0rc1.tar.gz(0.99 MB) anddyng-0.2.0rc1-cp312-abi3-manylinux_2_28_x86_64.whl(1.70 MB), the whole Python suite in fresh 3.12 and 3.13 venvs (543 passed, 67 skipped).ci/plugin_wheels.shwithDYNG_PLUGINS="cu12 cu13"from that sdist, with CI’s toolkits (the pinned RPMs ofci/cuda_toolkits.tomlunpacked byci/cuda_toolkit.py extract: CUDA 12.9 and 13.4; Wheels, the sdist and the release workflow, “The CI toolkit locally”):dyng_cu12-0.2.0rc1-...whl(6.0 MB) anddyng_cu13-0.2.0rc1-...whl(6.2 MB), the SASS / PTX check of the code objects, and its install tests on GPU 1 (the GPU smoke test, the fallbacks without a device and without a driver,pytest -m gpu55 passed, the suite withDYNG_CPU_ONLY=1543 passed) on 3.12 and 3.13.The
collectjob:twine check --stricton the four files;ci/wheel_check.pyon each (--platform manylinux_2_28_x86_64, and--require-libgompfor the core files), and--release-set --spliton all four (exactly the release’s distributions, one directory each).Installed into fresh Python 3.12.14 and 3.13.15 venvs with no development environment on
PATH(env -i PATH=/usr/bin:/bin), from an empty directory: the core wheel alone, core +dyng-cu12, core +dyng-cu13, andpip install "dyng[cu13]==0.2.0rc1"resolved from a local simple index laid out like TestPyPI (PEP 503 pages with SHA-256 fragments, served on 127.0.0.1) with--extra-index-url https://pypi.org/simple/(NumPy 2.5.3 from PyPI; pip chosedynganddyng-cu130.2.0rc1 from the local index). In each: the wheel’s test subset (cibuildwheel’stest-command: 269 passed / 2 skipped for the core, 268 / 3 with a plugin), the README quickstart (its output equal to the README’s),dyng --version, and with a pluginci/plugin_smoke.py --expect cudaandpytest -m gpuwithDYNG_REQUIRE_CUDA=1on GPU 1 (55 passed). SHA-256 of the files: sdistb68a063e..., CPU wheeldf96e3d8..., cu121a0de18c..., cu13ff465da0...(built locally; the tag’s files are CI’s builds).
Portability pre-checks (at bc1304f, /home/sskg8/Projects/dyng-work/ci-local/README.txt):
the Clang 18 syntax pass over the dev build’s 211 translation units and over the
DYNG_BUILD_PYTHON=ON database’s 228: 0 failures; the cpu-only build with
-D_FORTIFY_SOURCE=3 and the bindings: clean. No C++ changed in this step.
Deviations and decisions (each with the same intent)#
Five mosp readings were taken again; no gate reading comes from the storm window. From about 13:49 CDT (18:49 UTC) on 2026-10-07 a process outside this work triggered the automounting of network shares (about 7,100 CIFS mounts appeared under another user’s
/dfs/<user>/users/), and the mount daemons of every desktop session (udisksd, gvfs-udisks2-volume-monitor, tracker) kept about 15 cores busy. The contamination monitor rejected every round, and the harness stopped the OpenMP reading of road_usa (13:48:56-13:55:09 CDT, 12 rejected rounds, foreign load 2.1-17.9 cores) and the CUDA reading of roadNet-PA at the boost lock (13:55:09-13:57:38, 22 rejected rounds, the foreign load falling from 17.6 to 2.5 cores) without writing their records, as designed. Both were taken again from the same clone and builds after the sssp and cycle_count suites, withparity/bench_suite.py run ... --skip-existinginto the same records directory (16:57-17:42 CDT, a quiet machine, no round rejected).The storm’s window was then checked against every gate reading of the certificate (the start and end of each
perf_ab.pycommand in the logs). Its CPU load ended with the last rejected round at 13:57:38 CDT, but the next three mosp readings, CUDA at the boost lock of roadNet-CA (13:57:38-14:00:57), rgg (14:00:57-14:05:04) and road_usa (14:05:04-14:21:37), ran in its tail: every round passed the monitor (foreign load at most 1.99 cores), but the load average reached 27.0 (rgg) and 29.9 (road_usa) from tasks waiting on the mounts (uninterruptible, not running: the foreign runnable count stayed at 1-2), against 2-6 in the readings after 14:21. So that the certificate holds no reading from the storm or its tail, these three were taken again on 2026-10-08 between 03:59 and 04:25 UTC (22:59-23:25 CDT), from a fresh clone ofafcfb0f(the samelibrarypaths as8bc3e61) built with theparity-cudapreset, under the exclusive lock at the boost lock on GPU 0, with--skip-existingafter the three records were moved out of the records directory (kept in$DYNG_SCRATCH/runs/bench/r020-superseded-storm-tail/); one round was rejected and repeated, the foreign load stayed at or below 1.82 cores. The new readings agree with the replaced ones: compute 0.991-1.005 (before 0.992-1.005), end to end 0.755-0.946 (before 0.778-0.967). The other mosp readings ran before the storm (OpenMP roadNet-PA, roadNet-CA and rgg, 13:41:19-13:48:55, foreign load at most 1.89 cores) or after 14:21 (the default-clock readings, not gated; the memory readings, 14:44-14:48; the two readings of the first paragraph); the sssp and cycle_count suites ran from 14:48 to 16:57 CDT. The summaryipdps25_dynamosp_mosp.jsoncovers the whole suite and names both measured commits. No gate was loosened and--keep-contaminatedwas not used.tsan-openmplocally needs a wrapper of the conda Clang 18. The development machine has no system Clang; the M6b wrapper was in a scratch directory that no longer exists, so a new one (clang++-18 --gcc-install-dir=/usr/lib/gcc/x86_64-linux-gnu/12 --sysroot=/, the rpath of the conda libomp at link time) was written, plus-Wno-error=deprecated-declarations: GCC 12’s libstdc++ callsstd::get_temporary_bufferinstd::stable_sort, which Clang 18 reports as deprecated in C++17 (the same diagnosticclang_syntax.pyfilters; the hosted job uses Ubuntu’s Clang with its own libstdc++). Archer is the conda env’slibarcher.so.bak, passed withDYNG_ARCHER_LIBRARY. Nothing of the sanitizer configuration changed; the hostedtsan-openmp / clang-18job, a required check, remains the reference.The checks ran from several clones of the same commits (one per stream: the sanitizers; the mutation CTests, the pre-checks and the distributions;
ci/check.sh --parity;ci/gpu_local.sh), concurrently under the shared lock and never during a timing run.ci/check.sh --parityandci/gpu_local.shran atbc1304f, atdc2979e, atafcfb0f(after deviation 6) and, after the readings of deviation 1, again at this commit, whose runs are the records of the certificate; the counts above are the same in each run.A missing final newline. The first
ci/check.sh --parityatbc1304ffailed only its pre-commit step:parity/results/R020-investigations.json(step 3) had no newline at its end (theend-of-file-fixerhook). This commit adds it; no other file changed between the two runs but this retrospective. Beyondci/check.sh,ci/api_check.sh 8b9067e(againstmainas pushed; a clone’sorigin/mainis the development repository’s stale localmain) reports no breaking change.The golden mutations are recorded as one check,
mutation-goldens(all eleven mutations of the three suites; 0.1.0 recordedmutation-sssp).Two lint hooks exempt the certificate’s generated files. Staging
benchmarks/results/0.2.0rc1/showed two pre-commit failures thatci/check.sh(which lints the tracked files) could not see before the directory was committed:parity.jsonis 314 KB, above the 256 KB ofcheck-added-large-files(0.1.0rc1’s was 245 KB; 0.2 adds mosp’s suite, themosp_scaledigests and more replay configurations), andcodespellflags fragments of wrapped tool output (paths cut mid-word by twine’s line wrapping) in the verbatim log excerpts underchecks/..pre-commit-config.yamlnow excludesbenchmarks/results/<version>/parity.jsonfrom the size hook andbenchmarks/results/<version>/checks/from codespell; both are generated, committed as evidence and never edited, and every other file keeps both hooks. The file is not in the sdist, so the distributions are unchanged; therepochecks ran again. (At6c53336,ci/check.shfailed once more, on codespell: this paragraph quoted such a fragment. It was reworded, the run ofci/gpu_local.shat6c53336was stopped, and both ran at this commit.)
Step 5: the review fixes and the certificate measured again (review-fixes)#
Independent reviewers checked the branch at f90fb9c through four lenses (release readiness, the
certificate, the mosp API freeze, packaging and the user’s view); a skeptic confirmed 26
findings (two of them the same gap, the packaging scope, seen by two lenses). All are fixed;
of the two that needed the author, layout control was decided on 2026-10-08 and the PyPI names
of the plugins are an open decision in GOVERNANCE.md (below). One finding, the
end-to-end slowdown since 0.1.0, led to a library change (the CSR-triplet reader), so the whole
certificate was measured again on the final code; the orchestrator wanted the release
candidate’s code to be the final 0.2.0’s.
What changed#
Commit |
What (the findings it fixes) |
|---|---|
|
|
|
|
|
Both suite files and |
|
|
|
The C++ freeze is enforced: |
|
|
|
|
|
The |
|
The getting-started next steps name mosp and the tutorial algorithms; the wheel’s test subset runs |
|
|
|
The release candidate’s plugin install from TestPyPI alone ( |
|
CHANGELOG: the section “R020: review fixes”; the release date 2026-10-08 (CHANGELOG heading, |
|
GOVERNANCE.md: the author’s decision on layout control (2026-10-08, below) in the approvals log; the open decision removed |
|
|
(this commit) |
This section; the first certificate’s section marked superseded |
(the next commit) |
The certificate |
The findings and where each went#
# |
Finding (lens) |
Fixed in |
|---|---|---|
1 |
The tag-day procedure forgot the |
|
2, 14 |
The |
|
3 |
Step 3’s hosted |
|
4 |
The layout-control decision was due before |
the author decided it on 2026-10-08; |
5 |
Stale orchestrator items (27 required checks; ADR 0035 vs M8) (release readiness) |
this section: both settled (the ruleset changed on 2026-10-07; M8’s ADRs start at 0036) |
6 |
The final-release checklist named 0.1’s texts (release readiness) |
|
7 |
Step 8’s CPU smoke test without mosp (release readiness) |
|
8 |
The roadmap said the author merged 0.1.0’s pull requests (release readiness) |
|
9 |
|
|
10 |
|
|
11 |
|
|
12 |
dynG’s end to end 5-12 % slower than 0.1.0, unreported (certificate) |
|
13 |
rgg’s unsafe50k batch equals safe50k (certificate) |
|
15 |
The README’s “CUDA (fused)” for |
|
16 |
|
|
17 |
The C++ freeze was only a label (mosp freeze) |
|
18 |
The Python freeze test skipped itself in CI (mosp freeze) |
|
19 |
|
|
20 |
Python determinism and |
|
21 |
|
|
22 |
The PyPI description without mosp (packaging) |
|
23 |
The rc’s install mixed TestPyPI with PyPI for unregistered plugin names (packaging) |
|
24 |
|
|
25 |
The CLI’s |
|
26 |
Getting-started next steps and the wheel’s test comment (packaging) |
|
The end-to-end slowdown since 0.1.0 (finding 12)#
Attribution. A dynG-against-dynG stage A/B (the dyng-compat-mosp report lines and profiler
stages of 0.1.0’s d13d393 against 5a6cd66, sssp OpenMP 28 threads, safe50k, 11 alternated runs
per side under the exclusive lock; $DYNG_SCRATCH/runs/r020-e2e-ab/stages.log) put all of the
growth in one stage, [graph] read graph, the call of io::read_csr_triplet(): rgg +104.2 ms of
+103.7 ms end to end (583.2 -> 687.4 ms), roadNet-PA +27.5 of +19.4; every compute stage, the
batch read and the tree import were unchanged (within 1-7 ms, mostly faster). The ADR 0024 reading
over 17 heap layouts (sssp-openmp-<graph>.json there) agrees: end to end 1.109-1.116 (rgg) and
1.070-1.079 (roadNet-PA) for 0.99-1.01 on sosp_total.
The reviewer suspected 0b388f7 (M6b’s bounded reservations: read_values counts the line
breaks of the whole Values file). That count runs only on the strict path, which re-reads a file
after an error: the concurrent pass, the only one a valid graph takes, already counted them in
0.1.0 (its m is unknown there). The success path of the reader is the same source in 0.1.0 and
here (git diff d13d393 -- cpp/src/io/csr_triplet.cpp cpp/src/util touches only the strict
path), so the 18 % was, most likely, code placement: the text reader’s hot loop moved with the library around it (the
layout sensitivity of the text readers that investigation (b) measured, about 20 % from function
and branch alignment on this CPU, which has the JCC erratum). Reading the three files alone (a
driver linking each build’s libdyng.so, 15 alternated reads each, exclusive lock): 0.1.0 578 ms,
5a6cd66 692 ms on rgg; 143 and 167 ms on roadNet-PA. The Values file (121 MB, 41 million weights)
is the critical one of the three concurrent reads (about 525 of the 530 ms on rgg).
The fix (b1a4869). text_scanner::next_number() / next_number_in_line() scan a token and,
when it is 1 to 18 decimal digits, return its value on the way, so its characters are read once;
detail::integer_value() applies the range check and sends every other token (a sign, 19 or more
digits, anything else) through parse_integer(), so values, error messages and locations are
exactly the general path’s (pinned by TextScanner.NumbersMatchTheGeneralPath over the token
classes and three ranges). read_values() writes each weight straight into its objective’s
column once the columns exist, instead of through a per-line vector. Nothing of the fuzz guards
changed: the reservations stay bounded by what the file can hold (0b388f7), the strict re-read
on any error is the same, the corpus and regression replays pass (ctest -L fuzz, 14 tests), and
ci/fuzz.sh --time 120 csr_triplet ran 37,318 inputs without a finding. Matrix Market, the batch
and result readers keep the general path (they are not on a gated path; the edge-list reader of
cycle_count has its own parser, unchanged since 0.1.0, whose static_read moved -2 % to +4.5 %
between the two certificates).
Before and after (the reader alone, as above): rgg 578 ms (0.1.0) / 692 ms (5a6cd66) /
507 ms (b1a4869), roadNet-PA 143 / 167 / 121 ms. End to end, the stage A/B of 0.1.0
against b1a4869 ($DYNG_SCRATCH/runs/r020-e2e-ab2/stages.log): rgg safe50k end to end 1025.1 ms (0.1.0) -> 931.9 ms (-9.1 %), of
which [graph] read graph 625.6 -> 527.5 ms; roadNet-PA 312.2 -> 289.7 ms (-7.2 %), read graph
133.7 -> 116.0 ms (11 alternated runs per side, medians; the other stages as before). In the certificate, against 0.1.0’s certificate (the same suite file and protocol),
over the 24 sssp end-to-end readings, dynG’s median moved -3.6 % (from -11.8 to -1.0 %; the first
certificate of 0.2.0rc1: +8.2 %, from +5.3 to +12.2 %), the originals’ -0.6 % (first certificate:
-3.4 %), and the stages the fix does not touch did not move (sosp_total median 0.0 %, apply
-0.8 %). mosp’s 24 end-to-end readings moved -9.2 % against the first certificate (from -17.8 to
-4.5 %) and -4.4 % against M7’s (from -11.4 to -0.8 %), its compute 0.0 %. Examples (dynG’s
median end to end, ms: 0.1.0’s certificate or M7 / the first certificate / this one):
Reading |
Before (0.1.0 or M7) |
First certificate ( |
This certificate ( |
|---|---|---|---|
sssp OpenMP, rgg safe50k |
967.5 |
1079.9 |
890.4 |
sssp OpenMP, roadNet-PA safe50k |
304.7 |
332.9 |
283.9 |
sssp CUDA, road_usa safe50k |
5203.6 |
5661.7 |
5042.2 |
mosp OpenMP, rgg safe50k (M7) |
1249 |
1359.7 |
1135.8 |
mosp OpenMP, roadNet-CA safe50k (M7) |
807 |
855.7 |
757.6 |
mosp CUDA, rgg safe50k (M7) |
1275 |
1382.4 |
1246.9 |
How the certificate was measured again#
Every measured build comes from a clean git clone of the branch. The gates, the golden replays
and the golden mutations (phase A) ran at b1a4869, the last commit of the library scope
(clone $DYNG_SCRATCH/r020/cert3/clone-a, presets parity and parity-cuda; log
$DYNG_SCRATCH/runs/r020-cert3-phase-a.log, records $DYNG_SCRATCH/runs/bench/0.2.0rc1; the
first certificate’s records moved to runs/bench/0.2.0rc1-cert2). The sanitizer presets, the
mutation CTests, the portability pre-checks and the distributions ran at b1a4869 too, whose
tests and packaging paths are the release’s (runs/r020-cert3-stream1.log,
r020-cert3-stream2.log); ci/check.sh --parity, ci/api_check.sh 8b9067e and
ci/gpu_local.sh (the repo scope) at this commit, the last one outside
benchmarks/results/. The CUDA readings at the boost lock on GPU 0, the memory readings and the
replays on GPU 1; the orchestrator had reset both GPUs’ clocks after the interruption.
One reading taken again. Phase A ran from 12:41 to 20:01 UTC on 2026-10-08. Its first
reading, mosp OpenMP on roadNet-PA, was stopped by the contamination monitor without a record (22
rejected rounds, foreign load 2.0-24.9 cores; the harness stopped at 12:55:53 UTC). It had
waited for the shared lock behind this step’s own checks and, most likely, took the exclusive lock
when their last locked step ended (12:53:36) and then ran alongside the unlocked part of the
distributions check (the installs and test runs in fresh venvs, 12:53:37-12:56:28 UTC, which the
scratch script dist.sh runs outside the lock). The harness
behaved as designed: the reading wrote no record and the suite’s summary failed with “missing”.
The reading was taken again on its own, on a quiet machine, from the same clone and build
(parity/bench_suite.py run benchmarks/paper/ipdps25_dynamosp_mosp.yaml --skip-existing,
20:04-20:06 UTC; log $DYNG_SCRATCH/runs/r020-cert3-rerun-pa.log). No round was rejected and the
foreign load was at most 1.23 cores. The summary then covers the whole suite (60 / 60 gated
regions). Every other reading of the three suites completed in its first run (the few rounds
the monitor rejected were repeated, as the protocol says), and the readings waited for the lock
behind ci/check.sh --parity and ci/gpu_local.sh, which hold the shared lock for their steps.
Golden parity (byte equality with the originals; CUDA on GPU 1; at b1a4869):
Golden set |
Original |
Cases |
Configurations |
Result |
|---|---|---|---|---|
|
MOSP-OpenMP@c352151 (= MOSP-CUDA@e220ee2) |
495 |
sequential, openmp:1 / 4 / 16 / 28 / 56, sequential/int64, openmp:4/int64; cuda, cuda-fused, cuda-operators, cuda/int64, cuda-operators/int64 |
6435 / 6435 equal |
|
CycleEnumeration-GPU@0a976ad |
24 |
sequential, openmp:4, openmp:56 |
72 / 72 equal |
|
CycleEnumeration-GPU@0a976ad |
24 |
cuda, cuda:resident, cuda:int64 |
72 / 72 equal |
|
MOSP-OpenMP@c352151 (= MOSP-CUDA@e220ee2) |
20 |
sequential, openmp:28; cuda (the automatic engine), cuda-operators |
80 / 80 equal |
Performance gates (the protocol of step 4: PLAN 8.6 against the unpatched originals, the exclusive lock, OpenMP sssp and mosp with 28 pinned threads, cycle_count with 56, CUDA on GPU 0 at the boost clock lock, the COLLAB update at the base lock (ADR 0021), the default-clock readings recorded and not gated (ADR 0018), device memory on GPU 1). Every gated region of the three suites is within its gate: 270 / 270 (sssp 132, cycle_count 78, mosp 60). The worst (largest) ratio port / original per algorithm, backend and gate kind:
Algorithm (suite) |
Backend (clocks) |
Compute (gate) |
End to end (gate 1.10) |
Device memory (gate 1.05) |
|---|---|---|---|---|
|
OpenMP |
0.989 (1.05) |
1.036 ( |
- |
CUDA (boost) |
1.007 (1.10, region under 10 ms) |
0.947 |
0.858 |
|
CUDA (default clocks, not gated) |
1.061 |
0.960 |
- |
|
|
OpenMP (incl. COLLAB) |
0.951 (1.05) |
0.814 |
- |
CUDA (boost) |
1.004 (1.05) |
0.980 |
1.001 |
|
CUDA (base, COLLAB update) |
0.999 (1.05) |
0.987 |
- |
|
CUDA (default clocks, not gated) |
1.003 |
0.980 |
- |
|
|
OpenMP |
0.927 (1.05) |
0.859 |
- |
CUDA (boost) |
1.005 (1.05) |
0.880 |
1.019 |
|
CUDA (default clocks, not gated) |
1.008 |
0.895 |
- |
The best ratios: sssp 0.643 (CUDA, apply of roadNet-PA local10k), cycle_count 0.220 (CUDA, the
steady update chain of twitch), mosp 0.597 (OpenMP compute, roadNet-PA local10k). No accepted
reading has a flagged or kept contaminated round, and every record’s inputs were hashed by the
harness when its measurement started and equal the suite’s digests. The one default-clock region
above 1.05 (sssp CUDA, road_usa local10k, objective 0, 1.061) is the DVFS effect of ADR 0018, as
in the first certificate.
Checks (checks.json):
Check |
Scope |
Commit |
Result |
|---|---|---|---|
|
library |
|
the control passes; 11 / 11 mutations detected (as in step 4) |
|
tests |
|
839 / 839 tests passed, no report |
|
tests |
|
745 / 745 |
|
tests |
|
839 / 839 |
|
tests |
|
7 / 7 |
|
packaging |
|
passed: the four files, 12 venvs |
|
repo |
this commit |
every step passed (counts in |
|
repo |
this commit |
no breaking change |
|
repo |
this commit |
every step passed, compute-sanitizer memcheck, synccheck (sssp, mosp, cycle_count) and racecheck with 0 errors |
The portability pre-checks at b1a4869: the Clang 18 syntax pass over the dev build’s 211
translation units and the bindings’ 228, 0 failures; the cpu-only build with
-D_FORTIFY_SOURCE=3 and the bindings, clean. The distributions are those of step 4 rebuilt at
b1a4869 (the same toolkits, still the newest 12.x and 13.x of NVIDIA’s RHEL 8 repository on
2026-10-08: $DYNG_SCRATCH/runs/r020-cert2-toolkits.log, verify --signatures passed); in the
fresh 3.12 and 3.13 venvs, the wheel’s test subset (now with test_mosp.py) 295 passed / 2
skipped for the core and 294 / 3 with a plugin, pytest -m gpu 55 passed, and the documented form
(NumPy from PyPI, then --no-deps dyng and dyng-cu13 from the local index) passed
ci/plugin_smoke.py --expect cuda.
Deviations and decisions (each with the same intent)#
A library change after the review. The review found the end-to-end slowdown on a stable path; the orchestrator asked for it to be fixed in this step so that 0.2.0rc1’s code is the final 0.2.0’s. The change is confined to the CSR-triplet reader’s success path and keeps its values and errors (above); every part of the certificate was measured again on it.
The hosted
sanitizers.ymljobs are not in this certificate (release step 3 from 0.2.0). They exist only once the release pull request is open, and the branch has never been pushed; the localasan,tsanandtsan-openmprecords are in it (the last with the local Clang 18 wrapper of step 4’s deviation 2, so the hostedtsan-openmp / clang-18job stays the reference). The hosted jobs of the pull request’s head are recorded withparity/certify.py check --name sanitizers-hostedin a later commit of that pull request or in the tag-day pull request (release.mdstep 3; readiness item 3). They are required checks ofmain, so the pull request cannot be merged without them green.The clones’
origin/mainis main as pushed. A clone of the development repository has the repository’s stale localmain(7d64c90, before M6b) asorigin/main; since878c122,ci/check.sh’s API step also compares the frozen C++ API with it, and a provisional run atb1a4869failed there only: against7d64c90the enumeratortutorialof M6b is new. Every check clone of this step therefore setsorigin/mainto8b9067e(git update-ref, the merge of M6b as pushed) before it runs; nothing in the repository changed, and against8b9067eneither the C++ nor the Python API has a breaking change.The informational driver. The before/after reading of the reader alone uses a small driver in the scratch directory linking each build’s
libdyng.so(not a harness of the repository); it is informational, as finding 12 is, and no gate rests on it.One gate reading was taken again (mosp OpenMP roadNet-PA, above): the contamination monitor stopped it while this step’s own distributions check ran its unlocked venv tests. It was taken again alone with
--skip-existingfrom the same clone and build; no gate was loosened and--keep-contaminatedwas not used. Future check scripts should keep their venv test runs under the shared lock too (open item 7 below).
Results#
The review’s 26 confirmed findings are fixed (the table above); the one that needs the author
is open item 1 below. The second certificate of 0.2.0rc1 (benchmarks/results/0.2.0rc1/, the
next commit) replaces the first: every part was measured at b1a4869 (the library, tests and packaging
scopes) or at this commit (the repo scope), all on the final code, and parity/certify.py write
and check pass with the new required checks and the recomputed gates. Golden parity holds on
every backend (sssp 6435 / 6435, cycle_count 72 + 72, mosp_scale 80 / 80), the 270 gated regions
are within their gates, all eleven golden mutations and the seven mutation CTests are detected,
and the sanitizer presets, the distributions, ci/check.sh --parity, ci/api_check.sh and
ci/gpu_local.sh pass. dynG’s end-to-end readings are faster than 0.1.0’s again (sssp median
-3.6 %), so the slowdown of finding 12 is gone; compute is unchanged.
Readiness of 0.2.0rc1#
Ready for the release-candidate tag, from the AI assistant’s side. Done: criteria 1-7 of the
milestone (steps 1-5) and every confirmed review finding; the certificate states all parts
passed (parity.json, written on the committed tree). Waiting for the author or the
orchestrator, in order:
The orchestrator pushes
release-0.2.0and opens the release pull request; hosted CI runs its 27 required checks (the three sanitizer jobs among them since 2026-10-07, GOVERNANCE.md).wheels.ymlwith the plugins (manual dispatch on the pull request’s head, or its run on the pull request, which touchesVERSION) is green before the author merges, and the run is linked in the pull request (release.mdstep 4; not a required check, and the only build of the published setup).The three hosted
sanitizers.ymljobs of the pull request’s head are recorded withparity/certify.py check --name sanitizers-hosted --scope tests --commit <head> --result passed --evidence <the job logs>andparity.jsonwritten again, in a later commit of the pull request or in the tag-day pull request (deviation 2).The author merges the pull request (2026-10-07).
The tag day: the tag is pushed on 2026-10-09 (UTC), so the CHANGELOG heading and
CITATION.cffwere moved from 2026-10-08 to the tag day on this branch, before the release pull request (A tag day later than the final-release pull request;cebbd29), anddistributions,check-parity,api-check(against8b9067e) andgpu_localwere run again from fresh clones of the next commit and recorded, thenparity/certify.py write --version 0.2.0rc1(the last commit of the branch).The AI assistant tags
v0.2.0rc1on the merge commit and pushes it (2026-10-06);release.ymluploads the plugins to TestPyPI (testpypi-cu12,testpypi-cu13), thendyng(testpypi); a pre-release stops there. Then step 8: the smoke install from TestPyPI (mosp included), both plugins as CI built them on the GPU machine (The GPU tests of the published plugin wheels (steps 8 and 10)).The GitHub pre-release (the AI assistant, 2026-10-06).
Open items#
For the author:
The open decision on the PyPI names
dyng-cu12/dyng-cu13(GOVERNANCE.md, R020 review): reserve them, for example with a 0.0.1 placeholder throughpypi-cu12/pypi-cu13, before the release candidate is announced, or accept the window until 0.2.0. Until then the documented commands install the plugins from TestPyPI alone.Zenodo is still not connected, so 0.2.0 gets no DOI unless it is connected before the GitHub Release of 0.2.0 (
release.mdstep 12).
For the orchestrator:
The record of the hosted sanitizer jobs (readiness item 3); the tag date and its re-run checks are done (item 5).
The local branch
mainof the development clone is stale (7d64c90);ci/api_check.shwas run against8b9067e(main as pushed) and is unaffected.Settled since step 4 (no action): the
mainruleset requires the 27 checks since 2026-10-07 (GOVERNANCE.md), and M8 (m8a-escher-store) numbers its ADRs from 0036, so ADR 0035 does not clash.
Carried over, not blocking 0.2.0rc1:
update_traits::make_participantof sssp, cycle_count and mosp readscontainer_t::edge_type(detail, not public API); M8b’s hypergraph composition must say why it does not compile (ADR 0035).The
io/*,testing/*andgenerators/*headers that hold the mosp helpers are stilltracked, not frozen (their review, deferred by ADR 0023, is not recorded yet).A future wheel-vs-parity row of a short host-heavy update should be read over several processes per side (step 3, investigation (a)).
The local
tsan-openmptoolchain (step 4, deviation 2) could be made reproducible, for example aci/local_clang.shwrapper or a conda environment file with a matching libstdc++; the hosted job is unaffected.The text readers stay sensitive to code placement on this CPU (finding 12, investigation (b)): the 0.3 performance option of the author’s decision would address it; the reader’s remaining cost on rgg is about half reading and allocating (the whole-file read zero-fills its buffer, the line count), half parsing.
ci/api_snapshot.py --againstcompares declarations line by line, so an enumerator appended to a frozen enumeration (maturity_level::tutorialof M6b, seen against the stale7d64c90, deviation 3) counts as a breaking change; it fails safe (theapi-changelabel and a CHANGELOG entry pass it), but 0.3’s new maturity or backend values will need the label until the tool treats an appended enumerator as an addition.The local distribution check of this step (a scratch script modelled on
release.md, “Building a release locally”) ran its venv installs and tests outside the shared perf lock, so it could overlap a timing run (deviation 5 of step 5); the monitor caught it. A repository script for the local release build (for exampleci/release_local.sh) should run every heavy step underflock -s.