Stable C ABI
The AHRI TRE C ABI is the language-neutral adapter for Python, Julia, R, C++, and other host-language wrappers. It gives wrappers a stable unsafe boundary without asking every language package to bind directly to Rust crates or recreate TRE workflow semantics.
The ABI deliberately offers only the low-level pieces that every wrapper needs:
checking which library and protocol version were loaded, selecting a Client
bootstrap, creating client/session/result handles, sending protocol JSON,
reading response JSON, accessing optional payloads, and freeing anything the ABI
allocated. It does not provide one C function for every TRE workflow. Typed
functions such as list_datasets() or read_dataset_arrow() belong in
host-language wrappers above this layer.
Contract Layers
Wrappers should treat the C ABI as three related but separate contracts:
| Layer | Representative symbols | Wrapper use |
|---|---|---|
| C ABI surface | ahri_tre_abi_version(), exported structs, enum values, and ownership functions | Detect whether the loaded library has the handle, memory, and status surface the wrapper was compiled against. |
| Library package | ahri_tre_library_version() | Report the loaded package for diagnostics and support. Do not infer protocol behavior from this version. |
| Public TRE protocol | ahri_tre_protocol_version(), ahri_tre_protocol_compatibility_minimum(), ahri_tre_protocol_compatibility_maximum(), ahri_tre_protocol_compatibility_rule() | Decide whether the wrapper’s request and response envelopes are supported. |
Protocol compatibility is the behavioral compatibility contract. A wrapper should load the library, call the introspection functions, parse the reported protocol range, and fail fast when its required protocol is outside that range. Rust crate versions, CLI package versions, and dynamic library package versions are provenance metadata rather than substitutes for protocol checks.
How The ABI Works
At runtime, a wrapper creates an opaque ahri_tre_client and submits serialized
public protocol request envelopes with
ahri_tre_client_execute_protocol_json(). The C ABI forwards those envelopes to
the configured Trusted runtime, then returns an opaque ahri_tre_result
containing the serialized public protocol response JSON and any attached
payload metadata or bytes.
Client construction selects and validates the projected Client bootstrap, including Deployment identity, HTTPS origin, and TLS trust, before returning a handle. Profiles are discovered from the authenticated runtime and selected by logical identifier.
The high-level flow is:
host-language wrapper
-> C ABI library
-> authenticated Trusted-runtime HTTPS transport
-> public protocol request router
-> application workflows
-> protocol response JSON plus optional payloads
-> C ABI result handle
-> host-language objects
The C ABI keeps database, lake, OAuth/OIDC, daemon, and session internals out of the host-language process. Wrappers operate on protocol JSON, result handles, and copied or borrowed bytes; they do not receive raw PostgreSQL, DuckDB, DuckLake, token, Lake location, or runtime storage handles.
Client Bootstrap And Runtime
Client creation validates one projected Client bootstrap and keeps it immutable for the lifetime of the opaque handle. Callers may set client_bootstrap_path in ahri_tre_client_config. A NULL value selects /etc/ahri-tre/client.toml. If an explicit path is missing or invalid, creation fails without trying the canonical path.
The bootstrap supplies Deployment identity, Trusted-runtime HTTPS origin, and TLS trust. Profile metadata is server-discovered and selection sends only a logical identifier. Every client and selected-session protocol call uses that same authenticated remote transport. ABI v2 does not expose local runtime configuration, daemon discovery or lifecycle functions, endpoint or binary overrides, never-start flags, or daemon auto-start behavior. Host language wrappers should surface bootstrap selection and normal handle ownership only.
Protocol Execution
Public workflow semantics stay in protocol envelopes. C callers submit request JSON and receive response JSON. Python, Julia, R, and C++ wrappers may expose typed convenience APIs, but those APIs should generate protocol requests and parse protocol responses rather than depending on workflow-specific C symbols.
Use ahri_tre_client_execute_protocol_json() for requests that do not require
selected-session context. Use
ahri_tre_client_select_session_protocol_json() with a session.use request to
obtain an opaque selected-session handle, then call
ahri_tre_session_execute_protocol_json() for selected-session requests.
When execution returns AHRI_TRE_STATUS_OK, the result handle owns a response
envelope. That envelope can represent either workflow success or
protocol-shaped workflow failure. Wrappers should inspect the response JSON to
determine workflow outcome.
The ABI status channel is reserved for unsafe-boundary failures such as null pointers, invalid config, unsupported ABI config, invalid handles, invalid UTF-8 before a protocol envelope can be parsed, or allocation failure. Protocol validation errors, unsupported request kinds, governance failures, daemon transport failures that can be represented as protocol errors, and managed runtime unavailability are returned as response JSON whenever the ABI can produce a protocol-shaped envelope.
Result Ownership
The ABI uses explicit ownership classes:
| Value | Owner | Cleanup |
|---|---|---|
ahri_tre_client * | Caller after successful client creation | ahri_tre_client_free() |
ahri_tre_session * | Caller after successful session selection | ahri_tre_session_close() |
ahri_tre_result * | Caller after execution, selection, or lifecycle helper success | ahri_tre_result_free() |
Returned char * copies | Caller | ahri_tre_string_free() |
Returned uint8_t * copies | Caller | ahri_tre_bytes_free(ptr, len) with the exact returned length |
Borrowed views are owned by the result handle:
ahri_tre_result_response_json_borrowed()returns borrowed response JSON.ahri_tre_result_payload_descriptor()returns borrowed descriptor strings.ahri_tre_result_payload_bytes_borrowed()returns borrowed payload bytes when bytes are attached.
Borrowed data is valid only until the owning result is freed. Host-language wrappers should copy values before releasing the result when those values need to outlive the immediate call scope.
Cleanup functions accept NULL as a no-op. Double-free, freeing a pointer with
the wrong cleanup function, freeing foreign pointers, freeing copied bytes with
the wrong length, and concurrent mutation or cleanup of the same handle are
caller errors. First-stable same-handle concurrent use is not thread-safe unless
a future ABI symbol explicitly documents otherwise.
Payloads
Response JSON carries protocol refs and descriptors. Large or tabular data is attached to result handles only when the executor provides a payload. Arrow IPC is the first in-memory or streaming tabular transfer format, and Parquet remains the durable exported dataset format.
Wrappers should first inspect ahri_tre_result_payload_count(), then read each
ahri_tre_payload_descriptor. Descriptors expose safe protocol refs, media
types, suggested names, size values, and byte-availability flags. They must not
expose runtime Lake locations, datastore connection details, credentials, or local
temporary paths.
Some payloads are descriptor-only export artifacts. Wrappers must handle
data == NULL && len == 0 as a normal no-bytes case rather than a failure.
When bytes are present, wrappers can either consume a borrowed view before
freeing the result or request an owned copy and release it with
ahri_tre_bytes_free().
Diagnostics And Redaction
ABI diagnostics and protocol diagnostics are safe for wrapper logs. They must not include raw request bodies, passwords, tokens, auth artifacts, Restricted local references, lake internals, runtime storage locations, or raw handle addresses.
Status messages from ahri_tre_status_message() describe status classes only.
After client construction fails, ahri_tre_client_create_error_message() returns
the safe name-only diagnostic for that thread’s most recent construction attempt;
wrappers should copy it before another construction call on the same thread.
Detailed workflow and transport outcomes belong in protocol response JSON.
Wrappers should surface those structured JSON fields
through host-language exceptions, result objects, logs, or notebook displays
without assuming internal path or secret values will be present.
Wrapper Guidance
Wrapper packages should keep the C layer thin and deterministic:
- Load the library and perform introspection before using credentials or opening sessions.
- Compare the wrapper’s required protocol version against the reported compatibility range.
- Select an explicit projected Client bootstrap or use the canonical
/etc/ahri-tre/client.tomldocument. - Create the client once and retain its immutable authenticated Trusted-runtime transport capability.
- Serialize public protocol request envelopes with the host language’s JSON library.
- Treat
AHRI_TRE_STATUS_OKas “a result envelope is available”, not “workflow succeeded”. - Parse public protocol response JSON for workflow success, failure, warnings, and resource refs.
- Convert Arrow IPC or Parquet payloads at the host-language edge.
- Copy borrowed data before freeing the owning result when the host language object needs to persist.
- Release every owned handle, string, and byte buffer with the matching C ABI cleanup function.
Wrappers should not bypass the protocol by binding directly to application workflow internals, metadata adapters, lake adapters, or auth internals. They should also avoid adding wrapper-specific persistence formats or alternate control planes. The stable cross-language contract is public protocol JSON plus Arrow IPC, Parquet, and the C ABI ownership rules.
The generated header and the lower-level C examples live with the crate in
crates/ahri_tre_ffi_c.
External source acquisition
Use ahri_tre_client_acquire_source or ahri_tre_session_acquire_source for
public acquisition intent and separate bounded sensitive bytes. The historical
*_acquire_https names remain compatible aliases. This common interface covers
HTTPS, SQL and live REDCap acquisition; it does not add a C function per workflow.
Caller-owned credential bytes remain borrowed for the synchronous call and must
be cleared by the caller afterwards. Always inspect the returned protocol
response even when the ABI status is successful.
SQL ingestion supports Client DuckDB/SQLite query-result uploads and explicit Client/Trusted PostgreSQL/MSSQL reads. Local database paths never cross into the Trusted runtime. See the SQL source contract for endpoint, credential, risk, schema, version and cleanup rules. The tested source-built Linux bundle includes its matching private query worker; other native package and binding releases require their own installed qualification.