Contents

Lace-060 · Inter-Lace Transfer Protocol (ILTP)

Tags: transport, iltp

Purpose

ILTP binds the 050 interlace state machine to bidirectional byte carriers. One carrier may carry successive logical sessions. Each logical session begins with a preface pair, which carries each sender’s nonce, stream-encoding offers and optional port and selects each direction’s stream encoding, and owns one proof slot per role (::as with a Seal, or ::as unknown), role-assigned operands announced by hash and delivered as Blobs only when missing, one immutable exchange plan, concurrent symmetric rounds, and fixed point. Rounds exchange complete listings as deltas against the previous listing; 061 optimizes a session’s first listing exchange, and 065 compresses the item stream beneath both.

ILTP carries labelled line, list and record items, the last holding canonical proof, operand, and H3 response records, over host-established duplex byte capabilities. A capability may own one runtime-native duplex IO value or independent reader and writer values. A carrier starts after the host establishes that IO and fixes its role; dialing, listening, acceptance, process supervision, and service admission are outside ILTP. The host owns carrier IO and proof inputs; each logical session derives immutable session authority. The codec owns framing, 065 stream encoding, and canonical bytes; the carrier owner handles session cuts, and 050 owns protocol meaning and lifecycle.

Fixed ILTP 0.13 profile

ILTP 0.13 is the only supported version. It fixes:

Each direction’s 065 stream encoding is the only negotiated element. Record formats, policy profiles, advertisement capabilities, parser limits, and operation budgets are not negotiated. An incompatible set requires another protocol version.

Transport endpoint ownership

ILTP defines no cross-transport address or endpoint grammar, endpoint defaults, or generic dialing or listener API. Applications own native endpoint configuration and parsing, dialing, listening, and connection acceptance. They provide ILTP an already-established duplex byte capability.

Endpoint configuration and diagnostics MUST NOT become policy facts, exchange-plan or proof inputs, or protocol authority.

Stream roles and proof-assurance resolution

Before decoding any ILTP byte, the runtime fixes an explicit carrier role: CarrierRole::Listener is side0 and CarrierRole::Connector is side1. Paired endpoints MUST use complementary roles. Roles are not inferred from IO shape or accepted/dialed provenance, are not negotiated, and cannot be corrected in-band. A same-role pair follows ordinary wrong-phase peer-protocol or phase timeout behavior.

Established IO defaults to Disabled. The host may supply ConfidentialChannel or ChannelBound(binding) with its IO. The confidential-channel assertion is valid only when the host can assert confidentiality, integrity protection, and endpoint confinement for the complete ILTP channel, including proxies and plaintext forwarding legs. It does not secure the transport. Unbound ILTP proof nonces provide freshness, not binding to the encryption session; unauthenticated encryption alone does not prevent live proof relay. The complete assurance requirement and relay limitation are defined by 050. Endpoint configuration and diagnostics supply no assurance or By authority; connected peer authority requires both channel assurance and the role-ordered proof.

An optional expected peer By is an explicit connected-construction constraint under 050, not endpoint-derived authority or a proof-transcript input. It requires resolved ConfidentialChannel or ChannelBound(binding) assurance and never enables trust. The local channel implementation supplies ChannelBound from its completed protected channel, not a value received in peer proof text. Bound assurance requires a local By-secret and a nonempty peer proof even without a pin.

The host also supplies an optional local By-secret value and one unpredictable nonzero 32-byte carrier nonce seed. Rust nonce derivation and proof validation derive each logical session’s nonces and final Here and PeerConfidential facts.

Host-established byte capabilities

A runtime creates one move-only connected participant by atomically adopting either one runtime-native duplex IO value or a split reader and writer, together with an explicit carrier role, optional local By-secret, proof assurance, stream encodings, optional port, and optional expected peer By. For split IO, the reader carries peer-to-local bytes and the writer carries local-to-peer bytes. Construction defaults to Disabled proof assurance; there is no required public intermediate transport object. The stream encodings fix the 065 offers, and the port the Port field, the carrier writes in every preface it sends.

Adopted IO carries prefaces and 065-encoded ILTP items directly. Process stdin/stdout, child pipes, and similar resources are adopted only when the application explicitly transfers their owners; Lace does not spawn, supervise, restart, or interpret the process. The connected operation owns adopted IO through its complete lifecycle. Bounded completion settles final output, the end mark of a compressed outgoing encoding, and shutdown while it reads the peer direction to its end, and releases it once both finish, open operation retains it at fixed point, and cancellation or terminal failure retires it, all under 050. Listener, process, admission, and service lifecycle remain application-owned.

Channel-binding value

A protected channel’s implementation derives exactly 32 binding bytes under 050’s channel contract and supplies them with its established IO. ILTP defines no encryption suite, handshake, cipher framing, exporter method or method registry. Those details, including derivation domain separation and stream scope, belong to the channel implementation and its own security profile and vectors.

The exact proof field is:

Channel-Binding: <b64a>

<b64a> is the canonical 43-character 006 encoding of those 32 bytes. The complete field line is 60 bytes excluding LF, within 010’s bound. No method prefix, arbitrary binding text or alternate encoding is accepted. Binding is owned and immutable with the adopted IO; it is not negotiated through the peer transcript.

The verifier MUST compare the peer’s bound proof with its own local binding, not merely compare the two transmitted strings. A peer cannot enable assurance, substitute another binding through proof text. A missing, malformed, duplicate, unknown, or different binding fails without an unbound retry. Unbound contexts MUST reject bound proofs rather than ignore the field. Unbound proofs omit the field entirely; empty or sentinel bindings are invalid.

The binding is not secret, endpoint identity, expected-key substitute, or plan input. ILTP-generated proof material does not become a fact, store admission, or public observation. These setup rules reserve no application field and restrict no ordinary Seal-signing API; ordinary proof-shaped application records retain 010/020 semantics. The security and accepted signing-capability boundary is defined by 050.

Codec boundary

The codec owns:

The decoder emits one framing input at a time:

SessionPreface(nonce, offers, port)
NoProof
Policy(operand hash)
PolicyKnown
PolicyMissing
Listing(form, entries)        form: complete, unshared, or changed
Symbols(rows)
Want(hashes)
RoundEnd
Record(kind, record)          kind: proof, policy-body, or response

SessionPreface is consumed by the carrier owner; it is not a Datalog fact, endpoint message, record, store input, or public event. Every other input keeps its label and listing form but carries no phase, side, or round tag. The decoder checks each item’s shape under Items; it does not track logical sessions, establishment, listings, reconciliation, obligations, fences, or fixed point. The carrier and 050 machine decide whether each labelled item is legal in the current owning state; a label, form, or batch size that state does not permit is PeerProtocol.

The carrier supplies each local preface, carrying its configured offers and port, as an ordinary output item; the codec never invents or repeats a preface. The codec writes the preface raw, first ending a previous session’s compressed stream with its end mark. Once it has encoded the local preface and decoded the peer preface, the codec selects each direction’s encoding under 065 from the two offers and installs fresh state before it encodes or decodes a byte of the fresh session. It encodes each accepted output batch as one flushed unit, decodes peer bytes, and on one explicit operation emits the end mark of a compressed outgoing encoding. The semantic machine computes outgoing canonical sizes with the same renderer and enforces the same hard caps before routing, failing with LimitExceeded, so direct typed routing cannot bypass a connected-wire limit.

Per-item accounting

The 64 MiB codec cap applies independently to the current item, not to a caller’s input chunk or the lifetime of a decoder. Caps and accounting count decoded ILTP bytes under 065. push_bytes MAY contain any number of complete items. As soon as one item completes, the decoder resets current-item accounting before parsing following bytes from the same call.

The decoder rejects an item as soon as it cannot fit within its hard cap. A connected peer’s malformed, noncanonical, truncated, or oversized item is a 050 PeerProtocol failure. Local inability to canonically construct an output is an Integration failure.

Stream items

Each direction is a stream of self-delimiting items. A raw preface starts each logical session’s stream, and its other items travel in that direction’s 065 encoding. At an item boundary of the raw or decoded bytes, the decoder dispatches by the complete leading sequence:

Leading bytes Item
🪢: session preface, raw only
:: line, list, or record item

The decoder MUST match the full UTF-8 knot prefix, which begins with 0xF0. Any other leading sequence, including LF and a bare record glyph, is invalid.

A record inside a record item is delimited by 010 structure and Data-Length, not transport frames or scanning; one framing LF after it ends its item. Consecutive items are adjacent. Host read and write boundaries are transport fragmentation only.

Session preface and logical-session cut

Each logical session begins in each direction with one raw preface, a header block:

🪢: iltp/0.13\n
Nonce: <nonce>\n
Decode: <tokens>\n
Encode: <tokens>\n
Port: <port>\n
\n

The first line is the knot line: UTF-8 🪢 (F0 9F AA A2), the ASCII bytes : iltp/0.13, and LF, 16 bytes in all. Zero or more field lines follow in any order, each a 010 field line under 010’s name, value, and 128-byte line rules, and one empty line ends the block. Each field name occurs at most once. ILTP 0.13 defines four fields:

Field Presence Value
Nonce required the sender’s session nonce
Decode optional 065 tokens the sender decodes, in strictly ascending bytewise order
Encode optional 065 tokens the sender produces, in its preference order, without repetition
Port optional what the sender wants to talk to

<nonce> is the 006 canonical B64A encoding of exactly 32 bytes: exactly 43 ASCII characters, no padding, and canonical zero tail bits. The decoded bytes MUST be nonzero and fresh and unpredictable for that sender’s logical session.

<tokens> is one or more tokens separated by ,; each token matches [a-z0-9]{1,16} and none is identity. An absent Decode or Encode field offers identity only, so a sender with no compressed encoding omits both. Tokens are 065 stream-encoding offers; syntactically valid unknown tokens are ignored there, not rejected here.

<port> is one or more segments separated by /, each one or more bytes in 0x21..0x7E other than /. The 010 line bound limits it to 122 bytes. ILTP compares a port as bytes and assigns its segments no meaning; 050 defines what a port selects.

A receiver ignores a field whose name it does not define, after checking its line and that its name does not repeat. A minimal implementation therefore sends Nonce alone and reads only the fields it knows. A preface with only Nonce is 68 bytes; a complete preface, from the knot line through the empty line, is at most 512 bytes. The decoder rejects a preface as soon as it cannot complete within that cap or a line exceeds the 010 bound.

A different knot line, a missing Nonce, a repeated name, a line that is not a 010 field line, including CR or CRLF, and a malformed known field are invalid. A known field is malformed with padding, malformed B64A, noncanonical tail bits, wrong width, or all-zero bytes in its nonce; an empty or invalid token, misordered or repeated Decode tokens, repeated Encode tokens, or a listed identity; or an empty or invalid port segment. There is no version negotiation or parser for another version.

Both roles emit their local preface without waiting for the peer. Before the initial session machine exists, only the peer preface is legal; any other item is premature input. The carrier’s fixed role maps the two nonce texts to Challenge-side0 and Challenge-side1; they MUST differ within the pair. A preface is simultaneously the version marker in its knot line, the sender role’s peer-proof challenge in its Nonce, and that direction’s hard cut into a fresh logical session; the pair selects each direction’s 065 encoding for that session, and a selecting receiver chooses that session’s operand by the peer’s Port under 050. The preface itself supplies the challenge and reset cut; no additional challenge item, reset item, or reset acknowledgment is sent.

The complete bytes F0 9F AA A2 3A 20 are reserved for a preface at a raw item boundary: before a direction’s first preface, after a 065 end mark, or in an identity direction. A preface decoded from a compressed encoding is invalid. Once matched, malformed or unterminated remainder is a preface error and cannot fall through to another parser. Knot-shaped bytes inside Blob data remain data because record parsing consumes exactly Data-Length first. On a preface the decoder stops before following bytes and yields the preface to the carrier. The carrier performs the session transition, resets session state, and resumes decoding the bounded suffix only after the fresh machine exists.

The carrier derives local nonce bytes without another host callback:

BLAKE3_KEYED(
  key = carrier_nonce_seed,
  input = "lace-iltp/1.0/session-nonce/v1" || NUL || u64be(sequence)
)

The displayed lace-iltp/1.0/session-nonce/v1 value is an opaque, exact cryptographic domain string; it is independent of the ILTP 0.13 wire-version text and MUST NOT be changed to match it. Sequence starts at zero. Derivation consumes each value; u64::MAX is usable once. An all-zero candidate, or one equal to a peer nonce already known while forming the session, retries with the next sequence; exhaustion is Integration. An equal independently crossed pair is PeerProtocol. The receiver keeps no nonce history. The seed and sequence are private carrier state, not protocol authority, policy identity, or diagnostics.

Items

Every item after a preface starts with a :: label line. A line item is that line alone, a record item’s label is followed by one record and one LF, and a list item’s label is followed by rows and an empty line:

LF = %x0A   HT = %x09   SP = %x20
hash        = ("B." / "P." / "S.") digest43 ".H3"        ; 48 bytes
item        = line-item / record-item / list-item

line-item   = ( "::as unknown"
              / "::policy" SP "B." digest43 ".H3"
              / "::policy-known"
              / "::policy-missing"
              / "::round-end" ) LF
record-item = ( "::as"
              / "::policy-body"
              / "::response" ) LF record LF
list-item   = ( "::offers" / "::offers-unshared" ) LF *offer LF
            / "::offers-changed" LF *change LF
            / "::want" LF *(hash LF) LF
            / "::symbols" LF 1*symbol LF
offer       = hash LF *(HT field-line)
change      = "+" SP offer
            / "-" SP hash LF

digest43 is the canonical 43-character 006 digest text of an H3 hash, field-line is a 010 field line with its LF, record is one complete canonical 010 record, and symbol is a 061 row. Labels are exact lowercase text; an unknown label, an extra word or space, or a row the list does not permit is invalid, and a ::-led line inside a list cannot end or change it. An empty line ends a list and occurs nowhere else between items. Every label and row is UTF-8 without CR and at most 129 bytes excluding LF: one HT before a 010 field line within 010’s 128-byte bound.

A record item’s label line is followed directly by the unchanged record and then exactly one LF, which is framing outside the record. The decoder consumes the record by its 010 structure and Data-Length, never by scanning, then requires that LF: EOF before it is framing truncation and any other byte is a malformed item. Every label therefore starts its own line; data that already ends in LF shows a blank line before the next label.

The decoder also checks, as item shape: listing entries in strictly increasing hash order; bare offers in ::offers and ::offers-unshared and +/- changes in ::offers-changed; field lines as defined under Listing entries; ::want hashes strictly increasing; and at least one ::symbols row. Whether a batch has the 4^k rows of the current 061 step is a semantic check.

Item framing, not a line inside the item or a transport flush, completes an item. The 050 machine validates the item’s label and form against phase, listing bases and schema, the expected 061 batch size, request obligations, and other semantics. Peer items never become Datalog or runtime facts.

Connected establishment is role ordered

Every logical session uses this fixed grammar after both prefaces:

1. side0: ::as with its proof Seal, or ::as unknown
2. side1: ::as with its proof Seal, or ::as unknown
3. side0: ::policy <h0>
4. side1: ::policy <h1>, then ::policy-known or ::policy-missing for h0
5. side0: ::policy-body with h0's Blob if side1 sent ::policy-missing, then
          ::policy-known or ::policy-missing for h1
6. side1: ::policy-body with h1's Blob, only if side0 sent ::policy-missing
7. both endpoints independently begin Advertising

::as unknown is the empty proof form; a presented proof is one valid Seal in an ::as item, over an empty Blob and a Plex containing exactly these fields in canonical order. The label names no key: the Seal’s By line does, and ::as followed by anything other than unknown is an unknown label. unknown describes the sender as the receiver sees it; a side holding a By-secret sends ::as unknown whenever it presents no proof. The grammar has no additional proof delimiter, authority codec, or ready item.

The unbound proof Plex fields are:

Challenge-side0: <side0 preface nonce>
Challenge-side1: <side1 preface nonce>
Proof-Profile: lace-iltp-peer-proof-v3
Proof-Role: side0|side1

The bound form inserts exactly one Channel-Binding field between Challenge-side1 and Proof-Profile with the exact local binding defined above. Both forms use v3. Every other profile is invalid; there is no profile negotiation or fallback. Unbound v3 is the ConfidentialChannel proof form.

Each displayed field is singleton. The challenge values are the canonical B64A nonce texts from the two prefaces. The current proof phase fixes the sender role, and Proof-Role MUST match it. Proof-Profile MUST have the exact displayed value. No other Plex field is legal. The sender decodes its local preface nonce and uses those 32 bytes as the Seal Mark auxiliary input. The Mark subject is the embedded Plex-record digest. The marked challenges supply replay resistance without a clock or replay cache.

::as unknown means no presented proof. Disabled sends it and derives no authority, but still validates an exact presented proof after all common transcript checks pass in the unbound context. ConfidentialChannel derives PeerConfidential(peer_key) only from that unbound proof shape. ChannelBound(binding) requires a nonempty bound proof, even without a pin, and derives the same fact only after its binding matches the local channel. Validation checks complete Seal bytes and Mark, exact profile/role/field shape and empty Blob, both challenges, then the local binding, then any expected By. A missing or duplicate required field, wrong profile or role, additional field, nonempty Blob, or Mark over any subject other than the embedded Plex digest is peer protocol failure. After normal peer-proof validation, the receiver MUST enforce any expected peer By before advancing the received proof phase, as defined by 050. ::as unknown or a valid wrong-key proof then fails as PeerProtocol, before operands or application-record work. The expectation changes no proof bytes, wire grammar, or profile. Proof bytes never enter stores, facts, advertisements, transfers, or application events.

Canonical proof vector

This ConfidentialChannel side0 proof is byte-exact. The side0 and side1 nonce bytes are respectively 32 01 bytes and 32 02 bytes, with canonical texts:

Challenge-side0 = 0G410G410G410G410G410G410G410G410G410G410G4
Challenge-side1 = 0W820W820W820W820W820W820W820W820W820W820W8

The By-secret payload is 32 07 bytes. Mark construction uses the side0 nonce bytes as auxRand32. The complete 506-byte Seal ends with the empty Blob’s two LF bytes; its ::as item is 512 bytes:

🖧: S.4dIJ_MH8VxCN_9nHL1nPMydzq7B3ucKTv_muLLYHopx.H3
By: V.8qgOfsaEJkK1tzX51sTT9NKdM8J2SrzQq6k8cRPzvP0.H3
: FnjU536MixFCCr6W6nw94Ftkj73FBpvLWk2lLktyYfa_aGtCx1E3xzpwB7_wV7_ChnYQA~QWCEy8gWHSFh0DtG
🖧: P.fPx9bAyJ59peOlfG_Z5m4iSR8e5spPDB6VPakxMsUW4.H3
Challenge-side0: 0G410G410G410G410G410G410G410G410G410G410G4
Challenge-side1: 0W820W820W820W820W820W820W820W820W820W820W8
Proof-Profile: lace-iltp-peer-proof-v3
Proof-Role: side0
🖧: B.svyLzSM7ffc91i~XDbkMnuOsdjsw_6GrXpTSckqHlpO.H3
: 

Even after hashes and the Mark are recomputed, this proof is rejected if a required field is duplicated, Proof-Profile changes, either challenge differs from the current preface pair, the Blob becomes nonempty, or another field is added. It is rejected in the side1 proof phase because its role is side0. Any additional proof field is an additional-field failure. Replacing iltp/0.13 with any other version in either preface fails before proof processing.

The bound vector uses the same secret, role, nonces, and Mark auxiliary input, with a test binding of 32 03 bytes. These binding bytes exercise proof encoding; they are not a claimed handshake vector. This complete Seal is 567 bytes and ends with the empty Blob’s two LF bytes; its ::as item is 573 bytes:

🖧: S.w8QvcTX9qkLH4nWBMw3N87lbvwvS5l7xwqVQ91Zq7vt.H3
By: V.8qgOfsaEJkK1tzX51sTT9NKdM8J2SrzQq6k8cRPzvP0.H3
: hvKVbo4bpVnbYllAOtqG_~D0Ehm0AGRIk5Lc0sOoz8wohBE7SIDbULjoMFGL48nXRtUQBbzI0lt8eazFiuQUG0
🖧: P.QK5419cjcbgx0B~OKwI2HVJLbRZsrKTWRpQgVRkx7PK.H3
Challenge-side0: 0G410G410G410G410G410G410G410G410G410G410G4
Challenge-side1: 0W820W820W820W820W820W820W820W820W820W820W8
Channel-Binding: 0lC30lC30lC30lC30lC30lC30lC30lC30lC30lC30lC
Proof-Profile: lace-iltp-peer-proof-v3
Proof-Role: side0
🖧: B.svyLzSM7ffc91i~XDbkMnuOsdjsw_6GrXpTSckqHlpO.H3
: 

It is accepted only in the bound context with that exact local binding. A raw context rejects it. A bound context rejects the unbound vector, a missing proof, or a different locally derived binding even if all record hashes and the Mark validate.

Operands by hash

<h0> and <h1> are the H3 B. hash texts of the senders’ operand Blobs. Side1’s turn 4 is its ::policy item followed by its answer to side0’s announcement; side0’s turn 5 ends with its answer to side1’s. An answer is ::policy-known when its sender holds the announced operand and ::policy-missing otherwise. It repeats no hash: exactly one announcement is waiting for it. A receiver holds an announced operand when the hash equals its own operand’s or names an operand in its operand cache (050). Turn 5 always ends with its answer, so side1 learns whether to deliver. Side0 begins Advertising after turn 5 when it holds h1 and after receiving turn 6 otherwise; side1 begins after receiving turn 5 or after turn 6.

A delivered operand is a complete valid 010 Blob no larger than 1 MiB that hashes to the announced value and contains one canonical 030/040 module. The receiver adds it to its operand cache. A peer operand, held or delivered, whose canonical Blob is larger than the receiver’s max_operand_bytes fails with LimitExceeded. An unrequested Blob, a missing requested Blob, a Blob of another hash, an answer in side0’s announcement turn, an answer before side1’s announcement or a second announcement, a hash other than a B. hash, and any other item in an establishment turn are peer protocol failures. Operand bytes are setup-only and never admitted. Once both operands are known, both endpoints freshly derive authority and the same immutable plan. Every establishment item counts whole toward the logical session’s establishment control-byte budget.

An establishment record is received into memory and checked as soon as its length is known, before its data. A proof Seal is exactly 506 bytes in the unbound context, which Disabled and ConfidentialChannel validate, and 567 bytes bound. A proof record longer than its context’s size, a record item in a turn that permits none, and a requested Blob beyond 1 MiB fail as PeerProtocol; a requested Blob beyond max_operand_bytes fails with LimitExceeded.

Round stream order

Each endpoint emits this order in its own stream for every round:

listing: one ::offers-changed item, or 061 reconciliation items in a
         logical session's first round
::want item
zero or more ::response items for the peer request set  } only when either frozen
::round-end item                                        } request set is nonempty

The two streams progress concurrently. There is no side0-then-side1 ownership inside a symmetric round phase. Per-direction order and the 050 state machine permit temporary phase skew while preserving bounded input.

An endpoint starts sending its listing as soon as its local snapshot is ready. It sends its request only after its retained local writer has accepted every listing output and the complete peer listing has been materialized. It sends responses only after its own request has been accepted and the peer request received. It sends its fence after all local incoming and outgoing response obligations settle. When both frozen request sets are empty, the two ::want items are the round’s settled boundary and nothing else is sent for the round.

Listing entries

A listing describes zero or more advertised records. Each entry is one hash line, HASH as an offer or + HASH as an addition, followed by one line per projected field occurrence: HT, the field name, :, and the value. Lines are in bytewise name order, and a name’s repeated lines are its occurrences in index order, so indexes are implicit; the schema projects every occurrence of each selected name. A field line splits at the first : after its HT. Its name is a 010 field name or exactly :Type or :Length, and its value is the rest of the line, which may itself contain :.

Under the canonical 040 plan vector, the byte-exact 010 Plex vector projects this complete offer, whose field lines each begin with one HT, because the plan schema requires exactly App:

P.roNBC0l3CG4ezmPY5F3H_pLBDBecaAYZRbOJ1aT7pqK.H3
    App: chat
    App: archive

Omitting the archive occurrence, adding any out-of-schema field, changing either value, swapping the two lines, or placing a later name first makes the entry false or noncanonical and is a peer protocol failure.

Entries in every listing item are in strictly increasing bytewise order of their hash text, additions and withdrawals together. H3 hash texts have equal width, so this is decoded type-byte and digest order under 006. Local admission order never determines wire order.

A complete listing is an ::offers item of bare offers, none for the empty listing. Its canonical byte size B is that rendered item from its label through its terminating empty line: the empty listing has B = 10, and each offer adds 49 bytes for its hash line plus its field lines. This offer adds 74 bytes.

Listing forms

The item’s label names the base its entries apply to:

Label Base Entries
::offers the empty listing: a complete listing bare offers
::offers-unshared the 061 decoded intersection bare offers, possibly none
::offers-changed the sender’s previous local listing plus the entries it requested in the previous round + additions and - withdrawals

In ::offers-changed, the rows are:

- <hash>             in the base, not in the sender's listing
+ <hash>             in the sender's listing, not in the base
    <name>: <value>  its projection

Only ::offers-changed withdraws. - removes a base entry the sender does not offer: an offer it no longer makes, or a record it requested and does not offer back; it never deletes one. Withdrawn hashes MUST be a subset of the base and offered or added hashes MUST be disjoint from it; each entry is schema-validated as above. An empty ::offers-changed means the listing equals the base. The receiver applies the item to the base, producing the sender’s complete listing. References are always full hash text; there are no round numbers, listing versions, or reference tables. Each direction’s items apply in stream order.

Every round after a logical session’s first sends one ::offers-changed item. Its base is the sender’s previous local listing, which is the receiver’s previous peer listing, plus, for each hash of the sender’s previous frozen request set, the entry the receiver offered for that record in that round. Every requested record was admitted before the sender’s fence, and the sender lists it under the same schema, so a record it offers back needs no row and one it does not offer is a - row. The requested hashes are outside the sender’s previous listing: both its listing and its request come from the round’s one record boundary, and a request names only a record the requester lacks; a base that would hold one hash twice is a peer protocol failure. A previous request set is empty after an unfenced round, so an open round’s base is the previous listing alone.

The base adds no requested entries when the shared plan omits the sender’s 040 MaySend output: an operand defines no SelectStoredRecord/1, or the sender’s TxDisabled or the receiver’s RxDisabled has a rule whose body is true. That side’s listing is empty in every round, so its deltas stay empty. Both endpoints decide this from the plan.

Listings start empty, so the same rule would send each side’s complete listing in the first round and only changes afterwards. A logical session’s first round instead exchanges listings by the reconciliation steps of 061, whose fallback is exactly the complete ::offers listing and whose other items are ::offers-unshared listings and ::symbols batches.

A label the current state does not expect, a + or - row in ::offers or ::offers-unshared, a bare offer in ::offers-changed, a withdrawal outside the base, an addition inside the base, a repeated hash, entries out of hash order, or a schema-invalid entry is a peer protocol failure.

Requests

A ::want item lists the frozen request set, one full hash per line in strictly increasing bytewise order:

::want
P.roNBC0l3CG4ezmPY5F3H_pLBDBecaAYZRbOJ1aT7pqK.H3

A side with no requests sends ::want and the empty line. The item is the complete frozen request set and cannot be divided across items. A repeated or decreasing hash is a peer protocol failure; responses may still arrive in any order.

Response records

Each response is one ::response item holding one complete H3 record and its framing LF. Its glyph hash associates it with one outstanding peer request. The frozen request set determines the number of expected responses, so no batch delimiter is needed. A record is legal only when it matches an outstanding request in the endpoint’s Answering state.

Both directions may carry responses concurrently. Receiving, validating, and admitting responses proceeds concurrently with servicing peer requests, subject to the state-local slots and backpressure defined by 050.

Fences

In a round where either frozen request set is nonempty, each endpoint emits one ::round-end item after its local Answering obligations settle:

::round-end

Either fence may arrive first. A peer fence may arrive while local Answering is still finishing and occupies the round’s dedicated peer-fence slot. The two fences, not EOF or transport flush, establish that round’s settled boundary, and the next item in each direction is that endpoint’s next-round ::offers-changed.

When both frozen request sets are empty, no response can follow, so the two ::want items are already the settled boundary: no fence is sent and both endpoints enter fixed point. A fence received in such a round is a peer protocol failure.

Re-prefacing and hard replacement

A fresh preface at an item boundary retires that direction’s old bytes and contributes one half of a fresh logical-session pair. A preface received while a session is establishing, running a round, or at fixed point therefore resets the complete logical interlace; carrier role, proof assurance and binding, operand source, By-secret value, stream encodings, port, limits, and nonce generator remain carrier-owned. In a compressed direction, the sender first ends the old encoded stream with its 065 end mark; the fresh preface follows raw, and the fresh pair selects encodings again. The protected IO owner preserves its established channel and binding; re-prefacing does not repeat transport establishment. A selecting carrier selects each fresh logical session’s operand again from the fresh peer preface, and each session checks its expected peer By, if any, against its fresh peer proof. Proof result, authority facts, operands, plan, snapshots, obligations, retained listings, 061 reconciliation state, and unsettled machine-owned observations do not survive; the fresh session reconciles its first round again. The operand cache is not session state: the fresh session announces both operands again, and an unchanged peer operand crosses as its hash only.

Local replacement enters prefacing on a later carrier poll or step, emits one fresh local preface, and waits for the peer preface. During this wait, complete old peer items are decoded under the old encoding, canonically parsed, and discarded; they are never applied or semantically validated. They remain subject to framing and item hard caps and one phase-local received-control budget. A malformed item is terminal because a preface cannot interrupt it. Crossed fresh prefaces form one pair without acknowledgment; a second peer preface before fresh-machine construction is a duplicate protocol failure.

After the local preface has been accepted by the ordered writer and the peer preface consumed, one fresh machine starts at side0 proof. A preface followed by fresh proof bytes in one read stops decoding at the preface until that transition is complete. There is no rollback after a preface is emitted or consumed.

Open rounds

At fixed point, a local store advance starts local Advertising. The resulting ::offers-changed, empty when the listing is unchanged, is the peer’s round-start notification. Receiving one at fixed point starts the same new round, applies it to the previous peer listing, and starts local advertisement work.

Simultaneous local advances produce crossed ::offers-changed items. Stream order coalesces them into one round without side priority or another item. An empty ::offers-changed still starts the round.

State-local ingress and transport backpressure

The connected host hands decoded inputs to the carrier or 050 machine immediately. It reads or decodes only while the current state has a legal free peer slot and the endpoint has no active StoreCall. A preface is handled by the carrier rather than an endpoint slot. Without active StoreCall, one peer listing item, an ::offers-changed or in a first round one 061 step item, may arrive while matching local evaluation is pending and one peer fence while local Answering finishes.

When no legal slot is free or StoreCall is active, the host stops reading and relies on stream backpressure. At most one complete decoded input may wait for immediate handoff. Wrong-family, duplicate, skipped-phase, unrequested, or otherwise out-of-order input fails immediately rather than entering a semantic backlog.

A transport adapter may retain bounded transport bytes while semantic input is paused. Overflow of that transport buffer is a transport failure.

Retained writes

One retained writer owns one accepted connected output batch and offset until every byte is written. A batch may contain one or more complete ILTP items from one machine output. Partial writes preserve batch order and item framing. Awaiting a generic write call does not transfer semantic ownership.

On replacement, unaccepted old machine output is dropped, an accepted old batch drains completely, the old encoding’s end mark follows when it has one, the fresh raw preface is the next accepted output, and proof and operand output waits behind it. No second writer or reset queue exists. The machine may advance after the writer accepts ownership. The accepted batch still buffered when the machine enters fixed point, the settled round’s final ::want item, retains its original absolute Requesting deadline until fully written and flushed. Fixed-point observation, bounded completion, and clean close wait for that boundary. Bounded completion then writes and flushes a compressed direction’s end mark and shuts down while it reads the peer direction to its end, all under the same deadline, as 050 defines. The deadline is never reset or extended. A terminal local failure immediately stops output submission, drops any Lace-owned unwritten remainder, writes no end mark, and initiates transport closure without waiting for a peer close acknowledgment. Platform-owned queued bytes may still arrive; 050 defines the resulting uncertainty before successful fixed-point publication.

End of stream

Transport disconnect ends both directions. Each incremental decoder is finalized exactly once. EOF is boundary EOF only when the decoded bytes are at an item boundary and the direction’s encoding is at its end under 065.

Ordinary boundary EOF at 050 fixed point is clean and transitions the endpoint to closed. Boundary EOF during prefacing, establishment, reconciliation, or an active or mandatory round is premature peer input. Every other EOF, including EOF inside a preface, a line or list item, or a record item before its framing LF, and EOF before a compressed encoding’s end mark, is framing truncation. Every non-EOF transport error remains a transport failure.

Example session

Side0 holds the canonical 010 Plex vector and side1 holds nothing. Both run the 040 App policy vector, B.iqoRCCeq_CgxobqVC0gYoCBIUKbq3P~7mHmopRj6mBO.H3, so each holds the other’s operand and no policy body crosses. Both endpoints offer both 065 compressed encodings with zstd20c preferred, so both directions select zstd20c; side1 names the port dots, and side0 sends none. Each block below is one direction’s complete decoded stream from a run, its raw preface followed by the items 065 encodes; nonces are random per run, and field lines in listings begin with one HT. Side0’s 84-byte listing and side1’s empty one fit the 118-byte 061 bound, so both go whole at reconciliation step 0. The response’s data, hello, ends without LF; the record item’s framing LF puts the fence on its own line.

Side0 to side1, 542 decoded bytes:

🪢: iltp/0.13
Nonce: exo_ZMySNsAs0uupGo2K7CyVK74WSFmFidVcFO_lQMl
Decode: deflate12c,zstd20c
Encode: zstd20c,deflate12c

::as unknown
::policy B.iqoRCCeq_CgxobqVC0gYoCBIUKbq3P~7mHmopRj6mBO.H3
::policy-known
::offers
P.roNBC0l3CG4ezmPY5F3H_pLBDBecaAYZRbOJ1aT7pqK.H3
    App: chat
    App: archive

::want

::response
🖧: P.roNBC0l3CG4ezmPY5F3H_pLBDBecaAYZRbOJ1aT7pqK.H3
App: chat
App: archive
Group: room7
Name: note
TAI: tomorrow
🖧: B.kQIYZC1_~Q_ji4~Twe2_7m9Zn2scg_Cu9zNTkbk9zq8.H3
Data-Length: 5

hello
::round-end
::offers-changed

::want

Side1 to side0, 324 decoded bytes:

🪢: iltp/0.13
Nonce: XS9khCwcsx6OCktOy_ugiCo3~0xXFcsxMIyNILrnDUx
Decode: deflate12c,zstd20c
Encode: zstd20c,deflate12c
Port: dots

::as unknown
::policy B.iqoRCCeq_CgxobqVC0gYoCBIUKbq3P~7mHmopRj6mBO.H3
::policy-known
::offers

::want
P.roNBC0l3CG4ezmPY5F3H_pLBDBecaAYZRbOJ1aT7pqK.H3

::round-end
::offers-changed

::want
Item Side0 Side1
preface 122 B 133 B, with Port
::as unknown 13 B 13 B
::policy 58 B 58 B
::policy-known 15 B 15 B
round 1 ::offers 84 B 10 B, empty
round 1 ::want 8 B, empty 57 B
::response 204 B: 11-byte label, 192-byte record, LF —
::round-end 12 B 12 B
round 2 ::offers-changed 18 B, unchanged 18 B, its base holds the record it requested
round 2 ::want 8 B, empty 8 B, empty

The two streams progress concurrently after establishment. When neither side holds the other’s operand, side1 answers ::policy-missing, side0’s turn 5 is its ::policy-body item, 15 bytes beyond its Blob, followed by ::policy-missing, and side1 delivers its own Blob in turn 6.

Because side1’s round-1 request set was nonempty, both endpoints run round 2 as a delta round. Side1’s round-2 base already holds the record it requested and admitted, which it offers back, so its ::offers-changed is empty too. Both round-2 requests are empty, so the round reaches fixed point without fences. A later local commit at fixed point that changes neither listing costs each side an empty ::offers-changed and an empty ::want, 52 decoded bytes in total.

Hard caps and accounting

Item Hard cap
control line 129 bytes excluding LF
complete ILTP item 64 MiB
operand Blob 1 MiB
session preface 512 bytes
operand Blobs sent per direction per logical session at most 1
H3 Blob data 32 MiB, from 010

The complete-item cap resets after every item, including when one decoder input chunk contains several items. Stricter operand and 010 record limits still apply. A maximum H3 record with 512 Plex field occurrences at the field-line bound and a 32 MiB Blob payload remains below the 64 MiB item cap. These caps are fixed protocol limits, not peer-supplied settings.

A sender checks each round that its complete listing fits the item cap before encoding it, whether it travels whole, as 061 symbols, or as a delta; a listing grown through deltas that no longer fits fails locally with LimitExceeded rather than at the peer. Every listing item it sends, including a 061 ::offers-unshared listing, MUST also fit the item cap. A delta between two listings that each fit can exceed it; the sender then fails with LimitExceeded before the item is accepted for output.

Transferred-record accounting uses the complete canonical record byte length, including outer and embedded glyph lines, every Plex field line, the Seal By/Mark lines when present, Blob framing, and Blob data; a label line and the framing LF after the record are not record bytes. Received-control accounting uses decoded canonical ILTP bytes assigned to its current scope: every establishment item whole, including the ::as and ::policy-body items with their records and framing LFs; every ::want and ::round-end item; and the 12 framing bytes, the ::response label line and the LF after the record, of every received response. Listing traffic is charged in two parts. The endpoint charges the round’s materialized complete peer listing once at B, as if it had arrived as one ::offers item, whether it arrived whole, was applied from a delta, or was assembled after a 061 decode. The session adapter additionally charges each 51-byte - HASH line and every whole ::symbols item. A complete listing is therefore charged exactly once. The configured received-control budget still bounds the peer listing an endpoint retains; entries that came from a delta’s base count in B like any other. Direct routes exchange complete listings and charge B alone.

050’s configurable operation budgets are local and unnegotiated. Received control accounting belongs to one decoder direction and prefacing, establishment, or round. Transferred-record accounting belongs to its transfer direction and round. A re-preface starts fresh logical-session accounting but does not change configured values or add a reset-specific limit.