Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

LayerRepresentative symbolsWrapper use
C ABI surfaceahri_tre_abi_version(), exported structs, enum values, and ownership functionsDetect whether the loaded library has the handle, memory, and status surface the wrapper was compiled against.
Library packageahri_tre_library_version()Report the loaded package for diagnostics and support. Do not infer protocol behavior from this version.
Public TRE protocolahri_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:

ValueOwnerCleanup
ahri_tre_client *Caller after successful client creationahri_tre_client_free()
ahri_tre_session *Caller after successful session selectionahri_tre_session_close()
ahri_tre_result *Caller after execution, selection, or lifecycle helper successahri_tre_result_free()
Returned char * copiesCallerahri_tre_string_free()
Returned uint8_t * copiesCallerahri_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:

  1. Load the library and perform introspection before using credentials or opening sessions.
  2. Compare the wrapper’s required protocol version against the reported compatibility range.
  3. Select an explicit projected Client bootstrap or use the canonical /etc/ahri-tre/client.toml document.
  4. Create the client once and retain its immutable authenticated Trusted-runtime transport capability.
  5. Serialize public protocol request envelopes with the host language’s JSON library.
  6. Treat AHRI_TRE_STATUS_OK as “a result envelope is available”, not “workflow succeeded”.
  7. Parse public protocol response JSON for workflow success, failure, warnings, and resource refs.
  8. Convert Arrow IPC or Parquet payloads at the host-language edge.
  9. Copy borrowed data before freeing the owning result when the host language object needs to persist.
  10. 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.