host-abi.md (8273B)
tricu Host ABI
The portable Arboricx runners in lib/arboricx/arboricx.tri wrap successful
results in a tagged value envelope. Hosts decode the selected canonical payload.
This result envelope is separate from the IO action protocol interpreted by
src/IODriver.hs and from the browser's explicit result codecs.
Outer result protocol
Host ABI runners return the existing tricu result shape from lib/binary.tri:
ok value rest = pair true (pair value rest)
err code rest = pair false (pair code rest)
On success, value is a host ABI value.
On failure, code is a canonical Tree Calculus number. The host may report the numeric code and optionally inspect rest for debugging.
Host ABI value shape
A host ABI value is:
pair tag payload
The tag says how the host should interpret payload.
The payload is always the canonical/raw Tree Calculus representation for that type. The ABI envelope tags the payload; it does not replace or recursively wrap canonical Tree Calculus data.
Tags
Tags:
hostTreeTag = 0
hostStringTag = 1
hostNumberTag = 2
hostBoolTag = 3
hostListTag = 4
hostBytesTag = 5
Constructors
The ABI constructors are:
hostTree value
hostString characters
hostNumber n
hostBool b
hostList xs
hostBytes bytes
Each constructor returns:
pair tag payload
Examples:
hostString "hello"
hostNumber 42
hostBool true
hostList [1 2 3]
hostTree (t t t)
Payload conventions
Payloads use existing canonical tricu encodings:
| ABI value | Payload |
|---|---|
hostTree |
arbitrary raw Tree Calculus value |
hostString |
proper list of canonical Unicode scalar values (0..0x10ffff, excluding 0xd800..0xdfff) |
hostNumber |
canonical tricu number |
hostBool |
canonical tricu bool (false = t, true = t t) |
hostList |
canonical tricu list (t empty, pair head tail cons) |
hostBytes |
proper list of canonical naturals in 0..255 |
hostList payloads are raw canonical lists, not lists of host ABI values.
Accessors / matching
Accessors:
hostValueTag hostValue
hostValuePayload hostValue
A host can decode the envelope by destructuring the pair directly, but these helpers make the ABI explicit and testable.
Validation predicates
Typed runners validate that the raw application result can be interpreted as the requested type before wrapping it.
Predicates:
hostNumber? value
hostBool? value
hostList? value
hostString? value
hostBytes? value
These predicates validate canonical tree encodings.
Important ambiguity note:
Tree Calculus encodings are not globally disjoint. For example, t is also false, 0, and []. Typed runners intentionally interpret values according to the requested type.
Error behavior
Typed ABI runners return an error if the application result does not match the requested type.
Codec error:
errHostCodecFailed = 14
Example:
runArboricxToString bundle args
returns:
ok (hostString resultCharacters) rest
if resultCharacters is string-like, otherwise:
err errHostCodecFailed rest
The typed runner preserves the execution remainder on codec failure; it does not replace it with the rejected payload. Invalid dispatch tags and improper argument-list spines are rejected before bundle parsing/application, with code 14 and an empty remainder. The raw tree constructor remains unchecked; typed execution checks the selected payload interpretation.
String payloads are not UTF-8 byte streams. Wire manifest text uses validated UTF-8 octets, whereas the host string envelope carries Unicode scalar values.
Execution wrappers
The base self-hosted Arboricx runners are defined in lib/arboricx/arboricx.tri:
runArboricxArgs bundleBytes args
runArboricxArgsByName nameBytes bundleBytes args
Host ABI wrappers layer typed output envelopes on top:
runArboricxToTree bundleBytes args
runArboricxToString bundleBytes args
runArboricxToNumber bundleBytes args
runArboricxToBool bundleBytes args
runArboricxToList bundleBytes args
runArboricxToBytes bundleBytes args
Named-export variants:
runArboricxByNameToTree nameBytes bundleBytes args
runArboricxByNameToString nameBytes bundleBytes args
runArboricxByNameToNumber nameBytes bundleBytes args
runArboricxByNameToBool nameBytes bundleBytes args
runArboricxByNameToList nameBytes bundleBytes args
runArboricxByNameToBytes nameBytes bundleBytes args
Host usage
For a bundle whose default export is an unapplied function:
append "hello "
A host that expects a string result evaluates:
runArboricxToString bundleBytes ["james"]
On success, the result is:
ok (hostString "hello james") rest
The host then:
- unwraps
ok, - checks
hostStringTag, - decodes the canonical string payload.
Implementation reference
- Tree constructors, numbers, strings, and lists:
src/Runtime.hs - Result protocol:
lib/binary.tri - Arboricx parser/executor:
lib/arboricx/arboricx.tri - Host ABI implementation:
lib/arboricx/arboricx.tri
Invariants
The reference tests cover these invariants:
- Each constructor stores the correct tag and payload.
hostValueTagandhostValuePayloaddestructure values correctly.runArboricxToTreealways wraps successful raw results ashostTree.runArboricxToStringwraps canonical string results ashostString.runArboricxToNumberwraps canonical natural-number results ashostNumber.runArboricxToBoolwraps canonical booleans ashostBool.- A typed runner returns
errHostCodecFailedwhen validation fails. - Named-export typed runners select the requested export before wrapping.
Native source IO boundary
These are IO action tags, separate from the result-envelope tags above:
| Constructor | IO tag | Result payload |
|---|---|---|
readSourceFile path |
37 | Pair of exact file byte list and decoded Unicode scalar list. |
encodeSourceText characters |
38 | UTF-8 byte list; noncanonical naturals, non-scalars and improper lists fail. |
Both return ordinary IO Results with an empty remainder. Reading uses the same
read permission checks as readFile (tag 20), which remains an octet reader.
Source decoding is strict UTF-8 and rejects an initial BOM with a path-bearing
diagnostic. Embedded U+FEFF is an ordinary scalar, not stripped. Encoding does
not normalize text or translate newlines; it performs no filesystem IO.
These actions support the current-source native compiler candidate. Retained compiler bundles keep their recorded behavior until integrated publication.
Haskell filesystem permission boundary
Read/write grants are canonical path prefixes with directory-component boundaries. Checks resolve existing symlinked ancestors even when the final filename does not yet exist. Empty grants do not authorize paths; resolution errors deny access. Unrestricted read/write flags explicitly bypass the corresponding checks.
This is not a race-free filesystem sandbox: checking a pathname and subsequently opening it are separate operations. Use trusted directories and do not allow untrusted concurrent mutation of directory entries or symlinks.
Haskell IO run ownership
A host IO run owns its asynchronous OS workers and open socket registry. Root
completion and abortIO end the run; they do not leave detached workers running.
Normal return, failure and external cancellation cancel and join outstanding OS
workers before closing remaining registered sockets. Explicit socket close still
invalidates that handle immediately. Task handles and socket handles are local
to the run and cannot transfer resource ownership to another invocation.
Cancellation does not execute pending tricu continuations or language-level cleanup actions, and it cannot roll back completed effects or partial writes. Worker cancellation waits for host cleanup; an uninterruptible OS operation can delay termination beyond a requested timeout. This is not a hard process-kill deadline. Socket operations report operational IO errors as Results, but host asynchronous cancellation is not converted into an ordinary Result.