Retrospective: R010 (release preparation for 0.1.0rc1)#

Status: ready for the release candidate on the branch release-0.1.0 (from main at 8842acf, the merge of pull request #4 with M5). Nothing is published by this milestone: the tags v0.1.0rc1 / v0.1.0 and the GitHub Release are pushed by the orchestrator afterwards (the author’s decision of 2026-09-30, GOVERNANCE.md), and the PyPI deployment waits for the author’s approval. R010 was done in two steps, each by one implementer, and a third step fixed what the independent review found; each step has its section here.

Step 1: the benchmark-suite records and the release certificate (suites-cert)#

What was built#

Item

Where

The benchmark-suite records of PLAN 8.5 for 0.1’s algorithms

benchmarks/paper/ieee_tc_dyntrucy.yaml (cycle_count), benchmarks/paper/ipdps25_dynamosp_sosp.yaml (sssp)

The suite runner: validate, plan, run, summarize

parity/bench_suite.py

The parity certificate of PLAN 8.3 and the release checks

parity/certify.py (check, write)

The golden mutation checks of PLAN 8.4 for sssp

parity/mutate.py

Tests (no builds, GPUs or originals needed; run by ci/check.sh and lint.yml)

parity/tests/test_release_cert.py

Documentation

benchmarks/README.md = docs/developer/benchmarks.md; docs/developer/release.md steps 1-3; parity/README.md

The release gate, the golden replays and the checks on the release code

benchmarks/results/0.1.0rc1/ (parity.json, README.md, the suite summaries, the compacted records, checks.json)

Deviations from the plan (each with the same intent)#

  1. The sssp suite is ipdps25_dynamosp_sosp.yaml, not ipdps25_dynamosp.yaml. The DynaMOSP papers time the K = 3 per-objective updates plus the combined-graph step (mosp, 0.2); 0.1 ports the per-objective update (sssp), which the 0.1 gate compares per objective (PLAN 6.4.2). A suite named after the paper but timing only part of its pipeline would be misleading, so the 0.1 suite carries the _sosp suffix and says what it measures; the mosp-level suites ipdps25_dynamosp.yaml and tpds25_dynamosp.yaml come with mosp.

  2. The runner is parity/bench_suite.py over the A/B harness, not dyng-bench run. PLAN 8.5 item 2 describes an end-to-end driver through the public API (tools/bench, benchmarks/run.py); the gates of PLAN 8.6 are read by the existing A/B harness through the compat tools, which is what a release must certify. The suite format is the one of PLAN 8.5’s example (datasets, batches with seeds, backends, runs, metrics mapped to profiler stages, baselines, tolerances), extended with readings (which harness invocation, which clock lock, gated or recorded) and validated against the harness. A public-API driver can read the same files later.

  3. No benchmarks/baselines/<repo>-<commit>.json files. The original’s medians are in every compacted record and in the suite summaries (original_ms, original_mib), next to the hardware and the commit; a separate copy would duplicate them.

  4. The temporal datasets of DynTruCy (CollegeMsg, email-Eu-core, time-window mode) are listed under deferred in the suite and not run: the time-window and temporal modes arrive in 0.4.

  5. parity/mutate.py covers the sssp mutation only. The cycle_count mutations of PLAN 8.4 are CTests since M2a (ctest -L mutation, host and CUDA); mutate.py adds the one that needs the golden corpus (a skipped subtree invalidation, on each backend). The triad, label propagation and MOSP_ESCHER mutations come with their ports.

Results (benchmarks/results/0.1.0rc1/)#

The certificate parity.json (written by parity/certify.py write at b80ac38) states all parts passed; the measured builds are from clean clones at d13d393 (the gates, the golden replays, the sssp golden mutations) and 1049068 (the test-suite checks), both with the library, compat tools and build system of main at 8842acf, which the certificate checks.

Part

Result

Golden replays

sssp 495 cases on 10 configurations (sequential, OpenMP 1/4/16/28/56, int64, CUDA, CUDA int64): 4950 / 4950 byte-equal; cycle_count 24 cases on sequential, OpenMP 4 / 56 and cycle_count_cuda 24 cases on cuda, cuda:resident, cuda:int64: 144 / 144

ipdps25_dynamosp_sosp (sssp, 4 graphs x 3 batches)

132 / 132 gated regions within their gates (OpenMP 60, CUDA at boost clocks 60, CUDA memory 12); ratios 0.631-1.046; the default-clock readings (60 regions, 0.666-1.062) recorded

ieee_tc_dyntrucy (cycle_count, the M2a/M2b case lists)

78 / 78 (OpenMP 16, CUDA at boost 37, the COLLAB update at base 5, CUDA memory 20); ratios 0.229-1.005; the default-clock reading (42 regions, 0.222-1.003) recorded

asan / tsan presets

595 / 595 (26 skipped) and 520 / 520 (42 skipped)

ci/gpu_local.sh

every step passed: CUDA tests (gpu 252, cpu 605), the CUDA goldens, memcheck, synccheck, racecheck, clang-tidy

Mutation checks

the cycle_count CTests 7 / 7; the sssp golden mutation fails the goldens on sequential, OpenMP and CUDA, the control passes

What happened on the way#

  • A busy shared machine during the first pass. At times between 23:51 on 2026-09-30 and 01:00 CDT a workload outside the perf lock kept about 27 cores busy. The A/B harness of sssp rejected the rounds and, after 12 rejections, stopped two readings (CUDA road_usa_g at locked clocks, default-clock roadNet-PA), as designed; the OpenMP cycle_count harness flags such runs but does not repeat them, so its reading completed with almost every run flagged, and the summary passed it. bench_suite.py summarize now lists flagged runs per case under contaminated (without failing, PLAN 8.6), and the flagged reading was taken again with the two stopped ones and the default-clock cycle_count reading the usage limit had interrupted (README of the results, “Readings taken again”). Note for later: the cycle_count OpenMP harness should repeat flagged runs like the others (a change to parity/cycle_count_perf.py, deliberately not made during a release).

  • No re-measurement for the later commits. The commits after d13d393 change only harness scripts, documents and C++ tests, which the certificate verifies (git diff over the library paths is empty); a measurement is not repeated for them.

  • Portability pre-check (the C++ test changes of dd09893): the Clang 18 syntax pass over the dev build’s 161 translation units and the _FORTIFY_SOURCE=3 cpu-only build are clean.

Step 2: the release changes, the distributions and the checks on the release code (bump-meta)#

What changed#

Item

Where

VERSION 0.1.0rc1 (canonical PEP 440)

VERSION

The CHANGELOG section [0.1.0rc1] - 2026-10-01 (summary, the release preparation, the detailed record of M1a-M5) under an empty Unreleased

CHANGELOG.md

version: 0.1.0rc1, date-released: 2026-10-01 (cffconvert 2.0 --validate: valid, schema 1.2.0)

CITATION.cff

License-Expression: Apache-2.0 AND BSD-3-Clause AND MIT AND GPL-3.0-or-later WITH GCC-exception-3.1, checked in pyproject.toml, METADATA and PKG-INFO (with the License-File: lines and no legacy License:)

pyproject.toml, ci/wheel_check.py (+ self-test, ci/tests/test_wheel_check.py), docs/developer/wheels.md

sssp and cycle_count stable; scripts/regen.py outputs (registry table, Python registry, the algorithm tables)

the manifests, cpp/src/core/registry_table.inc, python/dyng/_algorithms.py, README.md, docs/index.md, docs/algorithms/

Status lines: “alpha: 0.1 release candidate” instead of pre-alpha

README.md, docs/index.md, CONTRIBUTING.md, SUPPORT.md, SECURITY.md

The approvals log: the decisions of 2026-09-30 (the AI assistant pushes v0.1.0rc1 / v0.1.0 and creates the GitHub Release, the author approves the pypi deployment; the licence expression; maturity stable) and the repository state of 2026-10-01 (M5 merged at 8842acf, pypa/cibuildwheel allowed, 24 required checks)

GOVERNANCE.md, docs/developer/repository_settings.md

Who does what for 0.1.0 and later releases; how to build a tag’s distributions locally as release.yml does

docs/developer/release.md, .github/workflows/release.yml (header comment only)

certify.py equivalence and the metadata rule of the certificate

parity/certify.py, parity/tests/test_release_cert.py, benchmarks/results/0.1.0rc1/equivalence.json

Deviations and decisions (each with the same intent)#

  1. The certificate accepts a metadata-only library difference with an equivalence record. Making the algorithms stable changes cpp/src (the two manifests and the generated registry table) after step 1 measured the gates, the replays and the sssp golden mutations at d13d393; under step 1’s rule (the measured commit has the release’s library sources) the certificate would fail, and measuring the whole gate again would take several hours of the exclusive lock that M7 also needs, for a change no measured program executes. Instead, parity/certify.py equivalence builds d13d393 and the release’s library over it in one clone (same paths, same VERSION, presets parity and parity-cuda) and compares the files: both compat tools are byte-identical and libdyng.so differs only inside dyng::algorithms() (553 bytes of .text) plus the build id, unwind and symbol tables, with every section and symbol at the same address. The rule is narrow: only the files of METADATA_PATHS, only the library scope (the gates, the replays, the golden mutations), and only with a passed pair whose release side has the release’s library. The test-suite checks read the registry, so they were run again on the release code instead. The rule and the record are in parity.json (metadata_paths, equivalences).

  2. The decision date. The author’s decisions were given on 2026-09-30; the approvals log and the comments that cite them use that date, while the release date of the CHANGELOG and CITATION.cff is 2026-10-01.

  3. The sdist carries the wheel’s licence expression. One [project] table serves both, so the sdist (whose own files are all Apache-2.0) also names the bundled components’ licences; a broader expression than the sdist needs, never a narrower one (docs/developer/wheels.md).

  4. The pre-alpha wording was replaced in CONTRIBUTING.md, SUPPORT.md and SECURITY.md too, not only in the README, so the repository says one thing.

Results (release code 07d46e2, a fresh clone of release-0.1.0; GPU 1)#

Check

Result

asan / tsan presets

595 / 595 (26 skipped) and 520 / 520 (42 skipped), the same skips as at 1049068

ci/gpu_local.sh

every step passed (CUDA tests gpu 252, cpu 605; the CUDA goldens; memcheck, synccheck, racecheck; clang-tidy)

Mutation CTests

7 / 7 (ctest --preset dev-cuda -L mutation)

ci/check.sh --parity

all checks passed (including ci/python.sh, ci/docs.sh, the scaffold and API checks, the parity preset’s goldens 4 / 4)

ci/docs.sh, ci/python.sh (again, separately)

OK; 396 passed, 4 skipped (the optional torch, cupy and pandas are not installed)

release.yml’s select with GITHUB_REF_NAME=v0.1.0rc1

kind=package, version=0.1.0rc1, prerelease=true; v0.1.0 and v0.1.0-rc.1 refused

ci/wheels.sh

dyng-0.1.0rc1.tar.gz (0.79 MB), dyng-0.1.0rc1-cp312-abi3-manylinux_2_28_x86_64.whl (1.50 MB); twine check --strict and ci/wheel_check.py --platform manylinux_2_28_x86_64 --require-libgomp passed; the licence expression in METADATA and PKG-INFO

The wheel alone in fresh Python 3.12 and 3.13 venvs

the whole suite 396 passed, 4 skipped; the cibuildwheel test subset 253 passed, 1 skipped (docs/api/cli.md is not in the test tree); the README quickstart prints the README’s output; dyng --version 0.1.0rc1

Portability pre-check

Clang 18 syntax pass over 174 translation units (with the Python bindings): 0 failures; the _FORTIFY_SOURCE=3 cpu-only build (with the bindings): clean

Certificate

parity/certify.py write on the clean tree: all parts passed (the checks above recorded in checks.json)

No tag was created and nothing was pushed or uploaded.

Notes for the orchestrator#

Superseded by the notes at the end of step 3 (the same points, plus what the review added).

Step 3: the review fixes (review-fix)#

The independent review of steps 1 and 2 found 15 problems, each confirmed by a second reviewer: the certificate could pass a partial suite, did not name the committed fixtures, claimed nothing about the inputs as measured, accepted checks without evidence or in too narrow a scope, and took the driver from the time it was written; and the release documents had gaps for the final release. All are fixed; none needed the gate to be measured again (deviations 1 and 2).

What changed#

Finding

Fix

A partial suite summary certified (high)

certify.py write requires each summary to be of the committed suite file (SHA-256), not partial, and to cover every reading x dataset of the suite’s plan (bench_suite.plan) exactly once, complete and with its record. bench_suite.py writes a narrowed execution (--readings, --datasets, --runs) as <suite>.partial.json, refuses an --out under benchmarks/results/ for it, and never replaces records unless --force (or keeps them with --skip-existing)

The committed in-repo goldens not in the certificate (high)

parity/fixtures/fixtures.toml lists the four sets of cpp/tests/data (mosp_changes: the generator outputs of mospPrep changes; mosp_graph_io, mosp_sssp, cycle_enum) with their generator scripts, originals and the CTests comparing them; certify.py check keeps the per-test results of the fixture tests from ctest output (--ctest-log, --tests-log); parity.json has committed_fixtures (the SHA-256 of each set’s files, the original and commit, each test pattern’s results) and fails for an unlisted directory or a pattern no release check passed

Inputs “not verified” (medium)

the sssp harness hashes every input when a run starts (inputs_hashed: run start); the summary verifies each record’s inputs (the harness’s digests, or, for records without them, the files hashed again and unchanged since the execution started) and fails otherwise; the certificate fails a suite whose inputs are not verified

Checks without evidence, scopes too narrow (medium)

scopes packaging and repo besides library and tests; --result needs --evidence; every log is recorded with its SHA-256 and a committed excerpt under checks/; required_steps recorded for a gpu_local summary; check-parity (scope repo), gpu_local (repo) and distributions (packaging) were run again on the final code with their logs

The driver taken at write time (low)

every harness record names the NVIDIA driver and the CUDA driver API version; the certificate takes the driver from the records, or, for records that predate the field, accepts the driver seen at write time only when it is shown unchanged since before the first measurement (deviation 1)

The metadata-equivalence rule undocumented

benchmarks/README.md (“The release certificate”) and release.md step 3

No certificate route for 0.1.0

release.md step 9 and its section “The final-release pull request”

O3 not tracked

GOVERNANCE.md (O3: needed before 0.1.0 reaches PyPI), release.md step 10, the notes below

“License: Apache-2.0.” on the PyPI page

python/README.md names the bundled components and their licences

Step 9 incomplete; no consistency check

release.md step 9 lists the status and install texts, SECURITY.md and the CHANGELOG links; ci/wheel_check.py --release-metadata checks VERSION, CHANGELOG and CITATION.cff against each other (in release.yml’s select job and ci/tests)

Roadmap and plan page

updated (who tags, rc1 on TestPyPI, M5 merged, R010 in progress)

new_algorithm.py skipped its CHANGELOG entry

it creates ### Added under an empty Unreleased (and --remove drops it again); tests for the three states

Zenodo timing

repository_settings.md row 14 agrees with PLAN 11.2 (M6); section 14 and release.md step 12: Zenodo on before the GitHub Release, or no DOI for 0.1.0

The release date fixed before the tag

release.md steps 7 and 10: ci/wheel_check.py --release-metadata --release-date today before tagging

wheels.md / THIRD_PARTY_LICENSES.txt on the sdist

docs/references.bib named; the sdist’s expression explained as the wheels’

The results README undercounted the second session

five readings come from the second session (the cycle_count CUDA memory reading was first taken there)

Two scope corrections found on the way: docs/references.bib is compiled into libdyng (for dyng::citation()), so it is a library path; the C++ test suites build examples/cpp and the README’s C++ quickstart, so examples and README.md are test paths. Neither changed since the measured commits.

Deviations and decisions (each with the same intent)#

  1. The 0.1.0rc1 records name no driver, and the gate was not measured again for it. The records of the first two steps predate the per-record field. Measuring the whole gate again (about five hours of the exclusive lock that M7 also needs) would only add the driver string. Instead certify.py shows that the driver seen when the certificate is written ran the measurements: the loaded kernel module is 590.48.01, the machine booted on 2026-08-10, the module file on disk (the only one a load since boot can have used) is 590.48.01 and was installed on 2026-08-05, and libcuda.so.1 -> libcuda.so.590.48.01 was installed on 2026-02-05, all before the first measurement (2026-10-01 02:28 UTC); the evidence is in parity.json (environment.driver.unchanged_since_first_measurement). From now on the records carry the driver and the certificate compares them.

  2. The sssp inputs of 0.1.0rc1 are verified after the fact. Their records carry the digests prepare wrote, not digests taken while measuring (and the memory records carry none). The summary hashes every input file again and requires each to equal the suite’s digest and to have been neither written nor replaced (modification and status-change times) since the execution started (the first command of the suite’s run log, 2026-09-30 21:28 CDT); the files were last changed between 2026-09-23 (the CSR files) and 2026-09-27 (the batches and initial trees). The cycle_count run records carry the digests their process took. New records are hashed when the run starts.

  3. The fixture tests’ results come from ctest’s per-test lines of the logs the checks already have (the asan and tsan logs at 07d46e2; ci/gpu_local.sh and ci/check.sh --parity on the final code), not from a JUnit file: they are parsed when the check is recorded, and only the fixture tests are kept (the full logs are hashed).

  4. The repo scope leaves out benchmarks/results/, which is committed after the checks; everything else, documents included, must be the release’s, so the whole-tree checks ran after the last change outside the results.

  5. The release-date check is a warning in release.yml and an error only before the tag (--release-date today, steps 7 and 10): once a tag is pushed, an error could not be fixed without deleting the tag.

  6. No cffconvert in CI. It is not in the development environment; CITATION.cff was validated with cffconvert 2.0 in step 2 and is unchanged, and --release-metadata checks its version and date against VERSION and the CHANGELOG. Adding cffconvert to lint.yml is left for a later infrastructure change.

  7. The M5 retrospective’s readiness list is not edited (it is the record of M5); O3 is tracked in GOVERNANCE.md, release.md step 10 and the notes below.

Results#

Recorded in benchmarks/results/0.1.0rc1/README.md and parity.json, written by certify.py write on the final tree: the suites re-summarized from the same records (132 / 132 and 78 / 78 gated regions, unchanged; inputs verified; the driver shown unchanged), the committed fixtures, and the checks with their scopes and evidence (asan, tsan and the mutation CTests of 07d46e2, whose test paths are unchanged; gpu_local, check-parity and distributions run again on the final code from a clean clone; the sssp golden mutations of d13d393 through the equivalence record). Tests: parity/tests and ci/tests (new cases for every fix). No C++ code changed, so the portability pre-check has nothing new to compile.

Notes for the orchestrator#

  • Push v0.1.0rc1 only on main after the release pull request is merged (merge commit): the certificate names the release-branch commit, which stays reachable through the merge. Before pushing, run python ci/wheel_check.py --release-metadata --release-date today: if the merge falls after 2026-10-01 (UTC), change the CHANGELOG date and date-released in a small pull request first (release.md step 7).

  • release.yml uploads the release candidate to TestPyPI only; step 8 of the checklist (the smoke install from TestPyPI on a CPU machine and on the GPU machine) follows.

  • Before the author approves the pypi deployment of v0.1.0, two author items must be settled: O3, the institution line and the years of NOTICE (“software developed at the Missouri University of Science and Technology”, “Copyright 2023-2026 their authors”), which ships in every distribution (GOVERNANCE.md, open decisions); and the licence expression, recorded “pending objection before the final PyPI approval”: if the author objects, change pyproject.toml, LICENSE_EXPRESSION in ci/wheel_check.py, docs/developer/wheels.md and python/README.md together before v0.1.0.

  • Zenodo: it archives only GitHub Releases published after it is switched on. If 0.1.0 should get a DOI, the author switches Zenodo on (checkpoint A4, repository_settings.md section 14) before the GitHub Release of 0.1.0 (step 11); otherwise the first DOI comes with a later release, or 0.1.0 is uploaded to Zenodo by hand.

  • The final release follows release.md step 9 (“The final-release pull request”): the status and install texts, SECURITY.md, the CHANGELOG links, and the certificate under benchmarks/results/0.1.0/ (the rc’s carried over with certify.py write --version 0.1.0 after the packaging and repo checks, and the C++ test suites since README.md changes, are run again on the final tree).