# FFI Policy: the boring, versioned C ABI (Katra3D §22)
## Rules
1. **Only integers, opaque `u64` handles, and simple structs** cross the
boundary. No `Arc<T>`, no `Box<T>`, no Rust enums with unstable layout,
no internal graph pointers.
2. **Handles are ids** into a process-wide table; `katra3d_handle_destroy`
releases them; double-destroy is rejected.
3. **Error codes are stable integers** (`KATRA_OK=0` … `KATRA_ERR_SYNC=6`)
with a stable string table (`katra3d_error_string`).
4. **Prefer semantic operations** over implementation internals:
`katra3d_register_resource`, `katra3d_submit_io_graph`,
`katra3d_submit_dependency`, `katra3d_trace_event`,
`katra3d_wait_completion` — the exported surface today is
profiler/flow/graph/memory handles (see `include/katra3d.h`).
5. **The ABI is versioned** (`katra3d_abi_version`), and the header
documents every enum table (scope/kind/phase/node/deadline/dependency)
with **append-only** semantics.
## Why boring
The C adapters exist for compatibility and migration. They must **not**
become Katra's internal architecture (§23): inside Rust, `storage`,
`memory`, `resource`, `scheduler`, `GPU`, `shader`, and `synchronization`
cooperate directly. Coarse-grained, batched ABI calls avoid creating a new
performance problem out of FFI chatter.
## Safety
The ABI crate is the one place where `unsafe` is expected: every exported
function validates handles, rejects null pointers, bounds string copies,
and documents its SAFETY invariants (§41). The C contract in the header
guarantees output pointers are valid for the call duration. The C header is
compiled by a court (`c_header_compiles`) whenever a C compiler is present,
and the handle lifecycle is covered by the `abi_handles` court.
## Adapters
`adapters/wine`, `adapters/proton`, `adapters/vkd3d`, `adapters/dxvk` are
the future thin adapters. They link against `libkatra3d` and translate
native calls into the ABI surface — they do not reimplement Katra internals.