Skip to content

ABI additive-change reviews

tools/check-abi-version.sh fails a build whose installed headers changed while MESHIOPLUSPLUS_ABI_VERSION did not. This file is how that judgement gets recorded when the change really was additive.

One row per release that changed an installed header while holding the ABI version. Adding a row is a claim that every listed header change is Tier C by doc/abi.md's criterion — a new inline function, constexpr variable, type, declaration or header, or an appended enumerator — and therefore cannot affect a consumer that was already compiled. It is not a rubber stamp: the whole point of the ABI number is that a consumer may trust it instead of re-pinning the release version, so a wrong entry here ships silent memory corruption.

The releases and headers changed columns are read by the gate, not just by people. tools/check-abi-version.sh looks for a row matching the ABI version and the release version in CMakeLists.txt, then requires every header that actually changed to be named in it — paths relative to src/cpp/include/meshioplusplus/, as written below. A header you changed but did not list is a red build naming it.

That specificity is the fix for a real defect: through v9.4.0 the gate matched any row whose first column was the current ABI number, so once any ABI-3 row existed, every later header change matched it and passed — reporting "records the additive review" about a review of a different release entirely. A row for one release says nothing about the next one.

If the change edited the body of an existing inline function or template, or moved a data member, it is Tier A/B and belongs in a version bump, not in this table.

ABIreleasesheaders changedwhy it is additive
3v9.3.0formats/exodus.hppAdds one new inline constexpr const char* kExodusAttributePrefix. No existing declaration touched, ExodusInfo unchanged. A consumer compiled against v9.2.0 headers has an identical translation unit either way — it simply does not have the name.
3v9.4.0abi_version.hpp, detail/abi_version_check.hpp, mesh.hppAll new: the ABI machinery itself. mesh.hpp gains only an #include; no type's definition changes. See the note in doc/abi.md about the one-way incompatibility this introduction creates (v9.4.0 headers reference a sentinel a ≤v9.3.0 library does not define), which fails closed at link time.
4v9.7.0formats/gmsh.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration). In formats/gmsh.hpp, all new: a GmshInfo struct (the MedInfo/ExodusInfo side-channel pattern) and two overloads that take it, read_gmsh(path, GmshInfo&, ReadOptions) and write_gmsh41(path, mesh, binary, const GmshInfo&). No existing declaration, default argument or type definition is touched, and neither existing function's mangled name changes — a consumer compiled against v9.6.0 headers links against exactly the same symbols. The one source-level (not ABI) consequence is that &read_gmsh and &write_gmsh41 are now ambiguous as bare function pointers, which is a compile error at the call site, never silent; registry.cpp wraps both in lambdas for that reason.
4v9.8.0formats/cgns.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row. formats/cgns.hpp's write_cgns(const std::string&, const Mesh&, int) and read_cgns(const std::string&) declarations are byte-identical to v9.7.0's — the CGNS rewrite (v9.8.0, a genuine CGNS/SIDS-compliant on-disk layout replacing the previous tetra-only encoding) is entirely confined to the .cpp implementation and the header's doc comment describing that on-disk layout. No declaration, default argument or type definition changed, so a consumer compiled against v9.7.0 headers links against exactly the same two symbols with exactly the same signatures; the only observable difference is that files written by the new library have a different (and, unlike before, actually-CGNS-compliant) byte layout, which is a file-format change, not an ABI one.
5v9.10.0operations/gradient.hpp, detail/cell_adjacency.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row. The other two are entirely new headers: operations/gradient.hpp declares the GradientOperator/GradientMethod enums, the GradientOptions/GradientResult aggregates, three MESHIOPLUSPLUS_API free functions and three inline constexpr const char* name constants; detail/cell_adjacency.hpp declares a CellIncidence aggregate and three MESHIOPLUSPLUS_API free functions. Nothing pre-existing is touched: no declaration is changed or removed, no default argument moves, no inline body is edited, and no existing type gains, loses or reorders a member. A consumer compiled against v9.9.0 headers therefore links against exactly the same symbols with exactly the same signatures and layouts — it simply does not have the new names. operations/partition.cpp was refactored onto cell_adjacency.hpp in the same release, but that is a .cpp and so out of scope here; operations/partition.hpp is byte-identical.
5v9.11.0operations/pipeline.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row. operations/pipeline.hpp is an entirely new header: it declares the PipelineValue alias, the PipelineStep/PipelineInput/PipelineOutput/Pipeline/PipelineStepReport/PipelineReport aggregates and thirteen MESHIOPLUSPLUS_API free functions (the typed engine, the JSON front-end and the option-spelling parsers). It defines no inline function bodies of its own beyond the implicit aggregate members, and nothing pre-existing is touched anywhere else in the installed tree: no declaration is changed or removed, no default argument moves, no inline body is edited, and no existing type gains, loses or reorders a member (ReadOptions/WriteOptions are consumed by value inside the new aggregates, not modified). A consumer compiled against v9.10.0 headers therefore links against exactly the same symbols with exactly the same signatures and layouts — it simply does not have the new names. The vendored nlohmann/json submodule is build-internal (pipeline.cpp only, PRIVATE include) and never reaches an installed header.
5v9.13.0formats/mdpa.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row. formats/mdpa.hpp's change is comment-only: the file-block description of Begin Nodes, the "Limitations" list and write_mdpa's doc comment are rewritten to say that arbitrary node ids are now read and that the writer still renumbers to 1..n. Every declaration is byte-identical — the three read_mdpa overloads, both write_mdpa overloads, MdpaInfo, MdpaEntityName and the MdpaProperties/MdpaProperty aliases — no member is added, moved, removed or reordered, no default argument moves, and the header defines no inline body to edit. A consumer compiled against v9.12.0 headers links against exactly the same symbols with exactly the same signatures and layouts; the only observable difference is which files the library will accept, which is a reader-behaviour change, not an ABI one. (The gate keys on paths, not content, which is why a comment-only header change still needs a row here.)
5v9.12.0operations/sequence.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row. operations/sequence.hpp is an entirely new header: it declares the SequenceTimeSource/SequenceTimeFrom/SequenceMode enums, the SequenceEntry/SequenceInput/SequenceOutput/SequencePipeline aggregates, one inline constexpr const char* name constant, and eighteen MESHIOPLUSPLUS_API free functions (the pure units, the capability queries, the streaming drivers and the JSON front-end -- the last four are declared here and defined in pipeline.cpp, which keeps the vendored nlohmann/json confined to that one TU and leaves operations/pipeline.hpp byte-identical). Nothing pre-existing is touched anywhere in the installed tree: no declaration is changed or removed, no default argument moves, no inline body is edited, and no existing type gains, loses or reorders a member. In particular Pipeline, PipelineInput, PipelineOutput and PipelineStep are untouched, which is exactly why the sequence types went into a new header rather than growing them: Pipeline embeds PipelineInput by value, so one added member would shift mSteps/mOutput under a consumer compiled against v9.11.0 headers -- a silent Tier A break. Those four types were unpinned in tests/cpp/test_abi_layout.cpp from v9.11.0 until this release, which is what made growing them look free; this release adds their MIO_ABI_LAYOUT lines alongside the four new sequence aggregates, so the same mistake now fails to compile. A consumer built against v9.11.0 headers links against exactly the same symbols with exactly the same signatures and layouts -- it simply does not have the new names.
5v9.14.0formats/mdpa.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row. formats/mdpa.hpp gains one new inline constexpr const char* kMdpaIdName = "mdpa:id"; (the kExodusAttributePrefix pattern) plus doc-comment updates to Begin Nodes/Begin Elements/write_mdpa describing the new preserve-on-write behaviour. No existing declaration is touched, removed or reordered: the three read_mdpa overloads, both write_mdpa overloads, MdpaInfo, MdpaEntityName and the MdpaProperties/MdpaProperty aliases are byte-identical to v9.13.0. A consumer compiled against v9.13.0 headers links against exactly the same symbols with exactly the same signatures and layouts — it simply does not have the new name, and the feature itself (the writer consulting point_data/cell_data["mdpa:id"] when present) is entirely a .cpp-side behaviour change reachable through unchanged function signatures.
5v9.15.0version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row -- and it is the only installed header this release touches. The whole change (polyhedral connectivity across the flat C ABI: the mio_poly_conn snapshot, the mio_mesh_add_polygon_block / _polyhedron_block setters and mio_mesh_cell_block_info_ex) lives in bindings/c/, bindings/fortran/, bindings/julia/, bindings/r/ and bindings/wasm/, none of which the gate's src/cpp/include/meshioplusplus scope covers -- and none of which is under MESHIOPLUSPLUS_ABI_VERSION at all, the C API being a separate SOVERSION 0 + append-only-reserved contract (see doc/abi.md). No C++ declaration, type, inline body or default argument changed anywhere, so a consumer compiled against v9.14.0 headers links against exactly the same symbols with exactly the same signatures and layouts.
5v9.16.0detail/polyhedron.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row. detail/polyhedron.hpp is an entirely new header: it declares the CellRings and PolyMeasure aggregates, the RingOrientation enum, the FacetKey/FacetKeyHash pair and eight MESHIOPLUSPLUS_API free functions (cell_rings, orient_rings, poly_measure, the polygon_area_vector/polygon_area overload pairs, polygon_centroid, cell_volume_from_corners). Nothing pre-existing is touched anywhere in the installed tree: no declaration is changed or removed, no default argument moves, no inline body is edited, and no existing type gains, loses or reorders a member -- in particular detail/geometry.hpp and detail/cell_faces.hpp are byte-identical, which is exactly why the kernel went into a new header rather than growing either of them. A consumer compiled against v9.15.0 headers therefore links against exactly the same symbols with exactly the same signatures and layouts -- it simply does not have the new names. The release's behaviour change (the corner-average fan replacing the node-first fan in stats/cell_measure/clean, and the polyhedral paths in gradient/quality/surface/smooth) is entirely .cpp-side, reachable through unchanged signatures.
5v9.17.0operations/convert_cells.hpp, detail/polyhedron.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row. operations/convert_cells.hpp's change is comment-only: the file-level description of Simplexify and convert_cells' @throws clause are rewritten to say that a polyhedron block now decomposes into tetrahedra rather than throwing. Every declaration is byte-identical -- ConvertCellsMode, ConvertCellsOptions, ConvertCellsResult, convert_cells and convert_cells_mode_from_name -- no member is added, moved, removed or reordered, no default argument moves, and the header defines no inline body to edit. A consumer compiled against v9.16.0 headers links against exactly the same symbols with exactly the same signatures and layouts; the only observable difference is which meshes the operation accepts, which is a behaviour change, not an ABI one. (The gate keys on paths, not content, which is why a comment-only header change still needs a row here -- the v9.13.0 row is the precedent.) detail/polyhedron.hpp is named here only because it was introduced in v9.16.0 and the gate compares against the last tag: its own row above reviews it, and it is byte-identical between v9.16.0 and v9.17.0.
5v9.18.0formats/cgns.hpp, detail/polyhedron.hpp, operations/convert_cells.hpp, version.hppversion.hpp is the routine release bump. formats/cgns.hpp gains two new MESHIOPLUSPLUS_API declarations, cgns_has_cgnslib() and read_cgns_mll(), plus doc comments; write_cgns and read_cgns are byte-identical, so a consumer compiled against v9.17.0 headers links against exactly the same symbols with the same signatures -- it simply does not have the two new names. The optional MESHIOPLUSPLUS_HAS_CGNSLIB macro guards only the implementation in src/cpp/src/formats/cgns_mll.cpp; no installed header's content depends on it, so the header is identical whether or not a consumer's build had cgnslib, and cgnslib.h never reaches an installed header. detail/polyhedron.hpp and operations/convert_cells.hpp are named only because the gate compares against the last tag: their own rows above review them, and both are byte-identical since v9.16.0 and v9.17.0 respectively.
5v9.19.0detail/vtk_cells.hpp, formats/cgns.hpp, detail/polyhedron.hpp, operations/convert_cells.hpp, version.hppversion.hpp is the routine release bump. detail/vtk_cells.hpp gains one new overload of reconstruct_cells taking the VTU faces/faceoffsets arrays -- a distinct mangled symbol, with the four-argument form left byte-identical and still refusing VTK type 42, so an already-compiled consumer links against exactly what it did before. That the addition is an overload rather than a changed signature is the whole point: changing the existing one would have renamed its symbol, which is Tier A. The other three headers are named only because the gate compares against the last tag; their own rows above review them and all three are byte-identical since the releases that introduced them. Note the MED POE and EnSight nsided/nfaced writers in this release touched no installed header at all -- both are entirely .cpp-side, reachable through unchanged signatures.
6v9.21.0detail/face_mesh.hpp, formats/cgns.hpp, version.hppversion.hpp is the routine release bump. detail/face_mesh.hpp gains one new overload of build_global_faces taking a const std::vector<std::size_t>& block filter — a distinct mangled symbol, with the one-argument form's declaration byte-identical, so an already-compiled consumer links against exactly what it did before. That it is an overload rather than a new defaulted parameter is the whole point: a default argument would have changed the existing function's signature and therefore its mangled name, which is Tier A. formats/cgns.hpp's change is comment-only (the file-level description now says NGON_n/NFACE_n are read and written by the hand-rolled path, not only by cgnslib); write_cgns, read_cgns, read_cgns_mll and cgns_has_cgnslib are byte-identical, no member moves and the header defines no inline body to edit. The gate keys on paths rather than content, which is why a comment-only header change still needs naming here — the v9.13.0 row is the precedent. A consumer compiled against v9.20.0 headers links against exactly the same symbols with the same signatures and layouts; it simply does not have the new overload.
6v9.23.0operations/refine.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row. operations/refine.hpp gains one new inline constexpr const char* kRefineEntityName = "refine:entity"; (the kExodusAttributePrefix/kMdpaIdName pattern) plus doc-comment updates to kRefineHangingName and the new constant. No existing declaration is touched, removed or reordered: RefineOptions, RefineResult, RefineClosure, RefineCompare and the refine declaration are byte-identical to v9.22.0, no member is added, moved or reordered, no default argument moves, and the header defines no inline body to edit. A consumer compiled against v9.22.0 headers links against exactly the same symbols with exactly the same signatures and layouts — it simply does not have the new name. The release's behaviour changes (a later balanced pass reusing an existing entity node instead of allocating a coincident duplicate, and the hanging rule being evaluated over the emitted cells rather than the input cells' entities) are entirely .cpp-side, reachable through unchanged signatures, and confined to RefineClosure::Balanced — the conforming closures and uniform refinement produce byte-identical output to v9.22.0.
6v9.24.0operations/refine.hpp, detail/grid_lattice.hpp, detail/point_triangle.hpp, detail/surface_distance.hpp, detail/tri_box.hpp, operations/sdf.hpp, operations/voxelize.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row. The other six are entirely new headers: detail/grid_lattice.hpp declares the LatticeSpec aggregate and five MESHIOPLUSPLUS_API free functions; detail/point_triangle.hpp and detail/tri_box.hpp are header-only inline (a TriangleFeature enum, a PointTriangleHit aggregate and two inline functions between them, none exported); detail/surface_distance.hpp declares the TriangleSoup/DistanceQuery/DistanceHit aggregates, the SurfaceEdgeKey alias and its hash, and four MESHIOPLUSPLUS_API free functions; operations/sdf.hpp declares five enums, eleven inline constexpr const char* name constants, the SurfaceQuality/SurfaceDistanceOptions/SurfaceDistanceResult/SdfOptions/SdfResult aggregates and eight MESHIOPLUSPLUS_API free functions; operations/voxelize.hpp declares the VoxelFill enum, one name constant, the VoxelOptions/VoxelResult aggregates and three MESHIOPLUSPLUS_API free functions. Nothing pre-existing is touched anywhere in the installed tree: no declaration is changed or removed, no default argument moves, no inline body is edited, and no existing type gains, loses or reorders a member. A consumer compiled against v9.23.0 headers therefore links against exactly the same symbols with exactly the same signatures and layouts -- it simply does not have the new names. Two things were done up front rather than after the fact, both learning from the v9.12.0 row above: SdfOptions and VoxelOptions embed SurfaceDistanceOptions by value, so operations/sdf.hpp ships the octree fields populated-but-reserved (growing the inner struct later would shift both outer structs' tails, a silent Tier A break), and all four option/quality aggregates plus LatticeSpec are pinned in tests/cpp/test_abi_layout.cpp from this release rather than from the one after the mistake. The C ABI's own new structs (mio_sdf_opts, mio_voxel_opts, mio_surface_quality) live in bindings/c/, which this gate's src/cpp/include/meshioplusplus scope does not cover and which is a separate SOVERSION 0 + append-only-reserved contract; their sizes are static_asserted in c_api.cpp and mirrored by the Fortran and Julia layout guards. operations/refine.hpp is listed here only because the gate diffs against the last tag and v9.23.0 was never tagged, so that release's one added inline constexpr const char* kRefineEntityName is still in the window; it is reviewed on its own row above and nothing further changed in it.
6v9.25.0formats/vti.hpp, operations/crop.hpp, operations/refine.hpp, detail/grid_lattice.hpp, detail/point_triangle.hpp, detail/surface_distance.hpp, detail/tri_box.hpp, operations/sdf.hpp, operations/sequence.hpp, operations/voxelize.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row. formats/vti.hpp is an entirely new header: four MESHIOPLUSPLUS_API free functions (write_vti, write_vti_codec, read_vti, read_vti_metadata), no types, no inline bodies. detail/grid_lattice.hpp gains one new aggregate (LatticeRequest) and two new MESHIOPLUSPLUS_API free functions (lattice_resolve, lattice_from_mesh); LatticeSpec and the five pre-existing functions are byte-identical, so an already-compiled consumer links against exactly what it did before. operations/crop.hpp gains one new MESHIOPLUSPLUS_API free function, crop_predicate — a new declaration, not a defaulted parameter on an existing one, so crop_bbox/crop_halfspace/CropMode/CropResult keep their exact signatures and layouts; the header also gains an #include of operations/refine.hpp (for RefineCompare), which adds a name to the translation unit and changes no definition. operations/refine.hpp gains one new MESHIOPLUSPLUS_API bool refine_compare_value(double, RefineCompare, double) declaration; its definition moved out of refine.cpp's anonymous namespace, which is a .cpp change and adds a symbol rather than altering one — RefineOptions, RefineResult, RefineClosure, RefineCompare, refine, refine_closure_from_name and refine_compare_from_name are byte-identical. operations/sdf.hpp is the one to read carefully and it is still Tier C: its change is doc-comment only plus one enumerator's comment. SdfStructure::Octree, SdfOptions::mRootResolution/mMaxDepth/mBandCells/mRecordLevels and the compute_sdf declaration all existed verbatim in v9.24.0 — shipped populated-but-reserved precisely so that implementing them would be a pure .cpp change, which is what this release did. No member is added, moved, removed or reordered in any of the five aggregates, no enumerator value changes, no default argument moves, and no inline body is edited. MIO_ABI_LAYOUT in tests/cpp/test_abi_layout.cpp therefore needed no new line for any of them. A consumer compiled against v9.24.0 headers links against exactly the same symbols with exactly the same signatures and layouts; it simply does not have read_vti/write_vti/crop_predicate/lattice_resolve/lattice_from_mesh/refine_compare_value, and compute_sdf — whose symbol it already had — now returns rather than throwing.
6v9.27.0version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row -- and it is the only installed header this window touches (v9.25.0..v9.27.0, since neither v9.26.0 nor v9.27.0 was tagged, so the gate diffs the whole span at once). Both releases in the span (to_pandas/to_polars in v9.26.0; edge_index/feature_matrix/write_dataset/to_torch/to_jax closing the roadmap's ML section in v9.27.0) are pure Python over existing machinery — src/python/meshioplusplus/_ml.py, _gpu.py's new to_torch/to_jax, _interop.py's to_pandas/to_polars/_frame_columns — and touch no C++ source, no binding, no installed header at all. A consumer compiled against v9.25.0 headers links against exactly the same symbols with exactly the same signatures and layouts.
6v9.30.0version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row -- and it is the only installed header this window touches (v9.27.0..v9.30.0, since neither v9.28.0 nor v9.29.0 was tagged, so the gate diffs the whole span at once). All three releases in the span close the roadmap's PhysicsNeMo section: v9.28.0 (the meshioplusplus.physicsnemo adapter and DatasetManifest), v9.29.0 (the browser dataset-manager page) and v9.30.0 (t->t+1 target pairing, the gated physicsnemo.mesh.Mesh bridge, persisted directory handles and per-entry quality summaries in that UI). Every one of them is pure Python (src/python/meshioplusplus/physicsnemo/, _dataset.py, the dataset CLI group, three MCP tools) or TypeScript (src/viewer/src/dataset/, src/viewer/src/worker/*, calling only the already-bound attachQuality/sequenceEntries/timeseriesToSequence/readMeshSelective) over existing machinery, and none of it touches C++ source, a binding, or any other installed header. A consumer compiled against v9.27.0 headers links against exactly the same symbols with exactly the same signatures and layouts.
6v10.0.0version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row -- and it is again the only installed header this window touches (still v9.27.0..HEAD, since v9.28.0/v9.29.0/v9.30.0 were never tagged either, so the gate keeps diffing the same span it did for the row above). This release is a pure version-number bump: MESHIOPLUSPLUS_VERSION_MAJOR 9 -> 10, MINOR/PATCH reset to 0/0, no other line in version.hpp or anywhere else in the installed tree changed. The functional content of the span (v9.28.0-v9.30.0's PhysicsNeMo work) is already reviewed on the v9.30.0 row directly above; this row exists only because the release-version token the gate matches against moved from 9.30.0 to 10.0.0, so that row's exact-token match no longer applies and a fresh one is required. A consumer compiled against v9.30.0 headers links against exactly the same symbols with exactly the same signatures and layouts -- the major version number is not itself part of any symbol's mangling or any type's layout.
7v10.9.0detail/data_ops.hpp, detail/spatial_hash.hpp, operations/conservative_interpolate.hpp, operations/data_integrate.hpp, operations/hessian.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row -- and this row covers the whole span since the last tag, v10.6.0..HEAD, since neither v10.7.0 nor v10.8.0 was tagged, so the gate diffs all three releases (conservative interpolation, field integration and the Hessian operation) at once. operations/conservative_interpolate.hpp, operations/data_integrate.hpp and operations/hessian.hpp are entirely new headers: conservative_interpolate.hpp declares the ConservativeInterpolateConflict enum, its _from_name parser, the ConservativeInterpolateOptions aggregate and conservative_interpolate(); data_integrate.hpp declares the FieldIntegralRegion/FieldIntegralArray/DataIntegrateReport/DataIntegrateOptions aggregates and data_integrate(); hessian.hpp declares one inline constexpr const char* kHessianSuffix, the HessianOptions/HessianResult aggregates and hessian() (itself implemented as two internal calls to the pre-existing gradient(), so it adds no new dependency on operations/gradient.hpp's declarations, which are byte-identical). detail/data_ops.hpp gains one new aggregate (WeightedSum, with inline Add/Merge/Mean members -- data_integrate's per-component reducer) and one new MESHIOPLUSPLUS_API free function, accumulate_weighted; every pre-existing declaration in the header (FiniteStats, accumulate_stats, combine_components, cell_measure, ...) is byte-identical. detail/spatial_hash.hpp gains one new template member function, SpatialGrid::ForEachInBox (the read-side twin of the pre-existing InsertBox, added for conservative_interpolate.cpp's bucket-grid overlap queries), plus a doc-comment addition describing it; GridKey, GridKeyHash, grid_quantize, Find, InsertBox, ForEachIn27 and ForEachInShell are byte-identical. Nothing pre-existing is touched anywhere in the installed tree: no declaration is changed or removed, no default argument moves, no existing inline body is edited, and no existing type gains, loses or reorders a member. A consumer compiled against v10.6.0 headers therefore links against exactly the same symbols with exactly the same signatures and layouts -- it simply does not have the new names. All three operations are documented as C++-core-only or Python-fallback-only in doc/conservative_interpolate.md/doc/field_integration.md/doc/hessian.md, and none is under any MESHIOPLUSPLUS_ABI_VERSION-relevant discrete-branch risk in an installed header -- the geometric-clip and winding-repair logic the release notes describe lives entirely in the corresponding .cpp files.
7v10.10.0operations/remesh.hpp, detail/decimate_common.hpp, version.hppversion.hpp is the routine release bump (macro values only, no declaration), as in every prior row. operations/remesh.hpp is an entirely new header: it declares the RemeshMetric enum, its _from_name parser, the RemeshOptions/RemeshResult aggregates and two MESHIOPLUSPLUS_API free functions (remesh, remesh_suggest_subdivide). detail/decimate_common.hpp gains one new MESHIOPLUSPLUS_API free function, decim_quadric_optimal_point(const double q[10], const double pFallback[3], double pOut[3]) -- the cofactor-solve-with-fallback logic hoisted out of decim_place's Optimal branch so remesh's quadric-metric cluster placement can share it. decim_place's own declaration is byte-identical (same three parameters, same DecimPlaced return type); only its definition in decimate_common.cpp changed to call the new function instead of inlining the solve, which is a .cpp-only change and does not touch this header at all. Nothing else pre-existing in either header is touched: no declaration is changed or removed, no default argument moves, no existing inline body is edited, and no existing type (DecimFaces, DecimCsr, DecimPlaced, DecimPlaceCtx) gains, loses or reorders a member. A consumer compiled against v10.9.0 headers therefore links against exactly the same symbols with exactly the same signatures and layouts -- it simply does not have remesh/remesh_suggest_subdivide/decim_quadric_optimal_point. The hoist's behaviour-preservation for decimate/decimate_volume (both consumers of decim_place) is verified by their full existing gtest suites, run unmodified both before and after -- the decimate_volume/v10.6.0 hoist precedent for this same file.

| 10 | v10.15.0 | detail/provenance.hpp, version.hpp | version.hpp is the routine release bump (macro values only, no declaration), as in every prior row. detail/provenance.hpp is an entirely new header: it declares one inline constexpr const char* kProvenanceTag, built at compile time from version.hpp's MESHIOPLUSPLUS_VERSION_STRING (the canonical one-line credit every format writer now emits, replacing the ad hoc per-writer strings that had drifted between the C++ core and its Python twins). No existing declaration, aggregate, enumerator or inline body anywhere in the installed tree is touched. A consumer compiled against v10.14.0 headers therefore links against exactly the same symbols with exactly the same signatures and layouts -- it simply does not have the new name. |

| 10 | v10.16.0 | detail/provenance.hpp, version.hpp | version.hpp is the routine release bump (macro values only, no declaration), as in every prior row. detail/provenance.hpp grows from v10.15.0's single kProvenanceTag constant into the full opt-in record: two new enums (ProvenanceMode, SlotTier), two new aggregates (ProvenanceNote, ProvenanceRecord), one new RAII class (ProvenanceScope), and ten new MESHIOPLUSPLUS_API free functions (current_provenance_mode, current_provenance, provenance_note, provenance_set_source, provenance_set_target, provenance_add_operation, provenance_lines, provenance_render_lines, provenance_render_xml_comment, provenance_timestamp). kProvenanceTag itself is byte-identical to v10.15.0 -- same declaration, same value, same position in the file. No existing declaration, aggregate, enumerator or inline body anywhere in the installed tree is touched, and no existing type gains, loses or reorders a member. A consumer compiled against v10.15.0 headers therefore links against exactly the same symbols with exactly the same signatures and layouts -- it simply does not have the new names. The runtime mechanism this header declares (a thread-local scope stack) lives entirely in the new src/cpp/src/detail/provenance.cpp, a .cpp file and therefore outside this gate's src/cpp/include/meshioplusplus scope regardless. |

Five further headers are named above only because the gate diffs against the last tag (v9.22.0) and neither v9.23.0 nor v9.24.0 was tagged, so their changes are still in the window. detail/point_triangle.hpp, detail/surface_distance.hpp, detail/tri_box.hpp and operations/voxelize.hpp are v9.24.0's entirely-new headers and are reviewed on that release's row above; nothing in them changed here beyond one comment in operations/voxelize.hpp (a "41 format tables" count becoming 42). operations/sequence.hpp's change in this release is likewise comment-only — the same format count in its file-level doc block — with every declaration, aggregate and enumerator byte-identical; the gate keys on paths rather than content, which is why it still needs naming (the v9.13.0 row is the precedent). The C ABI's own new struct (mio_compute_sdf_opts) and two new entry points live in bindings/c/, outside this gate's src/cpp/include/meshioplusplus scope and under the separate SOVERSION 0 + append-only-reserved contract; sizeof(mio_compute_sdf_opts) is static_asserted in c_api.cpp at 232 and mirrored by the Fortran and Julia layout guards. |

| 11 | v10.18.0 | formats/gid.hpp, version.hpp | version.hpp is the routine release bump (macro values only, no declaration), as in every prior row. formats/gid.hpp is an entirely new header: it declares the GidMode enum, three MESHIOPLUSPLUS_API free functions (gid_mode_from_name, gid_available, gid_build_option) and one MESHIOPLUSPLUS_API writer (write_gid), backing the new write-only gid format (GiD postprocess, on a vendored gidpost 2.14). No existing declaration, aggregate, enumerator or inline body anywhere in the installed tree is touched, and no existing type gains, loses or reorders a member. A consumer compiled against v10.17.0 headers therefore links against exactly the same symbols with exactly the same signatures and layouts -- it simply does not have the new names. The vendored gidpost C sources (src/cpp/third_party/gidpost/) are build-internal (formats/gid.cpp only, PRIVATE include dir, mirroring the pugixml/nlohmann-json precedent) and never reach an installed header. |

| 11 | v10.19.0 | formats/gid.hpp, version.hpp | version.hpp is the routine release bump (macro values only, no declaration), as in every prior row. formats/gid.hpp gains three new MESHIOPLUSPLUS_API declarations for the GiD reader -- read_gid, read_gid_metadata and gid_readable -- plus a new #include of read_options.hpp (for the ReadOptions/MeshMetadata those signatures name). Every pre-existing declaration is untouched: the GidMode enum has the same four enumerators with the same values, and gid_mode_from_name/gid_available/gid_build_option/write_gid keep their exact signatures and default arguments -- in particular gid_available deliberately keeps its v10.18.0 meaning (write capability) rather than being redefined, with gid_readable added as the read-side twin, precisely so an already-compiled consumer's behaviour cannot change. No aggregate gains, loses or reorders a member. A consumer compiled against v10.18.0 headers therefore links against exactly the same symbols with exactly the same signatures and layouts -- it simply does not have the three new names. The reader's own translation units (src/cpp/src/formats/gid_read.cpp and the format-private src/cpp/src/formats/gid_common.hpp) live outside src/cpp/include/meshioplusplus/ and so are outside this gate's scope entirely. Same release, additionally: gid.hpp gains a new enum class GidResultType (nine enumerators, std::int64_t underlying type -- deliberately meshio++'s own rather than a reuse of gidpost's GiD_ResultType, since this header must stay free of gidpost, which the reader is built without), one inline constexpr const char* (kGidResultTypePrefix), and three further MESHIOPLUSPLUS_API declarations (gid_result_type_name, gid_result_type_from_name, gid_result_dim_is_legal). All additive: no existing declaration, enumerator or aggregate is touched, and the new enum's values are static_asserted against gidpost's GiD_ResultType in gid.cpp (a compile-time check, not a runtime one, so drift is caught in this repo's own build rather than becoming an ABI concern). A consumer compiled against an earlier v10.19.0 snapshot of this header still links against exactly the same symbols with exactly the same signatures -- it simply does not have the four new names. Same release, additionally: gid.hpp gains two further inline constexpr const char* constants (kGidGaussPointsPrefix, kGidGaussCoordsPrefix) backing arbitrary Gauss-point-per-element support. Both are additive compile-time constants with no linkage of their own; no existing declaration, enumerator or aggregate is touched, and the Gauss-point machinery itself lives entirely in the non-installed src/cpp/src/formats/gid_common.hpp and the two format .cpp files, outside this gate's scope. A consumer compiled against an earlier v10.19.0 snapshot links against exactly the same symbols with the same signatures and layouts. Same release, additionally: GidMode gains an appended enumerator AsciiZipped = 4. The enum already declares an explicit : int underlying type, so its size and the values of every pre-existing enumerator are unchanged; appending is Tier C additive under doc/abi.md's own rule. No aggregate, signature or inline body is touched, and a consumer compiled against an earlier snapshot behaves identically — it simply never names the new mode. Same release, additionally: gid.hpp gains one further MESHIOPLUSPLUS_API free function (write_gid_series, the multi-step writer) plus an #include <functional> for the provider type it names. Purely additive -- deliberately a free function rather than a stateful class, so no new aggregate exists to have a layout at all, and no existing declaration, enumerator or inline body is touched. A consumer compiled against an earlier snapshot links against the same symbols with the same signatures. |

| 11 | v10.20.0 | formats/gid.hpp, version.hpp | version.hpp is the routine release bump (macro values only, no declaration), as in every prior row. formats/gid.hpp itself is untouched by this release -- it changed earlier in the same v10.17.0..HEAD span (the v10.18.0 and v10.19.0 rows above already review every one of those changes in full: the new header, the reader declarations, GidResultType, the Gauss-point constants, AsciiZipped, write_gid_series), and is named here only because this gate's base is still the last real tag (v10.17.0 -- neither v10.18.0, v10.19.0 nor this release has been tagged), so the diff span keeps accumulating and this release's row must account for the whole span, not just what it itself changed. This release's own work -- a new MESHIOPLUSPLUS_ZLIB_STATIC CMake option that vendors a pinned, FetchContent-fetched static zlib instead of find_package(ZLIB), and the cli.yml/wheels.yml flag flips that consume it so the release CLI binaries and every published wheel gain gid write support -- lives entirely in CMakeLists.txt and the two GitHub Actions workflow files: build/CI configuration, not C++ source, touching no header, binding or declaration anywhere. No existing type, enumerator, default argument or inline body changed in this release. A consumer compiled against v10.19.0 headers links against exactly the same symbols with exactly the same signatures and layouts. |

| 11 | v10.20.1 | version.hpp | version.hpp is the routine release bump (macro values only, no declaration), as in every prior row -- and it is the only installed header this release touches (v10.20.0 was tagged, so the gate's base moved and formats/gid.hpp drops out of the diff). The two fixes it ships -- the Windows CLI's zlibstatic target not inheriting MESHIOPLUSPLUS_STATIC_RUNTIME's /MT CRT setting, and a WASM provenance leak (writeMesh/convert/convertSurface/convertSurfaceOps not resetting the scope-less provenance record before writing) -- live entirely in CMakeLists.txt and bindings/wasm/js_bindings.cpp, neither of which is under src/cpp/include/meshioplusplus. A consumer compiled against v10.20.0 headers links against exactly the same symbols with exactly the same signatures and layouts. |

| 11 | v10.20.2 | version.hpp | version.hpp is the routine release bump (macro values only, no declaration), as in every prior row -- and, again, the only installed header this release touches. This release carries no code change at all: v10.20.1's tag push was lost to a GitHub Actions outage (the wasm/wheels/ci workflows never started a run for it, and packages's jobs all came back cancelled), so v10.20.2 simply re-cuts the identical tree under a new tag so the release workflows get a clean run. A consumer compiled against v10.20.1 headers links against exactly the same symbols with exactly the same signatures and layouts. | | 11 | v10.21.0 | version.hpp | version.hpp is the routine release bump (macro values only, no declaration), as in every prior row -- and, again, the only installed header this release touches. This release adds a Blender add-on: a new pure-Python module src/python/meshioplusplus/_blender.py, a new public meshioplusplus.formats() in _helpers.py, a backward-compatible qualifier= keyword on the private _gpu._require_framework, and a Blender 4.2+ extension under src/blender/ with its build script and CI jobs. Every one of those is Python, packaging or CI: nothing under src/cpp/include/meshioplusplus other than the version macros changed, no binding was touched, and no C++ declaration, type, enumerator, default argument or inline body changed anywhere. A consumer compiled against v10.20.2 headers links against exactly the same symbols with exactly the same signatures and layouts. | | 11 | v10.21.1 | version.hpp | version.hpp is the routine release bump (macro values only, no declaration), as in every prior row -- and, again, the only installed header this release touches. This release is documentation and tooling only: a generated SVG/PNG diagram pipeline under doc/diagrams/, a rewritten doc/roadmap.md, a new doc/architecture.md overview page, sidebar regrouping and several stale-count/broken-link fixes. Nothing under src/cpp/include/meshioplusplus other than the version macros changed, no binding was touched, and no C++ declaration, type, enumerator, default argument or inline body changed anywhere. A consumer compiled against v10.21.0 headers links against exactly the same symbols with exactly the same signatures and layouts. |

Released under the MIT License.