v0.8.0 final pre-release audit and remediation issue backlog
Source snapshot: e4cef21b66d0e22661307a3fc7e246e71611517a (pyvoro2-e4cef21.zip)
Declared version: 0.8.0.dev0
Final audit date: 2026-07-28
Status: completed historical audit and remediation record
Historical snapshot verdict: do not tag or publish v0.8.0 yet
This document replaced the earlier repository-review findings and their first-pass addendum as the remediation backlog for the audited snapshot. Its findings, issue contracts, and original verdict are preserved as historical evidence, not as the verdict for the finalized v0.8 release source.
The findings below retain the original audit snapshot and evidence. Current source references inside the preserved findings describe that audited snapshot unless a later resolution paragraph explicitly says otherwise.
Final resolution
R1–R9 were subsequently completed and independently accepted. The post-R9
correction that makes wheel and sdist distributions carry and verify the
mandatory COPYING payload was also accepted. One exact final release-source
commit is frozen only after the source-finalization pass and independent
review; issue #33 is the artifact/source qualification gate for that exact
commit before the public tag is created. Any tracked correction changes the
candidate and requires renewed review and qualification.
The audit's earlier expectation of a v0.8 Zenodo archive is superseded: v0.8.0 intentionally creates no new pyvoro2 Zenodo software-version record. Existing historical pyvoro2 and project/reproducibility Zenodo records remain valid.
The supplied CPython 3.13 manylinux wheel is not treated as a failed release
artifact. It was intentionally built as a development-only donor of compiled
_core and _core2d modules. Donor-wheel handling is a development-process
matter, not a source defect, provided that donor binaries are kept separate
from release artifacts and are never used for final qualification.
1. Final assessment
The project does not require another architectural rewrite. Its canonical forward and inverse namespaces, mathematical decomposition, documentation structure, and release tooling are strong. A fresh native build of the exact reviewed source passed the existing deterministic, fuzz, tooling, and notebook checks.
The release is nevertheless unsafe in its current state. Several adversarial inputs can:
- solve or report a different inverse objective from the one documented;
- return a grossly nonoptimal scalar proximal result as
optimal; - corrupt the native heap through
init_mem=0; - select a non-nearest periodic image in a triclinic cell while only warning;
- let Voro++ terminate the entire Python process despite the public duplicate controls;
- silently omit non-periodic generators and return the wrong clipped diagram;
- combine observations, fitted values, realization diagnostics, or reports that originate from different data states; or
- pass “strict” diagnostics despite missing or malformed data.
These defects are concentrated enough to remediate systematically. Umbrella issue #35 contains nine child issues, R1–R9. Separate issue #33 performs final qualification only after #35 is closed:
- R1–R5: release-blocking mathematical and native/geometry safety work;
- R6–R8: required inverse-result and diagnostic-integrity work;
- R9: public API, documentation, licensing, and release-contract cleanup;
- #33: clean artifact qualification and release gate after remediation.
Small presentation, performance, and convenience findings should be revisited in a focused polish audit after R1–R8, when the final behavior is stable.
2. Verification performed
The final review used native extensions compiled from the exact source snapshot, not the development donor wheel.
| Check | Result |
|---|---|
| Fresh-source deterministic test suite | 460 passed, 2 skipped |
| Fuzz/property suite | 8 passed, 2 skipped |
| Tooling tests | 58 passed |
| Notebook validation | 8 notebooks validated |
| Targeted numerical/objective probes | Confirmed the previously reported inverse defects |
| Subprocess native-safety probes | Confirmed heap corruption and process termination paths |
| Random triclinic nearest-image audit | Found five wrong default-image selections in 47 trials on one moderately skewed cell |
The official isolated package build, complete wheel matrix, strict documentation
build, and final lint run were not repeated in this audit environment. The zip
also contains no .git history. Those are explicit #33 gates rather than
assumed successes.
Verification logs retained with this audit include:
pyvoro2-final-review-fresh-native-pytest.log;pyvoro2-final-review-fuzz.log;pyvoro2-final-review-tooling.log;pyvoro2-final-review-notebooks.log;pyvoro2-v0.8-audit-probes.txt;pyvoro2-final-review-new-probes.log;pyvoro2-final-review-init-mem-cases.log;pyvoro2-final-review-triclinic-random-audit.log.
3. Material corrections to the earlier review
3.1 Development donor wheel
The earlier wheel-mismatch finding is withdrawn as a release defect. The wheel was intentionally used only as a source of compatible compiled extension modules while the Python source changed. Final distributions must still be built from the exact release commit, and #33 must verify that no donor binary is present in the release checkout or artifact directory.
3.2 Active project scope
The repository’s accepted v0.8 plan and ADR correctly define v0.8 as a feature-free technical-maintenance release. The April external backlog and architecture specification describe an earlier sequence in which prescribed cell measures were assigned to v0.8. They are historical planning inputs, not the current source contract. Any published copy should be marked superseded; prescribed measures remain outside this remediation.
3.3 Severity focus
The earlier audit intentionally included lower-priority maintainability and presentation findings. This final backlog promotes only work that is required to make v0.8 scientifically, operationally, and distributively safe. Deferred items are listed at the end rather than mixed into the release-blocking work.
4. Significant findings added or strengthened by the final audit
F-A — init_mem=0 can corrupt native memory
init_mem=0 and init_mem=False reach native container construction. In 3D,
a two-site computation reproducibly aborted with allocator corruption (exit
code -6, for example free(): invalid next size (fast)). In the vendored 2D
container, zero-length arrays are allocated; the growth path doubles zero to
zero and then writes at index zero. A simple 2D call may appear to succeed, but
it relies on undefined behavior. Negative values reach native allocation and
surface as low-level MemoryError exceptions.
This is a release blocker. Exact positive-integer validation is required in all public operations and again in C++ before any Voro++ container is constructed. Subprocess regression tests are mandatory.
Assigned issue: R3.
F-B — Default triclinic nearest-image inference is not correct
The current 3D triclinic resolver searches only the coefficient cube
[-image_search, image_search]^3, with default image_search=1. A best
candidate on the cube boundary triggers a warning but is still returned.
A confirmed example uses:
PeriodicCell.from_params(1, 1.5, 1, 0, 0, 1)
pi = [0.63696169, 0.26978671, 0.04097352]
pj = [0.01652764, 0.81327024, 0.91275558]
The default search chooses shift (1, 0, -1) at distance
0.6751921933; a wider search finds (2, -1, -1) at distance
0.4892356160. The inferred fitted weight difference changes accordingly.
A randomized comparison against exhaustive coefficient search found five
failures in 47 trials on a cell with condition number about 9.8.
Warning that a result may be wrong is not an adequate implementation of a function documented as nearest-image inference. The package needs a certified three-dimensional closest-lattice-vector calculation or an adaptive search with a correctness certificate.
Assigned issue: R4.
F-C — Public duplicate controls do not guarantee process safety
For a fully periodic 3D cell, two lattice-equivalent sites caused Voro++ to
print Duplicate and terminate Python when duplicate_check='off'. The same
happened with duplicate_check='warn': the wrapper warned and then allowed the
known fatal call to continue. A user-supplied threshold below Voro++’s native
safety distance, or duplicate_wrap=False, can also bypass the pre-check.
The duplicate policy therefore needs two layers:
- an unconditional backend-safety floor that always raises before native code;
- an optional user-configured near-duplicate policy above that floor.
Exact and backend-fatal duplicates must never be allowed to proceed under an
off or warn mode.
Assigned issue: R5, using the certified periodic-distance primitive from R4.
F-D — Frozen domain objects retain mutable caller data
Box, OrthorhombicCell, RectangularCell, and PeriodicCell validate their
inputs but retain caller-owned nested lists. Mutating the original bounds,
vectors, or origin later mutates the supposedly frozen domain and can invalidate
previous validation. Domain constructors must canonicalize and own immutable
numeric values.
Assigned issue: R3.
F-E — The native non-periodic containment convention is half-open
The native container accepts the lower bound and rejects the exact upper bound:
[lo, hi). A generator just below hi is accepted, while a generator exactly
at hi is silently omitted. The wrapper must validate this convention before
native insertion and apply it consistently to generator sets in compute,
locate, ghost_cells, inverse realization, and active-set workflows.
Assigned issue: R5.
F-F — Non-finite normalization tolerances can silently destroy topology
Planar topology normalization accepts normalization_tol=NaN or infinity. A
NaN path can emit only a NumPy warning while collapsing distinct vertices into
one global vertex. Finiteness/range validation must be centralized and applied
before normalization or diagnostics.
Assigned issue: R3 for input validation and R8 for diagnostic/normalization semantics.
F-G — Planar strict topology semantics contradict issue severity
Some normalized-topology conditions are emitted as warnings but still make the overall result false, so strict validation raises on warning-only findings. The 3D diagnostic layer instead derives failure from error-level conditions. A single severity contract is required across dimensions.
Assigned issue: R8.
5. Remediation issue index
The R-identifiers below are stable workstream identifiers. Focused GitHub child issues created from these contracts should link back to umbrella issue #35.
| ID | GitHub issue title | Priority | Depends on | Intended implementation unit |
|---|---|---|---|---|
| R1 | Correct and freeze the separator inverse objective contract | P0 | None | One design-gated issue and PR |
| R2 | Replace the scalar proximal loop with a certified bounded solver | P0 | R1 | One issue and PR |
| R3 | Harden public inputs, immutable value objects, and native preconditions | P0 | R1, R2 recommended | One issue; two sequential PRs/prompts permitted |
| R4 | Implement certified periodic nearest-image and minimum-image geometry | P0 | R3 | One issue and PR |
| R5 | Make forward generator containment and duplicate safety non-optional | P0 | R3, R4 | One issue and PR |
| R6 | Bind observations, fits, realizations, and reports to canonical source data | P1 | R3, R4 | One design-gated issue and PR |
| R7 | Make the active-set final state atomic and self-consistent | P1 | R1, R2, R6 | One issue and PR |
| R8 | Make diagnostics and strict validation severity-complete | P1 | R3, R5 | One issue and PR |
| R9 | Synchronize the public API, documentation, distribution, and release contract | Release required | R1–R8 | One issue and PR after a short polish audit |
After all nine R1–R9 children are complete, umbrella issue #35 closes. Qualification then proceeds under separate issue #33; it is not a child of
35 and does not begin while remediation remains open.
R1 — Correct and freeze the separator inverse objective contract
Milestone: v0.8.0 — technical maintenance and Python 3.14
Suggested labels: type:solver, type:api, type:tests, obs:separator,
release-blocker, breaking-change-risk
Dependencies: None
Implementation shape: one design-gated issue and one focused PR
Summary
Make every separator inverse backend, derivative, proximal subproblem, and reported objective describe one explicitly approved mathematical objective. The current implementation has three incompatible objective conventions and a scale-insensitive hard-bound check.
Confirmed defects
- The ADMM weight update uses
lambda * Iandlambda * w0for a reported regularizerlambda * ||w - w0||^2; the update requires a factor of two under that definition. Analytic and sparse solvers agree, while ADMM returns different weights and still reportsoptimal. ReciprocalBoundaryPenaltyclips the value denominator into a flat plateau but retains nonzero gradient and Hessian formulas on the plateau. At a default boundary, finite differences give zero gradient while the analytic gradient is about-1e12.SquaredLossusesr^2, while the quadratic branch ofHuberLossuses0.5*r^2. Changing only the loss class therefore changes the relative strength of regularization and penalties by a factor of two.- Hard-constraint satisfaction uses a fixed absolute
1e-12threshold that is not meaningful across the supported geometric scales. - Objective evaluation forms unweighted residual squares, unscaled candidate/reference differences, and inactive Huber branches before applying confidence or L2 strength, losing finite weighted binary64 values.
- Quadratic rows form
confidence * (alpha * alpha)and reconstruct the RHS asrho * z_obs; both groupings can lose finite curvature or RHS values. - Bellman–Ford applies the measurement-space absolute tolerance directly to weight-difference path distances, producing both false feasibility and false infeasibility.
- Reciprocal evaluation forms underflowing powers and computes unused derivatives during value-only evaluation for valid tiny parameters.
- ADMM can meet primal/dual tolerances and report success while its returned weights fail the authoritative hard-row predicate. Direct norm/RMS reductions can also overflow for finite results.
- Follow-up range-boundary review found remaining non-compositional arithmetic in Huber linear values, positive soft-interval terms, reciprocal quotient differences, affine residuals, quadratic-operator matvecs, and ADMM linear combinations. It also found that ADMM projected onto exact user bounds rather than the tolerance-expanded accepted set and that unconditional scalar row loops caused order-of-magnitude sparse-fit regressions.
The manuscript’s basic least-squares model uses the conventional half-factor, but the package’s public compatibility and the exact regularizer convention must be approved deliberately rather than inferred by the coding agent.
Approved implementation contract
The maintainer decisions requested by this audit are approved in issue #36 and recorded durably in ADR 0007:
- squared mismatch and the Huber quadratic branch use
0.5 * residual**2; - L2 is
0.5 * strength * ||weights - reference||**2, so the normal system remainsL_obs + strength * Iwith no extra factor of two; - soft-interval and exponential strengths retain their existing meanings;
- reciprocal penalties use the finite convex tangent continuation below
epsilon, with the exact branch formulas in ADR 0007; - zero-strength scalar penalties are mathematically absent;
- hard restrictions use the shared float64
1e-12 + 64 * eps * max(abs(values))comparison policy and reporthard_max_tolerance; - successful solver results require finite reported soft objectives, and the optional analytic ADMM warm start falls back only on its supported linear-algebra failure; and
- weighted mismatch and L2 evaluation preserves every finite representable value, with zero-confidence/zero-strength terms checked first;
- quadratic curvature and RHS rows are constructed directly and scale-safely
as
rho = confidence * alpha**2andq = confidence * alpha * (target - beta); - the hard predicate's accepted measurement interval is mapped into difference bounds before Bellman–Ford, with no second measurement tolerance in difference units;
- reciprocal value and derivatives are evaluated separately with scale-safe reciprocal products;
- hard-constrained ADMM success requires final hard-row satisfaction, and residual/convergence summaries use stable reductions;
- affine residuals, Huber linear values, positive soft-interval terms, reciprocal differences, operator matvecs, and ADMM combinations preserve finite complete expressions without materializing overflowing parts;
- ADMM projection uses the same tolerance-expanded measurement interval as the Bellman–Ford precheck and final hard predicate;
- bounded exact helpers use the same continuous-objective forward-gap rule as every other quadratic path; coordinatewise binary64 rounding is candidate generation, not proof of an optimum over a discrete lattice;
- ADMM numerical failures after completed iterations retain those iterations in public termination metadata;
- final active-set alignment is restricted to zero-L2 gauge components and is accepted only when exact binary64-input arithmetic proves that all component differences are unchanged; and
- these are v0.8 prerelease correctness corrections with no legacy mode.
Scope
- Add one authoritative mathematical/objective specification in code comments and current documentation.
- Correct ADMM mismatch/L2 relative scaling while retaining the existing
L_obs + strength * Imatrix and right-hand side under the approved half-factor convention. - Make value, gradient, Hessian, proximal objective, and report evaluation call a shared coherent scalar-penalty definition.
- Reject invalid reciprocal-penalty parameter combinations or define their exact mathematical meaning.
- Align squared and large-delta Huber behavior, or document and test an intentional difference.
- Introduce an explicit scale-aware hard-bound tolerance policy and report the tolerance used.
- Centralize stable scalar arithmetic, direct affine residual and quadratic-row construction, hard accepted-set mapping, and shared statistical reductions. Ordinary rows use vectorized arithmetic, with exceptional range-boundary rows taking the scale-safe fallback.
- Require hard-constrained ADMM termination to satisfy both iterative tolerances and the final measurement predicate, while preserving completed iteration metadata if final certification returns structured failure.
- Keep the bounded exact helper subordinate to the universal continuous objective certificate; do not add a binary64-lattice success route.
- Prevent final active-set gauge alignment from changing a positive-L2 solution or any fitted binary64 component difference.
- Explain the optional analytic warm-start fallback and add a characterization test showing that warm-start failure is not final solver failure.
- Update docstrings, theory/guide text, API inventory, changelog, and objective breakdown wording affected by the decision.
Non-goals
- Replacing the scalar proximal algorithm; that is R2.
- Adding new loss or penalty families.
- Broadly redesigning result classes or observation identity.
- Changing the active-set algorithm.
Required regression oracles
- Analytic, sparse, and ADMM parity with nonzero L2 strength and nonzero reference weights.
- Connected and disconnected observation graphs.
- Direct objective recomputation from returned weights equals every reported objective component and total.
- First- and second-derivative finite differences on both sides of every reciprocal-penalty branch and in branch neighborhoods.
- Large-delta Huber equivalence to squared loss under the approved convention.
- Zero-strength penalty invariance: adding a mathematically zero term must not change the solution or merely force a different objective.
- Hard-bound classification at small, ordinary, and large magnitudes.
- Extreme-scale finite weighted mismatch/L2 values and direct quadratic curvature/RHS rows, using Decimal or closed-form oracles.
- Large- and small-scale cyclic hard systems whose measurement-space accepted set contradicts a difference-space absolute tolerance.
- Tiny reciprocal parameters under strict floating-point error handling.
- Hard-constrained ADMM termination and large finite norm/RMS reductions.
- NaN or infinity must never produce
status='optimal'with a non-finite objective.
Acceptance criteria
- All solver paths minimize the same approved objective within documented tolerances.
- The two-site regularization counterexample gives backend-equivalent weights and objective values.
- Scalar penalty derivatives agree with independent finite differences away from intentionally nondifferentiable points.
- Public documentation contains the exact formulas implemented.
- Existing expected numerical changes are documented as corrections rather than hidden in a refactor.
Codex stop conditions
Codex must stop and request maintainer input if the issue/prompt does not state the exact objective formulas, reciprocal rule, or compatibility policy. It must not choose these scientific/API decisions itself.
R2 — Replace the scalar proximal loop with a certified bounded solver
Milestone: v0.8.0 — technical maintenance and Python 3.14
Suggested labels: type:solver, type:tests, obs:separator,
release-blocker
Dependencies: R1
Implementation shape: one issue and one focused PR
Summary
Replace the fixed-iteration projected Newton loop used for scalar ADMM proximal coordinates. Every returned coordinate must prove exact point KKT signs or localize the unique continuous minimizer between adjacent numeric binary64 values, or the solver must return a structured numerical failure.
Confirmed defect
The current routine performs up to 60 Newton steps without a bracket, line search, objective-decrease safeguard, or final stationarity check. It returns the last iterate when the loop exhausts.
Confirmed examples include:
- reciprocal penalty, target fraction
0: returned prediction0and reported objective999980, while an independent scalar search gives a minimum near0.05with objective0.0025; - exponential penalty, target
-2: returned approximately-1.1with objective about4.38e48, while an independent minimum is near0.0519with objective about4.25.
Both calls reported success.
An independent review of the first R2 implementation at a223979 found three
additional release blockers:
- private scalar exponential evaluation used complete exact source expressions while public values and reports rounded derived boundaries first;
- a contribution-scale KKT tolerance certified
y=-0.1even though exact cancellation placed the unique minimizer near-0.01220703125; and - fixed 180-digit
Decimallogarithms and exponentials ran in every ordinary scalar iteration, taking about 17 seconds for only 100 active exponential rows on the review host.
An independent review of the revised implementation at 7a012bf found two
remaining certificate violations in its ordinary enclosure model:
- exponential range reduction multiplied already-rounded
ln(2)limbs and used a heuristic epsilon-squared radius; a frozen cancellation case returned an adjacent bracket whose two exact derivatives were both negative; and - the terminal Huber comparison subtracted complete branch values and a cancelled double-double center derived its radius from only the final limbs; a frozen adjacent pair therefore selected the lower endpoint even though the exact rational objective difference was negative and required the upper endpoint under the solver's difference convention.
The R2 correction must consequently use derived outward balls from exact binary64 source values through every certified operation. A compensated center or agreement with a high-precision nearest-rounded value is not by itself a sign certificate.
Review of the integrated heterogeneous path also found that process-wide NumPy exception settings could turn array underflow into a whole-batch Python exception, reciprocal rows entered unsupported proposal arithmetic, and batch successes did not retain the scalar solver's reconstructable certificate evidence. The remediation boundary is fail-closed and lane-local: supported array arithmetic has a private exception policy, unresolved lanes alone route to scalar certification, unsupported reciprocal rows never enter it, and all successes retain common derivative, terminal-difference, selection, and localization evidence.
The historical finding above is retained. On the post-R1 issue #37 baseline,
the two source-controlled reproductions are: reciprocal y=0 with
F=1,999,980 versus y*=0.05, F=0.0025; and exponential
y=-1.3999999999999995 with F approximately
4.67537478463225e61 versus y* approximately
0.05193241017174900565, F approximately 4.251465264116678.
Approved numerical contract
ADR 0009 now requires complete-expression arithmetic through one compiled term
kernel; rigorous one-sided derivative enclosures separated from optimizer
tolerance; exact point signs; a maintained negative-left and positive-right
bracket to adjacent numeric binary64 values; direct termwise terminal objective
differences; physical and base-two-scaled twofold balls with derived outward
radii; certified ln(2) range reduction and polynomial exponential bounds; and
an 80/160-digit, four-decision bounded ambiguity fallback. A vector proposal
or compensated center has no certificate authority. A limit or unresolved
ambiguity is failure, never success.
Scope
- Introduce an independently testable scalar proximal solver.
- Use a certified bracketed method, such as safeguarded Newton with bisection or another monotone root/KKT method appropriate to the approved scalar derivatives.
- Evaluate and prove lower-, upper-, kink-, and smooth-point KKT signs explicitly.
- Use stable evaluation for exponential terms and reject irrecoverable non-finite states.
- Evaluate Huber terminal differences by exact branch partitioning and factored expressions rather than subtracting complete branch values.
- Vectorize heterogeneous ordinary ball evaluation while routing every exceptional or unresolved row through the scalar certified path.
- Make routing invariant under caller
numpy.errstatepolicy, preserve unaffected lane certificates when one lane is exceptional, and do not catch programming errors as numerical fallback. - Return
_NumericalFailureor the package’s approved structured failure when bracketing, convergence, or finite evaluation cannot be certified. - Integrate the routine into ADMM without changing unrelated solver APIs.
- Keep the canonical identical and heterogeneous 1/10/100/1000-row benchmark within the frozen ADR 0009 performance gates with zero common-case fallbacks.
Non-goals
- Revising the objective formulas chosen in R1.
- Replacing ADMM as a whole.
- Adding SciPy as a mandatory dependency.
- Changing active-set semantics.
Required regression oracles
- The two source-controlled reproductions plus independently generated coverage agree with an independent high-accuracy bounded scalar oracle.
- Randomized one-dimensional cases cover every mismatch/penalty combination.
- Returned interior and bound points satisfy independently verified exact one-sided KKT signs; approximate residual tolerance is not success.
- Non-point returns carry independently verified adjacent sign brackets.
- Adjacent endpoints are selected by an independently computed direct source difference, unaffected by large additive constants or irrelevant breakpoints.
- Frozen cancellation regressions reject the false same-sign bracket and the
wrong Huber endpoint from the
7a012bfreview. - Every ball primitive and certified exponential contains independently evaluated exact-rational or high-precision reference values.
- The generated audit independently checks ordinary/fallback enclosure containment, endpoint selection, fallback provenance, and localization for every algebraic and exponential case; success counts alone are insufficient.
- Private scalar, public array, result, report, and JSON paths agree on exact complete-expression semantics.
- Exhaustion, impossible bracketing, or non-finite evaluation returns failure,
never
optimal. - End-to-end ADMM results satisfy the public objective and KKT checks.
Acceptance criteria
- No code path returns an uncertified last Newton iterate as success.
- The scalar solver is directly unit-tested, not protected only through end-to-end ADMM examples.
- The public solver status, convergence fields, and objective report agree.
- Runtime remains reasonable for the existing deterministic and benchmark workflows.
- The frozen identical and heterogeneous 1/10/100/1000-row performance targets
pass with no ordinary-path
Decimaltranscendental calls and zero fallbacks on the canonical benchmark. - Benchmark claims come from instrumentation of actual batch eligibility, certification, scalar dispatch, high-precision fallback, and structured failure paths rather than counters inferred from returned floats.
R3 — Harden public inputs, immutable value objects, and native preconditions
Milestone: v0.8.0 — technical maintenance and Python 3.14
Suggested labels: type:api, type:tests, type:native,
release-blocker, breaking-change-risk
Dependencies: R1 and R2 should be merged first to avoid validating a solver
surface that is still changing
Implementation shape: one GitHub issue with two sequential implementation
parts/PRs permitted
Summary
Create one strict validation and ownership contract for public values, then add defense-in-depth checks at the pybind boundary. This issue deliberately groups widespread manifestations of the same missing invariant rather than creating a large number of nearly identical validation issues.
Critical native defect
init_mem=0 or False can corrupt the heap. The 3D path reproducibly aborts
Python; the 2D vendored implementation allocates zero-length arrays and writes
through them. Negative values reach native allocation and surface as low-level
memory errors. This alone makes R3 release-blocking.
Other confirmed defects in scope
- forward IDs convert floats, numeric strings, and booleans lossily;
- periodic shifts and input indices convert lossily;
- solver and active-set counts/tolerances admit fractional, Boolean, or non-finite values;
- masks use truthiness conversion;
- inverse models admit NaN/infinity;
L2Regularization.referenceis caller-mutable;Box.from_pointsdoes not clearly reject empty/non-finite inputs or negative/non-finite padding;- periodic remapping accepts non-finite points or
epsand can create int64 minimum shifts; - periodic flags convert strings such as
'False'toTrue; - domain objects retain mutable caller-owned bounds/vectors/origin;
- left-handed periodic cells fail late rather than at construction;
- block counts and other native resource controls use lossy
int(...)conversion and lack a defensible allocation guard; - non-finite normalization/diagnostic tolerances can poison downstream geometry.
Maintainer policies to approve
- Exact integer fields use
operator.indexsemantics and reject booleans. - Public Boolean fields accept only
booland intentionally supported NumPy Boolean scalars; no arbitrary truthiness. - Frozen/value objects own canonical immutable copies of retained data.
- Left-handed
PeriodicCellinput is rejected at construction (recommended) or normalized by one explicitly documented transform. - Native block-grid/resource limits use either a documented conservative cap, an estimated-memory guard, or an explicitly named unsafe override.
- Public tolerance fields state whether zero is allowed and whether the range is positive or non-negative.
Part A — Shared validators and native safety
- Add small, purpose-specific private validators for finite scalars/arrays, exact integers, exact booleans, positive/non-negative values, shapes, and owned read-only arrays.
- Avoid one generic validator with many flags and unclear error messages.
- Add C++ checks before every 2D/3D standard/power, boxed/periodic, compute/locate/ghost container construction:
init_mem > 0;- all block counts positive and products safe;
- finite, ordered bounds or valid periodic parameters;
- finite points, queries, and radii where applicable.
- Raise
py::value_erroror the approved Python exception before allocation. - Add subprocess tests for all inputs that previously could abort or corrupt the process.
Part B — Adoption and immutable ownership
Apply the validators and ownership rules to:
- all inverse model dataclasses;
- forward IDs;
- periodic shifts and observation input indices;
- solver and active-set options;
- active/subset masks;
- domain constructors and
from_pointshelpers; - periodic remapping and snapping epsilon;
- block counts,
init_mem, search radii, pair limits, and iteration counts; - normalization and diagnostic tolerance entry points where the allowed range is unambiguous;
- retained arrays, bounds, vectors, origins, masks, and references.
Implementation status (2026-08-09): R3-A and R3-B are complete, and the
integrated R3 review/regression gate was accepted. The completed reviewed R3
baseline is 555228cbf8f2179391a981ab59f005edb9e300b4; R4 starts from that
baseline. The historical R3 findings and rationale above remain the audit
record.
Canonical domain data should be stored as owned tuples of Python floats/bools or owned read-only NumPy arrays. Caller mutation after construction must not change a domain or model.
Non-goals
- Choosing nearest images in triclinic geometry; R4.
- Defining the duplicate/containment policy; R5.
- Defining diagnostic severity or
ok; R8. - Broad public API redesign.
Required regression coverage
- Parameterized NaN,
+Inf, and-Infmatrices across all public numerical models and options. - Floats, strings, and booleans rejected for every exact integer field.
- Truthy strings/numbers rejected for Boolean and mask fields.
- Caller mutation cannot alter any frozen model/domain or retained mask.
- Empty/non-finite points and invalid padding fail clearly.
- NaN/infinite remapping produces no NumPy cast warnings or sentinel shifts.
- Direct
_coreand_core2dcalls with invalid native preconditions raise safely in subprocesses. init_mem=0,False, and negative values never reach native allocation.- Boundary-valid values remain supported in 2D and 3D.
Acceptance criteria
- No invalid public value is first rejected by NumPy casting,
range, linear algebra, or Voro++ allocation. - Native entry points remain safe even when called directly.
- Valid documented NumPy integer/Boolean scalar inputs continue to work where intentionally supported.
- Part A and Part B may be reviewed separately, but the issue closes only after both are merged and the full suite passes.
R4 — Implement certified periodic nearest-image and minimum-image geometry
Milestone: v0.8.0 — technical maintenance and Python 3.14
Suggested labels: type:geometry, type:solver, type:tests,
obs:separator, release-blocker
Dependencies: R3
Implementation shape: one design-gated issue and one focused PR
Summary
Replace the bounded triclinic coefficient-cube search with a nearest-image primitive that is exact or returns an explicit inability to certify. Reuse the same geometry for periodic minimum-image duplicate distances when current periodic wrapping is enabled, while preserving the unwrapped Cartesian check when wrapping is disabled.
Confirmed defect
The default image_search=1 can return the wrong image and therefore the wrong
separator coefficients and fitted weight differences. A warning that the
selected shift lies on the search boundary does not repair the result. Random
audit cases require coefficients of magnitude two even for a moderately
conditioned cell.
Historical decision questions (resolved by ADR 0012 and R4)
ADR 0012 selected certified exact inference with image_search as a
correctness-neutral seed, deterministic index-oriented ties, structured
resource failure, and authoritative explicit shifts. The questions below
preserve the original audit chronology.
- Define the future meaning of public
image_search: - an initial search seed for an always-certified adaptive algorithm (recommended compatibility path);
- a deprecated compatibility argument ignored by an exact fixed-dimension solver; or
- an explicitly approximate mode separated from the default exact mode.
- Define deterministic tie-breaking for equidistant images.
- Define behavior for singular, nearly singular, or excessively ill-conditioned cells.
- Confirm whether user-provided explicit shifts remain authoritative even when they are not nearest. The recommended answer is yes: nearest-image inference and given-image observations are different contracts.
Scope
- Implement one internal 2D/3D periodic displacement API that returns:
- the chosen integer shift;
- the minimum-image Cartesian displacement;
- its squared distance;
- any certification/conditioning metadata needed internally.
- For fully triclinic 3D cells, use a fixed-dimension exact/certified closest-lattice-vector method, such as QR-based branch-and-bound/sphere enumeration or another mathematically justified method without a new mandatory dependency.
- Preserve simple exact handling for orthorhombic and partially periodic axes.
- Route inverse nearest-image resolution through the new primitive.
- Route periodic duplicate-distance evaluation through the same primitive when current periodic wrapping is enabled.
- Remove warning-only uncertainty from the default result.
- Document tie and conditioning behavior.
Non-goals
- Changing explicit user-supplied periodic shifts.
- Redesigning face/edge-shift reconstruction.
- Defining duplicate policy or thresholds; R5.
- Adding a general lattice-reduction public API.
Required independent oracles
- The confirmed audit example must select
(2, -1, -1)and match exhaustive search. - Random cells and point pairs must match an exhaustive large-window oracle in a bounded test regime.
- Include skewed, nearly orthogonal, translated-origin, and partially periodic cells.
- Translation by arbitrary lattice vectors must not change the physical minimum displacement.
- Reversing
(i, j)must negate the chosen shift/displacement under the documented tie rule. - Tie cases must be deterministic across runs and supported Python versions.
- Ill-conditioned cases must either remain certified or fail clearly; they may not silently return a boundary candidate.
Acceptance criteria
- The default nearest-image result is independent of an arbitrary coefficient cube and is mathematically certified.
- Separator coefficients and fitted results use the certified shift.
- Periodic duplicate detection with wrapping enabled obtains its distance from the same source of truth; disabling wrapping preserves the established unwrapped Cartesian check.
- No new mandatory runtime dependency is introduced without explicit maintainer approval.
Codex stop conditions
Codex must not merely increase the default search radius. It must stop if the
approved prompt does not specify the image_search compatibility policy or if
an exact method would require an unapproved mandatory dependency.
Implementation status (2026-08-09): issue #40 implements the certified
exact-dyadic/proof-box geometry and records the durable contract in ADR 0012.
The known regression, independent fixed-cube/random oracles, tie and
metamorphic invariants, resource failure, cache isolation, candidate counts,
separator inference, and evaluated duplicate-distance integration are covered.
When periodic wrapping is enabled (wrap=True, or duplicate_wrap=True in
forward operations), evaluated candidate-pair distances share the certified
primitive; disabling wrapping preserves the established unwrapped Cartesian
check. Complete seam scanning and mandatory safety independent of
duplicate_wrap remain R5. The dedicated mathematical/numerical R4 review was
approved. The final reviewed R4 baseline is
64324efbba9ba0b77194f7aab78bf51f415e6d7a; R5 starts from this state. The
historical R4 findings and rationale above remain the audit record.
R5 — Make forward generator containment and duplicate safety non-optional
Milestone: v0.8.0 — technical maintenance and Python 3.14
Suggested labels: type:geometry, type:api, type:native, type:tests,
release-blocker, breaking-change-risk
Dependencies: R3, R4
Implementation shape: one design-gated issue and one focused PR
Summary
Ensure that every generator entering Voro++ is valid for the chosen domain and cannot trigger a known duplicate fatal path. Optional user diagnostics may be configurable; backend safety may not be disabled.
Confirmed defects
- Non-periodic generators outside the native container are silently omitted, even when their bisector would alter the mathematically clipped diagram.
- The exact upper boundary is omitted because Voro++ uses
[lo, hi). - Exact 2D duplicates can produce overlapping full cells; exact 3D duplicates can produce all-empty output.
- Periodic lattice-equivalent duplicates can make Voro++ call
exit(1). duplicate_check='warn'warns and then proceeds into the fatal call.duplicate_check='off', a threshold below the native safety distance, orduplicate_wrap=Falsecan bypass protection.- The original periodic duplicate detector is not minimum-image aware; R4 supplies the corrected distance primitive for wrapped candidate-pair evaluation.
Maintainer policy required
Approve a two-layer duplicate contract. Recommended semantics:
- Mandatory native-safety check: always active, minimum-image aware, and
always raises for points within the backend-fatal/invalid distance. It is not
controlled by
duplicate_check,duplicate_threshold, orduplicate_wrap. - Optional user near-duplicate policy:
offskips only checks above the mandatory safety floor;warnwarns for user-threshold pairs that are safe enough to call native, but still raises for mandatory-safety violations;raiseraises at the user threshold;- a user threshold below the safety floor cannot weaken the mandatory check.
Also approve the generator containment contract:
- non-periodic axes require
lo <= x < hi; - periodic axes are remapped into the primary cell;
- errors identify input index, external ID, axis, value, and bounds.
Scope
- Centralize generator preparation for 2D and 3D operations.
- Apply containment and mandatory duplicate safety to:
compute;locategenerator sets;ghost_cellsgenerator sets;- standard and power modes;
- box, partial-periodic, orthorhombic, and triclinic domains;
- inverse realization and active-set paths before they call forward tessellation.
- Use R4’s minimum-image displacement for periodic checks.
- Keep explicit external IDs in error reports without passing them directly to native storage.
- Correct duplicate truncation wording to “at least N; showing N” when scanning stops early.
- Add mode-appropriate postconditions for expected inserted IDs where useful; deeper native insertion-status APIs may remain deferred if Python prevalidation is complete.
- Document containment and duplicate semantics with failing examples.
Non-goals
- Supporting mathematically clipped diagrams influenced by generators outside the native container. Such support would require a different backend or explicit algorithm and is not a v0.8 fix.
- Adding a public regular-triangulation API.
- Deep native observability beyond what is needed for safe v0.8 behavior.
Required regression coverage
- 2D and 3D outside-generator examples raise before native insertion.
- Lower boundary accepted; exact upper boundary rejected; neighboring
nextaftervalues follow the documented half-open convention. - Exact duplicates raise under default calls in standard and power modes.
- Periodic seam, corner, partial-periodic, and triclinic duplicates are caught.
off,warn, custom small thresholds, andduplicate_wrap=Falsecannot bypass mandatory safety.- Safe near-duplicates above the native floor follow the user-selected off/warn/raise behavior.
- Subprocess tests prove that all previously fatal examples return Python exceptions rather than terminating the interpreter.
- Inverse realization and active-set calls inherit the same safety behavior.
Acceptance criteria
- No public operation can knowingly pass an outside non-periodic generator or a backend-fatal duplicate to Voro++.
- The default API is safe without requiring users to discover a special flag.
- User-configurable diagnostics remain useful without weakening the safety floor.
- Errors are deterministic and actionable in both dimensions.
R6 — Bind observations, fits, realizations, and reports to canonical source data
Milestone: v0.8.0 — technical maintenance and Python 3.14
Suggested labels: type:api, type:diagnostics, type:tests,
obs:separator, breaking-change-risk
Dependencies: R3, R4
Implementation shape: one design-gated issue and one focused PR
Summary
Prevent cached separator geometry, predictions, realization diagnostics, and serialized reports from being combined with unrelated points, domains, shifts, or observation rows that happen to have compatible shapes.
Confirmed defects
- A
SeparatorObservationsobject resolved for sites at one distance can be fitted against different coordinates of the same shape; cached distances and deltas are still used. - Direct
SeparatorObservations(...)construction admitsi == j, inconsistentdistance,distance2, anddelta, inconsistent target representations, lossy shifts/indices, unsupported dimensions, and malformed warning values. SeparatorFitResult.to_records()can attach one fit’s predictions to an unrelated same-length observation set.- Complete JSON-friendly reports have family discriminators but no explicit schema/producer-version contract.
Historical decision questions (resolved by ADR 0014 and R6)
ADR 0014 selected fully validated direct construction, deterministic row/set
identity, optional exact source binding, and versioned reports. R6 retained the
schema name pyvoro2.inverse.separator.report, version 1, and the existing
power_weight_fit, realized_pair_diagnostics, and
self_consistent_power_fit kinds. The questions below preserve the original
audit chronology; they are no longer open R9 decisions.
- Construction policy: make validated resolver/factory construction the only public route (recommended), or fully validate every redundant field in the direct constructor.
- Geometry identity: choose an immutable context identity, deterministic fingerprint, or canonical equality of owned coordinates/domain metadata.
- Row identity: define how equivalent copied observations are recognized without allowing unrelated same-length rows.
- Report contract: choose either:
- versioned complete report bundles with schema and producer metadata (recommended for research archives); or
- an explicit package-version-only contract requiring users to record the pyvoro2 version externally.
- Decide whether serialized legacy discriminator values such as
power_weight_fitremain stable or are renamed in coordination with R9.
Scope
- Canonicalize and own observation geometry and row data.
- Enforce
i != j, valid dimensions/point counts, finite data, exact integer shifts/indices, and all distance/delta/target cross-field invariants. - Prevent fitting or realization with coordinates/domain/periodicity that do not match the resolved observations under the approved equivalence policy.
- Make
observation_view,to_records, report builders, realization, and active-set construction use one origin/equivalence check. - Prefer deriving redundant values from canonical fields where feasible.
- Apply the approved report schema/provenance policy and update exact-schema and JSON-round-trip tests.
- Mark retained arrays read-only where the public object is intended to be a value object.
Non-goals
- Redesigning the mathematical observation model.
- Adding prescribed cell-measure observations.
- Fixing active-set final-state assembly; R7.
- Performing the complete terminology audit; R9.
Required regression coverage
- Observations resolved at distance 2 cannot be fit or realized against points at distance 20.
- Equivalent copied inputs are accepted according to the approved identity policy.
- Same-length unrelated observations cannot receive a fit’s predictions or residuals.
- A parameterized malformed direct-construction matrix is rejected, or the raw constructor is no longer publicly reachable.
- Caller mutation cannot change retained observation geometry/rows.
- Report metadata/discriminators survive JSON round trips exactly.
- Records and reports preserve stable row identity through subsets and active masks.
Acceptance criteria
- Every fitted, realized, or serialized row can be traced to the geometry and observation data from which it was computed.
- Shape equality alone is never treated as origin equality.
- The report lifecycle is documented without promising more cross-version compatibility than intended.
Codex stop conditions
Codex must not invent a fingerprint, schema, field names, or serialized renaming policy. Those choices must be included in the approved implementation prompt.
R7 — Make the active-set final state atomic and self-consistent
Milestone: v0.8.0 — technical maintenance and Python 3.14
Suggested labels: type:solver, type:diagnostics, type:tests,
obs:separator
Dependencies: R1, R2, R6
Implementation shape: one issue and one focused PR
Implementation contract: issue #43
and ADR 0015
Summary
Ensure that the returned active mask, observation subset, final fit, weights, realization diagnostics, row records, path summary, and termination reason all describe the same accepted final state.
Confirmed defect
After cycle or maximum-iteration termination, the implementation performs a final refit. Some final outcomes are handled specially while others can retain the preceding outer termination and reuse realization diagnostics from a different mask/weight vector. The resulting object can therefore combine layers from different iterations.
The R7 implementation removes this mixed-state assembly. Every return now passes through one private accepted state consuming the R6 source/observation identity and active row IDs. Weighted final fits recompute all final layers; no-weights fits expose those layers as unavailable without changing an already established outer stop.
Scope
- Define one internal final-state record or builder containing:
- active mask and originating row identity;
- resolved observation subset;
- fit result and weights;
- realization result;
- per-row diagnostics/records;
- iteration/path metadata;
- termination/status.
- Treat every final-refit outcome explicitly: optimal, infeasible, numerical failure, missing weights, cycle, maximum iterations, and other documented statuses.
- Recompute realization/records from the accepted final state, or set layers to
Nonewith the final fit status as the availability reason. Never retain stale data. - Assert identity, mask, and iteration consistency during result construction.
- Preserve the documented distinction between theorem-backed inner fitting and heuristic outer refinement.
Non-goals
- Proving global convergence of the active-set algorithm.
- Changing hysteresis/add/drop policy without a separately approved finding.
- Revising observation identity; R6 supplies it.
Required regression coverage
Force and inspect:
- self-consistent termination;
- cycle termination;
- maximum-iteration termination;
- infeasible final refit;
- numerical final-refit failure;
- a final result with no weights;
- under-relaxed/gauge-aligned paths.
For every case, assert that all available layers share one mask, observation identity, weight vector, and iteration/state identifier.
Acceptance criteria
- No result layer refers to an earlier mask or stale weights.
- Solver status, outer termination, warnings, and availability of weights are mutually consistent.
- Reports and row records are constructible only from a coherent final state.
- Existing successful benchmark behavior is unchanged except where previous output was internally inconsistent.
- Active success and failure reports preserve the R6 envelope and round-trip exactly through strict JSON; unavailable weights-dependent values are null.
R8 — Make diagnostics and strict validation severity-complete
Milestone: v0.8.0 — technical maintenance and Python 3.14
Suggested labels: type:diagnostics, type:tests, type:api
Dependencies: R3, R5
Implementation shape: one policy-gated issue and one focused PR
Summary
Define one cross-dimensional meaning of diagnostic severity, overall ok, and
strict validation. Error-level findings and required mode invariants must not be
ignored; warning-only findings must not accidentally become fatal unless a
public strictness policy says so.
Confirmed defects
TessellationDiagnostics.okis based primarily on total measure and reciprocity; missing expected IDs and other error issues do not necessarily make it false.tessellation_check='raise'can accept a silently omitted generator.- malformed or non-finite cell measures are swallowed by broad exception handling or poison aggregates.
- non-finite/negative tolerances can make comparisons meaningless.
- repeated annotation uses
setdefault, so stale orphan/mismatch flags may survive a later successful analysis. - planar normalized-topology warnings can make overall
ok=False, causing strict validation to raise despite warning severity; 3D follows a different convention.
Policy to approve
Recommended contract:
ok = all required mode-specific invariants pass
and no issue with severity "error" exists
Warnings do not make ok=False unless an explicitly documented strictness mode
promotes that warning class. Strict public validation should use the final
diag.ok contract rather than reimplementing a weaker subset.
The issue must also define expected-ID rules for standard, ghost, and any other modes. In standard mode, every validated generator must have a valid returned cell/empty representation according to the public output contract.
Scope
- Centralize issue codes, severity, and the
okcalculation. - Make missing expected standard IDs an error and strict-check failure.
- Emit explicit
MALFORMED_MEASUREandNONFINITE_MEASUREerrors. - Catch only anticipated conversion/index errors.
- Validate all diagnostic and normalization tolerances before analysis.
- Reset/overwrite mutable diagnostic flags on every analysis run.
- Make 2D and 3D severity behavior consistent.
- Reconcile planar normalized-topology warning/error labels with their effect on
ok. - Make
validate_tessellationand publictessellation_checkpaths rely on the same policy.
Non-goals
- Changing generator containment/duplicate policy; R5.
- Rewriting topology normalization algorithms.
- Promoting every warning to an error.
Required regression coverage
- Missing expected IDs fail strict standard-mode validation.
- Malformed string, NaN, and infinity area/volume values produce explicit error
issues and
ok=False. - Negative, NaN, and infinite tolerances fail before analysis.
- Warning-only diagnostics have the approved nonfatal behavior in both dimensions.
- Re-running diagnostics after repairing data clears previous annotations.
- Basic and strict modes differ only as documented.
- Convenience diagnostics and standalone validation return the same result for the same cells/options.
Acceptance criteria
ok, severity, and strict behavior have one documented meaning.- No error-level issue can coexist with
ok=True. - Strict validation cannot pass a missing standard generator or malformed measure.
- Warning-only topology findings behave consistently across dimensions.
Resolution in the current v0.8 tree
Issue #44 and
ADR 0016
freeze the remediation. Spatial and planar analyzers share private
measure/expected-ID/severity aggregation, strict and compute wrappers consume
the final diag.ok, marked reanalysis resets owned flags, and warning-only
normalized-topology findings are nonfatal. The original confirmed defects
above remain as audit history and regression provenance.
Residual informational findings from the independent R8 review
The independent review of the accepted R8 implementation also recorded two non-blocking observations. They are preserved here for later assessment rather than assigned to v0.8 work or GitHub issues:
- Vacuous periodic reciprocity semantics. With no relevant internal
boundaries, the spatial and planar analyzers retain different historical
behavior. Spatial diagnostics fall back to treating face shifts as available
when any face record has
adjacent_shift; planar diagnostics leave edge shifts unavailable when there are no relevant internal edges. No failingcompute()case or R8 contract violation is known. Revisit only if this becomes user-visible or diagnostic result semantics are redesigned. - Malformed explicit boundary-shift values. R8 detects a missing
adjacent_shiftfield on relevant internal boundaries, but once the field is present the standalone analyzers assume the existing raw-record shape. A malformed present value can therefore raise during shift conversion rather than produce a dedicated structured tessellation issue. R8 intentionally did not define a malformed-boundary-record issue taxonomy. Revisit this as part of a coherent raw diagnostic-record validation/hardening pass rather than as a one-field patch.
These findings are informational only: they do not reopen R8 or block v0.8.0. They may remain deferred beyond v0.8.0 unless later evidence makes either one release-relevant or a dedicated robustness workstream is approved.
R9 — Synchronize the public API, documentation, distribution, and release contract
Milestone: v0.8.0 — technical maintenance and Python 3.14
Suggested labels: type:docs, type:api, type:packaging, type:release
Dependencies: R1–R8
Implementation shape: one issue and one focused cleanup PR after a short
post-remediation polish audit
Summary
Update every user-facing and distributable contract to describe the corrected v0.8 behavior, and close the remaining pre-release naming/licensing gaps without introducing new numerical features.
Scope
Active plan and release history
- Extend the active v0.8 plan with R1–R9, link to accepted umbrella issue #35, and identify #33 as the separate post-remediation qualification issue.
- Keep release notes explicitly draft/pending until #33 succeeds.
- Add a compact “correctness fixes identified during release audit” section. For each merged correction, record issue, PR, user-visible effect, migration impact, and regression oracle.
- Mark any published April plan/backlog as superseded by ADR 0006 and the active v0.8 plan; do not rewrite historical records as if they never existed.
Public API and terminology
- Search the entire repository case-insensitively for
powerfitandPowerFit. Classify every occurrence as historical, migration-only, intentionally retained public/schema vocabulary, or stale current terminology. - Document
PowerFitBounds,PowerFitPredictions,PowerFitObjectiveBreakdown, andSelfConsistentPowerFitResultas retained current advanced names, and retain stable powerfit-named guide/notebook paths. - Preserve the schema-1 report discriminator names fixed by R6.
- Preserve historical/migration references deliberately.
- Preserve weight-first
locate/ghost_cellsparity as a candidate v0.9 workstream without requiring its exact future issue grouping in R9. It remains deferred because v0.8 is feature-free.
Corrected behavior documentation
- Document the half-open non-periodic generator rule.
- Document mandatory duplicate safety versus optional near-duplicate policy.
- Document exact/certified nearest-image behavior and explicit given shifts.
- Document objective formulas, proximal failure semantics, observation identity, active-set result availability, and diagnostic severity.
- Correct the fraction-measurement wording that implies all connector-line
parameters must lie in
[0, 1]; between-site restrictions are separate. - Fix the
0D systemstypo and other small inconsistencies found in the final polish audit. - Make fuzz-marker documentation match actual default test behavior, or change the configuration deliberately.
Distribution and licensing
- Include the complete Voro++ license text in binary and source distributions,
not merely a
NOTICE.mdlink to a path absent from wheels. - Make
tools/check_dist.pyverify the third-party license and all notice targets. - Remove or replace the misleading
Operating System :: OS Independentclassifier for a platform-specific compiled extension. - Ensure release metadata and distribution checks reflect the final public surface and report-schema decision.
Non-goals
- New inverse observation families.
- Performance/GIL refactoring.
- A broad prose rewrite unrelated to corrected behavior.
- Final artifact qualification; issue #33.
Acceptance criteria
- Guides, reference pages, API inventory, ADRs, active plan, changelog, examples, notebooks, README, and release notes agree on the final behavior.
- Every
powerfit/PowerFitoccurrence is classified and every present-tense canonical occurrence is intentional. - Release-note correctness fixes are traceable to merged issues/PRs and real tests.
- The report schema/provenance policy is documented exactly.
- Built-distribution checks require the actual Voro++ license.
- Release-relevant deferrals are documented without describing them as completed; roadmap candidates need not already have exact follow-up issues.
Issue #33 — Qualify and release v0.8.0 from a clean, traceable artifact set
Milestone: v0.8.0 — technical maintenance and Python 3.14
Suggested labels: type:release, type:tests, type:packaging,
release-blocker
Dependencies: all R1–R9 work complete and umbrella issue #35 closed
Implementation shape: one final release issue; a small tooling/workflow PR
may be used before the release run
Summary
Build and qualify the complete v0.8.0 artifact set from one exact commit. This issue is a gate, not a place to hide unresolved design or correctness work.
Required clean-state conditions
- Start from a clean checkout of the exact candidate commit.
- No donor
_core/_core2dfile, extraction marker, local audit directory, or previous distribution is present in the build tree. - Version, tag candidate, changelog, release notes, API inventory, and milestone all identify the same release.
- Every R1–R9 issue is closed or has an explicitly accepted deferral that does not violate the release rule.
- Umbrella issue #35 is closed before issue #33 qualification begins.
Required validation
Source and native safety
- Flake8 and all repository-prescribed static checks.
- Strict MkDocs build.
- Deterministic, tooling, notebook, generated-file, and link checks.
- Fuzz/property tests with an increased deterministic iteration count.
- Source installations on CPython 3.10–3.14 across supported operating systems.
- A Linux ASan/UBSan native job covering container construction, invalid preconditions, duplicate safety, and representative forward operations.
- Subprocess regression tests for every formerly fatal/heap-corrupting input.
Distributions
- Build 20 wheels plus one sdist from the exact candidate commit:
- manylinux x86_64;
- Windows AMD64;
- macOS arm64;
- macOS x86_64;
- CPython 3.10–3.14.
- Build a wheel from the generated sdist in isolation.
- Run focused installed-wheel regression tests on every wheel, not only import smoke checks.
- Run at least one full installed-wheel suite per supported OS/architecture family.
- Check both SciPy-enabled and no-SciPy installations.
- Run
pip check, Twine/metadata checks, wheel-matrix checks, distribution content checks, and installed public-API checks. - Verify the actual Voro++ license, notice, report schema, and removed-package rules inside installed/built artifacts.
Provenance
Record in a machine-readable release manifest:
- exact commit SHA;
- source-tree/archive hash;
- vendored Voro++ hash;
- Python, compiler, CMake, scikit-build-core, pybind11, and cibuildwheel versions;
- artifact filenames, tags, and SHA-256 hashes;
- CI run identifiers;
- test/check results and qualified platform matrix.
Regression-oracle release checklist
The final gate must explicitly include tests for:
- nonzero-L2 backend parity;
- objective/gradient/Hessian consistency;
- direct scalar proximal KKT/optimality;
init_memand native precondition safety;- certified triclinic nearest images;
- mandatory duplicate safety under all public modes;
- non-periodic half-open containment;
- systematic NaN/infinity and exact-type rejection;
- immutable model/domain ownership;
- observation/geometry/row identity mismatch;
- active-set final-refit failures;
- missing IDs, malformed measures, and warning/error semantics;
- installed report schema/provenance behavior.
Acceptance criteria
- Every distribution is traceable to the exact qualified commit.
- Every confirmed correctness/safety defect has an independent regression oracle.
- All required checks pass against both source and installed artifacts.
- Release notes change from pending to completed only after evidence exists.
- The tag, GitHub Release, PyPI publication, and Zenodo archival steps use the qualified artifact set without rebuilding from another source state.
6. Finding-to-issue map
| Earlier/final finding | Final issue |
|---|---|
| ADMM L2 coefficient mismatch | R1 |
| Reciprocal penalty value/derivative mismatch | R1 |
| Squared/Huber normalization ambiguity | R1 |
| Fixed hard-bound tolerance | R1 |
| Unchecked scalar proximal Newton loop | R2 |
| NaN/Inf model acceptance and mutable regularization reference | R3 |
| Lossy IDs, shifts, input indices, counts, flags, masks, and options | R3 |
| Non-finite remapping, invalid padding, late handedness failure | R3 |
| Mutable caller-owned domain data | R3 |
init_mem=0 heap corruption and unsafe native preconditions |
R3 |
| Bounded triclinic nearest-image search | R4 |
| Periodic minimum-image duplicate distance | R4 |
| Outside/non-periodic generator omission and half-open boundary | R5 |
| Exact/periodic duplicates, process exit, and policy bypass | R5 |
| Truncated duplicate-report wording | R5 |
| Resolved observations reused with another geometry | R6 |
| Direct observation invariant failures | R6 |
| Same-length unrelated records/reports | R6 |
| Report schema/provenance contract | R6, documented in R9, checked in #33 |
| Inconsistent active-set final layers | R7 |
ok/strict checks ignore errors or missing IDs |
R8 |
| Malformed/non-finite measures swallowed | R8 |
| Invalid diagnostic tolerances and stale flags | R8 |
| Planar warning/error semantic contradiction | R8 |
Retained PowerFit* names and stable powerfit filenames requiring explicit classification |
R9 |
| Historical-plan and release-note inconsistencies | R9 |
| Audit issue/PR/test traceability | R9 |
| Missing Voro++ license in binary distribution contract | R9, verified in #33 |
| Missing release regression oracles/artifact provenance | #33 |
| Development donor wheel mismatch | Withdrawn as a source/release defect; donor process remains separate |
7. Explicitly deferred work
The following do not block v0.8 once documented as roadmap candidates or in later issues. ADR 0017 deliberately leaves exact future issue grouping to later planning:
- release the GIL during native-only computation;
- deeper native insertion observability beyond safe Python prevalidation;
- visualization hardening and broader headless coverage;
- forward/locate/ghost performance and memory benchmarks;
- weight-first
locateandghost_cellssupport (recommended v0.9); - broader PEP 561 typing work;
- deep refactoring of large branch-heavy functions;
- public regular-triangulation/dual diagnostics;
- prescribed measures and mixed inverse problems;
- upstream Voro++ behavior for extreme weight/radius scale ratios.
A short post-R8 polish audit should capture minor documentation, naming, and packaging inconsistencies for R9. It should not reopen approved mathematical or API contracts unless a genuine regression is demonstrated.
8. Final release rule
v0.8.0 is releasable only when:
- R1–R8 are merged and independently reviewed;
- every confirmed correctness/safety defect has a regression oracle;
- R9 makes the public and distribution contract match the implementation;
-
35 closes, then #33 qualifies all 20 wheels and the sdist from one exact
clean commit; - every release-relevant deferral is documented and outside the release-critical contract, without requiring every roadmap candidate to already have a GitHub issue.
A green pre-existing test suite alone is not sufficient evidence, because the most serious defects in this audit were absent from that suite.
9. Audit limitations
This is a broad adversarial snapshot audit, not a proof that no other defect exists. The source archive has no Git history, so the parent diff and issue implementation history could not be reviewed. Full multi-platform compilation and packaging remain #33 work. Small issues are intentionally deferred to the post-remediation polish audit, when the corrected contracts make them cheaper and less ambiguous to identify.