The guard added with the monoid-from-built-in rejection keyed on
"constructed with neither a Python func nor a numba func", which is true
for most built-ins but not all of them: binary.any, first, pair, and
second carry a Python orig_func as their UDT fallback while their typed
ops are still TypedBuiltinBinaryOp. All four sailed past the guard and
reproduced exactly what it exists to prevent: an opaque AttributeError
partway through registration, with binary.<name>._monoid left pointing
at the half-built monoid (binary.any.monoid is monoid.any flipped from
True to False after the rejected call). binary.any is even named in the
guard's own rationale as an op whose identity SuiteSparse ignores.
Key the probe on the typed ops as well: any TypedBuiltinBinaryOp among
them marks the op built-in. The two signals agree everywhere else,
including under mapnumpy=True, where the mapped numpy binary ops hold
the built-in's typed ops and no funcs, and are flagged by both tests
alike; the numpy monoids themselves are mapped without passing through
Monoid._build, so their registration is unaffected (verified under both
mapnumpy settings). A UDF's typed ops are never TypedBuiltinBinaryOp,
so nothing legitimate is newly rejected.
Stacked on #618. Overnight adversarial review of #617 found a hole in its guard: _is_builtin_binaryop keys on "no Python func and no numba func", but four built-ins (binary.any, first, pair, second) carry a Python orig_func as their UDT fallback. All four sailed past the guard and reproduced exactly what #617 exists to prevent:
binary.any is even named in #617's own rationale as an op whose identity SuiteSparse ignores. This is an incomplete fix in #617 rather than a regression: the four common cases (plus/times/min/max) were already rejected cleanly.
The fix keys the probe on the typed ops as well: any TypedBuiltinBinaryOp among them marks the op built-in. Verified that the two signals agree everywhere else, including under mapnumpy=True (where mapped numpy binary ops hold the built-in's typed ops and are flagged by both tests alike, and the numpy monoids never pass through Monoid._build), and that a UDF's typed ops are never TypedBuiltinBinaryOp, so nothing legitimate is newly rejected. The new test is teeth-checked: it fails with the opaque AttributeError without the fix.
Gates: full pinned suite 1113 passed / 145 skipped; test_op + test_numpyops green under both mapnumpy settings; pre-commit all hooks pass.