|
…
|
||
|---|---|---|
| .. | ||
| README.md | ||
Zig Guidance — Runtime FFI Boundary
Status: doctrine
Component: C ABI and runtime backend boundary
Zig should enter CORE through stable FFI surfaces, not through ad-hoc runtime imports. This document defines the shape of that boundary.
Why FFI discipline matters
The risk of adding Zig is not the language itself. The risk is boundary confusion:
Who owns this buffer?
Who frees this allocation?
Is this function allowed to mutate global state?
Is the backend selected intentionally?
Are errors typed or just logged?
Can replay identify which backend ran?
If those questions are ambiguous, Zig should not be merged.
ABI principles
1. C ABI first
All Zig substrate components should expose a C ABI first.
Python, Rust, Swift, or future edge binaries may bind to that ABI, but the ABI itself should not depend on Python object semantics.
2. Explicit versioning
Every library should expose:
unsigned int core_native_abi_version(void);
const char* core_native_build_id(void);
Runtime bindings must reject unknown ABI versions.
3. Caller-owned inputs
Input buffers should usually be caller-owned. Zig must treat them as borrowed unless the API explicitly copies them.
4. Explicit output ownership
If Zig allocates output, Zig must provide the matching free function.
Pattern:
int component_do_work(..., ComponentOutput** out, ComponentError* err);
void component_output_free(ComponentOutput* out);
No output allocation should be freed by Python, Rust, or libc unless the ABI explicitly says so.
5. Typed error surface
Do not print errors from Zig as the only failure signal. Return structured error codes and bounded messages.
Example:
typedef struct CoreNativeError {
int code;
char message[256];
} CoreNativeError;
Python bindings should translate these to typed Python exceptions.
Backend selection
Native backends must be selected explicitly.
Recommended environment variables:
CORE_NATIVE_BACKEND=python|rust|zig
CORE_CRDT_BACKEND=python|rust|zig
CORE_AUDIO_COMPILER_BACKEND=python|zig
CORE_RECALL_BACKEND=python|rust|zig
Rules:
- absence of Zig library must not break the Python path;
- presence of Zig library must not silently change runtime behavior;
- backend identity must be visible in diagnostics;
- backend identity should be recorded in replay evidence where behavior/performance could matter.
Memory ownership table
| Case | Preferred ownership |
|---|---|
| input NumPy matrix | Python owns; pass pointer/shape/stride only after contiguity check |
| audio input samples | caller owns unless canonicalizer explicitly copies |
| CRDT provenance bytes | Zig may copy into arena-owned storage |
| CRDT delta output | Zig owns; caller frees through Zig free function |
| AudioCompilationUnit output | Zig owns; caller frees through Zig free function |
| error message | caller passes error struct pointer; Zig writes bounded message |
| returned slices | avoid raw borrowed slices unless lifetime is obvious and documented |
Shape and dtype validation
All FFI surfaces must validate shape and dtype before computation.
Examples:
versor: exactly 32 f32 values
matrix: N x 32 f32, row-major contiguous
queries: B x 32 f32, row-major contiguous
audio samples: declared dtype, channel count, sample rate, length
edges: E x 2 i32 if graph diffusion is used
Invalid shape is a typed error, not undefined behavior.
Forbidden FFI behaviors
Zig FFI must not:
- hold Python object pointers;
- assume NumPy memory remains valid after caller scope ends;
- spawn hidden background threads;
- mutate global CORE state;
- allocate without a free path;
- panic across FFI boundary;
- return borrowed memory with unclear lifetime;
- reinterpret non-contiguous buffers as contiguous;
- hide fallback after partial mutation;
- suppress errors and continue.
Python binding shape
Python binding modules should be thin and boring:
core_native/zig_loader.py
core_native/crdt_zig.py
core_native/audio_zig.py
core_native/recall_zig.py
Responsibilities:
- locate library;
- check ABI version;
- check buffer contiguity;
- call C ABI;
- translate errors;
- free outputs;
- expose Pythonic wrappers;
- never define semantics not present in the reference contract.
Diagnostic command
A future CLI diagnostic should report:
core doctor --zig
library found: yes/no
ABI version: ...
build id: ...
enabled selectors: ...
CRDT backend available: yes/no
audio compiler backend available: yes/no
recall backend available: yes/no
last self-test: pass/fail
If a user asks for a required Zig backend and it is unavailable, fail fast with a clear error.
Runtime trace policy
Any turn/event affected by Zig-backed native behavior should be able to report:
native_backend: zig
native_component: crdt|audio|recall|algebra
abi_version: N
build_id: sha/hash/version
This does not mean every trace needs verbose native metadata. But replay-critical paths must not hide which substrate executed.
Recommendation
Add the FFI contract before adding serious Zig code.
A clean FFI boundary lets CORE evaluate Zig without infecting Python semantics, Rust parity work, or Workbench/operator flows.