Tags: transport, iltp
ILTP makes 050 interlace concrete over byte streams. It enables interlace machines to run over stdio, Unix sockets, TCP, or WebSocket streams by carrying setup facts, control facts, inline canonical resource text, and self-delimiting record bytes, while exposing transport identity and encryption claims only as explicit runtime facts.
This document is for ILTP binding and transport implementors. After reading it, an implementor should be able to parse transport addresses, carry interlace fact blocks, resource text, and record bytes over a stream, make required module/plan bytes available, and expose transport proofs as runtime facts.
Address strings, stream format, knot preface, fact blocks, puzzle-piece resource text blocks, record bytes, phase separation, transport bindings, peer identity proof, transport encryption indication, and limits.
An address string identifies a transport endpoint. Most addresses use:
scheme:address
stdio is also valid as a special literal with no
colon.
| Scheme | Address form | Default port |
|---|---|---|
stdio |
(empty) | โ |
unix |
/absolute/path |
โ |
tcp |
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
a decimal integer; when omitted, the scheme default applies. For
ws and wss, an absent path means
/interlace. Other paths are binding-defined deployment
details, not part of the standard address form. Query strings are not
part of the standard address form. URL-style ws:// and
wss:// strings are not standard Lace address strings.
Examples:
stdio
unix:/tmp/lace.sock
tcp:127.0.0.1:4790
tcp:example.com:9000
tcp:[::1]:4790
ws:127.0.0.1:4790/interlace
wss:example.com/interlace
The ILTP stream format is self-delimiting: a receiver can distinguish fact lines, resource blocks, stored records, comments, and phase separators from leading bytes, then use the relevant line, resource, or 010 record rules to know where each item ends. This keeps ILTP bindings simple without adding binary framing headers.
An ILTP stream is a bidirectional byte stream carrying lines of UTF-8 text, inline canonical resource text, and raw stored record bytes. No binary framing headers are added; the content is self-describing.
Each stream direction begins with the knot preface line:
๐ชข: iltp/1
The first bytes of an ILTP stream direction MUST be ๐ชข:
(0xF0 0x9F 0xAA 0xA2 0x3A 0x20). The preface line MUST end
with LF; CR and CRLF are invalid. It is ILTP-level framing, not a 050
fact block, not a Datalog fact, and not followed by a blank-line phase
separator. A receiver MUST reject a stream direction whose first line is
not the exact knot preface line, or whose next stream item after the
preface is a blank-line phase separator.
Fact lines follow the 050 fact block syntax:
Predicate('arg',...)
Lines end with LF only. CR and CRLF are invalid. Every fact line
begins with an ASCII letter [A-Za-z].
A resource text block supplies canonical text bytes for an identifier referenced by setup or hello facts. It begins with a puzzle-piece marker line:
๐งฉ: <id> <kind>
<canonical text line>
...
The marker line syntax is exactly
๐งฉ: <id> <kind> followed by LF: one
๐งฉ: marker (0xF0 0x9F 0xA7 0xA9 0x3A 0x20),
<id>, one ASCII space, <kind>,
then LF. CR, CRLF, trailing spaces, and extra tokens are invalid.
<id> and <kind> are UTF-8 NFC text
tokens with no spaces or tabs. The standard kinds are:
| Kind | Id shape | Body bytes |
|---|---|---|
lacegram |
R.<b64a> |
canonical 030 lacegram text |
exchange-plan |
E.<b64a> |
canonical 040 exchange-plan transcript |
The body is one or more non-empty UTF-8 NFC text lines ending with LF. A blank line ends the resource block. The blank-line terminator is not part of the resource. The recovered resource bytes are the body lines joined by LF with no trailing LF. Resource bodies MUST NOT contain CR, CRLF, or blank lines. Resource body lines MUST NOT be comment lines or begin with record, resource, or session markers.
Resource blocks are ILTP-level framing. They are not Datalog runtime
facts, advertisement facts, setup facts, hello facts, peer advertisement
facts, records, or 050 phases. A resource block MAY appear anywhere a
new fact block or record batch item could begin, before the referenced
object is needed. If a standard stream sender has not explicitly
established that the peer already has a referenced R.* or
E.* object, it MUST send the corresponding resource before
the first fact that references it.
After reading a resource block, the receiver MUST validate that the
recovered bytes are already canonical and recompute the identifier. It
MUST NOT accept non-canonical bytes by canonicalizing them after
receipt. A lacegram resource must validate as canonical 030
lacegram text and recompute to the announced
R.<b64a>; source containing = as a body
atom or removed builtins such as Prefix is invalid in
resource text. An exchange-plan resource must validate as a
canonical 040 exchange-plan transcript and recompute to the announced
E.<b64a>. An exchange-plan resource
supplies only the plan transcript; referenced operand R.*
bytes must be available separately.
Duplicate identical resource blocks are allowed. A conflicting resource body for an already known identifier, malformed marker line, unknown kind, id/kind shape mismatch, non-canonical body, identifier mismatch, oversized resource, or resource count overflow is a stream error.
A stored record begins with the markline ๐ง: (six bytes:
0xF0 0x9F 0x96 0xA7 0x3A 0x20). After the markline, the
receiver parses one complete stored record as defined by 010. For a Blob
record this includes reading exactly Data-Length bytes of
data; for a Plex record or Seal record this includes parsing the full
embedded record chain. During a record transfer response phase, a stored
record item MUST match a current outstanding 050 requested hash;
otherwise it is a malformed or out-of-phase record response and the
current round aborts.
At the start of a stream item, a receiver dispatches by leading bytes:
| Leading bytes | Meaning |
|---|---|
๐ชข: |
Session preface, only as the first line in each stream direction |
[A-Za-z] |
Fact line |
๐งฉ: |
Resource text block |
๐ง: |
Stored record markline |
โฏ๐ง: |
Trailer-hash record open marker, only when the binding supports that extension |
# |
Comment line |
| LF | Blank-line phase separator |
Parsers MUST match the full marker byte sequence for
๐ชข:, ๐งฉ:, ๐ง:, and
โฏ๐ง:; matching only the first byte is not sufficient because
several markers begin with 0xF0. Any other leading byte
sequence where a stream item is expected is invalid.
The standard ILTP binding carries stored record bytes. A WebSocket binding is a byte-stream adapter like TCP after the HTTP upgrade. It MUST carry ILTP stream bytes in binary WebSocket message payloads and MUST NOT use WebSocket text messages as protocol facts or fact blocks. WebSocket frame and message boundaries are transport fragmentation only and MUST NOT be treated as fact-block, phase, record, resource, or interlace-message boundaries.
A binding MAY also support 010 trailer-hash record transport as an
extension. In that case a record may begin with the trailer open marker
โฏ๐ง: whose first byte is 0xE2, and the binding
MUST parse and restore it according to 010 before validation.
Comments make captured streams inspectable and self-documenting
without entering fact blocks, record bytes, or hash computations. A line
whose first byte is # (0x23) is a comment. Comments are
ignored at the stream level and MUST NOT be interpreted as fact lines or
record data.
Rules:
# MUST be the first byte of the line.# and the terminating LF
MUST NOT exceed 128 bytes.# that is not the
first byte of a line is not a comment marker.#-prefixed lines without an intervening fact line, resource
block, record markline, or blank-line phase separator MUST be
rejected.A conforming parser MUST skip valid comment lines silently and MUST reject malformed comment placement such as consecutive comment lines.
Example:
# Round 1: Alice advertises
Advertised('P.a.H3','V.alice.H3')
AdvertisedField('P.a.H3','V.alice.H3','Group','0','u')
# Bob requests the new record
MayRequest('P.a.H3')
A blank line (bare LF, immediately after a fact line LF or valid comment line LF) ends the current fact block. The next non-blank content begins the next phase.
Interlace control fact blocks are each terminated by a blank line. They may carry setup, hello, advertisement, reconciliation, request, availability, or tick facts. The interlace engine drives the phase sequence; ILTP does not label phases. A blank line with no preceding fact lines in the current block represents an empty block. A block containing only comments is also empty after comments are stripped.
ExchangeOperand(Index,R,Origin,Facet) and
HelloExchangePlan(E) facts identify canonical module or
exchange-plan bytes. An exchange plan references canonical operand and
plan bytes. The ILTP binding MUST make canonical bytes for unknown
announced identifiers available to the peer before the peer needs to
validate, compile, lower, or evaluate them.
The standard interoperable ILTP mechanism is the ๐งฉ:
resource text block. A binding MAY also satisfy byte availability from a
cache, startup configuration, or another pre-established source. If a
sender has not established that the peer already has the needed
canonical bytes, it sends the corresponding resource text block before
the first ExchangeOperand, HelloExchangePlan,
or plan transcript that depends on those bytes. If required canonical
bytes are still unavailable when needed, setup or negotiation fails.
Both directions are independent. A peer MAY send fact lines or records while simultaneously receiving from the other side. The interlace engine reassembles logical phases from the received content.
A blank line terminates the current phase: it ends a fact block after a sequence of fact lines, or the record transfer batch after a sequence of record deliveries. Record data itself is parsed by 010 length or trailer rules; the blank line is read only after a complete record has ended. When a peer has no more content to send in the current phase, it sends a blank line. The other side detects the end of a fact block or record batch by the blank line.
Transport disconnect ends the stream in both directions. A disconnect at a phase boundary is a clean close with the facts and records received up to that point. A disconnect in the middle of a fact line, comment line, resource block, or record is an unexpected abort for the current interlace round.
Identifiers in this trace are symbolic.
A โ B: ๐ชข: iltp/1\n
B โ A: ๐ชข: iltp/1\n
A โ B: ๐งฉ: R.a lacegram\nSelectHave(P) :- Have(P).\nSelectAdvertised(P,S) :- Advertised(P,S).\n\nExchangeOperand('0','R.a','V.alice.H3','selector')\n\n
B โ A: ๐งฉ: R.b lacegram\nSelectHave(P) :- Have(P).\nSelectAdvertised(P,S) :- Advertised(P,S).\n\nExchangeOperand('1','R.b','V.bob.H3','selector')\n\n
# Both sides validate module bytes, compile exchange plan E.final.
A โ B: HelloExchangePlan('E.final')\nHelloTAI('1640995200:000000000')\nHelloTickInterval('10000000000')\nHelloRecordFormat('H3')\nHelloAdvertisedField('Type')\n\n
B โ A: HelloExchangePlan('E.final')\nHelloTAI('1640995200:000000000')\nHelloTickInterval('10000000000')\nHelloRecordFormat('H3')\nHelloAdvertisedField('Type')\n\n
A โ B: Advertised('B.a.H3','V.alice.H3')\nAdvertisedField('B.a.H3','V.alice.H3','Type','0','B')\n\n
B โ A: Advertised('B.b.H3','V.bob.H3')\nAdvertisedField('B.b.H3','V.bob.H3','Type','0','B')\n\n
A โ B: MayRequest('B.b.H3')\n\n
B โ A: MayRequest('B.a.H3')\n\n
A โ B: ๐ง: B.a.H3\nData-Length: 11\n\nhello world\n\n
B โ A: ๐ง: B.b.H3\nData-Length: 5\n\ndata!\n\n
050 defines the standard exchange runtime facts:
| Predicate | Arity | Meaning |
|---|---|---|
Here(V) |
1 | local verifier for this context, if any |
Peer(V) |
1 | peer verifier proven by the transport or binding |
Transport(S) |
1 | address string of the active connection |
TransportEncrypted() |
0 | active transport provides confidentiality, if true |
StartTAI(T) |
1 | shared negotiated exchange-start TAI |
TickTAI(T) |
1 | shared tick time for this evaluation |
ClockSkewSeconds(N) |
1 | absolute clock skew in decimal seconds |
This spec defines how transport bindings may justify
Peer, Transport, and
TransportEncrypted. Time facts are negotiated as defined by
050.
Transport(S) is set to the address string for the active
connection.
TransportEncrypted() is set only when the transport
binding proves that stream bytes are encrypted for confidentiality
between the endpoints. When absent, transport encryption is not proven
to the rule engine.
Peer(V) is set only when the transport or
application-defined binding proves that the peer controls verifier
V. When absent, peer verifier identity is not proven to the
rule engine.
Before evaluation begins, the runtime sets exchange runtime facts
from the transport or application-defined binding.
HelloSigner(V) is valid only when the binding proves that
the sender controls verifier V; it is not self-asserted.
When a remote HelloSigner(V) is accepted, the runtime sets
Peer(V). A received HelloSigner without valid
proof is a negotiation failure.
When Peer(V) is absent, no remote verifier identity is
available to rules. Lacegrams MAY use Peer(V) to gate
authority on proven identity; rules that depend on Peer(V)
produce no results when the peer identity is unproven.
When TransportEncrypted() is present, the runtime
asserts only transport confidentiality. It does not prove peer identity.
When it is absent, lacegrams that require
TransportEncrypted() produce no results.
Implementations MUST NOT expose private implementation state as runtime facts to peer-origin modules unless a profile explicitly defines that exposure.
The caller controls both stdin and stdout of the child process. The
runtime MAY set Peer(V) when the caller or parent process
proves the peer verifier. Transport('stdio') is set.
TransportEncrypted() is absent unless an
application-defined binding proves encryption.
The runtime MAY use OS peer credentials, such as
SO_PEERCRED on Linux or getpeereid on macOS,
to set Peer(V). The mapping from OS identity to verifier is
deployment-defined. When no mapping exists, Peer(V) is
absent. Transport('unix:/absolute/path') is set to the
bound path. TransportEncrypted() is absent unless an
application-defined binding proves encryption.
Bare TCP provides no peer identity proof and no encryption proof.
Peer(V), HelloSigner(V), and
TransportEncrypted() are absent unless an
application-defined binding proves them.
Transport('tcp:<host>:<port>') is set to the
remote endpoint address for the active connection, using the explicit or
default port chosen by address parsing.
A WebSocket transport carries the ILTP stream after WebSocket
upgrade. The upgrade request is connection setup only. After upgrade,
the binding MUST drive interlace over the WebSocket stream and MUST NOT
define application commands such as GET note,
POST note, or remote coordinate listing as protocol
operations.
Transport('ws:...') or Transport('wss:...')
is set to the accepted WebSocket address, with an omitted path
represented as /interlace. wss MAY set
TransportEncrypted() when TLS validation proves stream
confidentiality for the active connection. Bare ws does not
prove encryption. Neither ws nor wss proves
peer verifier identity by itself; Peer(V) and
HelloSigner(V) are present only when the WebSocket binding
or an application-defined proof verifies control of V.
Accept only from a transport-proven peer:
MayRequest(P) :- AdvertisedField(P,S,'Signed-By',_,V), Peer(V).
Accept from a peer whose Seal records validate as a known member:
MayRequest(P) :- AdvertisedField(P,S,'Signed-By',_,V), Member(V).
Member(V) :- Have(P), Field(P,'Group',_,'u'), Field(P,'App',_,'member'), Field(P,'Name',_,V).
Only accept over a local unix socket:
MayRequest(P) :- AdvertisedField(P,S,'Signed-By',_,V), Transport(T), TextShape(T,'unix:','','').
Require encrypted transport:
MayRequest(P) :- AdvertisedField(P,S,'Signed-By',_,V), TransportEncrypted().
Require both transport proof and membership:
MayRequest(P) :- AdvertisedField(P,S,'Signed-By',_,V), Peer(V), Member(V).
These examples hand-author MayRequest(P) and therefore
are trusted prepared-policy examples. In the exchange-plan route,
ordinary authoring uses 040 modules with SelectHave and
SelectAdvertised; setup compiles them into generated
MaySend and MayRequest enforcement predicates
rather than sending ad hoc wire commands.
| Limit | Value |
|---|---|
| Max fact line length | 1024 bytes excluding LF |
| Max comment line length | 128 bytes including LF |
| Max resource marker line length | 1024 bytes excluding LF |
| Max resource text bytes | 1 MiB per resource |
| Max resource text lines | 4096 per resource |
| Max resource count | 256 per exchange |
Record limits follow 010. Fact block and interlace phase limits follow 050.