Tags: transport, iltp
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.
ILTP 0.13 is the only supported version. It fixes:
By, By-secret value, and Mark
suite;lace-040-exchange-plan-v13 profile, including
its fixed lowering;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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
<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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
| 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.