~bigbes/tarantool

tarantool-protobuf

cf281e26ae0a966acaaa164bc7de5258364e8c52 — Eugene Blikh 2 months ago 7edbf97
c_runtime: WKT override-hook passthrough (ra6 3k)

A plan whose descriptor carries desc.encode / desc.decode now
dispatches through those overrides instead of erroring. Top-level
encode/decode, sub-message fields, and map<,message> values all
check the override refs and call them with the same contract the
pure-Lua codec uses: encode(value) -> body bytes; decode(buf) ->
value. runtime/pb/wkt.lua is unmodified.

ra6 3k
M .beads/issues.jsonl => .beads/issues.jsonl +1 -1
@@ 15,7 15,7 @@
{"_type":"issue","id":"tarantool-protobuf-h8v","title":"Encoder: codegen-time inline FFI writes (mode=full)","description":"Replace per-field wire.encode_* calls + ../table.concat chain with directly-emitted FFI byte writes at every field site in mode=full. Today every encode_len(s) is 'encode_varint(#s) .. s' — two allocations and a concat per string. Sidesteps the per-byte b:alloc(1) cliff that sank the earlier ibuf attempt (memory: tarantool_ibuf_perf). Expected: 1.5-2x encode throughput; alloc/op drops from ~130 KB at 100 KB Person to near zero. Biggest single perf move. Bench reference: bench/starwing_bench.lua + bench/COMPARISON.md show starwing C encoder at 2.3-3x ours across all sizes.","status":"closed","priority":1,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-05-17T15:47:04Z","created_by":"Eugene Blikh","updated_at":"2026-05-18T18:31:54Z","started_at":"2026-05-18T18:25:39Z","closed_at":"2026-05-18T18:31:54Z","close_reason":"Inlined 1-byte varint length prefix at every LEN emit site; +25-48% encode at 1KB+ Person. Full FFI-buffer rewrite deferred — this slice captured the dominant profile target without that level of disruption. See bench/PERF_LOG.md entry for details.","labels":["codegen","encoder","perf"],"dependency_count":0,"dependent_count":2,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-e4t","title":"LSP: plugin emits @class type stubs for generated messages","description":"Phase 3 of the LSP / LLM affordance work. Follows phase 1 (commit 8721548 — runtime annotations) and phase 2 (bd-74c — annotate codec/grpc/json/wkt).\n\nHighest-payoff phase for LLM grounding. Modify the protoc plugin (cmd/protoc-gen-tarantool/internal/gen) so each generated _pb.lua module emits ---@class blocks describing every message, plus typed @param/@return on the _encode/_decode/_descriptor surface. After this lands, anyone (human or LLM) who require('full.hello.hello_pb') gets full type info on Person, Address, etc., without reading the .proto.\n\nEmit shape (decision: inline at top of generated file; one file per .proto already includes all messages from that file):\n\n  ---@class hello.Person\n  ---@field name?           string\n  ---@field user_id?        ffi.cdata*           # uint64\n  ---@field age?            integer              # int32\n  ---@field weight_kg?      number               # double\n  ---@field emails?         string[]\n  ---@field address?        hello.Address\n  ---@field lucky_numbers?  integer[]\n  ---@field favorite_color? hello.Color          # enum alias\n  ---@field tags?           table\u003cstring,string\u003e # map\u003cstring,string\u003e\n  ---@field unknown_fields? string\n\n  ---@param tbl hello.Person\n  ---@return string\n  function M.Person_encode(tbl) ... end\n\n  ---@param bytes string\n  ---@return hello.Person\n  function M.Person_decode(bytes) ... end\n\nField-kind -\u003e Lua-type mapping:\n- scalar int32/uint32/sint32/fixed32/sfixed32/bool → integer/boolean\n- scalar int64/uint64/sint64/fixed64/sfixed64 → ffi.cdata* (LuaJIT cdata, per project convention)\n- scalar float/double → number\n- scalar string/bytes → string\n- enum → alias of integer (emit ---@alias hello.Color integer)\n- message → another @class reference\n- repeated T → T[]\n- map\u003cK,V\u003e → table\u003cK_lua, V_lua\u003e\n- oneof — all members are optional, generator should NOT emit a discriminator field; user calls view:which() / inspects which value is non-nil\n\nAll fields are optional (? suffix) since proto3 default-elision means absence-on-wire is indistinguishable from default value. Required fields in proto2 omit the ?.\n\nBoth mode=full and mode=runtime emit the same _encode/_decode wrapper signatures, so the stubs apply uniformly.\n\nVerify by:\n1. Regenerating examples/expected/ via just gen\n2. Confirming the generated _pb.lua files load and pass tests (no behavior change)\n3. Opening examples/expected/full/hello/hello_pb.lua in an LSP-aware editor and checking that hover on hello.Person, M.Person_encode shows the @class + typed signature\n4. Sanity: require a generated module from spike code and confirm autocomplete on the result-table field names","acceptance_criteria":"Plugin emits ---@class blocks + typed _encode/_decode signatures into every generated _pb.lua; just gen + just test pass; LSP hover on require('full.hello.hello_pb').Person_encode shows ---@param tbl hello.Person ---@return string; ---@class hello.Person is reachable via hover on a decoded value","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-05-23T09:30:19Z","created_by":"Eugene Blikh","updated_at":"2026-05-23T09:30:19Z","labels":["codegen","lsp"],"dependencies":[{"issue_id":"tarantool-protobuf-e4t","depends_on_id":"tarantool-protobuf-74c","type":"blocks","created_at":"2026-05-23T12:30:18Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-74c","title":"LSP: annotate codec/grpc/json/wkt with EmmyLua types","description":"Phase 2 of the LSP / LLM affordance work (phase 1 landed in commit 8721548 — .luarc.json, runtime/pb/_types.lua, init.lua + lazy.lua annotations).\n\nAdd ---@param / ---@return annotations to the remaining public-surface modules so editor hover and LLM context show types beyond just init.lua and the lazy views:\n\n- runtime/pb/codec.lua — encode(desc, t) / decode(desc, b); compile_writers / compile_readers (called from pb.finalize_message)\n- runtime/pb/grpc.lua — loopback / multiplex transport factories; the service client (factory(transport) -\u003e client_methods) and server (impl -\u003e {service, methods}) shapes\n- runtime/pb/json.lua — pb.json.encode(desc, t, opts) / pb.json.decode(desc, s, opts); opts shape (preserve_proto_field_names, emit_unpopulated, ...)\n- runtime/pb/wkt.lua — register(full_name, desc), lookup, any_pack/any_unpack, NULL sentinel\n\nFoundational @class declarations (pb.Descriptor, pb.Field, pb.GrpcTransport, pb.Json, pb.Wkt, pb.Module) already live in runtime/pb/_types.lua — extend as needed for opts shapes / map\u003cK,V\u003e generics.\n\nPure metadata change — no runtime behavior. Verify by running just test (must stay at 748 passing) and by opening a generated _pb.lua in an LSP-aware editor and confirming hover on require('pb').encode / .grpc.loopback / .json.encode shows the right signatures.","acceptance_criteria":"just test still green (748+ tests); hover in lua-language-server-aware editor shows typed signatures for pb.encode, pb.decode, grpc.loopback, json.encode, json.decode, wkt.register","status":"open","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-05-23T09:29:55Z","created_by":"Eugene Blikh","updated_at":"2026-05-23T09:29:55Z","labels":["docs","lsp"],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-rmf","title":"ra6 3k: WKT override-hook passthrough","description":"If a plan's descriptor has desc.encode and desc.decode set (the WKT pattern from runtime/pb/wkt.lua), the C runtime must call those Lua functions instead of walking fields. Plan compiler stores luaL_ref to those functions; encode/decode entry points check first. This is also the extension point for future per-message codegen C (c0i) — same hook. Depends on 3a only (just adds a check at entry to the encode/decode loop). Acceptance: hello.Event (uses Timestamp, Duration, Any, FieldMask, Wrappers, Struct) round-trips with PB_ENABLE_C=1 producing byte-equal output to mode=full; the WKT module is unmodified.","status":"open","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-05-18T20:21:13Z","created_by":"Eugene Blikh","updated_at":"2026-05-18T20:21:13Z","dependencies":[{"issue_id":"tarantool-protobuf-rmf","depends_on_id":"tarantool-protobuf-mq7","type":"blocks","created_at":"2026-05-18T23:21:55Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-rmf","title":"ra6 3k: WKT override-hook passthrough","description":"If a plan's descriptor has desc.encode and desc.decode set (the WKT pattern from runtime/pb/wkt.lua), the C runtime must call those Lua functions instead of walking fields. Plan compiler stores luaL_ref to those functions; encode/decode entry points check first. This is also the extension point for future per-message codegen C (c0i) — same hook. Depends on 3a only (just adds a check at entry to the encode/decode loop). Acceptance: hello.Event (uses Timestamp, Duration, Any, FieldMask, Wrappers, Struct) round-trips with PB_ENABLE_C=1 producing byte-equal output to mode=full; the WKT module is unmodified.","status":"in_progress","priority":2,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-05-18T20:21:13Z","created_by":"Eugene Blikh","updated_at":"2026-05-23T18:17:19Z","started_at":"2026-05-23T18:17:19Z","dependencies":[{"issue_id":"tarantool-protobuf-rmf","depends_on_id":"tarantool-protobuf-mq7","type":"blocks","created_at":"2026-05-18T23:21:55Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-m7u","title":"ra6 3i: proto2 — required, defaults, groups, extensions","description":"Proto2 semantic differences from proto3 that the C runtime must honor: (1) Required fields: decode-time enforcement when explicitly opted in (or default to skipping per existing pure-Lua behavior — match exactly); encode-time enforcement same. (2) Field defaults: present in the plan; emit them when missing on encode and substitute on decode where proto2 semantics require. (3) Groups (SGROUP/EGROUP wire format): encode/decode the framed group syntax. (4) Extensions: walk extension ranges, use plan-cached extension descriptor pointers, store in result._extensions. (5) Closed enum semantics for proto2. Depends on 3b + 3c + 3d + 3e + 3g + 3j (it's the join point — proto2 touches scalars, sub-messages, repeated, oneofs, unknown-fields). Acceptance: proto2_basic.BenchPayload and test_messages_proto2 round-trip byte-equal to mode=full at all bench sizes; conformance proto2 suite passes under PB_ENABLE_C=1.","status":"open","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-05-18T20:21:03Z","created_by":"Eugene Blikh","updated_at":"2026-05-18T20:21:03Z","dependencies":[{"issue_id":"tarantool-protobuf-m7u","depends_on_id":"tarantool-protobuf-hwe","type":"blocks","created_at":"2026-05-18T23:22:21Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"tarantool-protobuf-m7u","depends_on_id":"tarantool-protobuf-jc9","type":"blocks","created_at":"2026-05-18T23:22:22Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"tarantool-protobuf-m7u","depends_on_id":"tarantool-protobuf-mz6","type":"blocks","created_at":"2026-05-18T23:22:20Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"tarantool-protobuf-m7u","depends_on_id":"tarantool-protobuf-w3u","type":"blocks","created_at":"2026-05-18T23:22:22Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"tarantool-protobuf-m7u","depends_on_id":"tarantool-protobuf-wyp","type":"blocks","created_at":"2026-05-18T23:22:23Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"tarantool-protobuf-m7u","depends_on_id":"tarantool-protobuf-y1n","type":"blocks","created_at":"2026-05-18T23:22:20Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":6,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-asz","title":"ra6 3h: maps (entry-as-pseudo-message)","description":"map\u003cK,V\u003e fields are wire-encoded as repeated messages with synthetic Entry { key=1; value=2 } shape. Compile a map plan: outer field is K_REPEATED_MESSAGE with a synthesized Entry sub-plan. Encode: walk the Lua map via pairs(), emit each entry (this is the documented JIT exception — map fields are allowed pairs() in the hot path per CLAUDE.md). Decode: each entry yields a key + value, lua_settable into the result map. Depends on 3d (uses sub-message machinery). Acceptance: Person.ages_by_nickname / nickname_by_age / addresses_by_label round-trip; multi-key map fixtures behave correctly even though encode byte order differs (the existing map_test pattern).","status":"closed","priority":2,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-05-18T20:20:57Z","created_by":"Eugene Blikh","updated_at":"2026-05-23T17:07:06Z","started_at":"2026-05-23T16:45:29Z","closed_at":"2026-05-23T17:07:06Z","close_reason":"ra6 3h: maps (entry-as-pseudo-message) — encode walks the user map with lua_next (CLAUDE.md JIT exception), builds each entry payload in a stack-backed sub-buffer with synthetic tag(1,key) + tag(2,value), proto3-elides defaults independently for key and value, and emits outer tag + len + payload into the parent buffer. Map\u003c,message\u003e resolves the value sub-plan once and recurses through encode_body. Decode reads the entry payload bounded, dispatches inner id=1/id=2 (skipping anything else per spec), and lua_rawsets into a lazy-created result map table; missing key or value falls back to the proto3 zero. Reuses the existing list_stack_idx[] slot for the lazy map cache (a field is either repeated or map, never both). 12 new tests cover round-trip for ages_by_nickname (string→int32), nickname_by_age (int32→string), and addresses_by_label (string→message), plus default-elision, multi-key correctness, empty maps, and unknown-inner-id tolerance. Full suite 964/964 with PB_ENABLE_C=1.","dependencies":[{"issue_id":"tarantool-protobuf-asz","depends_on_id":"tarantool-protobuf-hwe","type":"blocks","created_at":"2026-05-18T23:22:19Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-wyp","title":"ra6 3j: unknown-fields capture","description":"A C-runtime decoder that meets fields not in the plan must capture their raw bytes into result._unknown_fields, and a subsequent C encode must re-emit them verbatim — same contract as test/unknown_test.lua against the pure-Lua paths. Bytes go in as a Lua string keyed by field number (mirror the existing convention). Depends on 3b + 3c (need the basic decode loop to know where to splice the unknown bytes). Acceptance: test/unknown_test.lua passes under PB_ENABLE_C=1; an evolved schema decoding bytes written by an old schema preserves the unknown fields through a C-side re-encode.","status":"closed","priority":2,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-05-18T20:20:40Z","created_by":"Eugene Blikh","updated_at":"2026-05-23T18:04:38Z","started_at":"2026-05-23T17:54:53Z","closed_at":"2026-05-23T18:04:38Z","close_reason":"Closed","dependencies":[{"issue_id":"tarantool-protobuf-wyp","depends_on_id":"tarantool-protobuf-mz6","type":"blocks","created_at":"2026-05-18T23:22:10Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"tarantool-protobuf-wyp","depends_on_id":"tarantool-protobuf-y1n","type":"blocks","created_at":"2026-05-18T23:22:09Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0}

M runtime/pb/c/c_runtime.c => runtime/pb/c/c_runtime.c +126 -36
@@ 1459,8 1459,6 @@ static void
encode_submessage_field(lua_State *L, enc_buf *b, pb_plan *plan,
                        pb_plan_field *f, int val_idx)
{
	if (lua_type(L, val_idx) != LUA_TTABLE)
		luaL_error(L, "message field requires a table value");
	if (plan->sub_plans_ref == LUA_NOREF)
		luaL_error(L, "plan '%s' has no sub-plans table",
		           plan->name != NULL ? plan->name : "?");


@@ 1479,6 1477,32 @@ encode_submessage_field(lua_State *L, enc_buf *b, pb_plan *plan,
		luaL_error(L, "sub-plan at index %d is not a userdata",
		           f->sub_plan_idx);

	/* WKT override: call desc.encode(value) for the body bytes; emit
	 * tag + len + body verbatim. Accepts any Lua type (datetime cdata,
	 * number, string, table) per the override's contract. */
	if (subplan->override_encode_ref != LUA_NOREF) {
		lua_rawgeti(L, LUA_REGISTRYINDEX,
		            subplan->override_encode_ref);
		lua_pushvalue(L, val_idx);
		lua_call(L, 1, 1);
		if (lua_type(L, -1) != LUA_TSTRING)
			luaL_error(L,
				"WKT encode for '%s' returned non-string",
				subplan->name != NULL ? subplan->name : "?");
		size_t blen;
		const char *bp = lua_tolstring(L, -1, &blen);
		ebuf_reserve(L, b, f->tag_len + 10 + blen);
		ebuf_put_tag(b, f);
		ebuf_put_varint(b, (uint64_t)blen);
		if (blen > 0)
			ebuf_put_bytes(b, (const uint8_t *)bp, blen);
		lua_settop(L, saved_top);
		return;
	}

	if (lua_type(L, val_idx) != LUA_TTABLE)
		luaL_error(L, "message field requires a table value");

	enc_buf sub;
	ebuf_init(&sub);
	encode_body(L, &sub, subplan, val_idx);


@@ 1570,19 1594,41 @@ encode_map_field(lua_State *L, enc_buf *b, pb_plan *plan,
		/* Value. Messages always emit (presence is meaningful); other
		 * kinds proto3-elide on default. */
		if (f->map_value_kind == PB_KIND_MESSAGE) {
			if (lua_type(L, v_idx) != LUA_TTABLE)
				luaL_error(L,
					"map<,message> value must be a table");
			enc_buf vbody;
			ebuf_init(&vbody);
			encode_body(L, &vbody, value_subplan, v_idx);
			ebuf_reserve(L, &entry,
				     1 + 10 + vbody.used);
			ebuf_put_byte(&entry, val_tag);
			ebuf_put_varint(&entry, (uint64_t)vbody.used);
			if (vbody.used > 0)
				ebuf_put_bytes(&entry, ebuf_base(&vbody),
				               vbody.used);
			if (value_subplan->override_encode_ref != LUA_NOREF) {
				/* WKT-typed map value: override owns body. */
				lua_rawgeti(L, LUA_REGISTRYINDEX,
				            value_subplan->override_encode_ref);
				lua_pushvalue(L, v_idx);
				lua_call(L, 1, 1);
				if (lua_type(L, -1) != LUA_TSTRING)
					luaL_error(L,
						"WKT encode for '%s' returned non-string",
						value_subplan->name != NULL
						? value_subplan->name : "?");
				size_t blen;
				const char *bp = lua_tolstring(L, -1, &blen);
				ebuf_reserve(L, &entry, 1 + 10 + blen);
				ebuf_put_byte(&entry, val_tag);
				ebuf_put_varint(&entry, (uint64_t)blen);
				if (blen > 0)
					ebuf_put_bytes(&entry,
						(const uint8_t *)bp, blen);
				lua_pop(L, 1);
			} else {
				if (lua_type(L, v_idx) != LUA_TTABLE)
					luaL_error(L,
						"map<,message> value must be a table");
				enc_buf vbody;
				ebuf_init(&vbody);
				encode_body(L, &vbody, value_subplan, v_idx);
				ebuf_reserve(L, &entry,
					     1 + 10 + vbody.used);
				ebuf_put_byte(&entry, val_tag);
				ebuf_put_varint(&entry, (uint64_t)vbody.used);
				if (vbody.used > 0)
					ebuf_put_bytes(&entry, ebuf_base(&vbody),
					               vbody.used);
			}
		} else if (!value_is_default_kind(L, f->map_value_kind,
		                                  v_idx)) {
			ebuf_reserve(L, &entry, 1);


@@ 1609,9 1655,12 @@ encode_body(lua_State *L, enc_buf *b, pb_plan *plan, int msg_idx)
{
	msg_idx = abs_idx(L, msg_idx);

	if (plan->has_override) {
	if (plan->override_encode_ref != LUA_NOREF) {
		/* Defensive: callers must dispatch via the override Lua-ref
		 * directly. Reaching encode_body here means a code-path bug. */
		luaL_error(L,
			"C encode on plan with override is unsupported (bd-rmf)");
			"internal: encode_body invoked on override plan '%s'",
			plan->name != NULL ? plan->name : "?");
	}

	if (plan->field_names_ref == LUA_NOREF || plan->n_fields == 0)


@@ 1696,13 1745,23 @@ static int
encode_lua(lua_State *L)
{
	pb_plan *plan = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT);
	luaL_checktype(L, 2, LUA_TTABLE);

	if (plan->has_override) {
		return luaL_error(L,
			"C encode on plan with override is unsupported (bd-rmf)");
	/* WKT override: desc.encode(value) returns the body bytes verbatim.
	 * The value is any Lua type the override accepts (datetime cdata,
	 * Lua number, string, table, etc.) — don't pre-check for TTABLE. */
	if (plan->override_encode_ref != LUA_NOREF) {
		lua_rawgeti(L, LUA_REGISTRYINDEX, plan->override_encode_ref);
		lua_pushvalue(L, 2);
		lua_call(L, 1, 1);
		if (lua_type(L, -1) != LUA_TSTRING)
			return luaL_error(L,
				"WKT encode for '%s' returned non-string",
				plan->name != NULL ? plan->name : "?");
		return 1;
	}

	luaL_checktype(L, 2, LUA_TTABLE);

	enc_buf b;
	ebuf_init(&b);
	encode_body(L, &b, plan, 2);


@@ 1959,6 2018,18 @@ decode_submessage_field(dec_ctx *c, pb_plan_field *f, int sub_plans_idx)
		           f->sub_plan_idx);
	lua_pop(L, 1);

	/* WKT override: feed the body slice to desc.decode(buf), push whatever
	 * Lua representation the override returns. */
	if (subplan->override_decode_ref != LUA_NOREF) {
		lua_rawgeti(L, LUA_REGISTRYINDEX,
		            subplan->override_decode_ref);
		lua_pushlstring(L, (const char *)(c->buf + c->pos),
		                (size_t)plen);
		lua_call(L, 1, 1);
		c->pos += (size_t)plen;
		return;
	}

	lua_createtable(L, 0, subplan->n_fields);
	int sub_result_idx = lua_gettop(L);



@@ 2036,16 2107,28 @@ decode_map_entry(dec_ctx *c, pb_plan_field *f, int sub_plans_idx,
				if (c->len - c->pos < sub_len)
					luaL_error(L,
						"truncated nested map<,message> value");
				lua_createtable(L, 0, vsub->n_fields);
				int new_val = lua_gettop(L);
				size_t saved2 = c->len;
				c->len = c->pos + (size_t)sub_len;
				decode_body(c, vsub, new_val);
				if (c->pos != c->len)
					luaL_error(L,
						"nested map<,message> body underflow");
				c->len = saved2;
				lua_replace(L, val_idx);
				if (vsub->override_decode_ref != LUA_NOREF) {
					/* WKT-typed map value: override consumes body. */
					lua_rawgeti(L, LUA_REGISTRYINDEX,
					            vsub->override_decode_ref);
					lua_pushlstring(L,
						(const char *)(c->buf + c->pos),
						(size_t)sub_len);
					lua_call(L, 1, 1);
					c->pos += (size_t)sub_len;
					lua_replace(L, val_idx);
				} else {
					lua_createtable(L, 0, vsub->n_fields);
					int new_val = lua_gettop(L);
					size_t saved2 = c->len;
					c->len = c->pos + (size_t)sub_len;
					decode_body(c, vsub, new_val);
					if (c->pos != c->len)
						luaL_error(L,
							"nested map<,message> body underflow");
					c->len = saved2;
					lua_replace(L, val_idx);
				}
			} else {
				dec_push_kind(c, f->map_value_kind);
				lua_replace(L, val_idx);


@@ 2092,9 2175,12 @@ static void
decode_body(dec_ctx *c, pb_plan *plan, int result_idx)
{
	lua_State *L = c->L;
	if (plan->has_override) {
	if (plan->override_decode_ref != LUA_NOREF) {
		/* Defensive: callers must dispatch via the override Lua-ref
		 * directly. Reaching decode_body here means a code-path bug. */
		luaL_error(L,
			"C decode on plan with override is unsupported (bd-rmf)");
			"internal: decode_body invoked on override plan '%s'",
			plan->name != NULL ? plan->name : "?");
	}

	result_idx = abs_idx(L, result_idx);


@@ 2319,9 2405,13 @@ decode_lua(lua_State *L)
	size_t buf_len;
	const char *buf = luaL_checklstring(L, 2, &buf_len);

	if (plan->has_override) {
		return luaL_error(L,
			"C decode on plan with override is unsupported (bd-rmf)");
	/* WKT override: desc.decode(buf) consumes the entire body and returns
	 * whatever Lua representation the override picks (e.g. datetime). */
	if (plan->override_decode_ref != LUA_NOREF) {
		lua_rawgeti(L, LUA_REGISTRYINDEX, plan->override_decode_ref);
		lua_pushlstring(L, buf, buf_len);
		lua_call(L, 1, 1);
		return 1;
	}

	lua_createtable(L, 0, plan->n_fields);

M test/c_runtime_decode_test.lua => test/c_runtime_decode_test.lua +5 -5
@@ 275,12 275,12 @@ for _, mode in ipairs({'full', 'runtime'}) do
            {name = 'x', _unknown_fields = unknown_tag})
    end

    function g.test_wkt_override_rejected()
        -- has_override plans don't expose field arrays; mirror 3b's gate.
    function g.test_wkt_override_passthrough()
        -- bd-rmf / ra6 3k: has_override plans dispatch to desc.decode(buf).
        local plan = c_runtime.compile_plan(pb.wkt.Timestamp_descriptor)
        t.assert_error_msg_contains('override', function()
            c_runtime.decode(plan, '')
        end)
        local bytes = pb.wkt.Timestamp_encode({seconds = 1700000000, nanos = 42})
        t.assert_equals(c_runtime.decode(plan, bytes),
                        pb.wkt.Timestamp_decode(bytes))
    end

    function g.test_truncated_input_errors()

M test/c_runtime_encode_test.lua => test/c_runtime_encode_test.lua +6 -5
@@ 292,12 292,13 @@ for _, mode in ipairs({'full', 'runtime'}) do
                        full_hello.Person_encode(msg))
    end

    function g.test_wkt_override_rejected()
        -- has_override plans skip field-walk; 3b does not handle them.
    function g.test_wkt_override_passthrough()
        -- bd-rmf / ra6 3k: has_override plans now dispatch to
        -- desc.encode(value) and return its bytes verbatim.
        local plan = c_runtime.compile_plan(pb.wkt.Timestamp_descriptor)
        t.assert_error_msg_contains('override', function()
            c_runtime.encode(plan, {seconds = 1})
        end)
        local v = {seconds = 1, nanos = 2}
        t.assert_equals(c_runtime.encode(plan, v),
                        pb.wkt.Timestamp_encode(v))
    end

    -- ---------- Repeated + packed (bd-jc9 / ra6 3e) ----------

A test/c_runtime_wkt_test.lua => test/c_runtime_wkt_test.lua +158 -0
@@ 0,0 1,158 @@
-- Tests for bd-rmf / ra6 3k: WKT override-hook passthrough.
--
-- A plan whose descriptor carries desc.encode / desc.decode dispatches
-- straight to those overrides instead of walking fields. The Event
-- message exercises every WKT shape we ship — Timestamp, Duration,
-- Empty, the <T>Value wrappers, Struct, Value, ListValue, Any,
-- FieldMask — so byte-equality with mode=full proves the override is
-- live at both the top level and when nested as a sub-message field.
--
-- Acceptance per bd-rmf: hello.Event with WKT sub-messages round-trips
-- byte-equal to mode=full pure-Lua, runtime/pb/wkt.lua is unmodified.

local t = require('luatest')
local datetime = require('datetime')

local pb = require('pb')
local c_runtime = pb.c_runtime

local function skip_if_no_c()
    if c_runtime == nil then
        t.skip('PB_ENABLE_C not set or pb.c_runtime not available')
    end
end

local full_hello

for _, mode in ipairs({'full', 'runtime'}) do
    local g = t.group('c_runtime_wkt.' .. mode)
    local hello

    g.before_all(function()
        skip_if_no_c()
        hello = require(mode .. '.hello.hello_pb')
        full_hello = require('full.hello.hello_pb')
    end)

    g.before_each(skip_if_no_c)

    -- ---------- Top-level override dispatch ----------

    function g.test_timestamp_top_level_encode_decode()
        local plan = c_runtime.compile_plan(pb.wkt.Timestamp_descriptor)
        local dt = datetime.new({timestamp = 1700000000, nsec = 123})
        local c_bytes = c_runtime.encode(plan, dt)
        t.assert_equals(c_bytes, pb.wkt.Timestamp_encode(dt))
        local out = c_runtime.decode(plan, c_bytes)
        t.assert(datetime.is_datetime(out))
        t.assert_equals(out.epoch, 1700000000)
        t.assert_equals(out.nsec, 123)
    end

    function g.test_duration_top_level_round_trip()
        local plan = c_runtime.compile_plan(pb.wkt.Duration_descriptor)
        local v = {seconds = 7200, nanos = 500}
        local c_bytes = c_runtime.encode(plan, v)
        t.assert_equals(c_bytes, pb.wkt.Duration_encode(v))
        t.assert_equals(c_runtime.decode(plan, c_bytes),
                        pb.wkt.Duration_decode(c_bytes))
    end

    function g.test_empty_top_level_encode_is_zero_bytes()
        local plan = c_runtime.compile_plan(pb.wkt.Empty_descriptor)
        t.assert_equals(c_runtime.encode(plan, {}), '')
    end

    function g.test_int32value_wrapper_top_level()
        local plan = c_runtime.compile_plan(pb.wkt.Int32Value_descriptor)
        -- The wrapper accepts the bare scalar; presence is meaningful.
        t.assert_equals(c_runtime.encode(plan, 42),
                        pb.wkt.Int32Value_encode(42))
    end

    -- ---------- Nested override dispatch (sub-message field) ----------

    function g.test_event_with_timestamp_byte_equal()
        local plan = c_runtime.compile_plan(hello.Event_descriptor)
        local msg = {
            title = 'hi',
            created_at = datetime.new(
                {timestamp = 1700000000, nsec = 123456789}),
        }
        t.assert_equals(c_runtime.encode(plan, msg),
                        full_hello.Event_encode(msg))
    end

    function g.test_event_with_duration_and_empty_byte_equal()
        local plan = c_runtime.compile_plan(hello.Event_descriptor)
        local msg = {
            title = 'hi',
            duration = {seconds = 60, nanos = 0},
            ack = {},
        }
        t.assert_equals(c_runtime.encode(plan, msg),
                        full_hello.Event_encode(msg))
    end

    function g.test_event_with_wrappers_byte_equal()
        local plan = c_runtime.compile_plan(hello.Event_descriptor)
        local msg = {
            retry_count = 5,
            note = 'remember',
            is_admin = true,
        }
        t.assert_equals(c_runtime.encode(plan, msg),
                        full_hello.Event_encode(msg))
    end

    function g.test_event_with_wrapper_zero_preserved()
        local plan = c_runtime.compile_plan(hello.Event_descriptor)
        -- Wrappers preserve presence at zero — the override must still
        -- emit tag + len(0). A naive "skip default" path would lose it.
        local msg = {retry_count = 0}
        t.assert_equals(c_runtime.encode(plan, msg),
                        full_hello.Event_encode(msg))
    end

    function g.test_event_with_fieldmask_byte_equal()
        local plan = c_runtime.compile_plan(hello.Event_descriptor)
        local msg = {update_mask = {paths = {'foo', 'bar.baz'}}}
        t.assert_equals(c_runtime.encode(plan, msg),
                        full_hello.Event_encode(msg))
    end

    -- ---------- Round-trip parity through the C path ----------
    --
    -- Encode in C, decode in C, compare against mode=full's decode of
    -- the same wire bytes. Asserts the override decode wrapper hands
    -- back the same Lua representation the pure-Lua codec does.

    function g.test_event_round_trip_through_c()
        local plan = c_runtime.compile_plan(hello.Event_descriptor)
        local dt = datetime.new({timestamp = 42, nsec = 500})
        local msg = {
            title = 'x',
            created_at = dt,
            duration = {seconds = 1, nanos = 2},
            ack = {},
            retry_count = 7,
            note = 'hello',
            update_mask = {paths = {'a', 'b'}},
        }
        local bytes = c_runtime.encode(plan, msg)
        local c_dec = c_runtime.decode(plan, bytes)
        local lua_dec = full_hello.Event_decode(bytes)

        t.assert_equals(c_dec.title, lua_dec.title)
        t.assert(datetime.is_datetime(c_dec.created_at))
        t.assert_equals(c_dec.created_at.epoch, lua_dec.created_at.epoch)
        t.assert_equals(c_dec.created_at.nsec,  lua_dec.created_at.nsec)
        t.assert_equals(tonumber(c_dec.duration.seconds),
                        tonumber(lua_dec.duration.seconds))
        t.assert_equals(c_dec.duration.nanos, lua_dec.duration.nanos)
        t.assert_equals(type(c_dec.ack), 'table')
        t.assert_equals(c_dec.retry_count, lua_dec.retry_count)
        t.assert_equals(c_dec.note, lua_dec.note)
        t.assert_equals(c_dec.update_mask.paths, lua_dec.update_mask.paths)
    end
end