The tests cover final destruction after a single registration, but omit the distinct accumulation, removal, and replacement lifetime paths implemented in NLopt.xs. Those omissions allow important proxy-retention and use-after-free regressions to pass unnoticed.\n\nFull review comments:\n\n- [P2] Exercise multiple callbacks in each constraint cache — /home/dj/Work/Math-NLopt/xt/lifetime/scalar_constraints.t:21-27\n  Each case registers only one constraint, but `_add_constraint` and `_add_mconstraint` append proxy storage because NLopt retains every callback pointer. If a later registration replaced or cleared the previous cache, these tests would still pass while optimization with two constraints could dereference freed storage. Register at least two callbacks—including scalar and vector constraints sharing the same equality/inequality cache—and verify both are invoked.\n\n- [P2] Verify removal releases constraint proxies immediately — /home/dj/Work/Math-NLopt/xt/lifetime/mconstraints.t:21-29\n  Destroying `$opt` only tests the `DESTROY` cleanup path. The separate `remove_equality_constraints` and `remove_inequality_constraints` paths call `av_clear` while the optimizer remains alive; if either stopped releasing callbacks and user data, every current test would still pass because destruction eventually clears the cache. Add removal cases that weakly track the callback/data and confirm they disappear before `$opt` is destroyed.\n\n- [P2] Exercise objective replacement before destruction — /home/dj/Work/Math-NLopt/xt/lifetime/objective.t:16-22\n  This installs only one objective, so it cannot validate the successful replacement branch in `set_objective`, which must clear the old `opt->objective` cache before storing the new proxy. Retaining every replaced callback would remain invisible because `no_leaks_ok` observes only the state after `$opt` is destroyed. Replace ordinary and preconditioned objectives while keeping the optimizer alive and verify the previous callback, preconditioner, and data are released.
