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 nonce preface pair and owns fresh proof, role-assigned operand Blobs, one immutable exchange plan, concurrent symmetric rounds, and fixed point.
ILTP carries canonical operand Blobs, control fact blocks, and full H3 response records over stdio, Unix sockets, TCP, QUIB, or WebSocket byte streams. A carrier starts after the host establishes the byte stream; listening, accepting, 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 and canonical bytes; the carrier owner handles session cuts, and 050 owns protocol meaning and lifecycle.
ILTP 1.0 is the only supported version. It fixes:
By, By-secret value, and Mark
suite;lace-040-exchange-plan-v9 profile, including
its fixed lowering;Record formats, policy profiles, advertisement capabilities, parser limits, and operation budgets are not negotiated. An incompatible set requires another protocol version.
QUIB means QUIC Interlace Binding. Its listener emits the exact six ASCII bytes below as authenticated QUIC handshake data:
QUIB/1
The value has no terminator. The client MUST require exact equality before it reports a connected QUIB profile. Missing or unequal authenticated handshake data is a typed transport-profile failure. These bytes are not an ILTP stream item, marker, preface, or negotiable value.
Most addresses use scheme:address; stdio is
the one colon-free literal.
| Scheme | Address form | Default port |
|---|---|---|
stdio |
(empty) | — |
unix |
/absolute/path |
— |
tcp |
host[:port] |
4790 |
quib |
host[:port] |
4790 |
ws |
host[:port][/interlace] |
80 |
wss |
host[:port][/interlace] |
443 |
Host is a hostname, IPv4 address, or bracketed IPv6 address. Port is
decimal. An absent WebSocket path means /interlace. Query
strings and URL-style ws:// or wss:// forms
are not standard Lace addresses.
stdio
unix:/tmp/lace.sock
tcp:127.0.0.1:4790
tcp:[::1]:4790
quib:example.com
ws:127.0.0.1:4790/interlace
wss:example.com/interlace
Before decoding establishment content, the binding fixes
listener/server as side0 and connector/client as
side1. The transport-owning runtime resolves one optional
public override exactly once:
automatic: QUIB exporter -> ChannelBound(exporter32)
canonical wss -> ConfidentialChannel
otherwise -> Disabled
override Disabled -> Disabled
override TrustConfidentialChannel -> keep ChannelBound when available,
otherwise ConfidentialChannel
The host also supplies canonical Transport(address), an
optional local By-secret value, and one unpredictable nonzero 32-byte
carrier nonce seed. Rust nonce derivation and proof validation, not a
connected host setter, derive each logical session’s nonces and final
Here and PeerConfidential facts. A trusted WSS
reverse-proxy path ending in backend WS uses the explicit trust
override; the proxy and backend leg are then part of the trusted
computing base.
The codec owns:
The decoder emits one framing input at a time:
SessionPreface(nonce)
FactBlock(rows)
Record(record)
SessionPreface is consumed by the carrier owner; it is
not a Datalog fact, endpoint message, record, store input, or public
event. rows may be empty. A fact block carries no semantic
family, phase, side, or round tag. A record carries no setup or response
tag. The decoder does not track logical sessions, establishment,
listings, obligations, fences, or fixed point. The carrier and 050
machine interpret each input from current owning state.
The encoder has one explicit begin_session(nonce)
operation that emits the preface before ordinary items in that
direction’s fresh session. Ordinary item encoding never invents or
repeats a preface. The semantic machine computes outgoing canonical
sizes with the same renderer and enforces the same hard caps before
routing, 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.
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. At an item boundary, the decoder dispatches by the complete leading sequence:
| Leading bytes | Item |
|---|---|
🪢: |
session preface |
[A-Za-z] |
nonempty fact block |
| LF | zero-row fact block |
🖧: |
complete H3 Blob, Plex, or Seal record |
The decoder MUST match the full UTF-8 knot and record-glyph prefixes;
both begin with 0xF0. Any other leading sequence is
invalid.
A stored record is delimited by 010 structure and
Data-Length, not transport frames or a following blank
line. Consecutive items may therefore be adjacent. WebSocket frame and
message boundaries are transport fragmentation only.
Each logical session begins in each direction with exactly:
🪢: iltp/1.0 <nonce>\n
<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. The complete preface is
exactly 59 bytes: UTF-8 🪢 (F0 9F AA A2),
ASCII : iltp/1.0, the nonce, and LF. CR, CRLF, missing or
extra text, padding, malformed B64A, noncanonical tail bits, wrong
width, and all-zero bytes are invalid. 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; an ordinary fact block or record is premature input. The
carrier’s fixed role maps the texts to Challenge-side0 and
Challenge-side1; they MUST differ within the pair. A
preface is simultaneously the version marker, the sender role’s
peer-proof challenge, and that direction’s hard cut into a fresh logical
session. There is no challenge fact block, reset fact, or reset
acknowledgment.
The complete bytes F0 9F AA A2 3A 20 are reserved for a
preface at an item boundary. 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, lets the carrier transition,
resets per-session state, and resumes from 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)
)
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.
A nonempty fact line is:
Predicate('constant',...)
Zero-arity facts use Predicate(). Predicate names and
constants follow 030. Each line is UTF-8 NFC, begins with an ASCII
letter, contains no CR, and ends with LF. One additional bare LF
terminates the block:
Row(...)\n
Row(...)\n
\n
An empty fact block is one bare LF:
\n
Fact-block framing, not a row inside the block or a transport flush, completes a listing. The codec accepts zero rows generically; 050 permits an empty block only where the current semantic state permits an empty listing.
The decoder validates canonical tuple text and fact-line and item caps. The 050 machine validates family, arity, grouping, duplication, ordering, phase, request obligations, and other semantics. Peer control tuples never become Datalog or runtime facts.
Every logical session uses this fixed grammar after both prefaces:
1. side0 proof Seal or zero-row fact block
2. side1 proof Seal or zero-row fact block
3. side0 operand Blob
4. side1 operand Blob
5. both endpoints independently begin Advertising
Proofs and operands remain ordinary generic codec items. There is no proof delimiter, empty-item kind, authority codec, negotiated proof profile, operand reuse signal, or ready item. A proof is one valid Seal over an empty Blob and a Plex containing exactly:
Group: lace
App: peer-proof
Name: session/side0|side1
TAI: 0000000000:000000000
Challenge-side0: <side0 preface nonce>
Challenge-side1: <side1 preface nonce>
[Channel-Binding-Hash: <b64a digest>]
Role comes from the proof phase and MUST match Name. No
other extra field is legal. ConfidentialChannel omits the
binding field. ChannelBound requires B64A text for:
BLAKE3(
"lace-session-peer-proof-channel-binding/v1"
|| NUL || "lace-🖧/quib/peer-proof-channel-binding-v1"
|| NUL || exporter32
)
The QUIB exporter output is exactly 32 bytes with label
lace-🖧/quib/peer-proof-channel-binding-v1 and context
iltp, from the same encrypted connection carrying this
stream. The sender decodes its local preface nonce and uses those 32
bytes as the Seal Mark auxiliary input. There is no TAI replay
mechanism: the fixed zero TAI is domain data, while the Mark over the
fresh nonce pair supplies replay resistance.
A zero-row proof block means no presented proof.
Disabled sends that block and derives no authority, but
accepts either exact unbound or structurally valid bound proof after
common transcript validation. Enabled assurance derives
PeerConfidential(peer_key) only from an exact proof of the
locally expected binding shape. Proof bytes never enter stores, facts,
advertisements, transfers, or application events.
Each operand is a complete valid 010 Blob no larger than 1 MiB containing one canonical 030/040 module. Operand bytes are setup-only and never admitted. After the side1 operand, both endpoints freshly derive authority and the same immutable plan. Proof and operand bytes count toward the logical session’s establishment control-byte budget.
Each endpoint emits this order in its own stream for every round:
advertisement fact block
request fact block
zero or more H3 responses to the peer request set
RoundComplete fact block
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 may send its advertisement as soon as its local snapshot is ready. It sends its request only after its own advertisement has been accepted and the peer advertisement received. 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.
An advertisement block contains zero or more complete advertisement
records. Each begins with one Advertised/1 row immediately
followed by its AdvertisedField/4 rows in field-name and
numeric-index order:
Advertised('B.A.H3')
AdvertisedField('B.A.H3','Type','0','B')
The plan-derived schema governs exact projection. Duplicate advertisement records, duplicate rows, omitted schema rows, and out-of-schema rows are invalid. A side with no advertisements sends the zero-row fact block.
Each listing is exactly one fact block. Partial writes do not create additional listings.
A request block contains zero or more unique
MayRequest/1 rows:
MayRequest('B.A.H3')
A side with no requests sends the zero-row fact block. The block is the complete frozen request set and cannot be divided across items.
Each response is one complete H3 stored-record item. 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.
Each endpoint emits one standalone block after its local Answering obligations settle:
RoundComplete()
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 the settled boundary.
If either frozen request set was nonempty, the next item in each direction is that endpoint’s next-round advertisement block. If both were empty, both endpoints enter fixed point.
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, address, proof assurance, channel binding, By-secret value, limits, and nonce generator remain carrier-owned. Proof result, authority facts, operands, plan, snapshots, obligations, and unsettled machine-owned observations do not survive.
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 canonically decoded and discarded. 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 advertisement block is the peer’s round-start notification. Receiving an advertisement block at fixed point starts the same new round, records the peer listing, and starts local advertisement work.
Simultaneous local advances produce crossed advertisement blocks. Stream order coalesces them into one round without side priority or another control item. A zero-row advertisement block 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 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 fresh 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, but bounded completion and clean close wait until the final fence batch is fully written. For QUIB, fully written at this layer means the codec writer is empty and every final ILTP byte has entered the QUIC send stream; accepting semantic output into the codec does not satisfy that boundary. A terminal local failure closes transport immediately and drops any unwritten remainder.
For browser WebSocket bindings, send() is enqueue
acceptance, not completion. The host retains final-output ownership
until bufferedAmount is zero or the platform supplies an
equivalent completion signal. Close or error while bytes remain is a
transport failure.
QUIB transport completion is authorized only after 050 fixed-point entry, an empty ILTP codec writer, and transfer of every final ILTP byte into the QUIC send stream. The authorization is monotonic and belongs to the runtime/interlace composition; fixed-point knowledge alone does not authorize transport settlement. A bounded route then begins graceful finish. An open route may authorize peer settlement without locally finishing its send stream.
After authorization, peer application close with code zero and
authenticated stateless reset are distinct successful settlement causes,
PeerClose and PeerStatelessReset. They
preserve the committed semantic result. On an open route either cause
also delivers one boundary EOF to 050 so the semantic machine emits
Closed(PeerClosed). Before authorization, zero-code peer
close is premature orderly EOF and stateless reset is a transport
failure. Nonzero peer application close is always a transport
failure.
The QUIB connection owner creates and retransmits stream FIN, waits
for final data and FIN acknowledgement during ordinary graceful finish,
emits normal close, and retains required endpoint events. The connection
first emits a RouteDrainEvent. After consuming it, the
endpoint emits RouteRemoval. The host takes that effect,
removes the exact generation-qualified route, and calls
confirm_route_removed. An AcceptedRouteLease
remains armed until that confirmation. QUIB finish, close, and drain use
transport deadlines and host cancellation rather than 050’s semantic
phase timeout.
Transport disconnect ends both directions. Each incremental decoder is finalized exactly once. Finalization distinguishes an item boundary from a partial preface, fact block, or record.
Ordinary boundary EOF at 050 fixed point is clean and transitions the endpoint to closed. A QUIB close is clean only under the post-fixed-point authorization above; a close before that boundary remains premature even after semantic fixed-point entry. Boundary EOF during prefacing, establishment, or an active or mandatory round is premature peer input. EOF inside a preface or ordinary item is framing truncation. An unauthorized stateless reset and every other non-EOF transport error remain transport failures.
Symbolic hashes abbreviate real H3 values. The two streams may cross after establishment; this example shows side1’s empty advertisement becoming ready first and side1 requesting side0’s record.
side0 -> side1: 🪢: iltp/1.0 <N0>\n
side1 -> side0: 🪢: iltp/1.0 <N1>\n
side0 -> side1: \n
side1 -> side0: \n
side0 -> side1: <complete B.OP0.H3 operand Blob>
side1 -> side0: <complete B.OP1.H3 operand Blob>
side1 -> side0: \n
side0 -> side1: Advertised('B.A.H3')\n\n
side0 -> side1: \n
side1 -> side0: MayRequest('B.A.H3')\n\n
side0 -> side1: <complete B.A.H3 response record>
side0 -> side1: RoundComplete()\n\n
side1 -> side0: RoundComplete()\n\n
Because one frozen request set was nonempty, both endpoints then run another complete round. If both request blocks in that round are empty, its two fences reach fixed point.
stdio, Unix, TCP, and WS default to
Disabled. They may use the explicit
TrustConfidentialChannel override only when the host can
assert confidentiality, integrity protection, and endpoint confinement
for the complete ILTP channel. There is no transport-authenticated
By-value injection.
QUIB automatically uses its 32-byte exporter and
ChannelBound proof. Explicit disable suppresses proof;
explicit trust retains the real binding.
WSS automatically uses ConfidentialChannel after
certificate and hostname verification. Explicit disable suppresses
proof. An accepted host that labels a channel wss: is
responsible for preserving WSS semantics. Plain WS behind a trusted WSS
reverse proxy uses explicit trust on the backend route.
After WebSocket upgrade, ILTP bytes MUST use binary messages; text
messages are invalid. The canonical omitted path is
/interlace.
| Item | Hard cap |
|---|---|
| fact line | 1024 bytes excluding LF |
| complete ILTP item | 64 MiB |
| operand Blob | 1 MiB |
| session preface | 59 bytes |
| operand Blobs sent per direction per logical session | 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. These caps are fixed protocol limits, not peer-supplied settings.
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.