ADR-002 — A C ABI, pull-based, is the runtime contract

Status: accepted (2026-08-25, spec v0.2). Resolves OQ0.
Context: v0.1 specified packaging, metadata, and trust obligations but no call-level contract — the two working cartridges had entirely different APIs (a C batch API vs. a Kotlin streaming class), so "replaceable as a unit" in practice meant "rewrite the adapter". Conformance (OQ5) had nothing to invoke. The user also requires consumption from any tech stack (C, Rust, Python, Go, JVM, Kotlin/Native).
Decision: One extern "C" ABI (cartridge_abi.h, abi.adoc) every cartridge exports: versioned, struct-size negotiated, pull-based streaming (no callbacks — callbacks force a threading contract onto every consumer language), explicit buffer ownership, per-handle errors, ctg_preflight for fleet-only requirements. Language facades (JNI, generated typed Kotlin, CLI) wrap it; they never replace it.
Alternatives rejected: a Kotlin-first interface (locks out non-JVM stacks, unverifiable from C tooling); callback-based streaming (threading contract leaks into every binding); gRPC/IPC (wrong cost model for on-device, single-process inference).
Consequences: conformance testing becomes possible (dlopen + invoke); Rust/C/Python consumers are first-class; existing cartridges need thin wrappers (roadmap Phase 3); binary-plane swap becomes real (same ABI + same io = package swap).