@@ 173,8 173,46 @@ fiber and bridges client ↔ handler via `fiber.channel`. All four flavors
- [ ] Optional output: ibuf-based encoder that writes into a caller-owned
`ffi.cdata` byte buffer instead of building a string list. Targets
hot RPC paths where allocation cost dominates.
-- [ ] Decoder fast path that returns an `msgpack.object`-like lazy view
+- [x] Decoder fast path that returns an `msgpack.object`-like lazy view
for nested messages; only materializes touched fields.
+ Shipped as `runtime/pb/lazy.lua` + `M.<Type>_decode_lazy` codegen
+ stubs in both modes. Surface: `:get / :has / :which / :iter /
+ :names` on MessageView; `:len / :at / :iter / :tolist` on
+ ArrayView; `:get / :has / :keys / :iter / :totable` on MapView.
+ Mutation via `:set` is supported and propagates sub-view edits
+ transparently (sub-MessageViews tracked on a flat array for
+ JIT-stable `is_dirty` — see lazy.lua's `_sub_msg_views`).
+ Re-encode is passthrough: untouched views return their original
+ bytes verbatim; partially-dirty views walk fields in id order,
+ splicing clean segments and re-emitting dirty ones. WKT
+ descriptors (those with `desc.decode`) are eager-wrapped so the
+ API stays uniform.
+
+ Conformance: every interop fixture round-trips byte-equal through
+ `decode_lazy(b):encode()`. Trace stability: gated by `make
+ jit-trace` — index pass, sparse `:get` x2, and passthrough
+ `:encode` all compile with no fatal aborts.
+
+ Workload characteristics (from `tarantool bench/lazy_bench.lua`,
+ Person at 1KB / 10KB / 100KB):
+ - **Passthrough re-encode** is the headline win: **1.0–1.5×**
+ faster than eager decode→encode across all sizes and both modes.
+ Untouched views never re-walk the wire.
+ - **Sparse read** (`:get` two top-level fields) is **0.60–0.77×
+ of eager** on the emails-heavy Person shape. Index-build
+ allocates one segment table per wire entry, and for messages
+ that scale via repeated leaves (no deeply-nested subtrees that
+ `skip_field` can vault over), that allocation cost cancels the
+ decode-skip savings. Lazy still wins this shape on payloads
+ dominated by nested submessages — that case isn't represented
+ in the current bench.
+ - **Mutate-then-reencode** is **0.81–1.07× of eager**; ~break-even
+ to slight loss on the same flat shape. Both decode+set+encode
+ and decode_lazy+set+encode walk the full byte range; lazy
+ splices substrings, eager re-emits per-field.
+ The honest framing: lazy is a *byte-passthrough* optimization;
+ it's not a universal speedup. Use it when you decode, touch few
+ fields, and re-encode — the common proxy/router shape.
### M7 — Developer ergonomics