Guest→Host Syscall Payload Contract v1#

Scope#

This document specifies the project-owned binary wire format used for MuduDB guest→host syscalls.

The wire format keeps the 16-byte header introduced in Phase 1 and encodes the message body with MessagePack. The MessagePack layout is project-controlled: it is derived from the canonical WIT definitions in mudu_binding/wit/ and generated by mgen into custom serde implementations for Rust and custom MessagePack formatters for C#. The implementation uses rmp_serde (or an equivalent MessagePack library on the C#/AssemblyScript side) only as the low-level encoder/decoder; it does not use the default rmp_serde derive behavior, which would serialize Rust enum variants as maps or strings and would not be stable across guest languages.

The canonical syscall boundary is the WIT function interface declared in uni-syscall.wit. The header and MessagePack body described here are the runtime’s internal serialization of those WIT function calls; guest code should call the WIT functions directly and let the generated bindings handle the wire format.

The logical data model is the existing mudu_binding::universal type family; this contract defines the wire encoding of those types.

The obsolete UniDatTypeId / UniMuTypeFamily enumerations are not part of this contract and have been removed from the binding layer. When a type-family value needs to cross the boundary, it is carried inside UniDataType / UniScalar instead.

Registry#

Property

Value

Format family

FormatKind::SyscallPayload

Magic

0x4D53_5350 (ASCII MSSP)

Current version

1

Supported range

[1, 1]

Registry

mudu/src/compat/mod.rs

The runtime decodes only the current version. An unsupported version fails fast with ErrorCode::UnsupportedFormatVersion. When the format version changes, MPK packages are rebuilt for the matching runtime; the runtime does not retain legacy decoders.

Version history#

Version

Date

Summary

1

2026-07-01

16-byte header (magic, version, flags, message kind) plus a MessagePack body. The MessagePack layout is generated from WIT by mgen and is project-controlled, not the default rmp_serde derive output.

Payload layout#

Every syscall request and every syscall response is encoded as a single self-describing message:

+--------------------------------+
| Header (16 bytes)              |
+--------------------------------+
| Body (variable, MessagePack)   |
+--------------------------------+

There is no separate length prefix outside the header; the WIT transport delivers the exact byte range.

Body encoding#

The body is a single MessagePack value. The exact byte sequence is controlled by the mgen templates and must not be inferred from generic rmp_serde defaults.

Source of truth#

The canonical schema is the set of WIT files under mudu_binding/wit/:

WIT file

Rust generated type

uni-data-type.wit

UniDataType

uni-data-value.wit

UniDataValue / UniDataValueField

uni-scalar.wit

UniScalar

uni-scalar-value.wit

UniScalarValue

uni-record-type.wit

UniRecordType / UniRecordField / UniFieldAttr

uni-result-type.wit

UniResultType

uni-result-set.wit

UniResultSet

uni-tuple-row.wit

UniTupleRow

uni-sql-stmt.wit

UniSqlStmt

uni-sql-param.wit

UniSqlParam

uni-query-argv.wit

UniQueryArgv

uni-command-argv.wit

UniCommandArgv

uni-command-result.wit

UniCommandResult / UniCommandReturn

uni-query-result.wit

UniQueryResult / UniQueryReturn

uni-error.wit

UniError

uni-oid.wit

UniOid

uni-syscall.wit

syscall function interface

mgen reads these WIT files and emits language-specific source files. For Rust it uses the templates in mudu_gen/templates/rust/; for C# it uses mudu_gen/templates/csharp/.

MessagePack encoding rules#

The generated code obeys the following rules, which override any default rmp_serde behavior:

WIT construct

MessagePack encoding

record

Fixed-length MessagePack array, one element per field, in declaration order.

variant

Two-element MessagePack array [tag, payload]. tag is a u32 discriminator assigned by declaration order (starting at 0). If a case has no payload, payload is a dummy 0u8 so the array always has length 2.

enum

Bare u32 discriminant, assigned by declaration order (starting at 0).

list<T>

MessagePack array of encoded T.

option<T>

MessagePack nil for none; otherwise the encoded T for some.

result<T, E>

MessagePack array [ok_tag, value] where ok_tag is 0u8 for ok and 1u8 for err, followed by the encoded T or E.

string

MessagePack str.

list<u8> / blob

MessagePack bin.

Primitive scalars (u8, i32, u64, f32, bool, char, …)

Standard MessagePack integer/float/bool/str representations.

box<T>

Same encoding as T; the box is transparent on the wire.

Because the layout is generated from WIT, the same rules apply to the C# and AssemblyScript guests. The Rust side uses rmp_serde with custom Serialize/Deserialize implementations; the C# side uses custom IMessagePackFormatter<T> implementations generated from the same WIT.

OID / UniOid#

OID is the logical 128-bit object identifier (mudu::common::id::OID). On the wire it is encoded as a record with two u64 fields, h and l, which yields a two-element MessagePack array [h, l]. Decoders reconstruct the logical OID as ((h as u128) << 64) | (l as u128).

Variant tag tables#

The tags below are derived from the WIT declarations in mudu_binding/wit/. They are listed here for convenience, but the WIT files remain the canonical source.

UniScalarValuevariant tags:

Tag

Variant

Payload

0

Bool

bool

1

U8

u8

2

I8

s8

3

U16

u16

4

I16

s16

5

U32

u32

6

I32

s32

7

U64

u64

8

U128

list<u8> (16 bytes, big-endian)

9

I64

s64

10

I128

list<u8> (16 bytes, big-endian)

11

F32

f32

12

F64

f64

13

Char

char (MessagePack str of one Unicode scalar)

14

String

string

15

Blob

list<u8> (MessagePack bin)

16

Numeric

string

17

Date

string

18

Time

string

19

Timestamp

string

20

TimestampTz

string

Note: the WIT uni-scalar-value defines blob for binary scalar payloads; UniScalarValue::Blob carries a Vec<u8>. U128 / I128 use 16-byte big-endian byte arrays to stay portable across guest languages that lack a native 128-bit integer MessagePack type.

UniDataValuevariant tags:

Tag

Variant

Payload

0

Scalar

UniScalarValue

1

Array

list<UniDataValue>

2

Record

list<UniDataValueField>

3

Binary

list<u8> (MessagePack bin)

A UniDataValueField is a record of [field_name: string, field_value: UniDataValue].

UniDataTypevariant tags:

Tag

Variant

Payload

0

Scalar

UniScalar

1

Array

UniDataType

2

Record

UniRecordType

3

Option

UniDataType

4

Tuple

list<UniDataType>

5

Result

UniResultType

6

Identifier

string

7

Binary

0u8 dummy (variant case has no inner type)

Note: box<T> is not a standalone variant. It appears only as a WIT syntax sugar for recursive types (array, option) and is transparent on the wire.

UniScalar — WIT enum, bare u32 discriminant:

Value

Name

Value

Name

0

Bool

11

F32

1

U8

12

F64

2

I8

13

Char

3

U16

14

String

4

I16

15

Blob

5

U32

16

Numeric

6

I32

17

Date

7

U64

18

Time

8

U128

19

Timestamp

9

I64

20

TimestampTz

10

I128

UniScalar and UniScalarValue now share the same tag numbering.

Composite type layouts#

The MessagePack encoding of each record/variant follows the generic rules above. For reference, the field order and payload shapes are:

  • UniOid[h: u64, l: u64] (record).

  • UniSqlStmt[sql_string: string] (record).

  • UniSqlParam[params: list<UniDataValue>] (record).

  • UniTupleRow[fields: list<UniDataValue>] (record).

  • UniFieldAttr[attr_name: string, attr_value: string] (record).

  • UniRecordField[field_name: string, field_type: UniDataType, field_attrs: list<UniFieldAttr>] (record).

  • UniRecordType[record_name: string, record_fields: list<UniRecordField>] (record).

  • UniResultType[ok: option<UniDataType>, err: option<UniDataType>] (record).

  • UniResultSet[eof: bool, row_set: list<UniTupleRow>, cursor: list<u8>] (record).

  • UniError[err_code: u32, err_msg: string, err_src: string, err_loc: string, err_details: list<u8>] (record).

  • UniQueryArgv[oid: UniOid, query: UniSqlStmt, param_list: UniSqlParam] (record).

  • UniCommandArgv[oid: UniOid, command: UniSqlStmt, param_list: UniSqlParam] (record).

  • UniQueryResult[tuple_desc: UniRecordType, result_set: UniResultSet] (record).

  • UniCommandResult[affected_rows: u64] (record).

  • UniCommandReturn — variant: ok(UniCommandResult) or err(UniError).

  • UniQueryReturn — variant: ok(UniQueryResult) or err(UniError).

  • UniSessionOpenArgv — currently a hand-written Rust record; its wire shape is [worker_id: UniOid]. It does not yet have a dedicated WIT file; if it is moved into WIT in the future it will follow the same encoding rules as above.

Relationship to the current session codec#

The KV syscalls (OpenRange) are currently hand-written binary in handle_sys_session.rs. Their existing conventions match this contract’s primitives. v1 wraps those payloads in the 16-byte header and uses the project-controlled MessagePack Result<T, E> encoding for errors, instead of the legacy MERR magic escape. Unifying the session codec onto the same mgen-generated MessagePack path is Phase 2 work.

Integrity mechanisms#

  • Magic check: decoders reject messages whose magic is not 0x4D53_5350.

  • Version check: decoders reject any version outside [1, 1], mapping to ErrorCode::UnsupportedFormatVersion.

  • Flags check: decoders reject any non-zero flags value.

  • Message-kind validation: decoders reject unknown/0 message_kind values.

  • Length checks: decoders require at least 16 bytes for the header.

  • MessagePack structural validation: decoders reject malformed MessagePack payloads, unknown variant tags, and record arrays of unexpected length.

  • UTF-8 validation: string fields must be valid UTF-8.

Structural body decode failures map to ErrorCode::Decode. Magic mismatches map to ErrorCode::CorruptedData via the compatibility registry.

Compatibility matrix#

Reader \ Writer

v1

v1

Compatible

Only version 1 is supported.

Upgrade and rollback rules#

  • Upgrade: A future v2 changes the header version and/or the body layout. When v2 is introduced, MPK packages are rebuilt for the matching runtime and offline migration handlers convert stored fixtures/test data.

  • No legacy decoders: the runtime decodes only the current version. A message with an unsupported version fails fast with ErrorCode::UnsupportedFormatVersion.

  • No legacy MPK support: packages are rebuilt per ABI version bump; versioning exists to make future upgrades explicit and controllable, not to preserve cross-version compatibility.

Deprecation policy#

Version 1 may be deprecated only after:

  1. All guest bindings (Rust, C#, AssemblyScript) emit and accept the new version.

  2. The new version has been the default for at least one release cycle.

  3. MPK packages have been rebuilt against the new runtime.

References#

  • Registry: mudu/src/compat/mod.rs

  • WIT schema source: mudu_binding/wit/

  • Syscall function interface: mudu_binding/wit/uni-syscall.wit

  • mgen Rust templates: mudu_gen/templates/rust/

  • mgen C# templates: mudu_gen/templates/csharp/

  • universal type family (Rust): mudu_binding/src/universal/

  • Existing hand-written KV codec: mudu_binding/src/codec/handle_sys_session.rs

  • SQL request/response codec: mudu_binding/src/codec/handle_sys_incoming.rs, handle_sys_outcoming.rs

  • Host syscall entry points: mudu_runtime/src/interface/kernel.rs

  • Design plan: doc/cn/todo/project-controlled-guest-host-abi.md