# C ABI The data ABI lets a host runtime keep buffers and fitted data adapters in its own memory while exposing deterministic descriptors and identity tables to the core. Validation failures written to `error_out` use the ADR-11 descriptor JSON shape: `category`, `code`, `severity`, `message`, `remediation_hint` and `context`. Null-pointer preflight errors still use the legacy human string because callers usually branch on `DagMlDataStatusCode` before parsing a payload. ## Current Scaffold `crates/dag-ml-data-capi/include/dag_ml_data.h` exposes: - version, string-free and tensor-free helpers; - `dagmldata_schema_fingerprint_json`; - `dagmldata_aggregation_policy_validate_json`, which validates an ADR-07 aggregation-policy payload (reducer name plus its parameters); - Arrow C Data `ArrowArray` and `ArrowSchema` structs plus release helpers; - `DagMlDataTensorF64`, an owned row-major f64 tensor descriptor with identity, shape, values, optional masks and feature names; - `DAG_ML_DATA_TENSOR_F64_ABI_VERSION`, the C-visible ABI version expected in each f64 tensor descriptor; - `DagMlDataTensorF32`, an owned row-major f32 tensor descriptor mirroring the f64 descriptor for bindings that consume f32 inputs natively (deep-learning frameworks, GPU pipelines); - `DAG_ML_DATA_TENSOR_F32_ABI_VERSION`, the C-visible ABI version expected in each f32 tensor descriptor; - `DagMlDataFeatureMatrixF64View`, a borrowed C view over one row-major f64 feature matrix; - `DagMlDataFeatureMatrixF64ColumnarView` plus `DagMlDataF64ColumnView`, a borrowed C view over one column-major f64 feature matrix with per-column validity bitmaps, mirroring Arrow/Parquet/NumPy columnar layouts; - `dagmldata_coordinator_identity_arrow_json` for identity-table smoke tests from a validated coordinator envelope; - `dagmldata_coordinator_target_arrow_json` for numeric target-table smoke tests from a validated envelope, materialization request, `DataView` and target table; - `dagmldata_coordinator_multi_target_arrow_json` for multi-output target export from the same envelope/view contracts, with one nullable f64 column per target and a per-target validity bitmap; - `dagmldata_coordinator_feature_arrow_json` for numeric observation-level feature-table smoke tests from the same coordinator/view contracts; - `dagmldata_coordinator_feature_fusion_arrow_json` for numeric multi-source fused feature-table smoke tests over already materialized coordinator feature blocks; - `dagmldata_coordinator_feature_collation_json` for JSON row-major tensor collation smoke tests over coordinator feature blocks; - `dagmldata_coordinator_feature_collation_tensor_f64_json` for ABI-owned row-major f64 tensor export over coordinator feature blocks; - `dagmldata_coordinator_feature_collation_tensor_f32_json` for ABI-owned row-major f32 tensor export over the same coordinator feature blocks, with rejection of values that do not round-trip into a finite f32; - `dagmldata_inmemory_provider_new_json` for a Rust-owned provider vtable that materializes data handles, creates view handles, exports view identity, exports numeric targets and supports release/destroy callbacks; - `dagmldata_inmemory_provider_new_with_features_json` for the same provider plus JSON feature tables used by binding conformance tests; - `dagmldata_inmemory_provider_new_with_f64_features_json` for the same provider plus typed row-major f64 feature matrices with an optional validity mask; - `dagmldata_inmemory_provider_new_with_f64_feature_views` for the same provider plus borrowed C `DagMlDataFeatureMatrixF64View` descriptors, avoiding JSON value transport for numeric feature matrices; - `dagmldata_inmemory_provider_new_with_f64_feature_columns` for the same provider plus borrowed C `DagMlDataFeatureMatrixF64ColumnarView` descriptors with per-column f64 slices and optional per-column validity bitmaps, avoiding the row-major transpose copy on the production columnar ingestion path; - `dagmldata_inmemory_provider_feature_buffer_manifest_json` for deterministic JSON manifests of provider-owned numeric feature buffers; - `dagmldata_inmemory_provider_data_feature_buffer_manifest_json` for data-handle-scoped feature-buffer bindings; - `dagmldata_inmemory_provider_feature_collation_json` for JSON row-major tensor collation from feature buffers owned by the in-memory provider; - `dagmldata_inmemory_provider_feature_collation_tensor_f64_json` for ABI-owned row-major f64 tensor export from provider-owned feature buffers; - `dagmldata_inmemory_provider_feature_collation_tensor_f32_json` for ABI-owned row-major f32 tensor export from the same provider-owned feature buffers, with the same finite-f32 rejection contract as the coordinator entry point; - `DataView.branch_view` (a `CoordinatorBranchView` carrying `view_id`, `branch_id`, `mode`, `selector`, `allow_overlap` and `metadata`), letting hosts pass `dag-ml` `BranchViewPlan` records straight through the existing `make_view` selector JSON. The in-memory provider executes `by_source` branch views natively and lets `separation` pass through; `by_metadata`, `by_tag` and `by_filter` are validated but require host-side filtering; - `FittedAdapterRef` and `FittedAdapterManifest`, a JSON-serializable fitted-adapter persistence contract with `validate()` for inline refs and `validate_portable()` for refs carrying a backend + safe relative URI + content fingerprint (the same portability rules as `dag-ml`'s `ArtifactRef::validate_portable`). The manifest enforces unique adapter ids and key↔ref consistency; - `dagmldata_fitted_adapter_ref_validate_json` and `dagmldata_fitted_adapter_manifest_validate_json` C ABI entry points that parse JSON, run the `validate()` or `validate_portable()` Rust check selected by the `require_portable` byte flag and return either `Ok` or `ValidationError` + an owned error string. The matching `fitted_adapter_ref.v1.schema.json` is published under `docs/contracts` and pinned in `conformance_pack.v1.json` for cross-repo digest equality; - `DagMlDataFittedAdapterStoreHandle` opaque pointer plus `dagmldata_inmemory_fitted_adapter_store_new` / `destroy` / `register_json` / `materialize_json` / `release` entry points exposing the Rust `InMemoryFittedAdapterStore` to non-Rust bindings. Host callers receive a `u64` handle from register / materialize and own its release through `dagmldata_inmemory_fitted_adapter_store_release` (by adapter id) and the store-level `destroy` call; - `DagMlDataVTable` with materialize/view/identity/target/feature/release hooks. The `feature_arrow` hook accepts either a plain feature-set id or a JSON feature-fusion selector. The vtable uses the shared `DAG_ML_DATA_PROVIDER_VTABLE_ABI_VERSION` macro and guarded `DagMlDataVTable` definition so `dag_ml_data.h` and `dag_ml.h` can be included together by bindings. The coordinator envelope wire shape is versioned as `CoordinatorDataPlanEnvelope` v1 and published at `docs/contracts/coordinator_data_plan_envelope.schema.json`. Runtime validation continues to check the stronger semantic contract: schema/data-plan/relation fingerprints, identity consistency and materialization-request compatibility. ## Ownership Rules | Object | Owner | Release path | |---|---|---| | Materialized data handle | Host | `DagMlDataVTable.release` | | View handle | Host | `DagMlDataVTable.release` | | Fitted adapter handle | Host | future fitted-adapter release hook | | Rust error/fingerprint string | Rust allocation returned through ABI | `dagmldata_string_free` | | Rust-owned f64 tensor descriptor and nested arrays | Rust allocation returned through ABI | `dagmldata_tensor_f64_free` | | Rust-owned f32 tensor descriptor and nested arrays | Rust allocation returned through ABI | `dagmldata_tensor_f32_free` | | Arrow arrays/schemas returned by Rust helpers | Rust allocation returned through ABI | `dagmldata_arrow_array_free`, `dagmldata_arrow_schema_free` | | Arrow arrays produced by host vtables | Producer of the Arrow array | Arrow C Data Interface release callback | | Rust-owned in-memory provider vtable | Rust allocation behind `user_data` | `DagMlDataVTable.destroy` or `dagmldata_inmemory_provider_destroy` | | Borrowed `DagMlDataFeatureMatrixF64View` inputs | Caller | copied during constructor call; caller may release after return | | Borrowed `DagMlDataFeatureMatrixF64ColumnarView` inputs (and nested `DagMlDataF64ColumnView` columns) | Caller | copied during constructor call; caller may release after return | ## Coordinator Identity Export `dagmldata_coordinator_identity_arrow_json` is a narrow smoke helper, not the final provider implementation. It validates a `CoordinatorDataPlanEnvelope` and exports one Arrow struct row per coordinator relation with: - `observation_id`, `sample_id`, `target_id`, `group_id`; - `origin_sample_id`, `source_id`, `is_augmented`. This is enough for ABI consumers to verify sample/repetition/group/augmentation identity transfer before full buffer-backed provider lifecycles exist. `dagmldata_coordinator_target_arrow_json` extends the smoke path to sample-level targets. It materializes the envelope, creates a `DataView`, aligns target values to the selected samples and emits `sample_id`, `target_id` and numeric `value` columns. Repeated observations are intentionally de-duplicated to one target value per sample. `dagmldata_coordinator_multi_target_arrow_json` uses the same materialization path for multi-output regression/classification targets. Its request carries `target_tables` and its Arrow result emits `sample_id` plus one nullable f64 column per target id. Missing or explicit null values are represented by that target column's validity bitmap rather than by dropping samples. `dagmldata_coordinator_feature_arrow_json` is intentionally observation-level. It materializes the same envelope/view, preserves repeated observations, applies `DataView.columns`, and emits `observation_id`, `sample_id` plus one numeric column per selected feature. This keeps the target aggregation rule separate from feature row identity. `dagmldata_coordinator_feature_fusion_arrow_json` exercises the pure Rust feature-fusion kernel through the C ABI. It accepts a request shaped as `{ feature_set_id, sources, alignment, source_layout?, policy? }`, where each source contains a `source_id` and a `CoordinatorFeatureBlock`. When present, `source_layout` is validated against those concrete blocks: source order, per-source preprocessing output feature-set/representation, and contiguous feature-axis concat spans must match. The exported Arrow table preserves reference-source repeated observations, broadcasts singleton rows from secondary sources, namespaces fused feature columns by default and refuses incoherent presence masks or ambiguous repeated secondary sources. `dagmldata_coordinator_feature_collation_json` exercises the pure Rust numeric late-collation kernel through the C ABI. It accepts `{ feature_block, policy? }` and returns a JSON `NumericTensorBlock` with observation/sample identity, row-major shape and values, optional presence mask and optional value-validity mask. It is a conformance helper, not a provider lifecycle. `dagmldata_coordinator_feature_collation_tensor_f64_json` exports the same result as an ABI-owned `DagMlDataTensorF64` instead of JSON. The tensor carries `abi_version`, block/representation/container strings, observation ids, sample ids, `shape`, contiguous row-major `values`, optional `presence_mask`, optional `validity_mask` and optional `feature_names`. Masks are byte arrays with values 0 or 1. The caller must release the tensor with `dagmldata_tensor_f64_free`. `dagmldata_coordinator_feature_collation_tensor_f32_json` mirrors that surface with a `DagMlDataTensorF32` output. The kernel still runs in f64 to preserve canonical numeric semantics; each value is cast to f32 at the ABI boundary and the call returns `ValidationError` if any padded value, finite input or padding fallback does not round-trip into a finite f32. The caller must release the tensor with `dagmldata_tensor_f32_free`. The same f32 entry point is available against provider-owned feature buffers through `dagmldata_inmemory_provider_feature_collation_tensor_f32_json`. `dagmldata_inmemory_provider_feature_collation_json` and `dagmldata_inmemory_provider_feature_collation_tensor_f64_json` exercise the same late-collation kernel against provider-owned typed numeric buffers. They accept `{ feature_set_id, policy? }` for a single provider feature table or `{ fusion, policy? }` where `fusion` is the provider feature-fusion selector. The JSON export is a conformance/debug path; the `DagMlDataTensorF64` export is the binding-oriented path. These helpers are specific to vtables created by `dagmldata_inmemory_provider_new_with_features_json`; they do not change the stable `DagMlDataVTable` layout. `dagmldata_inmemory_provider_feature_buffer_manifest_json` returns an array of `NumericFeatureBufferManifest` values for the provider-owned typed buffers. Each manifest includes the feature-set id, representation id, feature and observation ids, row/feature/value counts, estimated f64 storage bytes and a deterministic buffer fingerprint. Bindings can use this before creating feature views or tensors to verify that the provider loaded the expected data buffers. `dagmldata_inmemory_provider_data_feature_buffer_manifest_json` returns `NumericFeatureBufferBinding` values for one live materialized data handle. A binding is created by the core `NumericFeatureBufferArena` during `materialize` only when a provider buffer has the same output representation as the data handle and covers the handle's scoped coordinator observations for one or more source ids. The binding export refuses unknown or released data handles. ## In-Memory Provider VTable The in-memory provider is the current ABI conformance target. It accepts one validated coordinator envelope plus optional sample-level target tables and observation-level feature tables, then implements: - `materialize`: validates a coordinator materialization request and returns an opaque data handle whose coordinator relations are scoped to the requested `source_ids`; - `make_view`: applies a `DataView` to a data handle and returns an opaque view handle; - `view_identity`: returns the filtered relation table as Arrow C Data; - `target_arrow`: returns sample-level numeric targets aligned to the view; - `feature_arrow`: returns observation-level numeric features aligned to the view and filtered by `DataView.columns` when passed a plain feature-set id; when passed `{ feature_set_id, sources, alignment, source_layout?, policy? }` JSON, where each source is `{ source_id, feature_set_id, columns? }`, it fuses provider-owned source feature buffers through the core feature-fusion kernel; - `release` and `destroy`: release handles and provider state. The conformance provider can still receive small JSON fixture feature tables at construction time, but the preferred numeric paths are typed row-major `NumericFeatureMatrixF64` input with optional validity masks and direct borrowed C `DagMlDataFeatureMatrixF64View` descriptors. The borrowed descriptors are copied into Rust-owned buffers during construction; after the constructor returns, callers may release their input arrays. All construction paths convert once into column-major `NumericFeatureBuffer` values grouped by `NumericFeatureBufferArena` in the provider state. At `materialize`, the arena computes data-handle-scoped buffer bindings from the scoped coordinator relations and materialized output representation. `feature_arrow` exports are then view projections over bound buffers, not per-call JSON numeric parsing. Fusion selectors reuse those typed buffers, filter each source by source identity in the view, validate that each source buffer is bound to the parent data handle through the arena, and then call the same pure Rust fusion kernel used by the standalone ABI helper. Provider feature-collation selectors then collate either a single feature table or the fused block into deterministic row-major JSON or `DagMlDataTensorF64` tensors without reparsing feature values. Full provider implementations will use the same vtable shape while keeping production data buffers host-owned. `tests/c_header_smoke.rs` has two C checks: a header syntax smoke with `cc -fsyntax-only`, and a linked C program that loads the Rust `cdylib`, creates the provider vtable, materializes a view, exports identity, target and feature Arrow arrays, then releases all handles. The syntax smoke also includes the sibling `dag_ml.h` in both include orders when a `dag-ml` checkout is available, so shared data-provider vtable guards cannot drift silently. `tests/python_ctypes_smoke.rs` performs the same provider lifecycle from Python using only `ctypes`. It also runs `examples/python/provider_smoke.py` against the installable `dag_ml_data_provider` package (`crates/dag-ml-data-capi/bindings/python`), a stdlib-only ctypes wrapper around the provider vtable. The package is the binding-friendly conformance target for materialize, view creation, identity export, target export, feature export, release and destroy; it locates the cdylib via `library_path=` / `DAG_ML_DATA_CAPI_LIB` / the Cargo target dir. ## ABI Roadmap 1. Freeze byte/string/status conventions. 2. Add C smoke test for schema fingerprinting. 3. Add path-solving and data-plan validation over canonical JSON. 4. Add native host provider conformance against the current identity/target/feature behavior. 5. Add ABI conformance for the core multi-source feature-fusion and numeric collation kernels. 6. Route in-memory provider `feature_arrow` fusion selectors through the same kernel. 7. Route in-memory provider feature-collation selectors through provider-owned typed buffers. 8. Expose provider-backed collation as `DagMlDataTensorF64` for bindings. 9. Expose provider-owned feature-buffer manifests for binding conformance. 10. Bind compatible feature buffers to live materialized data handles and validate those bindings before Arrow/tensor export. 11. Add typed f64 matrix provider construction, including borrowed C matrix views, avoiding per-cell JSON values on the numeric conformance path. 12. Replace in-memory typed fixture buffers with production feature-buffer lifecycles.