Embedded structural records, legacy migration and scalar identity preservation.
Structural ZLM payload layouts
Evidence correction: historical kill counts from the native Core-1 mutation runner before
9a63b70ff4are withdrawn pending fresh replay. Stale binaries could produce false kills. This qualifies all such counts below; seedocs/mutation-evidence.md. Test inventories are not execution evidence.
This implementation reference describes the structural payload inside the ZLM2 envelope. It records existing formats and migration behavior; it allocates no semantic clause, wire tag, execution authority or benchmark support. All offsets below are record-relative bytes. Integers are little-endian, records are packed, strings are length-framed UTF-8 without an implicit terminator, and arithmetic used to find records must be bounds-checked.
Families and minor versions
| Magic and version | Role and framing |
|---|---|
ZLM0 1.0/1.1 |
Read-only legacy scalar format: 32-byte header, source spans, 32-byte instructions. Rebuild migrates it. |
ZLM1 1.0/1.1 |
Historical structural inputs. Headers are 40/44 bytes respectively. Rebuild emits a canonical later structural minor. |
ZLM1 1.2 |
Canonical scalar structural format; 44-byte header and 24-byte function headers. |
ZLM1 1.3 |
Closed enums, descriptor/local type references; 48-byte header and 32-byte function headers. |
ZLM1 1.4 |
Closed errors in the same descriptor namespace. |
ZLM1 1.5 |
Single-payload variants; every variant header is 24 bytes, including payloadless variants. |
ZLM1 1.6 |
Fixed-width scalar annotations in instruction word 1. |
ZLM1 1.7 |
Intrinsic Result descriptors and try. |
ZLM1 1.8 |
Sealed value fields, initializer kind, owned paths and cleanup edges; 48-byte function headers. |
ZLA2 1.8 |
Core-1 packed multi-field value payload, inside ZLM2 2.16. Same structural record widths as 1.8. |
ZLA2 1.9 |
Shared borrowing, inside ZLM2 2.17. |
ZLA2 1.10 |
Lexical shared locals, inside ZLM2 2.18. |
ZLA2 1.11 |
Lexical shared field projection, inside ZLM2 2.19. |
ZLA2 1.12 |
Forward-control lexical borrowing, inside ZLM2 2.20. |
ZLA2 1.13–1.17 |
Dynamic matching, compute dataflow/policy and State graph/topic extensions, inside ZLM2 2.21–2.25. |
ZLA2 1.18 |
Flow compensation carriers, inside ZLM2 2.26. |
ZLA2 1.19 |
Owned checkpoint and schema-bearing start carriers, inside ZLM2 2.27. |
ZLA2 1.20 |
Ordered finite-set carriers, inside ZLM2 2.28. |
ZLA2 1.21 |
Bounded two-factor product carriers, inside ZLM2 2.29; earlier carrier admission remains unchanged. |
Standalone ZLM1 schema 1.9 is a different runtime-rich format, with a 256-byte header and typed tables. Its exact layout is already specified in Core-0 checked artifacts. It is neither ZLA2 physical layout 1.9 nor a 48-byte structural header. A generic Core-0 reader’s acceptance of a family does not by itself establish valid embedding, domain, preview, checked-AST or typed-boundary admission in ZLM2.
The writer chooses the minimal required format, not the largest format the reader knows. Plain modules stay byte-identical in their earlier canonical minor. One-field values can remain structural ZLM1 1.8 in ZLM2 2.14; the packed multi-field Core-1 tranche uses ZLA2 1.8 instead. Later ZLA2 borrow layouts share physical widths but have different checked semantic requirements. There is no serialized loan table in these structural layouts: the decoder reconstructs and checks loans from typed locals, places and instructions.
Structural 1.0–1.2 intentionally omit local-type rows even when local slots exist. Scalar-format selection consults local spellings only when that table is present; parameter types, instruction annotations and bounded execution remain part of the preservation check. Rebuilding a supported legacy scalar carrier does not fabricate a local-type table or change its canonical bytes/identities. Typed locals in later layouts still participate in selecting scalar 1.6, and out-of-range local accesses remain invalid before serialization.
ZLA2 is an embedded carrier, not an independently admitted Core-0 module. Within ZLM2, structural ZLM1 and ZLA2 function/type strings additionally carry the closed Core-1 Tensor/TensorView spellings and compiler-generated Compute, State, Flow and Optimize selectors (including those inside length-framed private package selectors). Numeric selector fields use canonical bounded decimal spelling; unknown carrier families, extra fields, leading zeros, negative zero and out-of-range values reject. These spellings do not widen standalone ZLM1 admission.
For structural carriers in ZLM2 2.13–2.20, inner decoding validates record bounds, names, descriptors, ownership metadata, instruction ranges and structural stack/control flow first. Type-flow validation requires the enclosing domain/modality facts, so it runs after the outer reader restores and validates those facts, dedicated regions, semantic nodes and operands. Only that final checked module is published. A digest-valid carrier with inconsistent types or outer semantics still rejects. This supports owned/borrowed Algorithm bodies beside the four dedicated domains; it neither changes wire bytes nor activates a new edition or benchmark task. Legacy structural encoding likewise validates with the original Core-1 domain context, even though those facts are encoded only in the outer message rows. The separate ZLM0 and runtime-rich ZLM1 1.9 migration paths remain unchanged.
Scalar annotations select structural 1.6, which inherits the 24-byte variant row from 1.5 even when every enum/error variant is fieldless. Size calculation and emission use that same layout; the unused payload fields remain checked. Without sensitive scalar operations, fieldless enums/errors retain their earlier 1.3/1.4 layouts and bytes. Mixed scalar/enum/error fixtures cover this composition; they do not establish every combination of source features.
Structural section order
header
module name, owner name
imports: header, module name, owner name
closed descriptors: header, origin name, type name
value: repeated field header, field name, field type
otherwise: repeated variant header, variant name, optional payload name/type
functions: header, selector, result type, optional origin
parameters: header, name, type
typed locals: header, type spelling
owned paths
cleanup edges: header, drop-instruction indexes
instructions
Counts in the header count records, not bytes. Instructions are grouped by function; the module instruction count equals their sum. There is no separate inner checksum or final trailer in these structural families. An enclosing ZLM2 digest covers the complete payload and its outer tables. No extra bytes are permitted after the final instruction of the final function.
Header
| Offset | Width | Field |
|---|---|---|
| 0 | 4 | Magic: ZLM1 or ZLA2 |
| 4 | 2 | Major: 1 |
| 6 | 2 | Minor |
| 8 | 4 | Semantic profile: exact=0, modular=1, approximate=2 |
| 12 | 4 | Verification: checked=0, verified=1, trusted=2 |
| 16 | 4 | Owner kind: value=0, class=1 |
| 20 | 4 | Flags: bit 0 is open; other bits zero. A value cannot be open. |
| 24 | 4 | Module-name byte length |
| 28 | 4 | Owner-name byte length |
| 32 | 4 | Function count |
| 36 | 4 | Total instruction count |
| 40 | 4 | Import count; absent in 1.0, which has no imports |
| 44 | 4 | Closed-descriptor count; present from 1.3 |
The two names immediately follow the selected header, not a fixed maximum header. An import’s 8-byte header contains module-name length at 0 and owner-name length at 4, followed by those strings in that order. Imports are unique and sorted lexicographically by module, then owner.
Closed descriptors, variants and fields
The 24-byte descriptor header has six u32 fields: origin length at 0, type-name
length at 4, variant-or-field count at 8, kind at 12, visibility at 16, and
initializer function index at 20. Origin and type-name strings follow.
Kinds are enum=0, error=1, Result=2, value=3. Visibility is public=0/private=1. The initializer field is zero for non-values; for a value it identifies its checked initializer in the closure-wide function table. Descriptor kinds are minor-gated, not interchangeable because their record header matches.
An 8-byte variant header (1.3/1.4) holds name length at 0 and tag at 4. From 1.5, its 24-byte header additionally holds payload count at 8, payload-name length at 12, payload-type length at 16 and payload type reference at 20. The variant name precedes any payload-name/type strings. Tags are consecutive declaration-order ordinals. Payload count is zero or one. A payloadless variant has zero payload lengths and the scalar sentinel in its payload type reference; it has no payload strings. Ordinary payload variants are bounded to Int32; Result has its separately checked exact Ok/Err descriptor and payload rules.
A value field is a 56-byte header followed by name and type strings:
| Offset | Width | Field |
|---|---|---|
| 0 | 32 | Field semantic identity, opaque bytes |
| 32 | 4 | Name length |
| 36 | 4 | Type-spelling length |
| 40 | 4 | Visibility; currently public=0 |
| 44 | 4 | Mutability; currently zero |
| 48 | 4 | Type reference; bounded value fields use the scalar sentinel |
| 52 | 4 | Exact scalar kind |
Field order is declaration order, not alphabetical order. Field identity binds the declaring type and field contract; opaque identity bytes are not a license to bypass reconstruction and checking. Current structural value carriers have bounded immutable scalar fields and aggregate width restrictions; this layout does not make general classes or arbitrary aggregate shapes executable.
Functions and locals
| Offset | Width | Field |
|---|---|---|
| 0 | 4 | Selector byte length |
| 4 | 4 | Result-type spelling length |
| 8 | 4 | Parameter count, excluding the erased Class receiver |
| 12 | 4 | Local count, including parameter places |
| 16 | 4 | Instruction count for this function |
| 20 | 4 | Origin length from 1.3; zero before 1.3 |
| 24 | 4 | Result type reference from 1.3 |
| 28 | 4 | Local-type count from 1.3; equals local count |
| 32 | 4 | Callable kind from 1.8: message=0, initializer=1 |
| 36 | 4 | Declaring-type reference from 1.8 |
| 40 | 4 | Owned-path count from 1.8 |
| 44 | 4 | Cleanup-edge count from 1.8 |
Header sizes are 24, 32 and 48 bytes for pre-1.3, 1.3–1.7 and 1.8+ respectively. Selector, result type and (from 1.3) origin strings follow. Parameters have an 8-byte header containing name length at 0 and type length at 4, followed by the name and type. Typed locals have an 8-byte header containing type length at 0 and type reference at 4, followed by the type spelling. Untyped structural 1.0–1.2 has no serialized local-type records.
0xffffffff is the scalar type-reference sentinel; otherwise a reference names
the one closed-descriptor namespace. The sentinel is not an exact scalar kind:
the checked spelling distinguishes Bool, integer widths, and other admitted
intrinsics. Function indexes, descriptor indexes, local indexes and field
ordinals belong to different namespaces. Imported private selectors retain
their canonical framed names and matching origins; public callable origins
are additionally bound by the outer ZLM2 origin table.
Ownership and cleanup
An owned-path row is 24 bytes: six u32 fields at offsets 0, 4, 8, 12, 16 and
20 for place kind, place index, type reference, initialization order, entry
state and reserved zero. Place kinds are argument=0/local=1. Entry states are
uninitialized=0/initialized=1/moved=2. Parameter places occupy the initial
local indexes; argument/local classification must match the function counts.
A cleanup-edge header is 24 bytes: source instruction at 0, successor at 4,
kind at 8, drop count at 12, and zero words at 16 and 20. Kinds are normal=0,
error=1/control=2; a missing successor uses 0xffffffff. The header is followed
by drop_count u32 instruction indexes in checked cleanup order. These
indexes are function-relative and must point to appropriate explicit drops;
count equality alone is not ownership validation.
Instructions and immediate encodings
Each structural instruction is 16 bytes: u32 opcode at 0, u32 scalar kind
at 4 (reserved zero before 1.6), and 64-bit immediate bits at 8. There are no
source spans in this instruction. Signed constants use two’s-complement bits;
other operations interpret the immediate according to their checked opcode.
Scalar tags are legacy=0, Bool=1, Byte=2, Int8=3, Int16=4, Int32=5, Int64=6, UInt8=7, UInt16=8, UInt32=9, UInt64=10, Index=11. Legacy does not mean unknown authority or unrestricted conversion. It identifies the older scalar execution law and is checked against type context.
| Opcodes | Allocation |
|---|---|
| 0–7 | const, negate, add, subtract, multiply, divide, equal, less-than |
| 8–15 | load-local, store-local, call, jump, jump-if-false, assert-true, assert-equal, return |
| 16–24 (1.2+) | remainder, not-equal, less-or-equal, greater-than, greater-or-equal, bit-and, bit-or, bit-xor, bool-not |
| 25–26 (1.3+) | enum-constant, enum-is-variant |
| 27–28 (1.5+) | enum-construct-payload, enum-payload-extract |
| 29 (1.7+) | Result try |
| 30–34 (1.8+) | value-construct, field-get-copy, move-local, drop-local, drop-argument |
| 35 (ZLA2 1.9+) | shared borrow |
The structural maximum is not the maximum of the compiler’s general IR enum. Unit, mutable borrow and runtime-rich intrinsic opcodes belong to other allocated representations; they do not become valid structural opcodes merely because their integer fits the field.
Call immediates name closure-wide functions; load/store/move/drop immediates name function places, and jump immediates use checked function-relative instruction indexes. Enum immediates pack descriptor index into bits 32–63 and variant tag into bits 0–31. Field/borrow immediates pack descriptor into bits 32–63, place index into 0–15, place kind into bit 16, and field ordinal into 17–31. Result-try immediates put the Result descriptor in the low word and cleanup-edge index in the high word. Operation-specific checks still bind these facts to exact types, admitted uses and ownership/control paths.
Historical ZLM0 and normalization
The 32-byte ZLM0 header has magic at 0, u16 major/minor at 4/6, then u32
profile, instruction count, module-name length, owner-name length, message-name
length and flags at offsets 8, 12, 16, 20, 24 and 28. Minor 1.0 ignores the
flags word. Minor 1.1 takes owner kind from bits 0–7, openness from bit 8 and
verification from bits 16–23. The three names follow, then instructions.
Legacy decoding is not a claim that every unused historical bit is a canonical
modern reserved-zero field.
A ZLM0 instruction is 32 bytes: opcode at 0, an unused word at 4, immediate at 8, source start at 16 and source end at 24. The last three fields are 64 bits. Its opcode vocabulary is const=0, negate=1, add=2, subtract=3, multiply=4, divide=5, assert-equal=6, return=7. The decoder maps that vocabulary to the current checked scalar IR, validates spans and infers the historical Int32 result contract; it never treats opcode 7 as the structural less-than opcode.
Accepted historical bytes and canonical writer output are different sets. For example, a valid ZLM2 2.14 scalar envelope with a structural ZLM1 1.0 or 1.1 payload can execute normally, but rebuild canonicalizes the payload to 1.2. Rehashing the enclosing envelope does not change this migration rule. The public contract, checked behavior and applicable implementation identity must remain stable; the target artifact’s bytes and checksum may change. Normalizing that canonical result again is byte-identical. Rejecting an already admitted historical form to manufacture a byte-equality result would be a compatibility change, not a documentation correction.
ZER5-163 corrects a legacy metadata defect: the ZLM0 decoder had left the
Int32 result’s type reference at descriptor index zero. It now initializes the
scalar sentinel, so an embedded legacy result is correctly reflected as copy,
with the same public identities as its canonical reconstruction. This changes
the previously erroneous ownership/identity projection, not accepted bytes,
execution results, or canonical artifact output.
cli.zlm_payload_layouts walks actual canonical payloads and validates public
rebuild, reflection and execution results. Digest-valid negative cases exercise
record counts, reserved fields, mutability, ownership/cleanup and unknown
operations. Properties vary scalar behavior through historical normalization.
This evidence does not close ZER5-30’s remaining mixed-domain identity,
reflection and corpus-promotion acceptance.