MOR-1193: make unmanaged a positive finding in ManagedTxApi.bind - #2117
Conversation
|
Agent Review: PASS Two independent non-author reviews. The first returned BLOCKED; the fix was applied and a focused confirmation cleared it. Review 1 — full review, verdict BLOCKED on a docstringEvery behavioural check passed. The block was check #10, docstring claims verified against each interpreter's Load-bearing matrix: 29 cases × base/head × 3.11/3.12/3.13. Cases where head differs from base-3.12: NONE. Head is uniform across all three interpreters in all 29 cases. Exactly four diverged at base on 3.11, all removed:
Everything that reads unmanaged today still does — absent, instance The 9 mutations, all killed. Red-before is interpreter-specific: 3.11 → 2 failed / 4 passed; 3.12 and 3.13 → 6 passed. Gates on all three interpreters: 8547 passed, 0 failed; ruff clean; The blocking findingThe new False on 3.11 — the interpreter The fix, and Review 2The line now warns against A focused non-author confirmation established the change is docstring-only by comparing compiled code objects, not by reading the diff: They recovered the pre-fix text verbatim from a stale Corrected from the author's reportThe cost figures were inverted. Over 200k iterations: 3.11 head is 1.55 µs faster (2.92 → 1.37); 3.12/3.13 head is 0.4 µs slower (0.30 → 0.70). Base is cheap on 3.12+ because Non-blocking
Hardware boundarySoftware only. No hardware was involved and no hardware claim is made. This does not replace MOR-1033. |
bind decided managed-ness with isinstance against a runtime_checkable Protocol. On 3.11 that probes the non-callable member with hasattr, which swallows an AttributeError raised inside the property — a typo on a nested attribute will do — and answers "no supervisor". The caller then takes the legacy write with no lease, no owner and no watchdog. On 3.12+ getattr_static never invokes the property, so the raise came from bind's own read and propagated. quick.yml pins 3.11, so the silent-bypass face is the one the per-PR gate exercises and the loud one is what only full.yml sees. Settle absence with getattr_static — the same mechanism the stdlib's own protocol machinery uses from 3.12 — then read the member once, explicitly. A raising accessor now propagates everywhere instead of being read as absence. Unmanaged is a positive finding, never a fallback from a failed read. Measured across 29 configurations x base/head x 3.11/3.12/3.13: no case where head differs from base on 3.12. Head is uniform on all three. Exactly four cases diverged at base on 3.11 and all four are removed — a property raising AttributeError, a declared-but-unset __slots__ member, a __getattr__-conjured attribute, and a bare MagicMock. The last two are wider than the ticket's framing and worth naming: they go MANAGED to UNMANAGED on 3.11, matching what 3.12+ already did. Everything that reads unmanaged today still does — absent, instance None, class None, property returning None, MagicMock with managed_tx=None, plain object. The five web doubles that pin managed_tx = None are unaffected, so MOR-1013 Slice 3 holds. A first review BLOCKED this on a docstring: the ManagedTxCapable contract line claimed absence is settled statically "in the protocol machinery itself", which is false on 3.11 and would have told a backend author that isinstance is a safe absence probe — the very bypass being closed. It now warns against isinstance by name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Head changed after the review — disclosure, and why the verdict still standsI rebased this branch onto The previous What actually changed, established rather than assumed:
So Re-run on the rebased head: 3.13 full suite 8551 passed, 12 skipped, 5 deselected, 11 xfailed, 1 xpassed. 3.11 running; I will not merge until it lands. The reviewed content is intact, so the verdict below stands for this head. Agent Review: PASS Two independent non-author reviews. The first returned BLOCKED on a docstring claim; the fix was applied and a focused confirmation cleared it. Review 1 — full, verdict BLOCKED. Every behavioural check passed. Load-bearing matrix of 29 cases × base/head × 3.11/3.12/3.13: cases where head differs from base-3.12: NONE. Head is uniform across all three interpreters in all 29. Exactly four diverged at base on 3.11 — a property raising The 9 mutations, all killed. Red-before is interpreter-specific, as a divergence bug requires: 3.11 → 2 failed / 4 passed; 3.12 and 3.13 → 6 passed. The block: the new Review 2 — confirmation. Established the fix is docstring-only by comparing compiled code objects: 310 before and after, 8424 bytecode bytes both, every Corrected from the author's report: the cost figures were inverted. Over 200k iterations, 3.11 head is 1.55 µs faster and 3.12/3.13 are 0.4 µs slower — base is cheap on 3.12+ because Non-blocking: Software only. No hardware was involved and no hardware claim is made. This does not replace MOR-1033. |
Blocks MOR-1016. 3 files, +97 net LOC.
bindis the single function every managed TX ingress binds through — SDK, CLI and Web — so blast radius is high even though the corrected path is dormant until MOR-1016.The defect
binddecided managed-ness withisinstanceagainst aruntime_checkableProtocol. On 3.11 that probes the non-callable member withhasattr, which swallows anAttributeErrorraised inside the property and answers "no supervisor". The caller then takes the legacy write — no lease, no owner, no watchdog.quick.ymlpinsUV_PYTHON: "3.11", so the silent-bypass face is the one the per-PR gate exercises and the loud one is what onlyfull.ymland developer machines see. AnyAttributeErrorfrom inside the property body does this, including a typo on a nested attribute.The fix
getattr_staticsettles absence without invoking the descriptor — the same mechanism the stdlib's own protocol machinery uses from 3.12. The single explicit read is then the only backend codebindtouches, and its failures propagate on every interpreter.Unmanaged is a positive finding, never a fallback from a failed read.
Evidence
Independently verified across 29 configurations × base/head × 3.11/3.12/3.13.
Exactly four cases diverged at base on 3.11, all removed:
AttributeError__slots__declared but unset__getattr__-conjured supervisorMagicMockThe last two are wider than the ticket's framing and worth naming explicitly: they are not regressions — both match what 3.12+ already did — and both are covered by a green full suite on all three interpreters. The
MagicMockrow is the root cause of the exercise MOR-1013 Slice 3 went through; the five doubles that pinmanaged_tx = Noneare deliberate and untouched.Everything that reads unmanaged today still does: absent, instance
= None, class= None, property→None,MagicMockwithmanaged_tx = None, plainobject(),__slots__set toNone, data-descriptor→None,cached_property→None— UNMANAGED in all six columns.The
is Nonesentinel was hunted for a false negative. The only static-Noneconfigs whose live read yields a supervisor are__getattr__-based, which match base-3.12/3.13. The shadowing case is safe: a class attributeNoneshadowed by an instance supervisor givesgetattr_staticthe instance value → MANAGED everywhere. The four__getattr__definitions insrc/are module-level PEP 562 lazy-import shims, not class-level.9 mutations, all killed on all three interpreters.
M1(revert to theisinstanceform) is killable only on 3.11 — which is the author's framing of the defect, confirmed rather than assumed.Red-before is interpreter-specific, as it must be for a divergence bug: 3.11 → 2 failed / 4 passed; 3.12 and 3.13 → 6 passed. Not a generic red.
pytest tests/ --ignore=tests/integrationruff check/format --checklint-importsmypy src/diffvs base emptyThis was BLOCKED first, on a docstring
The first review passed every behavioural check and then blocked on check #10 — docstring claims verified against each interpreter's
typing.py.The new
ManagedTxCapablecontract line asserted that absence is settled without running the accessor "here and in the protocol machinery itself". That is false on 3.11: measured,isinstance(conjured, ManagedTxCapable)isTrueon 3.11 andFalseon 3.12/3.13. It would have told a backend author thatisinstanceis a safe absence probe — the very bypass this change closes — and it contradicted thebinddocstring 30 lines below it.The line now warns against
isinstanceby name and scopes the static-absence claim tobind.A focused non-author confirmation established the fix is docstring-only by comparing compiled code objects, not by reading the diff: 310 code objects before and after, 8424 bytecode bytes both, every
co_codebyte-identical, and a single changed constant —ManagedTxCapable's docstring, 602 → 799 chars.Corrected from the author's report
The reported cost figures were inverted. Measured over 200k iterations: on 3.11 head is 1.55 µs faster (2.92 → 1.37 µs); on 3.12/3.13 it is 0.4 µs slower (0.30 → 0.70 µs). Base is cheap on 3.12+ because
_ProtocolMeta.__instancecheck__hits the ABC cache; on 3.11 it re-walkshasattrevery call. Immaterial either way against a millisecond-scale I/O path, once per PTT command — but the direction is the opposite of what was claimed.Non-blocking
isinstance(radio, ManagedTxCapable)itself still diverges for a raising accessor (3.11False, 3.12+True).bindno longer depends on it andManagedTxCapablehas no consumer insrc/outsideradio_protocol.py, but tests assert on it and no future production code should route on it — which is now what the docstring says.Hardware boundary
Software only. No hardware was run and no hardware claim is made. MOR-1033 remains the FTX-1 physical acceptance gate.
Linear: MOR-1193