Three follow-ups identified in the PR-2 / PR-3 review pass:
1. ``serialization.maybe_externalize`` now keys batch dispatch off
the ``encoding`` string (``png_base64_batch``) instead of the
presence of a ``frames`` field. The old ``isinstance(frames, list)
and frames`` heuristic would silently take the batch branch on a
malformed envelope that mixed both fields and externalise garbage
under a non-batch encoding.
2. An empty / missing ``frames`` list on a ``png_base64_batch``
envelope now raises ``RnpProtocolError(INTERNAL)`` rather than
silently no-op-ing through to the wire (where the server's
``resolve_image_envelope_pngs`` rejects it after the round trip
anyway). Mirrors the server-side ``make_image_batch_envelope``
contract: an empty batch is a hard wire bug.
3. ``proxy_node._deserialize_output`` now accepts the externalised
``png_base64_batch_uri`` shape on the response path. No server
emits this shape today (future-proofing only — the symmetric
counterpart of the request-side externalisation), but the decoder
owns the symmetry: any envelope shape the client may externalise
on the way out, the client must also accept on the way in.
Per-frame URIs are fetched in parallel via ``asyncio.gather`` so
a 9-frame batch import is one round-trip rather than N. Frame
ordering is preserved (gather returns positional results) — the
IMAGE tensor's batch axis carries semantic order. Fetched bytes
are re-stamped as inline ``frames`` so the existing
``_decode_image_batch_envelope`` path handles them uniformly.
notes/run_batch_uri_followups.py — new standalone smoke test that
exercises all three changes (encoding-keyed dispatch, empty-frames
hard-error, response-side batch URI parallel fetch + ordering).
Amp-Thread-ID: https://ampcode.com/threads/T-019e2ce6-9403-720c-ae5b-1597b8068be7
Co-authored-by: Amp <amp@ampcode.com>