Correct typed-msg validation docs

Document `Started` as the eager sender-side payload check and
`Yield` plus `Return` as receiver-side decoding boundaries without
promising a symmetric error relay.

Separate the working task-scoped codec encoder from the private
per-dialog decoder and the incomplete `@context` hook params.

Link the planned typed `Start` contract and sender-side argument
validation follow-up in #514.

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
wkt/big_boi_docs_472_follow_ups
Gud Boi 2026-08-29 20:07:06 -04:00
parent 38883dca03
commit a40fb2ebde
1 changed files with 41 additions and 31 deletions

View File

@ -153,14 +153,16 @@ the high-rate stream path.
never even hits the wire. (You can opt out per-call with never even hits the wire. (You can opt out per-call with
``ctx.started(..., validate_pld_spec=False)`` if you measure ``ctx.started(..., validate_pld_spec=False)`` if you measure
a real cost.) a real cost.)
- ``Yield`` payloads are **never** checked inside - ``Yield`` and ``Return`` payloads are not checked before sending;
``MsgStream.send()``; they're validated receiver-side on each they're decoded against the dialog's spec by the receiver. A
``MsgStream.receive()``. A violation raises a ``MsgTypeError`` violation raises a ``MsgTypeError`` there and terminates that
in the receiver *and* relays an ``Error`` msg back so the dialog. The peer then observes the resulting protocol teardown;
offending sender gets one raised too. it is not guaranteed to receive the same ``MsgTypeError``.
- the remaining control msgs (``Start``, ``Return``) are likewise - ``Start`` arguments are dispatched through the RPC endpoint's
validated such that violations raise in the **sending** actor, Python signature. They are not payloads covered by the dialog's
pointing the traceback at the code that actually goofed. ``pld_spec``. A planned follow-up will derive a typed ``Start``
contract from endpoint annotations and validate arguments
sender-side; see `#514`_.
Anatomy of a ``MsgTypeError`` Anatomy of a ``MsgTypeError``
----------------------------- -----------------------------
@ -177,21 +179,21 @@ a msg fails to decode against the active spec. The useful bits:
``.src_uid``, ``.ipc_msg`` and the fancy ``.pformat()`` tb-box ``.src_uid``, ``.ipc_msg`` and the fancy ``.pformat()`` tb-box
rendering. rendering.
Practical reading guide: a *sender-side* MTE (``Started``, Practical reading guide: a *sender-side* MTE for ``Started`` points
``Return``) points straight at your offending ``await straight at the offending ``await ctx.started()`` call. A
ctx.started()`` or ``return`` statement, while a *receiver-side* *receiver-side* MTE for ``Yield`` or ``Return`` surfaces while the
MTE (``Yield``) surfaces from the consumer's ``receive()`` call peer decodes the payload. Either way the failure is scoped to that
with the relay copy delivered back to the producer. Either way one dialog; sibling contexts on the same channel keep right on
the failure is scoped to that one dialog; sibling contexts on the trucking.
same channel keep right on trucking.
Custom wire types: ``mk_codec()`` and friends Custom wire types: ``mk_codec()`` and friends
--------------------------------------------- ---------------------------------------------
msgspec covers a wide set of `builtin types`__ natively; for msgspec covers a wide set of `builtin types`__ natively; for
anything else you teach the codec via extension hooks. The anything else you teach the codec via extension hooks. The complete
easiest path is per-endpoint: ``@tractor.context()`` accepts public path currently available is task-scoped encoding:
``enc_hook``/``dec_hook`` params right alongside ``pld_spec``. ``tractor.msg.mk_codec()`` builds a codec with an ``enc_hook``, and
For full control build and apply a codec yourself; encode-side: ``tractor.msg.apply_codec()`` installs it for the current task. To
build and apply that transport codec:
__ https://jcristharif.com/msgspec/supported-types.html __ https://jcristharif.com/msgspec/supported-types.html
@ -206,8 +208,9 @@ __ https://jcristharif.com/msgspec/supported-types.html
with apply_codec(codec): # ContextVar-scoped override with apply_codec(codec): # ContextVar-scoped override
... # msgs sent by this task now encode NSPs ... # msgs sent by this task now encode NSPs
and decode-side, scoped to an open context (note the import from The context manager which temporarily installs payload-decoder
``tractor.msg._ops``, not yet re-exported): settings on an open context is separate and still private (note
the ``tractor.msg._ops`` import):
.. code:: python .. code:: python
@ -220,11 +223,15 @@ and decode-side, scoped to an open context (note the import from
): ):
... # this dialog's payloads decode as NSPs ... # this dialog's payloads decode as NSPs
``apply_codec()`` is ``ContextVar``-scoped: it overrides the ``apply_codec()`` is ``ContextVar``-scoped: it overrides the codec
codec for the current task (and only that task), not the whole for the current task (and only that task), not the whole process.
process. For complete working flows, including hook pairing rules ``@tractor.context()`` accepts ``enc_hook`` and ``dec_hook``
and roundtrip cases, see ``tests/msg/test_ext_types_msgspec.py`` parameters, but their runtime wiring is not yet a symmetric,
and ``tests/msg/test_pldrx_limiting.py``. end-to-end public hook pair: the encode hook is not consumed and
the decode hook is not applied on both peers. For the working flows
and their current boundaries, see
``tests/msg/test_ext_types_msgspec.py`` and
``tests/msg/test_pldrx_limiting.py``.
The runtime dogfoods this pattern with The runtime dogfoods this pattern with
:class:`tractor.msg.NamespacePath`: a ``str``-subtype shaped like :class:`tractor.msg.NamespacePath`: a ``str``-subtype shaped like
@ -253,13 +260,15 @@ escape hatch. Both are exercised end-to-end in
``tests/msg/test_pldrx_limiting.py`` and ``tests/msg/test_pldrx_limiting.py`` and
``tests/msg/test_ext_types_msgspec.py``. ``tests/msg/test_ext_types_msgspec.py``.
On the codec-hook side, the ``enc_hook``/``dec_hook`` pair is The codec constructor and task-scoped override are public; the
today only reachable via ``tractor.msg._ops``; a public *factory* per-dialog decoder override remains private, and the decorator hook
API for them is drafted in `#376`_ (from parameters remain incomplete. `#376`_ (from
`@guilledk <https://github.com/guilledk>`_, on the `@guilledk <https://github.com/guilledk>`_, on the
`auto_codecs <https://github.com/goodboy/tractor/tree/auto_codecs>`_ `auto_codecs <https://github.com/goodboy/tractor/tree/auto_codecs>`_
branch) — the likely long-term home for custom-type branch) instead drafts pair-building factories which derive
(de)serialization. matching ``enc_hook``/``dec_hook`` functions and encoder/decoder
pairs from a type spec. That automation, not hook availability,
is the proposed long-term home for custom-type (de)serialization.
If strongly-typed distributed systems get you going, we'd love If strongly-typed distributed systems get you going, we'd love
your input on any of the above. your input on any of the above.
@ -282,3 +291,4 @@ Where to next?
.. _#36: https://github.com/goodboy/tractor/issues/36 .. _#36: https://github.com/goodboy/tractor/issues/36
.. _#365: https://github.com/goodboy/tractor/issues/365 .. _#365: https://github.com/goodboy/tractor/issues/365
.. _#376: https://github.com/goodboy/tractor/pull/376 .. _#376: https://github.com/goodboy/tractor/pull/376
.. _#514: https://github.com/goodboy/tractor/issues/514