ADR 0035: The mosp API review and freeze (0.2)#
Status: Accepted under delegation (2026-10-07; GOVERNANCE.md, “Delegation of technical ADRs”) for the review, its amendments and the freeze mechanism. The maturity itself,
mospstable from 0.2.0, is the author’s decision of 2026-10-07 (GOVERNANCE.md, approvals log), taken on the condition that this review comes first and that the release certificate of 0.2.0 carries mosp’s full gate record. Nothing here changes a rule the author approved: the stability policy of PLAN Section 5.9, the performance gates and their protocol (ADRs 0018, 0021), the parity rules and the goldens are untouched; no public signature changed.Date: 2026-10-07
Deciders: the AI assistant, on the author’s behalf (R020, step mosp-freeze); the maturity: S M Shovan
Context#
mosp arrived in M7 (ADRs 0027, 0028, 0029) at maturity experimental, so PLAN 5.9 allowed its
API to change in any minor release. The author decided that it becomes stable in 0.2.0, as
sssp and cycle_count became stable in 0.1.0. From then on a change of <dyng/mosp.hpp>,
dyng.mosp or dyng mosp is a SemVer matter, so the API is reviewed first, the way ADR 0023
reviewed the 0.1 headers: against docs/developer/api_review_checklist.md (naming and the verb
vocabulary, the uniform contract of ADR 0006, Doxygen, exception guarantees and dyng::error,
host-compilability, ownership and ABI, the bindings), against sssp and cycle_count, and against
what the coming work could force to break: the hypergraph container and the ESCHER store of 0.3
(M8a / M8b), triad_count (M9), hyper_sssp (which builds on shortest paths, PLAN 6.4.8) and
label_propagation (0.4, PLAN 6.4.7, with the slotted row layout and vertex batches).
Reviewed: every public entity of cpp/include/dyng/mosp.hpp (the constants, options, stats,
result, compute(), update(), and the detail contract the baseline lists:
mosp_supported_v, DYNG_MOSP_TYPES_MESSAGE, update_traits<mosp::result>); the mosp parts of
shared headers (graph_properties::mosp_compatible(), io::write_path_costs(),
<dyng/testing/mosp_oracle.hpp>, generators::legacy::mosp_changes(), the include in
<dyng/dyng.hpp>); the Python module dyng.mosp (python/dyng/mosp.py,
python/bindings/mosp.cpp) with dyng.update() and the dyng.io / dyng.testing helpers; the
command line dyng mosp compute|update (python/dyng/cli/_algorithms.py); and
docs/algorithms/mosp.md.
Findings and what was done#
# |
Area |
Finding |
Resolution |
|---|---|---|---|
1 |
Naming |
The names follow ADR 0004 (clang-tidy’s naming rules pass): |
none needed |
2 |
The verb vocabulary |
|
none needed |
3 |
Contract uniformity with |
The skeleton is the same: an aggregate |
Accepted as the rule: an indexed family of arrays is a method with the index (ADR 0028 for Python). |
4 |
Fixed inputs |
|
Accepted. |
5 |
|
Every appended counter says deterministic, but the meaning of the inherited |
Documented in the header ( |
6 |
Doxygen |
|
Documented ( |
7 |
Exception guarantees |
|
Kept, with the reason in a comment and |
8 |
Exceptions leaving the library |
Every compiled entry point ( |
none needed |
9 |
Host-compilability |
|
none needed |
10 |
Ownership and ABI |
|
Accepted for 0.x (ADR 0023 finding 10). |
11 |
The memory space of |
ADR 0027 computed the costs on the host “first, the device later”. Moving them to device memory later would break every caller that reads them on the host. |
Committed in the header: |
12 |
Limits |
|
Documented: a later release may raise them (a relaxation, with a baseline update and a CHANGELOG entry), never lower them ( |
13 |
Composition |
|
none needed |
14 |
Python |
|
none needed |
15 |
Command line |
`dyng mosp compute |
update |
16 |
0.3: the hypergraph, the ESCHER store, |
mosp is a graph algorithm and keeps its own namespace; |
No API change needed. The diagnostic is |
17 |
0.4: |
It brings |
No API change needed. |
18 |
Objective selection |
|
Accepted: such a selection would be an appended field; |
19 |
The mosp parts of shared headers |
|
Unchanged; the review of those headers as a whole is still open (ADR 0023, Consequences). |
20 |
The tutorial algorithms |
|
Unchanged; not frozen by this ADR. |
Decision#
The mosp API is frozen as reviewed above, with the documentation amendments of findings 5, 6, 7, 11 and 12: every declaration of
<dyng/mosp.hpp>(the 39 lines of its baseline section, itsdetailcontract included), the Python names ofdyng.mosp, and the flags and output files ofdyng mosp. From 0.2.0 they follow PLAN 5.9’s stable tier: before 1.0 a break only in a minor release with a CHANGELOG “Changed” or “Removed” entry and a migration note; struct fields and enumerators only appended. No signature changed in the review, so the freeze needs no migration note.The maturity decides the freeze.
ci/api_snapshot.pymarksdyng/<name>.hppfrozen exactly when the algorithm’s manifest saysmaturity = "stable"(sssp and cycle_count since 0.1, mosp since 0.2), next to the frozen core headers of ADR 0023; every other algorithm header (the tutorial algorithms) is tracked. Before, the list was written by hand and labelledmosp.hppfrozen while the algorithm was experimental. A test keeps the label and the manifest in step (test_an_algorithm_header_is_frozen_exactly_when_its_manifest_says_stable).Both API checks fail on a deliberate mosp change, shown on 2026-10-07 in a throwaway clone of this branch (and kept as tests):
C++:
mosp::compute(..., const options& opt = {})with the default removed:ci/docs.sh --doxygen-onlyexits 1 with- template <...> [[nodiscard]] result< vertex_t > dyng::mosp::compute(const resources &res, const graph< vertex_t, edge_t, weight_t > &g, vertex_t source, const options &opt={}) + template <...> [[nodiscard]] result< vertex_t > dyng::mosp::compute(const resources &res, const graph< vertex_t, edge_t, weight_t > &g, vertex_t source, const options &opt) api-snapshot: the public C++ API differs from the baseline (above: - baseline, + headers).(
test_a_mosp_signature_change_fails_in_the_frozen_mosp_sectionpins the same on a synthetic Doxygen tree.)Python:
dyng.mosp.compute(graph, source, ...)withsourcerenamedroot:ci/api_check.sh 8b9067eexits 1 withcompute(source): Parameter was removedandcompute(root): Parameter was added as required; unchanged, it passes (“no breaking change”).ci/tests/test_api_check.pyruns griffe on two copies ofpython/dyngand pins three breaking changes ofdyng.mosp(a renamed parameter, a changedOptionsdefault, a removedResultaccessor).
What is not frozen:
dyng::detail(the participant, the solve), the profiler stage names other than those the benchmark suites use (PLAN 5.9), and the tracked headers of finding 19.
Amendments (2026-10-08, the R020 review)#
Accepted under delegation, as the review: documentation of the frozen API and its enforcement, no signature, default or name changed (the C++ baseline and the Python API are unchanged).
A1. Documentation of the frozen header and module. The reviewers found what this review missed: the field comment of
options::compute_path_costssaid thatpath_costs()throws “while” the option is false and called it a tunable, while the accessor’s@throws(and the code) follow the option of the lastcompute()orupdate();set_options()changes it for the nextupdate(). The field comment,set_options(), the PythonOptionsandResult.set_optionsdocstrings (and so thedyng mosphelp) and the option table ofdocs/algorithms/mosp.mdnow say so, and the option is no longer called a tunable (it decides what the result holds).update()’s@throws invalid_argument_erroradds the casessssp::update()documents and mosp reaches through the same checks: a graph or result of another backend, a batch copy refused bycopy_policy::error. The Python module docstring states the determinism exception of the C++ documentation (trees imported without canonicalization inside sssp’s packing window), andStatsgivesconverged,fallback_usedandengine_usedtheir C++ meaning and determinism (finding 5 had amended the header only). New tests pin whencompute_path_coststakes effect (MospBackend.ComputePathCostsTakesEffectAtTheNextUpdate,test_compute_path_costs_takes_effect_at_the_next_update).A2. The C++ freeze is enforced, not only labelled. Decision 2’s
frozenlabel changed only the section header of the baseline:ci/docs.sh --update-apiin the same pull request made a frozen change pass like a tracked one, and the label check that PLAN 5.9 (“Enforcement”) and ADR 0023 Decision 3 giveapi-check.ymlexisted for Python only. `ci/api_snapshot.py –against` now compares the frozen sections of the committed baseline with the base branch's, and `ci/api_check.sh` (`api-check.yml`, `ci/check.sh`) fails on a breaking change of the frozen C++ API (a removed or changed declaration, a frozen header removed or unfrozen) unless the pull request has the `api-change` label and a CHANGELOG entry, exactly as for Python; added lines are reported. `ci/tests/test_api_snapshot.py` shows a deliberate change of mosp's `compute_path_costs` default and of `max_objectives` failing against `HEAD`. A3. The Python demonstrations run in CI.
ci/tests/test_api_check.pyskipped itself on every hosted run (the harness job oflint.ymldid not install griffe). Bothlint.yml’s harness job andapi-check.ymlnow installgriffe==2.3.0and setDYNG_REQUIRE_GRIFFE=1, under which a missing griffe fails the module instead of skipping it.
Consequences#
From 0.2.0,
mospis covered likessspandcycle_count: the algorithm tables, the registry (dyng::algorithms(),dyng.algorithms()) anddocs/algorithms/mosp.mdsaystable.A later maturity change of any algorithm moves its header between frozen and tracked by its manifest alone (
scripts/regen.py, thenci/docs.sh --update-apifor the label).Open for M8b: the diagnostic of finding 16 (a static assertion that names the container when a graph result is composed with a hypergraph), for all three stable algorithms at once.