Retrospective: R021 (the final-release pull request for 0.2.0)#

Status: done on the branch release-0.2.0-final, ready for the final-release pull request (from main at 406c3a6, the merge of pull request #11 with R020; the signed tag v0.2.0rc1 points there). Release 0.2.0 is M7 + M6 (PLAN Appendix F); its release candidate 0.2.0rc1 is on TestPyPI since 2026-10-09 (dyng, dyng-cu12 and dyng-cu13, release.yml run 37871452302, attempt 3). R021 smoke-tests that release candidate from TestPyPI (docs/developer/release.md step 8), makes the final-release pull request (step 9: the version, the CHANGELOG, CITATION.cff, the status texts, the approvals log) and carries the release candidate’s certificate over to benchmarks/results/0.2.0/. Nothing is published by this milestone: the orchestrator pushes the branch; the author merges the pull request; the AI assistant pushes the signed tag v0.2.0 and creates the GitHub Release on the author’s behalf (GOVERNANCE.md, 2026-10-06); the author approves the PyPI deployments pypi-cu12 and pypi-cu13 (one dialog), then pypi. The planned tag day is 2026-10-14 (UTC).

Step 1: the smoke test of 0.2.0rc1 from TestPyPI (rc-smoke)#

Result: passed, everywhere; nothing to fix. No defect of the code or of the documentation was found, so the library and the release candidate’s documents are unchanged by this step and the certificate can still be carried over (step 9 of release.md).

How it was run#

On 2026-10-13 between 19:28 and 19:37 UTC, on the GPU machine (Debian 12, glibc 2.36, Linux 6.1; 2x RTX A5000, NVIDIA driver 590.48.01, which reports CUDA 13.1; the GPU tests on GPU 1), under the shared lock of perf.lock. Every venv is new, made by python -m venv from a plain interpreter (CPython 3.12.14 of the conda environment dyng-dev, used only as the base interpreter, and CPython 3.13.15 of $DYNG_SCRATCH/tools/py313), and every command ran in a clean environment (env -i with PATH=/usr/local/bin:/usr/bin:/bin, HOME, LANG=C.UTF-8, OMP_NUM_THREADS=4, PIP_NO_CACHE_DIR=1): no dev environment, no CUDA toolkit and no build of dynG on PATH, and no pip cache, so every file came from the indexes. The venvs’ own pip was used (25.0.1 on 3.12, 26.2.1 on 3.13), as a user’s would be. The install commands are exactly those of the release candidate’s README.md and docs/getting_started/install.md, and the tests and ci/ scripts are those of the tag v0.2.0rc1 (git archive v0.2.0rc1; the tag is 406c3a6, this branch’s start).

The scripts and logs are in the work area (not committed; the logs go into the certificate, step 3 of this milestone):

File

sha256

$DYNG_SCRATCH/r021/run_smoke.sh (cpu, plugin cu12 / cu13, extras cu12 / cu13)

6fd3bf57e2e5ffdd...

$DYNG_SCRATCH/r021/smoke_rc_cpu.py (the CPU package’s smoke test)

5cbc35ca48d444df...

$DYNG_SCRATCH/r021/step10_dryrun.sh (the step-10 block of release.md, on this run’s artifact)

5d3a0c75247a4d2b...

$DYNG_SCRATCH/runs/r021/smoke-rc1.log (the five logs below, concatenated)

df229fd3aba73104...

smoke-cpu.log, smoke-cu12.log, smoke-cu13.log, smoke-extras-cu12.log, smoke-extras-cu13.log

865e51b6..., 465b497b..., f2a9e977..., 2e35e81e..., 432f2072...

$DYNG_SCRATCH/runs/r021/step10-dryrun.log

946e63b680fb9028...

The files tested are the ones the release candidate published. pip’s installation reports (pip install --report) name the same three wheels in every venv, and their sha256 equal both the digests TestPyPI serves (https://test.pypi.org/pypi/<name>/0.2.0rc1/json, uploaded 2026-10-09 02:24-02:25 UTC) and the files of the dist artifact of run 37871452302 (gh run download, read-only):

File (TestPyPI, uploaded by release.yml run 37871452302)

sha256

dyng-0.2.0rc1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (1.48 MB)

b24495fe91b041ee3d195bd9d2aaec50cfaa94225c455ee4a15b798a98913158

dyng_cu12-0.2.0rc1-cp312-abi3-manylinux_2_28_x86_64.whl (5.59 MB)

431b5f93a3b9fb87ea64a9d50ba51f179030f0fbd2648d83fb8b9aef0cb23e43

dyng_cu13-0.2.0rc1-cp312-abi3-manylinux_2_28_x86_64.whl (5.84 MB)

9119bb17e479d7af546891ace1763ba3c1439213e17caf97f9944602a0b927a3

dyng-0.2.0rc1.tar.gz (on TestPyPI and in the artifact; not installed by the documented commands)

7ca8033646558c6a4bd9c9c07bf7a2d3873b382ff7f055a5fb92f20fab0d592f

At the same time pypi.org has dyng 0.1.0 as its latest release and no project dyng-cu12 or dyng-cu13 (HTTP 404), as the author’s decision of 2026-10-09 expects (the names are created by the final release).

The CPU package dyng==0.2.0rc1 (release.md step 8)#

In fresh venvs on Python 3.12 and 3.13, the command of the README and of install.md:

pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ dyng==0.2.0rc1

It installed dyng 0.2.0rc1 (the wheel above, from TestPyPI) and NumPy 2.5.4 (from PyPI), and nothing else. Then, from an empty directory:

Check

3.12.14

3.13.15

import dyng (from the venv’s site-packages), dyng.__version__

0.2.0rc1

0.2.0rc1

dyng.show_config()

dyng._core, “no CUDA plugin is installed”; sequential and openmp yes, cuda no (the CPU wheel, with the plugins’ install line); built with GNU 14.2.1, Release

the same

installed metadata

License-Expression: Apache-2.0 AND BSD-3-Clause AND MIT AND GPL-3.0-or-later WITH GCC-exception-3.1 (the CPU wheel’s expression, unchanged, as decided on 2026-10-06); Requires-Dist numpy>=1.26 and the extras cu12 / cu13 = dyng-cu12==0.2.0rc1 / dyng-cu13==0.2.0rc1

the same

sssp compute and update, on sequential and on openmp

five vertices: [0, 4, 1, 5, 7], after inserting 0->3 [0, 4, 1, 1, 3]

the same

cycle_count compute and update (max_length=4), both backends

histogram [0, 0, 1, 1, 0] (2 cycles), after deleting 2->3 [0, 0, 0, 1, 0] (1)

the same

mosp compute and update (the thesis example of dyng.mosp’s docstring, Pref (4, 1, 4)), both backends

path cost of u7 [10, 12, 10], after the batch [15, 3, 20] (the documented value); MOSP tree [-1, 0, 0, 1, 3, 4, 4] -> [-1, 0, 0, 2, 3, 1, 5]

the same

sequential == openmp

every array above equal

equal

dyng.citation("mosp")

the BibTeX entries

the same

dyng --version

dyng 0.2.0rc1

the same

a CLI command: dyng mosp compute --graph thesis.txt --format edges --num-weights 3 --preferences 4,1,4 --backend sequential (and openmp) --out DIR

both output trees byte-identical (diff -r); combinedGraph/mospCosts.txt gives 6 10 12 10, the Python compute’s value; dyng config prints the configuration

the same

beyond step 8: the release candidate’s python/tests against the installed wheel (pytest 9.1.1, hypothesis 6.167.1 from PyPI)

546 passed, 67 skipped

546 passed, 67 skipped

Every run printed SMOKE OK. The 67 skips are the GPU tests (no plugin installed) and the pandas round trip of the profiler (the venvs do not have pandas).

The plugins dyng[cu12]==0.2.0rc1 and dyng[cu13]==0.2.0rc1 (release.md step 8, “The GPU tests of the published plugin wheels”)#

Per plugin and per Python version (3.12, 3.13) a fresh venv, installed as install.md and the README say for the release candidate (NumPy and the test tools from PyPI first, then dyng and the plugin from TestPyPI alone, --no-deps):

pip install "numpy>=1.26" "pytest>=8" "hypothesis==6.167.1"
pip install -i https://test.pypi.org/simple/ --no-deps "dyng==0.2.0rc1" "dyng-cu13==0.2.0rc1"   # or dyng-cu12

then the checks of ci/plugin_wheels.sh with the tag’s ci/plugin_smoke.py (from an empty directory) and the tag’s python/tests (from the tag’s tree, against the installed packages):

Check

cu12 / 3.12

cu12 / 3.13

cu13 / 3.12

cu13 / 3.13

installed

dyng + dyng-cu12 0.2.0rc1

the same

dyng + dyng-cu13 0.2.0rc1

the same

CUDA_VISIBLE_DEVICES=1 ci/plugin_smoke.py --plugin cuN --expect cuda --version 0.2.0rc1

passed: dyng_cu12 (plugin cu12) chosen (“the driver supports CUDA 13.1; no CUDA 13 plugin is installed, and CUDA 12 runs on it”), toolkit 12.9, static runtime, architectures 75-real to 100-real, 120; sssp (both engines), cycle_count and mosp on cuda equal the sequential backend

passed (the same)

passed: dyng_cu13 (plugin cu13) chosen (“it matches the driver’s CUDA major (CUDA 13.1)”), toolkit 13.4, the same architectures

passed (the same)

the fallback without a device: CUDA_VISIBLE_DEVICES= ... --expect fallback --reason "no CUDA device is visible"

passed: one dyng.BackendWarning, dyng._core, the plugin’s module imports and reports its build, Resources.cuda() raises NotSupportedError naming the plugin and the reason

passed

passed

passed

the fallback without a driver: ci/without_cuda_driver.sh ... --expect fallback --reason "no CUDA driver" (user namespaces are available here)

passed: “no CUDA driver: libcuda.so.1 cannot be loaded (install the NVIDIA driver, 525.60.13 or newer for CUDA 12)”

passed

passed (”… 580.65.06 or newer for CUDA 13”)

passed

CUDA_VISIBLE_DEVICES=1 DYNG_REQUIRE_CUDA=1 pytest -m gpu python/tests

55 passed, 7 skipped

55 passed, 7 skipped

55 passed, 7 skipped

55 passed, 7 skipped

DYNG_CPU_ONLY=1 pytest python/tests (plugin installed)

546 passed, 67 skipped

546 passed, 67 skipped

546 passed, 67 skipped

546 passed, 67 skipped

The 7 skips of the GPU tests are the DLPack / CUDA array interface round trips with CuPy and PyTorch, which these venvs do not install (the optional extras; ci/plugin_wheels.sh runs them only with DYNG_PLUGIN_INTEROP_PYTHON; M6a recorded them on the local wheels).

The extras, as the final release will document them. In two more fresh venvs per plugin (3.12, 3.13): NumPy from PyPI, then pip install -i https://test.pypi.org/simple/ "dyng[cu13]==0.2.0rc1" (and [cu12]), from TestPyPI alone. The extra resolved to the plugin of the same version (dyng + dyng-cuN 0.2.0rc1, the files above), and ci/plugin_smoke.py --expect cuda passed on GPU 1 in all four. This is the install line of the final release (pip install "dyng[cu13]", PyPI then), checked here on the release candidate’s metadata.

The step-10 procedure, rehearsed. The block of release.md “The GPU tests of the published plugin wheels” that the AI assistant runs on the final tag’s release.yml run before the author approves pypi-cu12 and pypi-cu13 was run once as written, from a clone of the tag, on the dist artifact of run 37871452302 (with d=$DYNG_SCRATCH/r021/release-0.2.0rc1 instead of $DYNG_SCRATCH/release-0.2.0rc1, to keep this milestone’s files together): twine check and ci/wheel_check.py passed for the core and both plugin wheels, and for both plugins on 3.12 and 3.13 the smoke test on GPU 1, both fallbacks, pytest -m gpu (55 passed, 7 skipped) and the DYNG_CPU_ONLY=1 suite (546 passed, 67 skipped) passed. The procedure works as documented; on the tag day only run= changes.

What it means for the release#

  • Acceptance criterion 1 is met: no blocker for the final release from the release candidate.

  • No code, test or document changed in this step, so the library is still that of 0.2.0rc1’s measured commits and the certificate can be carried over (release.md step 9).

  • release.md step 8 names “a CPU machine and the GPU machine”. As for 0.1.0 (R011), only the GPU machine is available to this milestone; the CPU package’s host backends ran on it, and the plugins’ behaviour on a machine without a GPU or a driver was checked with the two fallbacks above (CUDA_VISIBLE_DEVICES= and ci/without_cuda_driver.sh); the hosted runners of the release run checked the plugins’ install without a GPU too (its “Plugin cuN install test, no GPU” jobs, green).

Step 2: the final-release pull request (final-pr)#

The final-release changes (release.md step 9)#

Every item of docs/developer/release.md, “The final-release pull request (step 9)”, for 0.2.0 with the release date 2026-10-14, the planned tag day:

Item

Commit

What changed

VERSION, CITATION.cff, CHANGELOG.md

f40d7f7

VERSION 0.2.0; CITATION.cff version: 0.2.0, date-released: "2026-10-14" (cffconvert --validate: valid for schema 1.2.0); the section ## [0.2.0rc1] - 2026-10-09 renamed ## [0.2.0] - 2026-10-14 under the empty ## [Unreleased], its summary written for the release with the install lines from PyPI (pip install dyng, pip install "dyng[cu13]", "dyng[cu12]"; no TestPyPI), a “Final release (R021)” subsection, and the link references [Unreleased]: .../compare/v0.2.0...main and [0.2.0]: .../compare/v0.1.0...v0.2.0 (the [0.2.0rc1] reference removed with its heading, as for 0.1.0)

the status texts

e2bd54b

README.md (the status block “Alpha: 0.2.0”, the status table’s rows of mosp and of the Python package, the install lines: the release candidate’s TestPyPI commands removed, pip install "dyng[cu13]" added), docs/index.md (the same block and install lines), docs/getting_started/index.md and install.md (no “until 0.2.0 is released”; pip install dyng names 0.2.0), SECURITY.md (the status paragraph; supported: main and 0.2.x; 0.2.0rc1, 0.1.x and 0.1.0rc1 no longer), CONTRIBUTING.md, SUPPORT.md, docs/citing.md (0.1.0 and 0.2.0 have no DOI: Zenodo is still not connected, step 12), docs/roadmap.md (“Where we are”: 0.2.0 is released, its certificate benchmarks/results/0.2.0/), docs/developer/plan.md (the status line and the 0.2.0 row: done)

GOVERNANCE.md, the publishers

26ed796

the approvals log: the author’s decision of 2026-10-09 on the plugin names (accepted window, no placeholders; the open decision R020 added is closed) and the publishers’ repository state of 2026-10-09; docs/developer/release.md (“If a publish job fails”): the lesson of the release candidate’s failed publish jobs; docs/developer/repository_settings.md (item 11b, section 11): the publishers’ state

the hosted sanitizer record, this page

this page’s commit

docs/developer/release.md step 3: how a carried-over certificate records the release candidate’s hosted sanitizers.yml jobs (deviation 4 below)

python ci/wheel_check.py --release-metadata and --release-metadata --release-date 2026-10-14 pass (VERSION, CHANGELOG.md and CITATION.cff agree on 0.2.0 and the date); --release-date today fails until the tag day, as it should (release.md step 10 runs it on the tag day).

What is left of the release candidate’s wording. grep -rn "0.2.0rc1\|release candidate" over the user-facing files (README.md, docs/index.md, docs/getting_started/, SECURITY.md, CONTRIBUTING.md, SUPPORT.md, docs/citing.md, docs/roadmap.md, docs/developer/plan.md, python/README.md) finds only history that stays true after the release: SECURITY.md’s sentence that the release candidates were published on TestPyPI only and their rows “no; please upgrade to 0.2.0”, its policy that problems found in a release candidate are fixed before the release, the roadmap’s and the plan’s account of 0.1.0rc1 and 0.2.0rc1, and the install guide’s “find_package(dyng 0.2) finds 0.2.x (the release candidates included)”. The documentation site’s banner needs no change: docs/conf.py shows none for a final VERSION. That grep was too narrow: it missed five lines that called 0.2.0’s own content “not released yet” (the maturity lines of four algorithm pages and the tutorial’s “clone main, not the 0.1.0 release”), which the review of the pull request found (step 3 below); step 9 of release.md now names those pages and greps for not released and new in X.Y too.

The publishers’ failures, read back. The record in GOVERNANCE.md and release.md is taken from the run itself (gh run view 37871452302 --attempt N, read-only): attempt 1 (01:47 UTC) failed in both TestPyPI plugin jobs with invalid-publisher (“valid token, but no corresponding publisher”, claims environment: testpypi-cu12 / testpypi-cu13); attempt 2 (02:20 UTC) with “400 Non-user identities cannot create new projects. This was probably caused by successfully using a pending publisher but specifying the project name incorrectly”; attempt 3 (02:24 UTC) ran only the publish jobs (the builds, the install tests and collect keep their attempt-1 start times) and uploaded the plugins, then dyng. That is why the files on TestPyPI are the first attempt’s dist artifact (step 1’s hashes).

The certificate, carried over (release.md step 9)#

The library did not change since the release candidate’s measured commits: git diff from b1a4869 (the gates, the replays, the golden mutations) and from d553b79 (the head of pull request #11, whose hosted sanitizer jobs are recorded with the scope library) to this branch touches no path of the library scope. So benchmarks/results/0.2.0/ is a copy of benchmarks/results/0.2.0rc1/ (cp -r), and the certificate’s measured commits are b1a4869, d553b79 and the commit of the re-run checks below; 616718a, where the release candidate’s re-run checks of its tag day ran, is no longer measured (its records were replaced):

  • Carried over unchanged: the golden replays of sssp, cycle_count and mosp (parity-*.json), the three suite summaries and the compacted records of the performance gates (ipdps25_*, ieee_tc_dyntrucy*), and the golden mutations (mutation-goldens.json, library scope).

  • Run again on the final tree (they see the new VERSION, CHANGELOG.md, CITATION.cff and the status texts), each from its own fresh git clone of this branch at the last commit outside benchmarks/results/ (the last commit of this page; a first run at c3512fe was replaced by a second one after the review fixes of step 3, which changed README.md, CHANGELOG.md, CONTRIBUTING.md and documents), concurrently under the shared lock and never during a timing run, with the scripts of R020’s tag day adapted for the final tag ($DYNG_SCRATCH/r021/cert/), and recorded with parity/certify.py check --version 0.2.0 under their existing names, replacing the release candidate’s records:

    • packaging: distributions: release.yml’s select step for GITHUB_REF_NAME=v0.2.0 (it must report kind=package, version=0.2.0, prerelease=false, plugins=true, the three distributions; v0.2.0rc1 and v0.2.0-rc.1 refused), ci/wheel_check.py --release-metadata (and --release-date 2026-10-14), ci/wheels.sh, ci/plugin_wheels.sh with the pinned CI toolkits (CUDA 12.9 and 13.4), twine check --strict, ci/wheel_check.py on each file and --release-set --split, then fresh Python 3.12 and 3.13 venvs: the core wheel alone and with each plugin (the wheel’s test subset, the README quickstart, dyng --version, and with a plugin ci/plugin_smoke.py --expect cuda and pytest -m gpu on GPU 1), and the final release’s documented install lines, pip install dyng, pip install "dyng[cu13]" and "dyng[cu12]" without a version, against a local simple index holding the three distributions with PyPI as the extra index (PyPI has only dyng 0.1.0 and no plugin yet, so pip must pick 0.2.0 from the local index and the extras must resolve to the plugin of the same version); and, from the second run, the upgrade from 0.1.0 (step 3): a venv with dyng==0.1.0 from PyPI keeps it for pip install "dyng[cuN]" and gets 0.2.0 and the plugin of the same version with pip install -U "dyng[cuN]", whose plugin smoke test then passes on GPU 1 (cu12 and cu13, 3.12 and 3.13).

    • repo: check-parity (ci/check.sh --parity), api-check (ci/api_check.sh 406c3a6, the frozen C++ API and the Python API against main as pushed, the merge of 0.2.0rc1) and gpu_local (ci/gpu_local.sh, every step on GPU 1, the compute-sanitizer steps included).

    • tests (README.md, whose C++ quickstart is a test, changed): asan, tsan, tsan-openmp (ci/sanitizers.sh; tsan-openmp with R020’s local Clang 18 wrapper and Archer, deviation 5) and mutation-ctests (ctest --preset dev-cuda -L mutation, GPU 1).

  • Added: sanitizers-hosted, the hosted sanitizers.yml jobs of the release candidate’s pull request #11 (run 37870378099 on its head d553b79, the event pull_request, which tested the merge 5694faa of d553b79 into 8b9067e, the same tree): asan / gcc-13 839 of 839 tests, tsan / gcc-13 745 of 745, tsan-openmp / clang-18 839 of 839, all green (release step 3), with the job logs (gh run view --log, read-only) as evidence and the scope library (deviation 4).

  • The smoke test of 0.2.0rc1 from TestPyPI (step 1) is described in the certificate’s README.md, its combined log copied to checks/smoke-rc1.txt, as for 0.1.0.

  • Then parity/certify.py write --version 0.2.0 on the committed tree, which checks every record against the final commit in the paths of its scope and writes parity.json naming 0.2.0 and that commit, and the tables of the certificate’s README.md; it is the last commit of the branch.

The results of the re-run checks are in the certificate (benchmarks/results/0.2.0/README.md, its “Checks” table generated from parity.json, every log named with its SHA-256 in checks.json and excerpted under checks/): certify.py write passes only when every part passed on the release’s code, so a certificate on this branch means they did.

Deviations from the plan#

  1. The step-10 rehearsal’s directory (step 1): $DYNG_SCRATCH/r021/release-0.2.0rc1 instead of $DYNG_SCRATCH/release-0.2.0rc1, to keep the milestone’s files together.

  2. One machine for step 8 (step 1): release.md names a CPU machine and the GPU machine; only the GPU machine was available, as in R011. The CPU package’s host backends ran on it, and the plugins without a GPU or a driver were covered by the two fallbacks and by the release run’s hosted install tests without a GPU.

  3. More than the docs ask (step 1): the CPU wheel’s whole pytest suite, the dyng[cuN] extras from TestPyPI, and the step-10 rehearsal. Nothing failed.

  4. The hosted sanitizer record has the scope library. Release step 3 records the hosted sanitizers.yml jobs of the release pull request with --scope tests. Those of pull request #11 tested d553b79, whose tests scope differs from the final tree in README.md (only its status block and install lines; the C++ quickstart is unchanged), so a tests record would be stale and certify.py write would refuse it. The library is the release’s, so the record is made with --scope library, and the tests scope is covered by the local presets run again on the final tree (which are the required checks). release.md step 3 now says so. The hosted jobs of the final-release pull request itself run once it is opened; they can be recorded with --scope tests in the tag-day pull request if one is needed (A tag day later than the final-release pull request).

  5. tsan-openmp locally uses R020’s wrapper of the conda Clang 18.1.8 (--gcc-install-dir GCC 12, --sysroot=/, -Wno-deprecated-declarations) and its libarcher.so.bak (DYNG_ARCHER_LIBRARY), as R020 deviation 2 describes; the hosted tsan-openmp / clang-18 job remains the reference.

  6. The documented install of the final release cannot run against PyPI before the release, so the distributions check runs pip install dyng and pip install "dyng[cuN]" (no version) against a local simple index with the three distributions and PyPI as the extra index, which is what PyPI will serve after the three uploads.

  7. CMakeLists.txt keeps its outdated benchmark text in 0.2.0 (step 3, finding 7). The option DYNG_BUILD_BENCHMARKS is described as “Build nvbench / Google Benchmark / dyng-bench”, a comment says the suites “come in 0.2”, and with the option ON configure warns “has no effect yet (the benchmark suites of 0.2)”. CMakeLists.txt is a path of the library scope (it builds every measured program), so changing even these strings would make the carried-over gates, golden replays and golden mutations stale: certify.py write would refuse them and 0.2.0 would have to be measured again (steps 1-3), for a message that only appears when a user turns the option ON. The text is fixed on main after the release (open items); the user-facing pages (CONTRIBUTING.md, the how-to) already say the suites are planned for a later release.

  8. The micro-benchmark suites and dyng-bench (PLAN 8.5, item 1, and the targets of 4.2) are not in 0.2.0. CMakeLists.txt and CONTRIBUTING.md had placed them in 0.2 and in the 0.1 series; no milestone of 0.1 or 0.2 built them. The release gates run the paper suites benchmarks/paper/*.yaml with parity/bench_suite.py instead of dyng-bench run (as since R010), and the operators’ micro-benchmarks have no replacement. They move to a later release; the roadmap’s next releases do not list them yet.

Step 3: the review of the final-release pull request#

Independent reviewers read the branch at 0512066; each finding was checked by a second reader. All were fixed in this branch, except the CMake part of finding 7 (deviation 7):

#

Finding (severity)

Fix

Commit

1

The maturity lines of sssp, mosp, dynamic_bfs and triangle_delta said their 0.2.0 content was “not released yet”, and the tutorial sent readers to main, “not the 0.1.0 release”; step 9’s list and grep could not find them (medium; found by two reviewers)

the lines say “new in 0.2.0” / “since 0.2.0”; the tutorial works on the v0.2.0 tag and on main; release.md step 9 names the algorithm and tutorial pages and greps for not released, new in X.Y and the previous release’s version; documentation.md says the final-release pull request drops “not released yet”

3e20722, ab88446

2

release.md “Building a release locally” hard-coded v0.2.0rc1, its wheel directories and its select output, so for v0.2.0 it would compare against the wrong values and check the rc’s files (low)

the block reads v=$(cat VERSION), uses wheels/$v and wheels/$v-plugins, and states select’s output for a release candidate (prerelease=true) and a final release (prerelease=false, plugins=true)

3e20722

3

The who-does-what table named only release-0.2.0 for 0.2.0’s release branch (low)

both branches named: release-0.2.0 for 0.2.0rc1, release-0.2.0-final for 0.2.0

3e20722

4

The roadmap’s “Where we are” opened with “0.1.0 is released” (low)

it opens with 0.2.0 (released, how to install and upgrade, the certificate), then 0.1.0 as history

4d906fe

5

The certificate README’s “What was run, and how (measured for 0.2.0rc1)” described check records that 0.2.0’s checks.json no longer holds, without the disclaimer 0.1.0’s README has (medium)

a 0.1.0-style opening paragraph says which of the release candidate’s records were replaced and by what

the certificate commit

6

The certificate README’s “Carried over” paragraph and this page named 616718a as a measured commit and left out d553b79 (low)

both name the measured commits b1a4869, d553b79 and the re-run commit; 616718a only as the commit of the replaced records

this page, the certificate commit

7

CMakeLists.txt (the option DYNG_BUILD_BENCHMARKS and its warning) and CONTRIBUTING.md promised benchmark suites “of 0.2” / “with the 0.1 series” (low)

CONTRIBUTING.md and the how-to “Add an algorithm” (which said the same) say the micro-benchmarks are planned for a later release; CMakeLists.txt deferred (deviation 7)

4d906fe, 75a76a1

8

No upgrade path was documented: with dyng 0.1.0 installed, pip install "dyng[cu13]" keeps 0.1.0 and only warns that it lacks the extra, while 0.1.0’s own error message names exactly that line (medium)

pip install -U "dyng[cu13]" for upgraders in README.md, docs/index.md, the install guide (with the reason), the CHANGELOG summary (the GitHub Release’s text) and the roadmap; release.md step 9 asks for the upgrade line when a release adds extras; the distributions check now runs both lines from a 0.1.0 venv

4d906fe, 3e20722

These changes touch README.md (tests scope), CHANGELOG.md and CONTRIBUTING.md (packaging) and documents (repo), none a path of the library scope, so the certificate still carries over; every check of the tests, packaging and repo scopes was run again from fresh clones of the last commit of this page and recorded in place of the first run’s records, and parity/certify.py write ran again on the committed tree.

Readiness for the tag and the three PyPI approvals#

  1. The orchestrator pushes the branch and opens the final-release pull request (base main, head release-0.2.0-final), with the checklist of release.md. Required checks: the 27 of the main ruleset. Before the author merges it, a manual wheels.yml run with the plugins, green on the pull request’s head commit, is linked in the pull request (release step 4: the only build of the published setup before the tag).

  2. The author merges the pull request (merge commit, ADR 0019).

  3. The tag day, 2026-10-14 (UTC): on the merge commit python ci/wheel_check.py --release-metadata --release-date today passes; if the tag falls on a later day, the dates move in a small pull request first and the packaging and repo checks run again (A tag day later than the final-release pull request). The AI assistant pushes the signed tag v0.2.0 on the author’s behalf (git tag -s -m "dynG 0.2.0" v0.2.0, GOVERNANCE.md 2026-10-06).

  4. release.yml builds the distributions anew from the tag (select: a final release, plugins on), uploads dyng-cu12 and dyng-cu13 to TestPyPI (the projects exist there since 0.2.0rc1, so their publishers are ordinary ones now), then dyng, then waits for pypi-cu12 and pypi-cu13.

  5. Before the author approves pypi-cu12 and pypi-cu13, the AI assistant runs release.md’s “The GPU tests of the published plugin wheels” on that run’s dist artifact on GPU 1 (step 1 rehearsed it: only run= changes) and reports the result.

  6. The author approves pypi-cu12 and pypi-cu13 in one dialog; their uploads create the projects dyng-cu12 and dyng-cu13 on PyPI through the pending publishers the author checked on 2026-10-09. If a plugin job fails, the publisher is fixed on PyPI and the failed jobs of the same run are re-run (release.md, “If a publish job fails”); dyng stays unpublished until both plugins are there. Then the author approves pypi.

  7. The AI assistant creates the GitHub Release v0.2.0 from the CHANGELOG section with the run’s distributions (step 11); no DOI (step 12: Zenodo is not connected). After the merge the deploy job of docs.yml publishes the site without the banner.

  8. Step 14: the announcement, the next .dev0 (0.3.0.dev0) in main.

Open items#

For the author:

  1. Merge the final-release pull request once it is green (and the wheels.yml run is linked).

  2. The three PyPI approvals of the v0.2.0 run: pypi-cu12 and pypi-cu13 together, then pypi, after the GPU tests of the run’s plugin wheels are reported green.

  3. Optional, not due for 0.2.0: connecting Zenodo before the GitHub Release would give 0.2.0 a DOI (checkpoint A4); otherwise 0.2.0 has none, as docs/citing.md now says.

For the orchestrator:

  1. Push the branch, open the pull request, dispatch wheels.yml (with the plugins) on its head and link the run; report to the author when it is green and ready to merge.

  2. The tag day: if the tag is not pushed on 2026-10-14 (UTC), release.md’s late-tag-day procedure applies before the tag.

  3. Optional: the hosted sanitizers.yml jobs of this pull request can be recorded with --scope tests in a tag-day pull request (deviation 4); the certificate does not require them.

  4. main of the development repository should follow origin/main (406c3a6); the API check of this milestone was run against 406c3a6 explicitly.

  5. After the release, on main (0.3.0.dev0): bring CMakeLists.txt’s DYNG_BUILD_BENCHMARKS description, comment and warning to “planned for a later release” (deviation 7), and give the micro-benchmark suites and dyng-bench a release in docs/roadmap.md and docs/developer/plan.md (deviation 8).